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