diff options
author | 3gg <3gg@shellblade.net> | 2025-08-30 16:53:58 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2025-08-30 16:53:58 -0700 |
commit | 6aaedb813fa11ba0679c3051bc2eb28646b9506c (patch) | |
tree | 34acbfc9840e02cb4753e6306ea7ce978bf8b58e /src/contrib/SDL-3.2.20/test/testcontroller.c | |
parent | 8f228ade99dd3d4c8da9b78ade1815c9adf85c8f (diff) |
Update to SDL3
Diffstat (limited to 'src/contrib/SDL-3.2.20/test/testcontroller.c')
-rw-r--r-- | src/contrib/SDL-3.2.20/test/testcontroller.c | 2243 |
1 files changed, 2243 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/test/testcontroller.c b/src/contrib/SDL-3.2.20/test/testcontroller.c new file mode 100644 index 0000000..b717b30 --- /dev/null +++ b/src/contrib/SDL-3.2.20/test/testcontroller.c | |||
@@ -0,0 +1,2243 @@ | |||
1 | /* | ||
2 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
3 | |||
4 | This software is provided 'as-is', without any express or implied | ||
5 | warranty. In no event will the authors be held liable for any damages | ||
6 | arising from the use of this software. | ||
7 | |||
8 | Permission is granted to anyone to use this software for any purpose, | ||
9 | including commercial applications, and to alter it and redistribute it | ||
10 | freely. | ||
11 | */ | ||
12 | |||
13 | /* Simple program to test the SDL controller routines */ | ||
14 | |||
15 | #define SDL_MAIN_USE_CALLBACKS | ||
16 | #include <SDL3/SDL.h> | ||
17 | #include <SDL3/SDL_main.h> | ||
18 | #include <SDL3/SDL_test.h> | ||
19 | #include <SDL3/SDL_test_font.h> | ||
20 | |||
21 | #ifdef SDL_PLATFORM_EMSCRIPTEN | ||
22 | #include <emscripten/emscripten.h> | ||
23 | #endif | ||
24 | |||
25 | #include "gamepadutils.h" | ||
26 | #include "testutils.h" | ||
27 | |||
28 | #if 0 | ||
29 | #define DEBUG_AXIS_MAPPING | ||
30 | #endif | ||
31 | |||
32 | #define TITLE_HEIGHT 48.0f | ||
33 | #define PANEL_SPACING 25.0f | ||
34 | #define PANEL_WIDTH 250.0f | ||
35 | #define MINIMUM_BUTTON_WIDTH 96.0f | ||
36 | #define BUTTON_MARGIN 16.0f | ||
37 | #define BUTTON_PADDING 12.0f | ||
38 | #define GAMEPAD_WIDTH 512.0f | ||
39 | #define GAMEPAD_HEIGHT 560.0f | ||
40 | |||
41 | #define SCREEN_WIDTH (PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING + PANEL_WIDTH) | ||
42 | #define SCREEN_HEIGHT (TITLE_HEIGHT + GAMEPAD_HEIGHT) | ||
43 | |||
44 | typedef struct | ||
45 | { | ||
46 | bool m_bMoving; | ||
47 | int m_nLastValue; | ||
48 | int m_nStartingValue; | ||
49 | int m_nFarthestValue; | ||
50 | } AxisState; | ||
51 | |||
52 | typedef struct | ||
53 | { | ||
54 | SDL_JoystickID id; | ||
55 | |||
56 | SDL_Joystick *joystick; | ||
57 | int num_axes; | ||
58 | AxisState *axis_state; | ||
59 | |||
60 | SDL_Gamepad *gamepad; | ||
61 | char *mapping; | ||
62 | bool has_bindings; | ||
63 | |||
64 | int audio_route; | ||
65 | int trigger_effect; | ||
66 | } Controller; | ||
67 | |||
68 | static SDLTest_CommonState *state; | ||
69 | static SDL_Window *window = NULL; | ||
70 | static SDL_Renderer *screen = NULL; | ||
71 | static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING; | ||
72 | static GamepadImage *image = NULL; | ||
73 | static GamepadDisplay *gamepad_elements = NULL; | ||
74 | static GamepadTypeDisplay *gamepad_type = NULL; | ||
75 | static JoystickDisplay *joystick_elements = NULL; | ||
76 | static GamepadButton *setup_mapping_button = NULL; | ||
77 | static GamepadButton *done_mapping_button = NULL; | ||
78 | static GamepadButton *cancel_button = NULL; | ||
79 | static GamepadButton *clear_button = NULL; | ||
80 | static GamepadButton *copy_button = NULL; | ||
81 | static GamepadButton *paste_button = NULL; | ||
82 | static char *backup_mapping = NULL; | ||
83 | static bool done = false; | ||
84 | static bool set_LED = false; | ||
85 | static int num_controllers = 0; | ||
86 | static Controller *controllers; | ||
87 | static Controller *controller; | ||
88 | static SDL_JoystickID mapping_controller = 0; | ||
89 | static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID; | ||
90 | static int last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID; | ||
91 | static bool binding_flow = false; | ||
92 | static int binding_flow_direction = 0; | ||
93 | static Uint64 binding_advance_time = 0; | ||
94 | static SDL_FRect title_area; | ||
95 | static bool title_highlighted; | ||
96 | static bool title_pressed; | ||
97 | static SDL_FRect type_area; | ||
98 | static bool type_highlighted; | ||
99 | static bool type_pressed; | ||
100 | static char *controller_name; | ||
101 | static SDL_Joystick *virtual_joystick = NULL; | ||
102 | static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID; | ||
103 | static float virtual_axis_start_x; | ||
104 | static float virtual_axis_start_y; | ||
105 | static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; | ||
106 | static bool virtual_touchpad_active = false; | ||
107 | static float virtual_touchpad_x; | ||
108 | static float virtual_touchpad_y; | ||
109 | |||
110 | static int s_arrBindingOrder[] = { | ||
111 | /* Standard sequence */ | ||
112 | SDL_GAMEPAD_BUTTON_SOUTH, | ||
113 | SDL_GAMEPAD_BUTTON_EAST, | ||
114 | SDL_GAMEPAD_BUTTON_WEST, | ||
115 | SDL_GAMEPAD_BUTTON_NORTH, | ||
116 | SDL_GAMEPAD_BUTTON_DPAD_LEFT, | ||
117 | SDL_GAMEPAD_BUTTON_DPAD_RIGHT, | ||
118 | SDL_GAMEPAD_BUTTON_DPAD_UP, | ||
119 | SDL_GAMEPAD_BUTTON_DPAD_DOWN, | ||
120 | SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, | ||
121 | SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, | ||
122 | SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, | ||
123 | SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, | ||
124 | SDL_GAMEPAD_BUTTON_LEFT_STICK, | ||
125 | SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, | ||
126 | SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, | ||
127 | SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, | ||
128 | SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, | ||
129 | SDL_GAMEPAD_BUTTON_RIGHT_STICK, | ||
130 | SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, | ||
131 | SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, | ||
132 | SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, | ||
133 | SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, | ||
134 | SDL_GAMEPAD_BUTTON_BACK, | ||
135 | SDL_GAMEPAD_BUTTON_START, | ||
136 | SDL_GAMEPAD_BUTTON_GUIDE, | ||
137 | SDL_GAMEPAD_BUTTON_MISC1, | ||
138 | SDL_GAMEPAD_ELEMENT_INVALID, | ||
139 | |||
140 | /* Paddle sequence */ | ||
141 | SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, | ||
142 | SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, | ||
143 | SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, | ||
144 | SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, | ||
145 | SDL_GAMEPAD_ELEMENT_INVALID, | ||
146 | }; | ||
147 | |||
148 | |||
149 | static const char *GetSensorName(SDL_SensorType sensor) | ||
150 | { | ||
151 | switch (sensor) { | ||
152 | case SDL_SENSOR_ACCEL: | ||
153 | return "accelerometer"; | ||
154 | case SDL_SENSOR_GYRO: | ||
155 | return "gyro"; | ||
156 | case SDL_SENSOR_ACCEL_L: | ||
157 | return "accelerometer (L)"; | ||
158 | case SDL_SENSOR_GYRO_L: | ||
159 | return "gyro (L)"; | ||
160 | case SDL_SENSOR_ACCEL_R: | ||
161 | return "accelerometer (R)"; | ||
162 | case SDL_SENSOR_GYRO_R: | ||
163 | return "gyro (R)"; | ||
164 | default: | ||
165 | return "UNKNOWN"; | ||
166 | } | ||
167 | } | ||
168 | |||
169 | /* PS5 trigger effect documentation: | ||
170 | https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes | ||
171 | */ | ||
172 | typedef struct | ||
173 | { | ||
174 | Uint8 ucEnableBits1; /* 0 */ | ||
175 | Uint8 ucEnableBits2; /* 1 */ | ||
176 | Uint8 ucRumbleRight; /* 2 */ | ||
177 | Uint8 ucRumbleLeft; /* 3 */ | ||
178 | Uint8 ucHeadphoneVolume; /* 4 */ | ||
179 | Uint8 ucSpeakerVolume; /* 5 */ | ||
180 | Uint8 ucMicrophoneVolume; /* 6 */ | ||
181 | Uint8 ucAudioEnableBits; /* 7 */ | ||
182 | Uint8 ucMicLightMode; /* 8 */ | ||
183 | Uint8 ucAudioMuteBits; /* 9 */ | ||
184 | Uint8 rgucRightTriggerEffect[11]; /* 10 */ | ||
185 | Uint8 rgucLeftTriggerEffect[11]; /* 21 */ | ||
186 | Uint8 rgucUnknown1[6]; /* 32 */ | ||
187 | Uint8 ucLedFlags; /* 38 */ | ||
188 | Uint8 rgucUnknown2[2]; /* 39 */ | ||
189 | Uint8 ucLedAnim; /* 41 */ | ||
190 | Uint8 ucLedBrightness; /* 42 */ | ||
191 | Uint8 ucPadLights; /* 43 */ | ||
192 | Uint8 ucLedRed; /* 44 */ | ||
193 | Uint8 ucLedGreen; /* 45 */ | ||
194 | Uint8 ucLedBlue; /* 46 */ | ||
195 | } DS5EffectsState_t; | ||
196 | |||
197 | static void CyclePS5AudioRoute(Controller *device) | ||
198 | { | ||
199 | DS5EffectsState_t effects; | ||
200 | |||
201 | device->audio_route = (device->audio_route + 1) % 4; | ||
202 | |||
203 | SDL_zero(effects); | ||
204 | switch (device->audio_route) { | ||
205 | case 0: | ||
206 | /* Audio disabled */ | ||
207 | effects.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */ | ||
208 | effects.ucSpeakerVolume = 0; /* Minimum volume */ | ||
209 | effects.ucHeadphoneVolume = 0; /* Minimum volume */ | ||
210 | effects.ucAudioEnableBits = 0x00; /* Output to headphones */ | ||
211 | break; | ||
212 | case 1: | ||
213 | /* Headphones */ | ||
214 | effects.ucEnableBits1 |= (0x80 | 0x10); /* Modify audio route and headphone volume */ | ||
215 | effects.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */ | ||
216 | effects.ucAudioEnableBits = 0x00; /* Output to headphones */ | ||
217 | break; | ||
218 | case 2: | ||
219 | /* Speaker */ | ||
220 | effects.ucEnableBits1 |= (0x80 | 0x20); /* Modify audio route and speaker volume */ | ||
221 | effects.ucSpeakerVolume = 100; /* Maximum volume */ | ||
222 | effects.ucAudioEnableBits = 0x30; /* Output to speaker */ | ||
223 | break; | ||
224 | case 3: | ||
225 | /* Both */ | ||
226 | effects.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */ | ||
227 | effects.ucSpeakerVolume = 100; /* Maximum volume */ | ||
228 | effects.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */ | ||
229 | effects.ucAudioEnableBits = 0x20; /* Output to both speaker and headphones */ | ||
230 | break; | ||
231 | } | ||
232 | SDL_SendGamepadEffect(device->gamepad, &effects, sizeof(effects)); | ||
233 | } | ||
234 | |||
235 | static void CyclePS5TriggerEffect(Controller *device) | ||
236 | { | ||
237 | DS5EffectsState_t effects; | ||
238 | |||
239 | Uint8 trigger_effects[3][11] = { | ||
240 | /* Clear trigger effect */ | ||
241 | { 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, | ||
242 | /* Constant resistance across entire trigger pull */ | ||
243 | { 0x01, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0 }, | ||
244 | /* Resistance and vibration when trigger is pulled */ | ||
245 | { 0x06, 15, 63, 128, 0, 0, 0, 0, 0, 0, 0 }, | ||
246 | }; | ||
247 | |||
248 | device->trigger_effect = (device->trigger_effect + 1) % SDL_arraysize(trigger_effects); | ||
249 | |||
250 | SDL_zero(effects); | ||
251 | effects.ucEnableBits1 |= (0x04 | 0x08); /* Modify right and left trigger effect respectively */ | ||
252 | SDL_memcpy(effects.rgucRightTriggerEffect, trigger_effects[device->trigger_effect], sizeof(trigger_effects[0])); | ||
253 | SDL_memcpy(effects.rgucLeftTriggerEffect, trigger_effects[device->trigger_effect], sizeof(trigger_effects[0])); | ||
254 | SDL_SendGamepadEffect(device->gamepad, &effects, sizeof(effects)); | ||
255 | } | ||
256 | |||
257 | static void ClearButtonHighlights(void) | ||
258 | { | ||
259 | title_highlighted = false; | ||
260 | title_pressed = false; | ||
261 | |||
262 | type_highlighted = false; | ||
263 | type_pressed = false; | ||
264 | |||
265 | ClearGamepadImage(image); | ||
266 | SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
267 | SetGamepadTypeDisplayHighlight(gamepad_type, SDL_GAMEPAD_TYPE_UNSELECTED, false); | ||
268 | SetGamepadButtonHighlight(setup_mapping_button, false, false); | ||
269 | SetGamepadButtonHighlight(done_mapping_button, false, false); | ||
270 | SetGamepadButtonHighlight(cancel_button, false, false); | ||
271 | SetGamepadButtonHighlight(clear_button, false, false); | ||
272 | SetGamepadButtonHighlight(copy_button, false, false); | ||
273 | SetGamepadButtonHighlight(paste_button, false, false); | ||
274 | } | ||
275 | |||
276 | static void UpdateButtonHighlights(float x, float y, bool button_down) | ||
277 | { | ||
278 | ClearButtonHighlights(); | ||
279 | |||
280 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
281 | SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y), button_down); | ||
282 | } else if (display_mode == CONTROLLER_MODE_BINDING) { | ||
283 | SDL_FPoint point; | ||
284 | int gamepad_highlight_element = SDL_GAMEPAD_ELEMENT_INVALID; | ||
285 | char *joystick_highlight_element; | ||
286 | |||
287 | point.x = x; | ||
288 | point.y = y; | ||
289 | if (SDL_PointInRectFloat(&point, &title_area)) { | ||
290 | title_highlighted = true; | ||
291 | title_pressed = button_down; | ||
292 | } else { | ||
293 | title_highlighted = false; | ||
294 | title_pressed = false; | ||
295 | } | ||
296 | |||
297 | if (SDL_PointInRectFloat(&point, &type_area)) { | ||
298 | type_highlighted = true; | ||
299 | type_pressed = button_down; | ||
300 | } else { | ||
301 | type_highlighted = false; | ||
302 | type_pressed = false; | ||
303 | } | ||
304 | |||
305 | if (controller->joystick != virtual_joystick) { | ||
306 | gamepad_highlight_element = GetGamepadImageElementAt(image, x, y); | ||
307 | } | ||
308 | if (gamepad_highlight_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
309 | gamepad_highlight_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, x, y); | ||
310 | } | ||
311 | SetGamepadDisplayHighlight(gamepad_elements, gamepad_highlight_element, button_down); | ||
312 | |||
313 | if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { | ||
314 | int gamepad_highlight_type = GetGamepadTypeDisplayAt(gamepad_type, x, y); | ||
315 | SetGamepadTypeDisplayHighlight(gamepad_type, gamepad_highlight_type, button_down); | ||
316 | } | ||
317 | |||
318 | joystick_highlight_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, x, y); | ||
319 | SetJoystickDisplayHighlight(joystick_elements, joystick_highlight_element, button_down); | ||
320 | SDL_free(joystick_highlight_element); | ||
321 | |||
322 | SetGamepadButtonHighlight(done_mapping_button, GamepadButtonContains(done_mapping_button, x, y), button_down); | ||
323 | SetGamepadButtonHighlight(cancel_button, GamepadButtonContains(cancel_button, x, y), button_down); | ||
324 | SetGamepadButtonHighlight(clear_button, GamepadButtonContains(clear_button, x, y), button_down); | ||
325 | SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, x, y), button_down); | ||
326 | SetGamepadButtonHighlight(paste_button, GamepadButtonContains(paste_button, x, y), button_down); | ||
327 | } | ||
328 | } | ||
329 | |||
330 | static int StandardizeAxisValue(int nValue) | ||
331 | { | ||
332 | if (nValue > SDL_JOYSTICK_AXIS_MAX / 2) { | ||
333 | return SDL_JOYSTICK_AXIS_MAX; | ||
334 | } else if (nValue < SDL_JOYSTICK_AXIS_MIN / 2) { | ||
335 | return SDL_JOYSTICK_AXIS_MIN; | ||
336 | } else { | ||
337 | return 0; | ||
338 | } | ||
339 | } | ||
340 | |||
341 | static void RefreshControllerName(void) | ||
342 | { | ||
343 | const char *name = NULL; | ||
344 | |||
345 | SDL_free(controller_name); | ||
346 | controller_name = NULL; | ||
347 | |||
348 | if (controller) { | ||
349 | if (controller->gamepad) { | ||
350 | name = SDL_GetGamepadName(controller->gamepad); | ||
351 | } else { | ||
352 | name = SDL_GetJoystickName(controller->joystick); | ||
353 | } | ||
354 | } | ||
355 | |||
356 | if (name) { | ||
357 | controller_name = SDL_strdup(name); | ||
358 | } else { | ||
359 | controller_name = SDL_strdup(""); | ||
360 | } | ||
361 | } | ||
362 | |||
363 | static void SetAndFreeGamepadMapping(char *mapping) | ||
364 | { | ||
365 | SDL_SetGamepadMapping(controller->id, mapping); | ||
366 | SDL_free(mapping); | ||
367 | } | ||
368 | |||
369 | static void SetCurrentBindingElement(int element, bool flow) | ||
370 | { | ||
371 | int i; | ||
372 | |||
373 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
374 | RefreshControllerName(); | ||
375 | } | ||
376 | |||
377 | if (element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
378 | binding_flow_direction = 0; | ||
379 | last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID; | ||
380 | } else { | ||
381 | last_binding_element = binding_element; | ||
382 | } | ||
383 | binding_element = element; | ||
384 | binding_flow = flow || (element == SDL_GAMEPAD_BUTTON_SOUTH); | ||
385 | binding_advance_time = 0; | ||
386 | |||
387 | for (i = 0; i < controller->num_axes; ++i) { | ||
388 | controller->axis_state[i].m_nFarthestValue = controller->axis_state[i].m_nStartingValue; | ||
389 | } | ||
390 | |||
391 | SetGamepadDisplaySelected(gamepad_elements, element); | ||
392 | } | ||
393 | |||
394 | static void SetNextBindingElement(void) | ||
395 | { | ||
396 | int i; | ||
397 | |||
398 | if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
399 | return; | ||
400 | } | ||
401 | |||
402 | for (i = 0; i < SDL_arraysize(s_arrBindingOrder); ++i) { | ||
403 | if (binding_element == s_arrBindingOrder[i]) { | ||
404 | binding_flow_direction = 1; | ||
405 | SetCurrentBindingElement(s_arrBindingOrder[i + 1], true); | ||
406 | return; | ||
407 | } | ||
408 | } | ||
409 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
410 | } | ||
411 | |||
412 | static void SetPrevBindingElement(void) | ||
413 | { | ||
414 | int i; | ||
415 | |||
416 | if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
417 | return; | ||
418 | } | ||
419 | |||
420 | for (i = 1; i < SDL_arraysize(s_arrBindingOrder); ++i) { | ||
421 | if (binding_element == s_arrBindingOrder[i]) { | ||
422 | binding_flow_direction = -1; | ||
423 | SetCurrentBindingElement(s_arrBindingOrder[i - 1], true); | ||
424 | return; | ||
425 | } | ||
426 | } | ||
427 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
428 | } | ||
429 | |||
430 | static void StopBinding(void) | ||
431 | { | ||
432 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
433 | } | ||
434 | |||
435 | typedef struct | ||
436 | { | ||
437 | int axis; | ||
438 | int direction; | ||
439 | } AxisInfo; | ||
440 | |||
441 | static bool ParseAxisInfo(const char *description, AxisInfo *info) | ||
442 | { | ||
443 | if (!description) { | ||
444 | return false; | ||
445 | } | ||
446 | |||
447 | if (*description == '-') { | ||
448 | info->direction = -1; | ||
449 | ++description; | ||
450 | } else if (*description == '+') { | ||
451 | info->direction = 1; | ||
452 | ++description; | ||
453 | } else { | ||
454 | info->direction = 0; | ||
455 | } | ||
456 | |||
457 | if (description[0] == 'a' && SDL_isdigit(description[1])) { | ||
458 | ++description; | ||
459 | info->axis = SDL_atoi(description); | ||
460 | return true; | ||
461 | } | ||
462 | return false; | ||
463 | } | ||
464 | |||
465 | static void CommitBindingElement(const char *binding, bool force) | ||
466 | { | ||
467 | char *mapping; | ||
468 | int direction = 1; | ||
469 | bool ignore_binding = false; | ||
470 | |||
471 | if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
472 | return; | ||
473 | } | ||
474 | |||
475 | if (controller->mapping) { | ||
476 | mapping = SDL_strdup(controller->mapping); | ||
477 | } else { | ||
478 | mapping = NULL; | ||
479 | } | ||
480 | |||
481 | /* If the controller generates multiple events for a single element, pick the best one */ | ||
482 | if (!force && binding_advance_time) { | ||
483 | char *current = GetElementBinding(mapping, binding_element); | ||
484 | bool native_button = (binding_element < SDL_GAMEPAD_BUTTON_COUNT); | ||
485 | bool native_axis = (binding_element >= SDL_GAMEPAD_BUTTON_COUNT && | ||
486 | binding_element <= SDL_GAMEPAD_ELEMENT_AXIS_MAX); | ||
487 | bool native_trigger = (binding_element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER || | ||
488 | binding_element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER); | ||
489 | bool native_dpad = (binding_element == SDL_GAMEPAD_BUTTON_DPAD_UP || | ||
490 | binding_element == SDL_GAMEPAD_BUTTON_DPAD_DOWN || | ||
491 | binding_element == SDL_GAMEPAD_BUTTON_DPAD_LEFT || | ||
492 | binding_element == SDL_GAMEPAD_BUTTON_DPAD_RIGHT); | ||
493 | |||
494 | if (native_button) { | ||
495 | bool current_button = (current && *current == 'b'); | ||
496 | bool proposed_button = (binding && *binding == 'b'); | ||
497 | if (current_button && !proposed_button) { | ||
498 | ignore_binding = true; | ||
499 | } | ||
500 | /* Use the lower index button (we map from lower to higher button index) */ | ||
501 | if (current_button && proposed_button && current[1] < binding[1]) { | ||
502 | ignore_binding = true; | ||
503 | } | ||
504 | } | ||
505 | if (native_axis) { | ||
506 | AxisInfo current_axis_info = { 0, 0 }; | ||
507 | AxisInfo proposed_axis_info = { 0, 0 }; | ||
508 | bool current_axis = ParseAxisInfo(current, ¤t_axis_info); | ||
509 | bool proposed_axis = ParseAxisInfo(binding, &proposed_axis_info); | ||
510 | |||
511 | if (current_axis) { | ||
512 | /* Ignore this unless the proposed binding extends the existing axis */ | ||
513 | ignore_binding = true; | ||
514 | |||
515 | if (native_trigger && | ||
516 | ((*current == '-' && *binding == '+' && | ||
517 | SDL_strcmp(current + 1, binding + 1) == 0) || | ||
518 | (*current == '+' && *binding == '-' && | ||
519 | SDL_strcmp(current + 1, binding + 1) == 0))) { | ||
520 | /* Merge two half axes into a whole axis for a trigger */ | ||
521 | ++binding; | ||
522 | ignore_binding = false; | ||
523 | } | ||
524 | |||
525 | /* Use the lower index axis (we map from lower to higher axis index) */ | ||
526 | if (proposed_axis && proposed_axis_info.axis < current_axis_info.axis) { | ||
527 | ignore_binding = false; | ||
528 | } | ||
529 | } | ||
530 | } | ||
531 | if (native_dpad) { | ||
532 | bool current_hat = (current && *current == 'h'); | ||
533 | bool proposed_hat = (binding && *binding == 'h'); | ||
534 | if (current_hat && !proposed_hat) { | ||
535 | ignore_binding = true; | ||
536 | } | ||
537 | /* Use the lower index hat (we map from lower to higher hat index) */ | ||
538 | if (current_hat && proposed_hat && current[1] < binding[1]) { | ||
539 | ignore_binding = true; | ||
540 | } | ||
541 | } | ||
542 | SDL_free(current); | ||
543 | } | ||
544 | |||
545 | if (!ignore_binding && binding_flow && !force) { | ||
546 | int existing = GetElementForBinding(mapping, binding); | ||
547 | if (existing != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
548 | SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH; | ||
549 | SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST; | ||
550 | SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST; | ||
551 | if (binding_element == action_forward) { | ||
552 | /* Bind it! */ | ||
553 | } else if (binding_element == action_backward) { | ||
554 | if (existing == action_forward) { | ||
555 | bool bound_backward = MappingHasElement(controller->mapping, action_backward); | ||
556 | if (bound_backward) { | ||
557 | /* Just move on to the next one */ | ||
558 | ignore_binding = true; | ||
559 | SetNextBindingElement(); | ||
560 | } else { | ||
561 | /* You can't skip the backward action, go back and start over */ | ||
562 | ignore_binding = true; | ||
563 | SetPrevBindingElement(); | ||
564 | } | ||
565 | } else if (existing == action_backward && binding_flow_direction == -1) { | ||
566 | /* Keep going backwards */ | ||
567 | ignore_binding = true; | ||
568 | SetPrevBindingElement(); | ||
569 | } else { | ||
570 | /* Bind it! */ | ||
571 | } | ||
572 | } else if (existing == action_forward) { | ||
573 | /* Just move on to the next one */ | ||
574 | ignore_binding = true; | ||
575 | SetNextBindingElement(); | ||
576 | } else if (existing == action_backward) { | ||
577 | ignore_binding = true; | ||
578 | SetPrevBindingElement(); | ||
579 | } else if (existing == binding_element) { | ||
580 | /* We're rebinding the same thing, just move to the next one */ | ||
581 | ignore_binding = true; | ||
582 | SetNextBindingElement(); | ||
583 | } else if (existing == action_delete) { | ||
584 | /* Clear the current binding and move to the next one */ | ||
585 | binding = NULL; | ||
586 | direction = 1; | ||
587 | force = true; | ||
588 | } else if (binding_element != action_forward && | ||
589 | binding_element != action_backward) { | ||
590 | /* Actually, we'll just clear the existing binding */ | ||
591 | /*ignore_binding = true;*/ | ||
592 | } | ||
593 | } | ||
594 | } | ||
595 | |||
596 | if (ignore_binding) { | ||
597 | SDL_free(mapping); | ||
598 | return; | ||
599 | } | ||
600 | |||
601 | mapping = ClearMappingBinding(mapping, binding); | ||
602 | mapping = SetElementBinding(mapping, binding_element, binding); | ||
603 | SetAndFreeGamepadMapping(mapping); | ||
604 | |||
605 | if (force) { | ||
606 | if (binding_flow) { | ||
607 | if (direction > 0) { | ||
608 | SetNextBindingElement(); | ||
609 | } else if (direction < 0) { | ||
610 | SetPrevBindingElement(); | ||
611 | } | ||
612 | } else { | ||
613 | StopBinding(); | ||
614 | } | ||
615 | } else { | ||
616 | /* Wait to see if any more bindings come in */ | ||
617 | binding_advance_time = SDL_GetTicks() + 30; | ||
618 | } | ||
619 | } | ||
620 | |||
621 | static void ClearBinding(void) | ||
622 | { | ||
623 | CommitBindingElement(NULL, true); | ||
624 | } | ||
625 | |||
626 | static void SetDisplayMode(ControllerDisplayMode mode) | ||
627 | { | ||
628 | float x, y; | ||
629 | SDL_MouseButtonFlags button_state; | ||
630 | |||
631 | if (mode == CONTROLLER_MODE_BINDING) { | ||
632 | /* Make a backup of the current mapping */ | ||
633 | if (controller->mapping) { | ||
634 | backup_mapping = SDL_strdup(controller->mapping); | ||
635 | } | ||
636 | mapping_controller = controller->id; | ||
637 | if (MappingHasBindings(backup_mapping)) { | ||
638 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
639 | } else { | ||
640 | SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_SOUTH, true); | ||
641 | } | ||
642 | } else { | ||
643 | if (backup_mapping) { | ||
644 | SDL_free(backup_mapping); | ||
645 | backup_mapping = NULL; | ||
646 | } | ||
647 | mapping_controller = 0; | ||
648 | StopBinding(); | ||
649 | } | ||
650 | |||
651 | display_mode = mode; | ||
652 | SetGamepadImageDisplayMode(image, mode); | ||
653 | SetGamepadDisplayDisplayMode(gamepad_elements, mode); | ||
654 | |||
655 | button_state = SDL_GetMouseState(&x, &y); | ||
656 | SDL_RenderCoordinatesFromWindow(screen, x, y, &x, &y); | ||
657 | UpdateButtonHighlights(x, y, button_state ? true : false); | ||
658 | } | ||
659 | |||
660 | static void CancelMapping(void) | ||
661 | { | ||
662 | SetAndFreeGamepadMapping(backup_mapping); | ||
663 | backup_mapping = NULL; | ||
664 | |||
665 | SetDisplayMode(CONTROLLER_MODE_TESTING); | ||
666 | } | ||
667 | |||
668 | static void ClearMapping(void) | ||
669 | { | ||
670 | SetAndFreeGamepadMapping(NULL); | ||
671 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
672 | } | ||
673 | |||
674 | static void CopyMapping(void) | ||
675 | { | ||
676 | if (controller && controller->mapping) { | ||
677 | SDL_SetClipboardText(controller->mapping); | ||
678 | } | ||
679 | } | ||
680 | |||
681 | static void PasteMapping(void) | ||
682 | { | ||
683 | if (controller) { | ||
684 | char *mapping = SDL_GetClipboardText(); | ||
685 | if (MappingHasBindings(mapping)) { | ||
686 | StopBinding(); | ||
687 | SDL_SetGamepadMapping(controller->id, mapping); | ||
688 | RefreshControllerName(); | ||
689 | } else { | ||
690 | /* Not a valid mapping, ignore it */ | ||
691 | } | ||
692 | SDL_free(mapping); | ||
693 | } | ||
694 | } | ||
695 | |||
696 | static void CommitControllerName(void) | ||
697 | { | ||
698 | char *mapping = NULL; | ||
699 | |||
700 | if (controller->mapping) { | ||
701 | mapping = SDL_strdup(controller->mapping); | ||
702 | } else { | ||
703 | mapping = NULL; | ||
704 | } | ||
705 | mapping = SetMappingName(mapping, controller_name); | ||
706 | SetAndFreeGamepadMapping(mapping); | ||
707 | } | ||
708 | |||
709 | static void AddControllerNameText(const char *text) | ||
710 | { | ||
711 | size_t current_length = (controller_name ? SDL_strlen(controller_name) : 0); | ||
712 | size_t text_length = SDL_strlen(text); | ||
713 | size_t size = current_length + text_length + 1; | ||
714 | char *name = (char *)SDL_realloc(controller_name, size); | ||
715 | if (name) { | ||
716 | SDL_memcpy(&name[current_length], text, text_length + 1); | ||
717 | controller_name = name; | ||
718 | } | ||
719 | CommitControllerName(); | ||
720 | } | ||
721 | |||
722 | static void BackspaceControllerName(void) | ||
723 | { | ||
724 | size_t length = (controller_name ? SDL_strlen(controller_name) : 0); | ||
725 | if (length > 0) { | ||
726 | controller_name[length - 1] = '\0'; | ||
727 | } | ||
728 | CommitControllerName(); | ||
729 | } | ||
730 | |||
731 | static void ClearControllerName(void) | ||
732 | { | ||
733 | if (controller_name) { | ||
734 | *controller_name = '\0'; | ||
735 | } | ||
736 | CommitControllerName(); | ||
737 | } | ||
738 | |||
739 | static void CopyControllerName(void) | ||
740 | { | ||
741 | SDL_SetClipboardText(controller_name); | ||
742 | } | ||
743 | |||
744 | static void PasteControllerName(void) | ||
745 | { | ||
746 | SDL_free(controller_name); | ||
747 | controller_name = SDL_GetClipboardText(); | ||
748 | CommitControllerName(); | ||
749 | } | ||
750 | |||
751 | static void CommitGamepadType(SDL_GamepadType type) | ||
752 | { | ||
753 | char *mapping = NULL; | ||
754 | |||
755 | if (controller->mapping) { | ||
756 | mapping = SDL_strdup(controller->mapping); | ||
757 | } else { | ||
758 | mapping = NULL; | ||
759 | } | ||
760 | mapping = SetMappingType(mapping, type); | ||
761 | SetAndFreeGamepadMapping(mapping); | ||
762 | } | ||
763 | |||
764 | static const char *GetBindingInstruction(void) | ||
765 | { | ||
766 | switch (binding_element) { | ||
767 | case SDL_GAMEPAD_ELEMENT_INVALID: | ||
768 | return "Select an element to bind from the list on the left"; | ||
769 | case SDL_GAMEPAD_BUTTON_SOUTH: | ||
770 | case SDL_GAMEPAD_BUTTON_EAST: | ||
771 | case SDL_GAMEPAD_BUTTON_WEST: | ||
772 | case SDL_GAMEPAD_BUTTON_NORTH: | ||
773 | switch (SDL_GetGamepadButtonLabelForType(GetGamepadImageType(image), (SDL_GamepadButton)binding_element)) { | ||
774 | case SDL_GAMEPAD_BUTTON_LABEL_A: | ||
775 | return "Press the A button"; | ||
776 | case SDL_GAMEPAD_BUTTON_LABEL_B: | ||
777 | return "Press the B button"; | ||
778 | case SDL_GAMEPAD_BUTTON_LABEL_X: | ||
779 | return "Press the X button"; | ||
780 | case SDL_GAMEPAD_BUTTON_LABEL_Y: | ||
781 | return "Press the Y button"; | ||
782 | case SDL_GAMEPAD_BUTTON_LABEL_CROSS: | ||
783 | return "Press the Cross (X) button"; | ||
784 | case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: | ||
785 | return "Press the Circle button"; | ||
786 | case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: | ||
787 | return "Press the Square button"; | ||
788 | case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: | ||
789 | return "Press the Triangle button"; | ||
790 | default: | ||
791 | return ""; | ||
792 | } | ||
793 | break; | ||
794 | case SDL_GAMEPAD_BUTTON_BACK: | ||
795 | return "Press the left center button (Back/View/Share)"; | ||
796 | case SDL_GAMEPAD_BUTTON_GUIDE: | ||
797 | return "Press the center button (Home/Guide)"; | ||
798 | case SDL_GAMEPAD_BUTTON_START: | ||
799 | return "Press the right center button (Start/Menu/Options)"; | ||
800 | case SDL_GAMEPAD_BUTTON_LEFT_STICK: | ||
801 | return "Press the left thumbstick button (LSB/L3)"; | ||
802 | case SDL_GAMEPAD_BUTTON_RIGHT_STICK: | ||
803 | return "Press the right thumbstick button (RSB/R3)"; | ||
804 | case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: | ||
805 | return "Press the left shoulder button (LB/L1)"; | ||
806 | case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: | ||
807 | return "Press the right shoulder button (RB/R1)"; | ||
808 | case SDL_GAMEPAD_BUTTON_DPAD_UP: | ||
809 | return "Press the D-Pad up"; | ||
810 | case SDL_GAMEPAD_BUTTON_DPAD_DOWN: | ||
811 | return "Press the D-Pad down"; | ||
812 | case SDL_GAMEPAD_BUTTON_DPAD_LEFT: | ||
813 | return "Press the D-Pad left"; | ||
814 | case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: | ||
815 | return "Press the D-Pad right"; | ||
816 | case SDL_GAMEPAD_BUTTON_MISC1: | ||
817 | return "Press the bottom center button (Share/Capture)"; | ||
818 | case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: | ||
819 | return "Press the upper paddle under your right hand"; | ||
820 | case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: | ||
821 | return "Press the upper paddle under your left hand"; | ||
822 | case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: | ||
823 | return "Press the lower paddle under your right hand"; | ||
824 | case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: | ||
825 | return "Press the lower paddle under your left hand"; | ||
826 | case SDL_GAMEPAD_BUTTON_TOUCHPAD: | ||
827 | return "Press down on the touchpad"; | ||
828 | case SDL_GAMEPAD_BUTTON_MISC2: | ||
829 | case SDL_GAMEPAD_BUTTON_MISC3: | ||
830 | case SDL_GAMEPAD_BUTTON_MISC4: | ||
831 | case SDL_GAMEPAD_BUTTON_MISC5: | ||
832 | case SDL_GAMEPAD_BUTTON_MISC6: | ||
833 | return "Press any additional button not already bound"; | ||
834 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: | ||
835 | return "Move the left thumbstick to the left"; | ||
836 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: | ||
837 | return "Move the left thumbstick to the right"; | ||
838 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: | ||
839 | return "Move the left thumbstick up"; | ||
840 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: | ||
841 | return "Move the left thumbstick down"; | ||
842 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: | ||
843 | return "Move the right thumbstick to the left"; | ||
844 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: | ||
845 | return "Move the right thumbstick to the right"; | ||
846 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: | ||
847 | return "Move the right thumbstick up"; | ||
848 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: | ||
849 | return "Move the right thumbstick down"; | ||
850 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: | ||
851 | return "Pull the left trigger (LT/L2)"; | ||
852 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: | ||
853 | return "Pull the right trigger (RT/R2)"; | ||
854 | case SDL_GAMEPAD_ELEMENT_NAME: | ||
855 | return "Type the name of your controller"; | ||
856 | case SDL_GAMEPAD_ELEMENT_TYPE: | ||
857 | return "Select the type of your controller"; | ||
858 | default: | ||
859 | return ""; | ||
860 | } | ||
861 | } | ||
862 | |||
863 | static int FindController(SDL_JoystickID id) | ||
864 | { | ||
865 | int i; | ||
866 | |||
867 | for (i = 0; i < num_controllers; ++i) { | ||
868 | if (id == controllers[i].id) { | ||
869 | return i; | ||
870 | } | ||
871 | } | ||
872 | return -1; | ||
873 | } | ||
874 | |||
875 | static void SetController(SDL_JoystickID id) | ||
876 | { | ||
877 | int i = FindController(id); | ||
878 | |||
879 | if (i < 0 && num_controllers > 0) { | ||
880 | i = 0; | ||
881 | } | ||
882 | |||
883 | if (i >= 0) { | ||
884 | controller = &controllers[i]; | ||
885 | } else { | ||
886 | controller = NULL; | ||
887 | } | ||
888 | |||
889 | RefreshControllerName(); | ||
890 | } | ||
891 | |||
892 | static void AddController(SDL_JoystickID id, bool verbose) | ||
893 | { | ||
894 | Controller *new_controllers; | ||
895 | Controller *new_controller; | ||
896 | SDL_Joystick *joystick; | ||
897 | |||
898 | if (FindController(id) >= 0) { | ||
899 | /* We already have this controller */ | ||
900 | return; | ||
901 | } | ||
902 | |||
903 | new_controllers = (Controller *)SDL_realloc(controllers, (num_controllers + 1) * sizeof(*controllers)); | ||
904 | if (!new_controllers) { | ||
905 | return; | ||
906 | } | ||
907 | |||
908 | controller = NULL; | ||
909 | controllers = new_controllers; | ||
910 | new_controller = &new_controllers[num_controllers++]; | ||
911 | SDL_zerop(new_controller); | ||
912 | new_controller->id = id; | ||
913 | |||
914 | new_controller->joystick = SDL_OpenJoystick(id); | ||
915 | if (new_controller->joystick) { | ||
916 | new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick); | ||
917 | new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state)); | ||
918 | } | ||
919 | |||
920 | joystick = new_controller->joystick; | ||
921 | if (joystick) { | ||
922 | if (verbose && !SDL_IsGamepad(id)) { | ||
923 | const char *name = SDL_GetJoystickName(joystick); | ||
924 | const char *path = SDL_GetJoystickPath(joystick); | ||
925 | char guid[33]; | ||
926 | SDL_Log("Opened joystick %s%s%s", name, path ? ", " : "", path ? path : ""); | ||
927 | SDL_GUIDToString(SDL_GetJoystickGUID(joystick), guid, sizeof(guid)); | ||
928 | SDL_Log("No gamepad mapping for %s", guid); | ||
929 | } | ||
930 | } else { | ||
931 | SDL_Log("Couldn't open joystick: %s", SDL_GetError()); | ||
932 | } | ||
933 | |||
934 | if (mapping_controller) { | ||
935 | SetController(mapping_controller); | ||
936 | } else { | ||
937 | SetController(id); | ||
938 | } | ||
939 | } | ||
940 | |||
941 | static void DelController(SDL_JoystickID id) | ||
942 | { | ||
943 | int i = FindController(id); | ||
944 | |||
945 | if (i < 0) { | ||
946 | return; | ||
947 | } | ||
948 | |||
949 | if (display_mode == CONTROLLER_MODE_BINDING && id == controller->id) { | ||
950 | SetDisplayMode(CONTROLLER_MODE_TESTING); | ||
951 | } | ||
952 | |||
953 | /* Reset trigger state */ | ||
954 | if (controllers[i].trigger_effect != 0) { | ||
955 | controllers[i].trigger_effect = -1; | ||
956 | CyclePS5TriggerEffect(&controllers[i]); | ||
957 | } | ||
958 | SDL_assert(controllers[i].gamepad == NULL); | ||
959 | if (controllers[i].axis_state) { | ||
960 | SDL_free(controllers[i].axis_state); | ||
961 | } | ||
962 | if (controllers[i].joystick) { | ||
963 | SDL_CloseJoystick(controllers[i].joystick); | ||
964 | } | ||
965 | |||
966 | --num_controllers; | ||
967 | if (i < num_controllers) { | ||
968 | SDL_memcpy(&controllers[i], &controllers[i + 1], (num_controllers - i) * sizeof(*controllers)); | ||
969 | } | ||
970 | |||
971 | if (mapping_controller) { | ||
972 | SetController(mapping_controller); | ||
973 | } else { | ||
974 | SetController(id); | ||
975 | } | ||
976 | } | ||
977 | |||
978 | static void HandleGamepadRemapped(SDL_JoystickID id) | ||
979 | { | ||
980 | char *mapping; | ||
981 | int i = FindController(id); | ||
982 | |||
983 | SDL_assert(i >= 0); | ||
984 | if (i < 0) { | ||
985 | return; | ||
986 | } | ||
987 | |||
988 | if (!controllers[i].gamepad) { | ||
989 | /* Failed to open this controller */ | ||
990 | return; | ||
991 | } | ||
992 | |||
993 | /* Get the current mapping */ | ||
994 | mapping = SDL_GetGamepadMapping(controllers[i].gamepad); | ||
995 | |||
996 | /* Make sure the mapping has a valid name */ | ||
997 | if (mapping && !MappingHasName(mapping)) { | ||
998 | mapping = SetMappingName(mapping, SDL_GetJoystickName(controllers[i].joystick)); | ||
999 | } | ||
1000 | |||
1001 | SDL_free(controllers[i].mapping); | ||
1002 | controllers[i].mapping = mapping; | ||
1003 | controllers[i].has_bindings = MappingHasBindings(mapping); | ||
1004 | } | ||
1005 | |||
1006 | static void HandleGamepadAdded(SDL_JoystickID id, bool verbose) | ||
1007 | { | ||
1008 | SDL_Gamepad *gamepad; | ||
1009 | Uint16 firmware_version; | ||
1010 | SDL_SensorType sensors[] = { | ||
1011 | SDL_SENSOR_ACCEL, | ||
1012 | SDL_SENSOR_GYRO, | ||
1013 | SDL_SENSOR_ACCEL_L, | ||
1014 | SDL_SENSOR_GYRO_L, | ||
1015 | SDL_SENSOR_ACCEL_R, | ||
1016 | SDL_SENSOR_GYRO_R | ||
1017 | }; | ||
1018 | int i; | ||
1019 | |||
1020 | i = FindController(id); | ||
1021 | if (i < 0) { | ||
1022 | return; | ||
1023 | } | ||
1024 | SDL_Log("Gamepad %" SDL_PRIu32 " added", id); | ||
1025 | |||
1026 | SDL_assert(!controllers[i].gamepad); | ||
1027 | controllers[i].gamepad = SDL_OpenGamepad(id); | ||
1028 | |||
1029 | gamepad = controllers[i].gamepad; | ||
1030 | if (gamepad) { | ||
1031 | if (verbose) { | ||
1032 | SDL_PropertiesID props = SDL_GetGamepadProperties(gamepad); | ||
1033 | const char *name = SDL_GetGamepadName(gamepad); | ||
1034 | const char *path = SDL_GetGamepadPath(gamepad); | ||
1035 | SDL_GUID guid = SDL_GetGamepadGUIDForID(id); | ||
1036 | char guid_string[33]; | ||
1037 | SDL_GUIDToString(guid, guid_string, sizeof(guid_string)); | ||
1038 | SDL_Log("Opened gamepad %s, guid %s%s%s", name, guid_string, path ? ", " : "", path ? path : ""); | ||
1039 | |||
1040 | firmware_version = SDL_GetGamepadFirmwareVersion(gamepad); | ||
1041 | if (firmware_version) { | ||
1042 | SDL_Log("Firmware version: 0x%x (%d)", firmware_version, firmware_version); | ||
1043 | } | ||
1044 | |||
1045 | if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN, false)) { | ||
1046 | SDL_Log("Has player LED"); | ||
1047 | } | ||
1048 | |||
1049 | if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false)) { | ||
1050 | SDL_Log("Rumble supported"); | ||
1051 | } | ||
1052 | |||
1053 | if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN, false)) { | ||
1054 | SDL_Log("Trigger rumble supported"); | ||
1055 | } | ||
1056 | |||
1057 | if (SDL_GetGamepadPlayerIndex(gamepad) >= 0) { | ||
1058 | SDL_Log("Player index: %d", SDL_GetGamepadPlayerIndex(gamepad)); | ||
1059 | } | ||
1060 | |||
1061 | switch (SDL_GetJoystickTypeForID(id)) { | ||
1062 | case SDL_JOYSTICK_TYPE_WHEEL: | ||
1063 | SDL_Log("Controller is a wheel"); | ||
1064 | break; | ||
1065 | case SDL_JOYSTICK_TYPE_ARCADE_STICK: | ||
1066 | SDL_Log("Controller is an arcade stick"); | ||
1067 | break; | ||
1068 | case SDL_JOYSTICK_TYPE_FLIGHT_STICK: | ||
1069 | SDL_Log("Controller is a flight stick"); | ||
1070 | break; | ||
1071 | case SDL_JOYSTICK_TYPE_DANCE_PAD: | ||
1072 | SDL_Log("Controller is a dance pad"); | ||
1073 | break; | ||
1074 | case SDL_JOYSTICK_TYPE_GUITAR: | ||
1075 | SDL_Log("Controller is a guitar"); | ||
1076 | break; | ||
1077 | case SDL_JOYSTICK_TYPE_DRUM_KIT: | ||
1078 | SDL_Log("Controller is a drum kit"); | ||
1079 | break; | ||
1080 | case SDL_JOYSTICK_TYPE_ARCADE_PAD: | ||
1081 | SDL_Log("Controller is an arcade pad"); | ||
1082 | break; | ||
1083 | case SDL_JOYSTICK_TYPE_THROTTLE: | ||
1084 | SDL_Log("Controller is a throttle"); | ||
1085 | break; | ||
1086 | default: | ||
1087 | break; | ||
1088 | } | ||
1089 | } | ||
1090 | |||
1091 | for (i = 0; i < SDL_arraysize(sensors); ++i) { | ||
1092 | SDL_SensorType sensor = sensors[i]; | ||
1093 | |||
1094 | if (SDL_GamepadHasSensor(gamepad, sensor)) { | ||
1095 | if (verbose) { | ||
1096 | SDL_Log("Enabling %s at %.2f Hz", GetSensorName(sensor), SDL_GetGamepadSensorDataRate(gamepad, sensor)); | ||
1097 | } | ||
1098 | SDL_SetGamepadSensorEnabled(gamepad, sensor, true); | ||
1099 | } | ||
1100 | } | ||
1101 | |||
1102 | if (verbose) { | ||
1103 | char *mapping = SDL_GetGamepadMapping(gamepad); | ||
1104 | if (mapping) { | ||
1105 | SDL_Log("Mapping: %s", mapping); | ||
1106 | SDL_free(mapping); | ||
1107 | } | ||
1108 | } | ||
1109 | } else { | ||
1110 | SDL_Log("Couldn't open gamepad: %s", SDL_GetError()); | ||
1111 | } | ||
1112 | |||
1113 | HandleGamepadRemapped(id); | ||
1114 | SetController(id); | ||
1115 | } | ||
1116 | |||
1117 | static void HandleGamepadRemoved(SDL_JoystickID id) | ||
1118 | { | ||
1119 | int i = FindController(id); | ||
1120 | |||
1121 | SDL_assert(i >= 0); | ||
1122 | if (i < 0) { | ||
1123 | return; | ||
1124 | } | ||
1125 | SDL_Log("Gamepad %" SDL_PRIu32 " removed", id); | ||
1126 | |||
1127 | if (controllers[i].mapping) { | ||
1128 | SDL_free(controllers[i].mapping); | ||
1129 | controllers[i].mapping = NULL; | ||
1130 | } | ||
1131 | if (controllers[i].gamepad) { | ||
1132 | SDL_CloseGamepad(controllers[i].gamepad); | ||
1133 | controllers[i].gamepad = NULL; | ||
1134 | } | ||
1135 | } | ||
1136 | |||
1137 | static Uint16 ConvertAxisToRumble(Sint16 axisval) | ||
1138 | { | ||
1139 | /* Only start rumbling if the axis is past the halfway point */ | ||
1140 | const Sint16 half_axis = (Sint16)SDL_ceil(SDL_JOYSTICK_AXIS_MAX / 2.0f); | ||
1141 | if (axisval > half_axis) { | ||
1142 | return (Uint16)(axisval - half_axis) * 4; | ||
1143 | } else { | ||
1144 | return 0; | ||
1145 | } | ||
1146 | } | ||
1147 | |||
1148 | static bool ShowingFront(void) | ||
1149 | { | ||
1150 | bool showing_front = true; | ||
1151 | int i; | ||
1152 | |||
1153 | /* Show the back of the gamepad if the paddles are being held or bound */ | ||
1154 | for (i = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; ++i) { | ||
1155 | if (SDL_GetGamepadButton(controller->gamepad, (SDL_GamepadButton)i) || | ||
1156 | binding_element == i) { | ||
1157 | showing_front = false; | ||
1158 | break; | ||
1159 | } | ||
1160 | } | ||
1161 | if ((SDL_GetModState() & SDL_KMOD_SHIFT) && binding_element != SDL_GAMEPAD_ELEMENT_NAME) { | ||
1162 | showing_front = false; | ||
1163 | } | ||
1164 | return showing_front; | ||
1165 | } | ||
1166 | |||
1167 | static void SDLCALL VirtualGamepadSetPlayerIndex(void *userdata, int player_index) | ||
1168 | { | ||
1169 | SDL_Log("Virtual Gamepad: player index set to %d", player_index); | ||
1170 | } | ||
1171 | |||
1172 | static bool SDLCALL VirtualGamepadRumble(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
1173 | { | ||
1174 | SDL_Log("Virtual Gamepad: rumble set to %d/%d", low_frequency_rumble, high_frequency_rumble); | ||
1175 | return true; | ||
1176 | } | ||
1177 | |||
1178 | static bool SDLCALL VirtualGamepadRumbleTriggers(void *userdata, Uint16 left_rumble, Uint16 right_rumble) | ||
1179 | { | ||
1180 | SDL_Log("Virtual Gamepad: trigger rumble set to %d/%d", left_rumble, right_rumble); | ||
1181 | return true; | ||
1182 | } | ||
1183 | |||
1184 | static bool SDLCALL VirtualGamepadSetLED(void *userdata, Uint8 red, Uint8 green, Uint8 blue) | ||
1185 | { | ||
1186 | SDL_Log("Virtual Gamepad: LED set to RGB %d,%d,%d", red, green, blue); | ||
1187 | return true; | ||
1188 | } | ||
1189 | |||
1190 | static void OpenVirtualGamepad(void) | ||
1191 | { | ||
1192 | SDL_VirtualJoystickTouchpadDesc virtual_touchpad = { 1, { 0, 0, 0 } }; | ||
1193 | SDL_VirtualJoystickSensorDesc virtual_sensor = { SDL_SENSOR_ACCEL, 0.0f }; | ||
1194 | SDL_VirtualJoystickDesc desc; | ||
1195 | SDL_JoystickID virtual_id; | ||
1196 | |||
1197 | if (virtual_joystick) { | ||
1198 | return; | ||
1199 | } | ||
1200 | |||
1201 | SDL_INIT_INTERFACE(&desc); | ||
1202 | desc.type = SDL_JOYSTICK_TYPE_GAMEPAD; | ||
1203 | desc.naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
1204 | desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT; | ||
1205 | desc.ntouchpads = 1; | ||
1206 | desc.touchpads = &virtual_touchpad; | ||
1207 | desc.nsensors = 1; | ||
1208 | desc.sensors = &virtual_sensor; | ||
1209 | desc.SetPlayerIndex = VirtualGamepadSetPlayerIndex; | ||
1210 | desc.Rumble = VirtualGamepadRumble; | ||
1211 | desc.RumbleTriggers = VirtualGamepadRumbleTriggers; | ||
1212 | desc.SetLED = VirtualGamepadSetLED; | ||
1213 | |||
1214 | virtual_id = SDL_AttachVirtualJoystick(&desc); | ||
1215 | if (virtual_id == 0) { | ||
1216 | SDL_Log("Couldn't attach virtual device: %s", SDL_GetError()); | ||
1217 | } else { | ||
1218 | virtual_joystick = SDL_OpenJoystick(virtual_id); | ||
1219 | if (!virtual_joystick) { | ||
1220 | SDL_Log("Couldn't open virtual device: %s", SDL_GetError()); | ||
1221 | } | ||
1222 | } | ||
1223 | } | ||
1224 | |||
1225 | static void CloseVirtualGamepad(void) | ||
1226 | { | ||
1227 | int i; | ||
1228 | SDL_JoystickID *joysticks = SDL_GetJoysticks(NULL); | ||
1229 | if (joysticks) { | ||
1230 | for (i = 0; joysticks[i]; ++i) { | ||
1231 | SDL_JoystickID instance_id = joysticks[i]; | ||
1232 | if (SDL_IsJoystickVirtual(instance_id)) { | ||
1233 | SDL_DetachVirtualJoystick(instance_id); | ||
1234 | } | ||
1235 | } | ||
1236 | SDL_free(joysticks); | ||
1237 | } | ||
1238 | |||
1239 | if (virtual_joystick) { | ||
1240 | SDL_CloseJoystick(virtual_joystick); | ||
1241 | virtual_joystick = NULL; | ||
1242 | } | ||
1243 | } | ||
1244 | |||
1245 | static void VirtualGamepadMouseMotion(float x, float y) | ||
1246 | { | ||
1247 | if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) { | ||
1248 | if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { | ||
1249 | const float MOVING_DISTANCE = 2.0f; | ||
1250 | if (SDL_fabs(x - virtual_axis_start_x) >= MOVING_DISTANCE || | ||
1251 | SDL_fabs(y - virtual_axis_start_y) >= MOVING_DISTANCE) { | ||
1252 | SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false); | ||
1253 | virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; | ||
1254 | } | ||
1255 | } | ||
1256 | } | ||
1257 | |||
1258 | if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { | ||
1259 | if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || | ||
1260 | virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { | ||
1261 | int range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN); | ||
1262 | float distance = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), 0.0f, 1.0f); | ||
1263 | Sint16 value = (Sint16)(SDL_JOYSTICK_AXIS_MIN + (distance * range)); | ||
1264 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, value); | ||
1265 | } else { | ||
1266 | float distanceX = SDL_clamp((x - virtual_axis_start_x) / GetGamepadImageAxisWidth(image), -1.0f, 1.0f); | ||
1267 | float distanceY = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), -1.0f, 1.0f); | ||
1268 | Sint16 valueX, valueY; | ||
1269 | |||
1270 | if (distanceX >= 0) { | ||
1271 | valueX = (Sint16)(distanceX * SDL_JOYSTICK_AXIS_MAX); | ||
1272 | } else { | ||
1273 | valueX = (Sint16)(distanceX * -SDL_JOYSTICK_AXIS_MIN); | ||
1274 | } | ||
1275 | if (distanceY >= 0) { | ||
1276 | valueY = (Sint16)(distanceY * SDL_JOYSTICK_AXIS_MAX); | ||
1277 | } else { | ||
1278 | valueY = (Sint16)(distanceY * -SDL_JOYSTICK_AXIS_MIN); | ||
1279 | } | ||
1280 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, valueX); | ||
1281 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, valueY); | ||
1282 | } | ||
1283 | } | ||
1284 | |||
1285 | if (virtual_touchpad_active) { | ||
1286 | SDL_FRect touchpad; | ||
1287 | GetGamepadTouchpadArea(image, &touchpad); | ||
1288 | virtual_touchpad_x = (x - touchpad.x) / touchpad.w; | ||
1289 | virtual_touchpad_y = (y - touchpad.y) / touchpad.h; | ||
1290 | SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f); | ||
1291 | } | ||
1292 | } | ||
1293 | |||
1294 | static void VirtualGamepadMouseDown(float x, float y) | ||
1295 | { | ||
1296 | int element = GetGamepadImageElementAt(image, x, y); | ||
1297 | |||
1298 | if (element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
1299 | SDL_FPoint point = { x, y }; | ||
1300 | SDL_FRect touchpad; | ||
1301 | GetGamepadTouchpadArea(image, &touchpad); | ||
1302 | if (SDL_PointInRectFloat(&point, &touchpad)) { | ||
1303 | virtual_touchpad_active = true; | ||
1304 | virtual_touchpad_x = (x - touchpad.x) / touchpad.w; | ||
1305 | virtual_touchpad_y = (y - touchpad.y) / touchpad.h; | ||
1306 | SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f); | ||
1307 | } | ||
1308 | return; | ||
1309 | } | ||
1310 | |||
1311 | if (element < SDL_GAMEPAD_BUTTON_COUNT) { | ||
1312 | virtual_button_active = (SDL_GamepadButton)element; | ||
1313 | SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, true); | ||
1314 | } else { | ||
1315 | switch (element) { | ||
1316 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: | ||
1317 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: | ||
1318 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: | ||
1319 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: | ||
1320 | virtual_axis_active = SDL_GAMEPAD_AXIS_LEFTX; | ||
1321 | break; | ||
1322 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: | ||
1323 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: | ||
1324 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: | ||
1325 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: | ||
1326 | virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHTX; | ||
1327 | break; | ||
1328 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: | ||
1329 | virtual_axis_active = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; | ||
1330 | break; | ||
1331 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: | ||
1332 | virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; | ||
1333 | break; | ||
1334 | } | ||
1335 | virtual_axis_start_x = x; | ||
1336 | virtual_axis_start_y = y; | ||
1337 | } | ||
1338 | } | ||
1339 | |||
1340 | static void VirtualGamepadMouseUp(float x, float y) | ||
1341 | { | ||
1342 | if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) { | ||
1343 | SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false); | ||
1344 | virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; | ||
1345 | } | ||
1346 | |||
1347 | if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { | ||
1348 | if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || | ||
1349 | virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { | ||
1350 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, SDL_JOYSTICK_AXIS_MIN); | ||
1351 | } else { | ||
1352 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, 0); | ||
1353 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, 0); | ||
1354 | } | ||
1355 | virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID; | ||
1356 | } | ||
1357 | |||
1358 | if (virtual_touchpad_active) { | ||
1359 | SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, false, virtual_touchpad_x, virtual_touchpad_y, 0.0f); | ||
1360 | virtual_touchpad_active = false; | ||
1361 | } | ||
1362 | } | ||
1363 | |||
1364 | static void DrawGamepadWaiting(SDL_Renderer *renderer) | ||
1365 | { | ||
1366 | const char *text = "Waiting for gamepad, press A to add a virtual controller"; | ||
1367 | float x, y; | ||
1368 | |||
1369 | x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; | ||
1370 | y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2; | ||
1371 | SDLTest_DrawString(renderer, x, y, text); | ||
1372 | } | ||
1373 | |||
1374 | static void DrawGamepadInfo(SDL_Renderer *renderer) | ||
1375 | { | ||
1376 | const char *type; | ||
1377 | const char *serial; | ||
1378 | char text[128]; | ||
1379 | float x, y; | ||
1380 | |||
1381 | if (title_highlighted) { | ||
1382 | Uint8 r, g, b, a; | ||
1383 | |||
1384 | SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); | ||
1385 | |||
1386 | if (title_pressed) { | ||
1387 | SDL_SetRenderDrawColor(renderer, PRESSED_COLOR); | ||
1388 | } else { | ||
1389 | SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR); | ||
1390 | } | ||
1391 | SDL_RenderFillRect(renderer, &title_area); | ||
1392 | |||
1393 | SDL_SetRenderDrawColor(renderer, r, g, b, a); | ||
1394 | } | ||
1395 | |||
1396 | if (type_highlighted) { | ||
1397 | Uint8 r, g, b, a; | ||
1398 | |||
1399 | SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); | ||
1400 | |||
1401 | if (type_pressed) { | ||
1402 | SDL_SetRenderDrawColor(renderer, PRESSED_COLOR); | ||
1403 | } else { | ||
1404 | SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR); | ||
1405 | } | ||
1406 | SDL_RenderFillRect(renderer, &type_area); | ||
1407 | |||
1408 | SDL_SetRenderDrawColor(renderer, r, g, b, a); | ||
1409 | } | ||
1410 | |||
1411 | if (controller->joystick) { | ||
1412 | SDL_snprintf(text, sizeof(text), "(%" SDL_PRIu32 ")", SDL_GetJoystickID(controller->joystick)); | ||
1413 | x = SCREEN_WIDTH - (FONT_CHARACTER_SIZE * SDL_strlen(text)) - 8.0f; | ||
1414 | y = 8.0f; | ||
1415 | SDLTest_DrawString(renderer, x, y, text); | ||
1416 | } | ||
1417 | |||
1418 | if (controller_name && *controller_name) { | ||
1419 | x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2; | ||
1420 | y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2; | ||
1421 | SDLTest_DrawString(renderer, x, y, controller_name); | ||
1422 | } | ||
1423 | |||
1424 | if (SDL_IsJoystickVirtual(controller->id)) { | ||
1425 | SDL_strlcpy(text, "Click on the gamepad image below to generate input", sizeof(text)); | ||
1426 | x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; | ||
1427 | y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2 + FONT_LINE_HEIGHT + 2.0f; | ||
1428 | SDLTest_DrawString(renderer, x, y, text); | ||
1429 | } | ||
1430 | |||
1431 | type = GetGamepadTypeString(SDL_GetGamepadType(controller->gamepad)); | ||
1432 | x = type_area.x + type_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(type)) / 2; | ||
1433 | y = type_area.y + type_area.h / 2 - FONT_CHARACTER_SIZE / 2; | ||
1434 | SDLTest_DrawString(renderer, x, y, type); | ||
1435 | |||
1436 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
1437 | Uint64 steam_handle = SDL_GetGamepadSteamHandle(controller->gamepad); | ||
1438 | if (steam_handle) { | ||
1439 | SDL_snprintf(text, SDL_arraysize(text), "Steam: 0x%.16" SDL_PRIx64, steam_handle); | ||
1440 | y = SCREEN_HEIGHT - 2 * (8.0f + FONT_LINE_HEIGHT); | ||
1441 | x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); | ||
1442 | SDLTest_DrawString(renderer, x, y, text); | ||
1443 | } | ||
1444 | |||
1445 | SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x", | ||
1446 | SDL_GetJoystickVendor(controller->joystick), | ||
1447 | SDL_GetJoystickProduct(controller->joystick)); | ||
1448 | y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; | ||
1449 | x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); | ||
1450 | SDLTest_DrawString(renderer, x, y, text); | ||
1451 | |||
1452 | serial = SDL_GetJoystickSerial(controller->joystick); | ||
1453 | if (serial && *serial) { | ||
1454 | SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial); | ||
1455 | x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; | ||
1456 | y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; | ||
1457 | SDLTest_DrawString(renderer, x, y, text); | ||
1458 | } | ||
1459 | } | ||
1460 | } | ||
1461 | |||
1462 | static const char *GetButtonLabel(SDL_GamepadType type, SDL_GamepadButton button) | ||
1463 | { | ||
1464 | switch (SDL_GetGamepadButtonLabelForType(type, button)) { | ||
1465 | case SDL_GAMEPAD_BUTTON_LABEL_A: | ||
1466 | return "A"; | ||
1467 | case SDL_GAMEPAD_BUTTON_LABEL_B: | ||
1468 | return "B"; | ||
1469 | case SDL_GAMEPAD_BUTTON_LABEL_X: | ||
1470 | return "X"; | ||
1471 | case SDL_GAMEPAD_BUTTON_LABEL_Y: | ||
1472 | return "Y"; | ||
1473 | case SDL_GAMEPAD_BUTTON_LABEL_CROSS: | ||
1474 | return "Cross (X)"; | ||
1475 | case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: | ||
1476 | return "Circle"; | ||
1477 | case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: | ||
1478 | return "Square"; | ||
1479 | case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: | ||
1480 | return "Triangle"; | ||
1481 | default: | ||
1482 | return "UNKNOWN"; | ||
1483 | } | ||
1484 | } | ||
1485 | |||
1486 | static void DrawBindingTips(SDL_Renderer *renderer) | ||
1487 | { | ||
1488 | const char *text; | ||
1489 | SDL_FRect image_area, button_area; | ||
1490 | float x, y; | ||
1491 | |||
1492 | GetGamepadImageArea(image, &image_area); | ||
1493 | GetGamepadButtonArea(done_mapping_button, &button_area); | ||
1494 | x = image_area.x + image_area.w / 2; | ||
1495 | y = image_area.y + image_area.h; | ||
1496 | y += (button_area.y - y - FONT_CHARACTER_SIZE) / 2; | ||
1497 | |||
1498 | text = GetBindingInstruction(); | ||
1499 | |||
1500 | if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
1501 | SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); | ||
1502 | } else { | ||
1503 | Uint8 r, g, b, a; | ||
1504 | SDL_FRect rect; | ||
1505 | SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH; | ||
1506 | bool bound_forward = MappingHasElement(controller->mapping, action_forward); | ||
1507 | SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST; | ||
1508 | bool bound_backward = MappingHasElement(controller->mapping, action_backward); | ||
1509 | SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST; | ||
1510 | bool bound_delete = MappingHasElement(controller->mapping, action_delete); | ||
1511 | |||
1512 | y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2; | ||
1513 | |||
1514 | rect.w = 2.0f + (FONT_CHARACTER_SIZE * SDL_strlen(text)) + 2.0f; | ||
1515 | rect.h = 2.0f + FONT_CHARACTER_SIZE + 2.0f; | ||
1516 | rect.x = x - rect.w / 2; | ||
1517 | rect.y = y - 2.0f; | ||
1518 | |||
1519 | SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); | ||
1520 | SDL_SetRenderDrawColor(renderer, SELECTED_COLOR); | ||
1521 | SDL_RenderFillRect(renderer, &rect); | ||
1522 | SDL_SetRenderDrawColor(renderer, r, g, b, a); | ||
1523 | SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); | ||
1524 | |||
1525 | y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN); | ||
1526 | |||
1527 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
1528 | text = "(press RETURN to complete)"; | ||
1529 | } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE || | ||
1530 | binding_element == action_forward || | ||
1531 | binding_element == action_backward) { | ||
1532 | text = "(press ESC to cancel)"; | ||
1533 | } else { | ||
1534 | static char dynamic_text[128]; | ||
1535 | SDL_GamepadType type = GetGamepadImageType(image); | ||
1536 | if (binding_flow && bound_forward && bound_backward) { | ||
1537 | if (binding_element != action_delete && bound_delete) { | ||
1538 | SDL_snprintf(dynamic_text, sizeof(dynamic_text), "(press %s to skip, %s to go back, %s to delete, and ESC to cancel)", GetButtonLabel(type, action_forward), GetButtonLabel(type, action_backward), GetButtonLabel(type, action_delete)); | ||
1539 | } else { | ||
1540 | SDL_snprintf(dynamic_text, sizeof(dynamic_text), "(press %s to skip, %s to go back, SPACE to delete, and ESC to cancel)", GetButtonLabel(type, action_forward), GetButtonLabel(type, action_backward)); | ||
1541 | } | ||
1542 | text = dynamic_text; | ||
1543 | } else { | ||
1544 | text = "(press SPACE to delete and ESC to cancel)"; | ||
1545 | } | ||
1546 | } | ||
1547 | SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); | ||
1548 | } | ||
1549 | } | ||
1550 | |||
1551 | static void UpdateGamepadEffects(void) | ||
1552 | { | ||
1553 | if (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) { | ||
1554 | return; | ||
1555 | } | ||
1556 | |||
1557 | /* Update LED based on left thumbstick position */ | ||
1558 | { | ||
1559 | Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX); | ||
1560 | Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); | ||
1561 | |||
1562 | if (!set_LED) { | ||
1563 | set_LED = (x < -8000 || x > 8000 || y > 8000); | ||
1564 | } | ||
1565 | if (set_LED) { | ||
1566 | Uint8 r, g, b; | ||
1567 | |||
1568 | if (x < 0) { | ||
1569 | r = (Uint8)(((~x) * 255) / 32767); | ||
1570 | b = 0; | ||
1571 | } else { | ||
1572 | r = 0; | ||
1573 | b = (Uint8)(((int)(x)*255) / 32767); | ||
1574 | } | ||
1575 | if (y > 0) { | ||
1576 | g = (Uint8)(((int)(y)*255) / 32767); | ||
1577 | } else { | ||
1578 | g = 0; | ||
1579 | } | ||
1580 | |||
1581 | SDL_SetGamepadLED(controller->gamepad, r, g, b); | ||
1582 | } | ||
1583 | } | ||
1584 | |||
1585 | if (controller->trigger_effect == 0) { | ||
1586 | /* Update rumble based on trigger state */ | ||
1587 | { | ||
1588 | Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); | ||
1589 | Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); | ||
1590 | Uint16 low_frequency_rumble = ConvertAxisToRumble(left); | ||
1591 | Uint16 high_frequency_rumble = ConvertAxisToRumble(right); | ||
1592 | SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250); | ||
1593 | } | ||
1594 | |||
1595 | /* Update trigger rumble based on thumbstick state */ | ||
1596 | { | ||
1597 | Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); | ||
1598 | Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY); | ||
1599 | Uint16 left_rumble = ConvertAxisToRumble(~left); | ||
1600 | Uint16 right_rumble = ConvertAxisToRumble(~right); | ||
1601 | |||
1602 | SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250); | ||
1603 | } | ||
1604 | } | ||
1605 | } | ||
1606 | |||
1607 | SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) | ||
1608 | { | ||
1609 | SDL_ConvertEventToRenderCoordinates(screen, event); | ||
1610 | |||
1611 | switch (event->type) { | ||
1612 | case SDL_EVENT_JOYSTICK_ADDED: | ||
1613 | AddController(event->jdevice.which, true); | ||
1614 | break; | ||
1615 | |||
1616 | case SDL_EVENT_JOYSTICK_REMOVED: | ||
1617 | DelController(event->jdevice.which); | ||
1618 | break; | ||
1619 | |||
1620 | case SDL_EVENT_JOYSTICK_AXIS_MOTION: | ||
1621 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
1622 | if (event->jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { | ||
1623 | SetController(event->jaxis.which); | ||
1624 | } | ||
1625 | } else if (display_mode == CONTROLLER_MODE_BINDING && | ||
1626 | event->jaxis.which == controller->id && | ||
1627 | event->jaxis.axis < controller->num_axes && | ||
1628 | binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
1629 | const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 gamepad needed 96 */ | ||
1630 | AxisState *pAxisState = &controller->axis_state[event->jaxis.axis]; | ||
1631 | int nValue = event->jaxis.value; | ||
1632 | int nCurrentDistance, nFarthestDistance; | ||
1633 | if (!pAxisState->m_bMoving) { | ||
1634 | Sint16 nInitialValue; | ||
1635 | pAxisState->m_bMoving = SDL_GetJoystickAxisInitialState(controller->joystick, event->jaxis.axis, &nInitialValue); | ||
1636 | pAxisState->m_nLastValue = nValue; | ||
1637 | pAxisState->m_nStartingValue = nInitialValue; | ||
1638 | pAxisState->m_nFarthestValue = nInitialValue; | ||
1639 | } else if (SDL_abs(nValue - pAxisState->m_nLastValue) <= MAX_ALLOWED_JITTER) { | ||
1640 | break; | ||
1641 | } else { | ||
1642 | pAxisState->m_nLastValue = nValue; | ||
1643 | } | ||
1644 | nCurrentDistance = SDL_abs(nValue - pAxisState->m_nStartingValue); | ||
1645 | nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue); | ||
1646 | if (nCurrentDistance > nFarthestDistance) { | ||
1647 | pAxisState->m_nFarthestValue = nValue; | ||
1648 | nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue); | ||
1649 | } | ||
1650 | |||
1651 | #ifdef DEBUG_AXIS_MAPPING | ||
1652 | SDL_Log("AXIS %d nValue %d nCurrentDistance %d nFarthestDistance %d", event->jaxis.axis, nValue, nCurrentDistance, nFarthestDistance); | ||
1653 | #endif | ||
1654 | /* If we've gone out far enough and started to come back, let's bind this axis */ | ||
1655 | if (nFarthestDistance >= 16000 && nCurrentDistance <= 10000) { | ||
1656 | char binding[12]; | ||
1657 | int axis_min = StandardizeAxisValue(pAxisState->m_nStartingValue); | ||
1658 | int axis_max = StandardizeAxisValue(pAxisState->m_nFarthestValue); | ||
1659 | |||
1660 | if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MIN) { | ||
1661 | /* The negative half axis */ | ||
1662 | (void)SDL_snprintf(binding, sizeof(binding), "-a%d", event->jaxis.axis); | ||
1663 | } else if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MAX) { | ||
1664 | /* The positive half axis */ | ||
1665 | (void)SDL_snprintf(binding, sizeof(binding), "+a%d", event->jaxis.axis); | ||
1666 | } else { | ||
1667 | (void)SDL_snprintf(binding, sizeof(binding), "a%d", event->jaxis.axis); | ||
1668 | if (axis_min > axis_max) { | ||
1669 | /* Invert the axis */ | ||
1670 | SDL_strlcat(binding, "~", SDL_arraysize(binding)); | ||
1671 | } | ||
1672 | } | ||
1673 | #ifdef DEBUG_AXIS_MAPPING | ||
1674 | SDL_Log("AXIS %d axis_min = %d, axis_max = %d, binding = %s", event->jaxis.axis, axis_min, axis_max, binding); | ||
1675 | #endif | ||
1676 | CommitBindingElement(binding, false); | ||
1677 | } | ||
1678 | } | ||
1679 | break; | ||
1680 | |||
1681 | case SDL_EVENT_JOYSTICK_BUTTON_DOWN: | ||
1682 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
1683 | SetController(event->jbutton.which); | ||
1684 | } | ||
1685 | break; | ||
1686 | |||
1687 | case SDL_EVENT_JOYSTICK_BUTTON_UP: | ||
1688 | if (display_mode == CONTROLLER_MODE_BINDING && | ||
1689 | event->jbutton.which == controller->id && | ||
1690 | binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
1691 | char binding[12]; | ||
1692 | |||
1693 | SDL_snprintf(binding, sizeof(binding), "b%d", event->jbutton.button); | ||
1694 | CommitBindingElement(binding, false); | ||
1695 | } | ||
1696 | break; | ||
1697 | |||
1698 | case SDL_EVENT_JOYSTICK_HAT_MOTION: | ||
1699 | if (display_mode == CONTROLLER_MODE_BINDING && | ||
1700 | event->jhat.which == controller->id && | ||
1701 | event->jhat.value != SDL_HAT_CENTERED && | ||
1702 | binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
1703 | char binding[12]; | ||
1704 | |||
1705 | SDL_snprintf(binding, sizeof(binding), "h%d.%d", event->jhat.hat, event->jhat.value); | ||
1706 | CommitBindingElement(binding, false); | ||
1707 | } | ||
1708 | break; | ||
1709 | |||
1710 | case SDL_EVENT_GAMEPAD_ADDED: | ||
1711 | HandleGamepadAdded(event->gdevice.which, true); | ||
1712 | break; | ||
1713 | |||
1714 | case SDL_EVENT_GAMEPAD_REMOVED: | ||
1715 | HandleGamepadRemoved(event->gdevice.which); | ||
1716 | break; | ||
1717 | |||
1718 | case SDL_EVENT_GAMEPAD_REMAPPED: | ||
1719 | HandleGamepadRemapped(event->gdevice.which); | ||
1720 | break; | ||
1721 | |||
1722 | case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: | ||
1723 | RefreshControllerName(); | ||
1724 | break; | ||
1725 | |||
1726 | #ifdef VERBOSE_TOUCHPAD | ||
1727 | case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: | ||
1728 | case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: | ||
1729 | case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: | ||
1730 | SDL_Log("Gamepad %" SDL_PRIu32 " touchpad %" SDL_PRIs32 " finger %" SDL_PRIs32 " %s %.2f, %.2f, %.2f", | ||
1731 | event->gtouchpad.which, | ||
1732 | event->gtouchpad.touchpad, | ||
1733 | event->gtouchpad.finger, | ||
1734 | (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN ? "pressed at" : (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_UP ? "released at" : "moved to")), | ||
1735 | event->gtouchpad.x, | ||
1736 | event->gtouchpad.y, | ||
1737 | event->gtouchpad.pressure); | ||
1738 | break; | ||
1739 | #endif /* VERBOSE_TOUCHPAD */ | ||
1740 | |||
1741 | #ifdef VERBOSE_SENSORS | ||
1742 | case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: | ||
1743 | SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")", | ||
1744 | event->gsensor.which, | ||
1745 | GetSensorName((SDL_SensorType) event->gsensor.sensor), | ||
1746 | event->gsensor.data[0], | ||
1747 | event->gsensor.data[1], | ||
1748 | event->gsensor.data[2], | ||
1749 | event->gsensor.sensor_timestamp); | ||
1750 | break; | ||
1751 | #endif /* VERBOSE_SENSORS */ | ||
1752 | |||
1753 | #ifdef VERBOSE_AXES | ||
1754 | case SDL_EVENT_GAMEPAD_AXIS_MOTION: | ||
1755 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
1756 | if (event->gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { | ||
1757 | SetController(event->gaxis.which); | ||
1758 | } | ||
1759 | } | ||
1760 | SDL_Log("Gamepad %" SDL_PRIu32 " axis %s changed to %d", | ||
1761 | event->gaxis.which, | ||
1762 | SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event->gaxis.axis), | ||
1763 | event->gaxis.value); | ||
1764 | break; | ||
1765 | #endif /* VERBOSE_AXES */ | ||
1766 | |||
1767 | case SDL_EVENT_GAMEPAD_BUTTON_DOWN: | ||
1768 | case SDL_EVENT_GAMEPAD_BUTTON_UP: | ||
1769 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
1770 | if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { | ||
1771 | SetController(event->gbutton.which); | ||
1772 | } | ||
1773 | } | ||
1774 | #ifdef VERBOSE_BUTTONS | ||
1775 | SDL_Log("Gamepad %" SDL_PRIu32 " button %s %s", | ||
1776 | event->gbutton.which, | ||
1777 | SDL_GetGamepadStringForButton((SDL_GamepadButton) event->gbutton.button), | ||
1778 | event->gbutton.state ? "pressed" : "released"); | ||
1779 | #endif /* VERBOSE_BUTTONS */ | ||
1780 | |||
1781 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
1782 | if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN && | ||
1783 | controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5) { | ||
1784 | /* Cycle PS5 audio routing when the microphone button is pressed */ | ||
1785 | if (event->gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) { | ||
1786 | CyclePS5AudioRoute(controller); | ||
1787 | } | ||
1788 | |||
1789 | /* Cycle PS5 trigger effects when the triangle button is pressed */ | ||
1790 | if (event->gbutton.button == SDL_GAMEPAD_BUTTON_NORTH) { | ||
1791 | CyclePS5TriggerEffect(controller); | ||
1792 | } | ||
1793 | } | ||
1794 | } | ||
1795 | break; | ||
1796 | |||
1797 | case SDL_EVENT_MOUSE_BUTTON_DOWN: | ||
1798 | if (virtual_joystick && controller && controller->joystick == virtual_joystick) { | ||
1799 | VirtualGamepadMouseDown(event->button.x, event->button.y); | ||
1800 | } | ||
1801 | UpdateButtonHighlights(event->button.x, event->button.y, event->button.down); | ||
1802 | break; | ||
1803 | |||
1804 | case SDL_EVENT_MOUSE_BUTTON_UP: | ||
1805 | if (virtual_joystick && controller && controller->joystick == virtual_joystick) { | ||
1806 | VirtualGamepadMouseUp(event->button.x, event->button.y); | ||
1807 | } | ||
1808 | |||
1809 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
1810 | if (GamepadButtonContains(setup_mapping_button, event->button.x, event->button.y)) { | ||
1811 | SetDisplayMode(CONTROLLER_MODE_BINDING); | ||
1812 | } | ||
1813 | } else if (display_mode == CONTROLLER_MODE_BINDING) { | ||
1814 | if (GamepadButtonContains(done_mapping_button, event->button.x, event->button.y)) { | ||
1815 | if (controller->mapping) { | ||
1816 | SDL_Log("Mapping complete:"); | ||
1817 | SDL_Log("%s", controller->mapping); | ||
1818 | } | ||
1819 | SetDisplayMode(CONTROLLER_MODE_TESTING); | ||
1820 | } else if (GamepadButtonContains(cancel_button, event->button.x, event->button.y)) { | ||
1821 | CancelMapping(); | ||
1822 | } else if (GamepadButtonContains(clear_button, event->button.x, event->button.y)) { | ||
1823 | ClearMapping(); | ||
1824 | } else if (controller->has_bindings && | ||
1825 | GamepadButtonContains(copy_button, event->button.x, event->button.y)) { | ||
1826 | CopyMapping(); | ||
1827 | } else if (GamepadButtonContains(paste_button, event->button.x, event->button.y)) { | ||
1828 | PasteMapping(); | ||
1829 | } else if (title_pressed) { | ||
1830 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, false); | ||
1831 | } else if (type_pressed) { | ||
1832 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_TYPE, false); | ||
1833 | } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { | ||
1834 | int type = GetGamepadTypeDisplayAt(gamepad_type, event->button.x, event->button.y); | ||
1835 | if (type != SDL_GAMEPAD_TYPE_UNSELECTED) { | ||
1836 | CommitGamepadType((SDL_GamepadType)type); | ||
1837 | StopBinding(); | ||
1838 | } | ||
1839 | } else { | ||
1840 | int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID; | ||
1841 | char *joystick_element; | ||
1842 | |||
1843 | if (controller->joystick != virtual_joystick) { | ||
1844 | gamepad_element = GetGamepadImageElementAt(image, event->button.x, event->button.y); | ||
1845 | } | ||
1846 | if (gamepad_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
1847 | gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event->button.x, event->button.y); | ||
1848 | } | ||
1849 | if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
1850 | /* Set this to false if you don't want to start the binding flow at this point */ | ||
1851 | const bool should_start_flow = true; | ||
1852 | SetCurrentBindingElement(gamepad_element, should_start_flow); | ||
1853 | } | ||
1854 | |||
1855 | joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event->button.x, event->button.y); | ||
1856 | if (joystick_element) { | ||
1857 | CommitBindingElement(joystick_element, true); | ||
1858 | SDL_free(joystick_element); | ||
1859 | } | ||
1860 | } | ||
1861 | } | ||
1862 | UpdateButtonHighlights(event->button.x, event->button.y, event->button.down); | ||
1863 | break; | ||
1864 | |||
1865 | case SDL_EVENT_MOUSE_MOTION: | ||
1866 | if (virtual_joystick && controller && controller->joystick == virtual_joystick) { | ||
1867 | VirtualGamepadMouseMotion(event->motion.x, event->motion.y); | ||
1868 | } | ||
1869 | UpdateButtonHighlights(event->motion.x, event->motion.y, event->motion.state ? true : false); | ||
1870 | break; | ||
1871 | |||
1872 | case SDL_EVENT_KEY_DOWN: | ||
1873 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
1874 | if (event->key.key >= SDLK_0 && event->key.key <= SDLK_9) { | ||
1875 | if (controller && controller->gamepad) { | ||
1876 | int player_index = (event->key.key - SDLK_0); | ||
1877 | |||
1878 | SDL_SetGamepadPlayerIndex(controller->gamepad, player_index); | ||
1879 | } | ||
1880 | break; | ||
1881 | } else if (event->key.key == SDLK_A) { | ||
1882 | OpenVirtualGamepad(); | ||
1883 | } else if (event->key.key == SDLK_D) { | ||
1884 | CloseVirtualGamepad(); | ||
1885 | } else if (event->key.key == SDLK_R && (event->key.mod & SDL_KMOD_CTRL)) { | ||
1886 | SDL_ReloadGamepadMappings(); | ||
1887 | } else if (event->key.key == SDLK_ESCAPE) { | ||
1888 | done = true; | ||
1889 | } | ||
1890 | } else if (display_mode == CONTROLLER_MODE_BINDING) { | ||
1891 | if (event->key.key == SDLK_C && (event->key.mod & SDL_KMOD_CTRL)) { | ||
1892 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
1893 | CopyControllerName(); | ||
1894 | } else { | ||
1895 | CopyMapping(); | ||
1896 | } | ||
1897 | } else if (event->key.key == SDLK_V && (event->key.mod & SDL_KMOD_CTRL)) { | ||
1898 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
1899 | ClearControllerName(); | ||
1900 | PasteControllerName(); | ||
1901 | } else { | ||
1902 | PasteMapping(); | ||
1903 | } | ||
1904 | } else if (event->key.key == SDLK_X && (event->key.mod & SDL_KMOD_CTRL)) { | ||
1905 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
1906 | CopyControllerName(); | ||
1907 | ClearControllerName(); | ||
1908 | } else { | ||
1909 | CopyMapping(); | ||
1910 | ClearMapping(); | ||
1911 | } | ||
1912 | } else if (event->key.key == SDLK_SPACE) { | ||
1913 | if (binding_element != SDL_GAMEPAD_ELEMENT_NAME) { | ||
1914 | ClearBinding(); | ||
1915 | } | ||
1916 | } else if (event->key.key == SDLK_BACKSPACE) { | ||
1917 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
1918 | BackspaceControllerName(); | ||
1919 | } | ||
1920 | } else if (event->key.key == SDLK_RETURN) { | ||
1921 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
1922 | StopBinding(); | ||
1923 | } | ||
1924 | } else if (event->key.key == SDLK_ESCAPE) { | ||
1925 | if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
1926 | StopBinding(); | ||
1927 | } else { | ||
1928 | CancelMapping(); | ||
1929 | } | ||
1930 | } | ||
1931 | } | ||
1932 | break; | ||
1933 | case SDL_EVENT_TEXT_INPUT: | ||
1934 | if (display_mode == CONTROLLER_MODE_BINDING) { | ||
1935 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
1936 | AddControllerNameText(event->text.text); | ||
1937 | } | ||
1938 | } | ||
1939 | break; | ||
1940 | case SDL_EVENT_QUIT: | ||
1941 | done = true; | ||
1942 | break; | ||
1943 | default: | ||
1944 | break; | ||
1945 | } | ||
1946 | |||
1947 | if (done) { | ||
1948 | return SDL_APP_SUCCESS; | ||
1949 | } else { | ||
1950 | return SDL_APP_CONTINUE; | ||
1951 | } | ||
1952 | } | ||
1953 | |||
1954 | SDL_AppResult SDLCALL SDL_AppIterate(void *appstate) | ||
1955 | { | ||
1956 | /* If we have a virtual controller, send a virtual accelerometer sensor reading */ | ||
1957 | if (virtual_joystick) { | ||
1958 | float data[3] = { 0.0f, SDL_STANDARD_GRAVITY, 0.0f }; | ||
1959 | SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_ACCEL, SDL_GetTicksNS(), data, SDL_arraysize(data)); | ||
1960 | } | ||
1961 | |||
1962 | /* Wait 30 ms for joystick events to stop coming in, | ||
1963 | in case a gamepad sends multiple events for a single control (e.g. axis and button for trigger) | ||
1964 | */ | ||
1965 | if (binding_advance_time && SDL_GetTicks() > (binding_advance_time + 30)) { | ||
1966 | if (binding_flow) { | ||
1967 | SetNextBindingElement(); | ||
1968 | } else { | ||
1969 | StopBinding(); | ||
1970 | } | ||
1971 | } | ||
1972 | |||
1973 | /* blank screen, set up for drawing this frame. */ | ||
1974 | SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE); | ||
1975 | SDL_RenderClear(screen); | ||
1976 | SDL_SetRenderDrawColor(screen, 0x10, 0x10, 0x10, SDL_ALPHA_OPAQUE); | ||
1977 | |||
1978 | if (controller) { | ||
1979 | SetGamepadImageShowingFront(image, ShowingFront()); | ||
1980 | UpdateGamepadImageFromGamepad(image, controller->gamepad); | ||
1981 | if (display_mode == CONTROLLER_MODE_BINDING && | ||
1982 | binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
1983 | SetGamepadImageElement(image, binding_element, true); | ||
1984 | } | ||
1985 | RenderGamepadImage(image); | ||
1986 | |||
1987 | if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { | ||
1988 | SetGamepadTypeDisplayRealType(gamepad_type, SDL_GetRealGamepadType(controller->gamepad)); | ||
1989 | RenderGamepadTypeDisplay(gamepad_type); | ||
1990 | } else { | ||
1991 | RenderGamepadDisplay(gamepad_elements, controller->gamepad); | ||
1992 | } | ||
1993 | RenderJoystickDisplay(joystick_elements, controller->joystick); | ||
1994 | |||
1995 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
1996 | RenderGamepadButton(setup_mapping_button); | ||
1997 | } else if (display_mode == CONTROLLER_MODE_BINDING) { | ||
1998 | DrawBindingTips(screen); | ||
1999 | RenderGamepadButton(done_mapping_button); | ||
2000 | RenderGamepadButton(cancel_button); | ||
2001 | RenderGamepadButton(clear_button); | ||
2002 | if (controller->has_bindings) { | ||
2003 | RenderGamepadButton(copy_button); | ||
2004 | } | ||
2005 | RenderGamepadButton(paste_button); | ||
2006 | } | ||
2007 | |||
2008 | DrawGamepadInfo(screen); | ||
2009 | |||
2010 | UpdateGamepadEffects(); | ||
2011 | } else { | ||
2012 | DrawGamepadWaiting(screen); | ||
2013 | } | ||
2014 | SDL_Delay(16); | ||
2015 | SDL_RenderPresent(screen); | ||
2016 | |||
2017 | return SDL_APP_CONTINUE; | ||
2018 | } | ||
2019 | |||
2020 | SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[]) | ||
2021 | { | ||
2022 | bool show_mappings = false; | ||
2023 | int i; | ||
2024 | float content_scale; | ||
2025 | int screen_width, screen_height; | ||
2026 | SDL_FRect area; | ||
2027 | int gamepad_index = -1; | ||
2028 | |||
2029 | /* Initialize test framework */ | ||
2030 | state = SDLTest_CommonCreateState(argv, 0); | ||
2031 | if (!state) { | ||
2032 | return SDL_APP_FAILURE; | ||
2033 | } | ||
2034 | |||
2035 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1"); | ||
2036 | SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "auto"); | ||
2037 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); | ||
2038 | SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1"); | ||
2039 | SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); | ||
2040 | SDL_SetHint(SDL_HINT_JOYSTICK_LINUX_DEADZONES, "1"); | ||
2041 | |||
2042 | /* Enable input debug logging */ | ||
2043 | SDL_SetLogPriority(SDL_LOG_CATEGORY_INPUT, SDL_LOG_PRIORITY_DEBUG); | ||
2044 | |||
2045 | /* Parse commandline */ | ||
2046 | for (i = 1; i < argc;) { | ||
2047 | int consumed; | ||
2048 | |||
2049 | consumed = SDLTest_CommonArg(state, i); | ||
2050 | if (!consumed) { | ||
2051 | if (SDL_strcmp(argv[i], "--mappings") == 0) { | ||
2052 | show_mappings = true; | ||
2053 | consumed = 1; | ||
2054 | } else if (SDL_strcmp(argv[i], "--virtual") == 0) { | ||
2055 | OpenVirtualGamepad(); | ||
2056 | consumed = 1; | ||
2057 | } else if (gamepad_index < 0) { | ||
2058 | char *endptr = NULL; | ||
2059 | gamepad_index = (int)SDL_strtol(argv[i], &endptr, 0); | ||
2060 | if (endptr != argv[i] && *endptr == '\0' && gamepad_index >= 0) { | ||
2061 | consumed = 1; | ||
2062 | } | ||
2063 | } | ||
2064 | } | ||
2065 | if (consumed <= 0) { | ||
2066 | static const char *options[] = { "[--mappings]", "[--virtual]", "[index]", NULL }; | ||
2067 | SDLTest_CommonLogUsage(state, argv[0], options); | ||
2068 | return SDL_APP_FAILURE; | ||
2069 | } | ||
2070 | |||
2071 | i += consumed; | ||
2072 | } | ||
2073 | if (gamepad_index < 0) { | ||
2074 | gamepad_index = 0; | ||
2075 | } | ||
2076 | |||
2077 | /* Initialize SDL (Note: video is required to start event loop) */ | ||
2078 | if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { | ||
2079 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError()); | ||
2080 | return SDL_APP_FAILURE; | ||
2081 | } | ||
2082 | |||
2083 | SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt"); | ||
2084 | |||
2085 | if (show_mappings) { | ||
2086 | int count = 0; | ||
2087 | char **mappings = SDL_GetGamepadMappings(&count); | ||
2088 | int map_i; | ||
2089 | SDL_Log("Supported mappings:"); | ||
2090 | for (map_i = 0; map_i < count; ++map_i) { | ||
2091 | SDL_Log("\t%s", mappings[map_i]); | ||
2092 | } | ||
2093 | SDL_Log("%s", ""); | ||
2094 | SDL_free(mappings); | ||
2095 | } | ||
2096 | |||
2097 | /* Create a window to display gamepad state */ | ||
2098 | content_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); | ||
2099 | if (content_scale == 0.0f) { | ||
2100 | content_scale = 1.0f; | ||
2101 | } | ||
2102 | screen_width = (int)SDL_ceilf(SCREEN_WIDTH * content_scale); | ||
2103 | screen_height = (int)SDL_ceilf(SCREEN_HEIGHT * content_scale); | ||
2104 | window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, SDL_WINDOW_HIGH_PIXEL_DENSITY); | ||
2105 | if (!window) { | ||
2106 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s", SDL_GetError()); | ||
2107 | return SDL_APP_FAILURE; | ||
2108 | } | ||
2109 | |||
2110 | screen = SDL_CreateRenderer(window, NULL); | ||
2111 | if (!screen) { | ||
2112 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s", SDL_GetError()); | ||
2113 | SDL_DestroyWindow(window); | ||
2114 | return SDL_APP_FAILURE; | ||
2115 | } | ||
2116 | |||
2117 | SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE); | ||
2118 | SDL_RenderClear(screen); | ||
2119 | SDL_RenderPresent(screen); | ||
2120 | |||
2121 | /* scale for platforms that don't give you the window size you asked for. */ | ||
2122 | SDL_SetRenderLogicalPresentation(screen, (int)SCREEN_WIDTH, (int)SCREEN_HEIGHT, | ||
2123 | SDL_LOGICAL_PRESENTATION_LETTERBOX); | ||
2124 | |||
2125 | |||
2126 | title_area.w = GAMEPAD_WIDTH; | ||
2127 | title_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN; | ||
2128 | title_area.x = PANEL_WIDTH + PANEL_SPACING; | ||
2129 | title_area.y = TITLE_HEIGHT / 2 - title_area.h / 2; | ||
2130 | |||
2131 | type_area.w = PANEL_WIDTH - 2 * BUTTON_MARGIN; | ||
2132 | type_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN; | ||
2133 | type_area.x = BUTTON_MARGIN; | ||
2134 | type_area.y = TITLE_HEIGHT / 2 - type_area.h / 2; | ||
2135 | |||
2136 | image = CreateGamepadImage(screen); | ||
2137 | if (!image) { | ||
2138 | SDL_DestroyRenderer(screen); | ||
2139 | SDL_DestroyWindow(window); | ||
2140 | return SDL_APP_FAILURE; | ||
2141 | } | ||
2142 | SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT); | ||
2143 | |||
2144 | gamepad_elements = CreateGamepadDisplay(screen); | ||
2145 | area.x = 0; | ||
2146 | area.y = TITLE_HEIGHT; | ||
2147 | area.w = PANEL_WIDTH; | ||
2148 | area.h = GAMEPAD_HEIGHT; | ||
2149 | SetGamepadDisplayArea(gamepad_elements, &area); | ||
2150 | |||
2151 | gamepad_type = CreateGamepadTypeDisplay(screen); | ||
2152 | area.x = 0; | ||
2153 | area.y = TITLE_HEIGHT; | ||
2154 | area.w = PANEL_WIDTH; | ||
2155 | area.h = GAMEPAD_HEIGHT; | ||
2156 | SetGamepadTypeDisplayArea(gamepad_type, &area); | ||
2157 | |||
2158 | joystick_elements = CreateJoystickDisplay(screen); | ||
2159 | area.x = PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING; | ||
2160 | area.y = TITLE_HEIGHT; | ||
2161 | area.w = PANEL_WIDTH; | ||
2162 | area.h = GAMEPAD_HEIGHT; | ||
2163 | SetJoystickDisplayArea(joystick_elements, &area); | ||
2164 | |||
2165 | setup_mapping_button = CreateGamepadButton(screen, "Setup Mapping"); | ||
2166 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(setup_mapping_button) + 2 * BUTTON_PADDING); | ||
2167 | area.h = GetGamepadButtonLabelHeight(setup_mapping_button) + 2 * BUTTON_PADDING; | ||
2168 | area.x = BUTTON_MARGIN; | ||
2169 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
2170 | SetGamepadButtonArea(setup_mapping_button, &area); | ||
2171 | |||
2172 | cancel_button = CreateGamepadButton(screen, "Cancel"); | ||
2173 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(cancel_button) + 2 * BUTTON_PADDING); | ||
2174 | area.h = GetGamepadButtonLabelHeight(cancel_button) + 2 * BUTTON_PADDING; | ||
2175 | area.x = BUTTON_MARGIN; | ||
2176 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
2177 | SetGamepadButtonArea(cancel_button, &area); | ||
2178 | |||
2179 | clear_button = CreateGamepadButton(screen, "Clear"); | ||
2180 | area.x += area.w + BUTTON_PADDING; | ||
2181 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(clear_button) + 2 * BUTTON_PADDING); | ||
2182 | area.h = GetGamepadButtonLabelHeight(clear_button) + 2 * BUTTON_PADDING; | ||
2183 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
2184 | SetGamepadButtonArea(clear_button, &area); | ||
2185 | |||
2186 | copy_button = CreateGamepadButton(screen, "Copy"); | ||
2187 | area.x += area.w + BUTTON_PADDING; | ||
2188 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING); | ||
2189 | area.h = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING; | ||
2190 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
2191 | SetGamepadButtonArea(copy_button, &area); | ||
2192 | |||
2193 | paste_button = CreateGamepadButton(screen, "Paste"); | ||
2194 | area.x += area.w + BUTTON_PADDING; | ||
2195 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(paste_button) + 2 * BUTTON_PADDING); | ||
2196 | area.h = GetGamepadButtonLabelHeight(paste_button) + 2 * BUTTON_PADDING; | ||
2197 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
2198 | SetGamepadButtonArea(paste_button, &area); | ||
2199 | |||
2200 | done_mapping_button = CreateGamepadButton(screen, "Done"); | ||
2201 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(done_mapping_button) + 2 * BUTTON_PADDING); | ||
2202 | area.h = GetGamepadButtonLabelHeight(done_mapping_button) + 2 * BUTTON_PADDING; | ||
2203 | area.x = SCREEN_WIDTH / 2 - area.w / 2; | ||
2204 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
2205 | SetGamepadButtonArea(done_mapping_button, &area); | ||
2206 | |||
2207 | /* Process the initial gamepad list */ | ||
2208 | SDL_AppIterate(NULL); | ||
2209 | |||
2210 | if (gamepad_index < num_controllers) { | ||
2211 | SetController(controllers[gamepad_index].id); | ||
2212 | } else if (num_controllers > 0) { | ||
2213 | SetController(controllers[0].id); | ||
2214 | } | ||
2215 | |||
2216 | return SDL_APP_CONTINUE; | ||
2217 | } | ||
2218 | |||
2219 | void SDLCALL SDL_AppQuit(void *appstate, SDL_AppResult result) | ||
2220 | { | ||
2221 | CloseVirtualGamepad(); | ||
2222 | while (num_controllers > 0) { | ||
2223 | HandleGamepadRemoved(controllers[0].id); | ||
2224 | DelController(controllers[0].id); | ||
2225 | } | ||
2226 | SDL_free(controllers); | ||
2227 | SDL_free(controller_name); | ||
2228 | DestroyGamepadImage(image); | ||
2229 | DestroyGamepadDisplay(gamepad_elements); | ||
2230 | DestroyGamepadTypeDisplay(gamepad_type); | ||
2231 | DestroyJoystickDisplay(joystick_elements); | ||
2232 | DestroyGamepadButton(setup_mapping_button); | ||
2233 | DestroyGamepadButton(done_mapping_button); | ||
2234 | DestroyGamepadButton(cancel_button); | ||
2235 | DestroyGamepadButton(clear_button); | ||
2236 | DestroyGamepadButton(copy_button); | ||
2237 | DestroyGamepadButton(paste_button); | ||
2238 | SDLTest_CleanupTextDrawing(); | ||
2239 | SDL_DestroyRenderer(screen); | ||
2240 | SDL_DestroyWindow(window); | ||
2241 | SDL_Quit(); | ||
2242 | SDLTest_CommonDestroyState(state); | ||
2243 | } | ||