summaryrefslogtreecommitdiff
path: root/src/contrib/SDL-3.2.20/test/testcontroller.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/contrib/SDL-3.2.20/test/testcontroller.c')
-rw-r--r--src/contrib/SDL-3.2.20/test/testcontroller.c2243
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
44typedef struct
45{
46 bool m_bMoving;
47 int m_nLastValue;
48 int m_nStartingValue;
49 int m_nFarthestValue;
50} AxisState;
51
52typedef 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
68static SDLTest_CommonState *state;
69static SDL_Window *window = NULL;
70static SDL_Renderer *screen = NULL;
71static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING;
72static GamepadImage *image = NULL;
73static GamepadDisplay *gamepad_elements = NULL;
74static GamepadTypeDisplay *gamepad_type = NULL;
75static JoystickDisplay *joystick_elements = NULL;
76static GamepadButton *setup_mapping_button = NULL;
77static GamepadButton *done_mapping_button = NULL;
78static GamepadButton *cancel_button = NULL;
79static GamepadButton *clear_button = NULL;
80static GamepadButton *copy_button = NULL;
81static GamepadButton *paste_button = NULL;
82static char *backup_mapping = NULL;
83static bool done = false;
84static bool set_LED = false;
85static int num_controllers = 0;
86static Controller *controllers;
87static Controller *controller;
88static SDL_JoystickID mapping_controller = 0;
89static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
90static int last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
91static bool binding_flow = false;
92static int binding_flow_direction = 0;
93static Uint64 binding_advance_time = 0;
94static SDL_FRect title_area;
95static bool title_highlighted;
96static bool title_pressed;
97static SDL_FRect type_area;
98static bool type_highlighted;
99static bool type_pressed;
100static char *controller_name;
101static SDL_Joystick *virtual_joystick = NULL;
102static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
103static float virtual_axis_start_x;
104static float virtual_axis_start_y;
105static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
106static bool virtual_touchpad_active = false;
107static float virtual_touchpad_x;
108static float virtual_touchpad_y;
109
110static 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
149static 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*/
172typedef 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
197static 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
235static 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
257static 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
276static 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
330static 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
341static 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
363static void SetAndFreeGamepadMapping(char *mapping)
364{
365 SDL_SetGamepadMapping(controller->id, mapping);
366 SDL_free(mapping);
367}
368
369static 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
394static 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
412static 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
430static void StopBinding(void)
431{
432 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
433}
434
435typedef struct
436{
437 int axis;
438 int direction;
439} AxisInfo;
440
441static 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
465static 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, &current_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
621static void ClearBinding(void)
622{
623 CommitBindingElement(NULL, true);
624}
625
626static 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
660static void CancelMapping(void)
661{
662 SetAndFreeGamepadMapping(backup_mapping);
663 backup_mapping = NULL;
664
665 SetDisplayMode(CONTROLLER_MODE_TESTING);
666}
667
668static void ClearMapping(void)
669{
670 SetAndFreeGamepadMapping(NULL);
671 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
672}
673
674static void CopyMapping(void)
675{
676 if (controller && controller->mapping) {
677 SDL_SetClipboardText(controller->mapping);
678 }
679}
680
681static 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
696static 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
709static 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
722static 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
731static void ClearControllerName(void)
732{
733 if (controller_name) {
734 *controller_name = '\0';
735 }
736 CommitControllerName();
737}
738
739static void CopyControllerName(void)
740{
741 SDL_SetClipboardText(controller_name);
742}
743
744static void PasteControllerName(void)
745{
746 SDL_free(controller_name);
747 controller_name = SDL_GetClipboardText();
748 CommitControllerName();
749}
750
751static 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
764static 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
863static 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
875static 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
892static 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
941static 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
978static 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
1006static 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
1117static 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
1137static 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
1148static 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
1167static void SDLCALL VirtualGamepadSetPlayerIndex(void *userdata, int player_index)
1168{
1169 SDL_Log("Virtual Gamepad: player index set to %d", player_index);
1170}
1171
1172static 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
1178static 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
1184static 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
1190static 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
1225static 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
1245static 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
1294static 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
1340static 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
1364static 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
1374static 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
1462static 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
1486static 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
1551static 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
1607SDL_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
1954SDL_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
2020SDL_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
2219void 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}