2
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
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.
8
Permission is granted to anyone to use this software for any purpose,
9
including commercial applications, and to alter it and redistribute it
13
/* Simple program to test the SDL controller routines */
16
#include <SDL3/SDL_main.h>
17
#include <SDL3/SDL_test.h>
18
#include <SDL3/SDL_test_font.h>
20
#ifdef SDL_PLATFORM_EMSCRIPTEN
21
#include <emscripten/emscripten.h>
24
#include "gamepadutils.h"
28
#define DEBUG_AXIS_MAPPING
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
40
#define SCREEN_WIDTH (PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING + PANEL_WIDTH)
41
#define SCREEN_HEIGHT (TITLE_HEIGHT + GAMEPAD_HEIGHT)
55
SDL_Joystick *joystick;
57
AxisState *axis_state;
61
SDL_bool has_bindings;
67
static SDL_Window *window = NULL;
68
static SDL_Renderer *screen = NULL;
69
static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING;
70
static GamepadImage *image = NULL;
71
static GamepadDisplay *gamepad_elements = NULL;
72
static GamepadTypeDisplay *gamepad_type = NULL;
73
static JoystickDisplay *joystick_elements = NULL;
74
static GamepadButton *setup_mapping_button = NULL;
75
static GamepadButton *done_mapping_button = NULL;
76
static GamepadButton *cancel_button = NULL;
77
static GamepadButton *clear_button = NULL;
78
static GamepadButton *copy_button = NULL;
79
static GamepadButton *paste_button = NULL;
80
static char *backup_mapping = NULL;
81
static SDL_bool done = SDL_FALSE;
82
static SDL_bool set_LED = SDL_FALSE;
83
static int num_controllers = 0;
84
static Controller *controllers;
85
static Controller *controller;
86
static SDL_JoystickID mapping_controller = 0;
87
static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
88
static int last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
89
static SDL_bool binding_flow = SDL_FALSE;
90
static int binding_flow_direction = 0;
91
static Uint64 binding_advance_time = 0;
92
static SDL_FRect title_area;
93
static SDL_bool title_highlighted;
94
static SDL_bool title_pressed;
95
static SDL_FRect type_area;
96
static SDL_bool type_highlighted;
97
static SDL_bool type_pressed;
98
static char *controller_name;
99
static SDL_Joystick *virtual_joystick = NULL;
100
static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
101
static float virtual_axis_start_x;
102
static float virtual_axis_start_y;
103
static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
104
static SDL_bool virtual_touchpad_active = SDL_FALSE;
105
static float virtual_touchpad_x;
106
static float virtual_touchpad_y;
108
static 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,
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,
147
static const char *GetSensorName(SDL_SensorType sensor)
150
case SDL_SENSOR_ACCEL:
151
return "accelerometer";
152
case SDL_SENSOR_GYRO:
154
case SDL_SENSOR_ACCEL_L:
155
return "accelerometer (L)";
156
case SDL_SENSOR_GYRO_L:
158
case SDL_SENSOR_ACCEL_R:
159
return "accelerometer (R)";
160
case SDL_SENSOR_GYRO_R:
167
/* PS5 trigger effect documentation:
168
https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes
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 */
195
static void CyclePS5AudioRoute(Controller *device)
197
DS5EffectsState_t state;
199
device->audio_route = (device->audio_route + 1) % 4;
202
switch (device->audio_route) {
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 */
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 */
218
state.ucEnableBits1 |= (0x80 | 0x20); /* Modify audio route and speaker volume */
219
state.ucSpeakerVolume = 100; /* Maximum volume */
220
state.ucAudioEnableBits = 0x30; /* Output to speaker */
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 */
230
SDL_SendGamepadEffect(device->gamepad, &state, sizeof(state));
233
static void CyclePS5TriggerEffect(Controller *device)
235
DS5EffectsState_t state;
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 },
246
device->trigger_effect = (device->trigger_effect + 1) % SDL_arraysize(effects);
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));
255
static void ClearButtonHighlights(void)
257
title_highlighted = SDL_FALSE;
258
title_pressed = SDL_FALSE;
260
type_highlighted = SDL_FALSE;
261
type_pressed = SDL_FALSE;
263
ClearGamepadImage(image);
264
SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
265
SetGamepadTypeDisplayHighlight(gamepad_type, SDL_GAMEPAD_TYPE_UNSELECTED, SDL_FALSE);
266
SetGamepadButtonHighlight(setup_mapping_button, SDL_FALSE, SDL_FALSE);
267
SetGamepadButtonHighlight(done_mapping_button, SDL_FALSE, SDL_FALSE);
268
SetGamepadButtonHighlight(cancel_button, SDL_FALSE, SDL_FALSE);
269
SetGamepadButtonHighlight(clear_button, SDL_FALSE, SDL_FALSE);
270
SetGamepadButtonHighlight(copy_button, SDL_FALSE, SDL_FALSE);
271
SetGamepadButtonHighlight(paste_button, SDL_FALSE, SDL_FALSE);
274
static void UpdateButtonHighlights(float x, float y, SDL_bool button_down)
276
ClearButtonHighlights();
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) {
282
int gamepad_highlight_element = SDL_GAMEPAD_ELEMENT_INVALID;
283
char *joystick_highlight_element;
287
if (SDL_PointInRectFloat(&point, &title_area)) {
288
title_highlighted = SDL_TRUE;
289
title_pressed = button_down;
291
title_highlighted = SDL_FALSE;
292
title_pressed = SDL_FALSE;
295
if (SDL_PointInRectFloat(&point, &type_area)) {
296
type_highlighted = SDL_TRUE;
297
type_pressed = button_down;
299
type_highlighted = SDL_FALSE;
300
type_pressed = SDL_FALSE;
303
if (controller->joystick != virtual_joystick) {
304
gamepad_highlight_element = GetGamepadImageElementAt(image, x, y);
306
if (gamepad_highlight_element == SDL_GAMEPAD_ELEMENT_INVALID) {
307
gamepad_highlight_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, x, y);
309
SetGamepadDisplayHighlight(gamepad_elements, gamepad_highlight_element, button_down);
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);
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);
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);
328
static int StandardizeAxisValue(int nValue)
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;
339
static void RefreshControllerName(void)
341
const char *name = NULL;
343
SDL_free(controller_name);
344
controller_name = NULL;
347
if (controller->gamepad) {
348
name = SDL_GetGamepadName(controller->gamepad);
350
name = SDL_GetJoystickName(controller->joystick);
355
controller_name = SDL_strdup(name);
357
controller_name = SDL_strdup("");
361
static void SetAndFreeGamepadMapping(char *mapping)
363
SDL_SetGamepadMapping(controller->id, mapping);
367
static void SetCurrentBindingElement(int element, SDL_bool flow)
371
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
372
RefreshControllerName();
375
if (element == SDL_GAMEPAD_ELEMENT_INVALID) {
376
binding_flow_direction = 0;
377
last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
379
last_binding_element = binding_element;
381
binding_element = element;
382
binding_flow = flow || (element == SDL_GAMEPAD_BUTTON_SOUTH);
383
binding_advance_time = 0;
385
for (i = 0; i < controller->num_axes; ++i) {
386
controller->axis_state[i].m_nFarthestValue = controller->axis_state[i].m_nStartingValue;
389
SetGamepadDisplaySelected(gamepad_elements, element);
392
static void SetNextBindingElement(void)
396
if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
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], SDL_TRUE);
407
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
410
static void SetPrevBindingElement(void)
414
if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
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], SDL_TRUE);
425
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
428
static void StopBinding(void)
430
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
439
static SDL_bool ParseAxisInfo(const char *description, AxisInfo *info)
445
if (*description == '-') {
446
info->direction = -1;
448
} else if (*description == '+') {
455
if (description[0] == 'a' && SDL_isdigit(description[1])) {
457
info->axis = SDL_atoi(description);
463
static void CommitBindingElement(const char *binding, SDL_bool force)
467
SDL_bool ignore_binding = SDL_FALSE;
469
if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
473
if (controller->mapping) {
474
mapping = SDL_strdup(controller->mapping);
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
SDL_bool native_button = (binding_element < SDL_GAMEPAD_BUTTON_MAX);
483
SDL_bool native_axis = (binding_element >= SDL_GAMEPAD_BUTTON_MAX &&
484
binding_element <= SDL_GAMEPAD_ELEMENT_AXIS_MAX);
485
SDL_bool native_trigger = (binding_element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER ||
486
binding_element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER);
487
SDL_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);
493
SDL_bool current_button = (current && *current == 'b');
494
SDL_bool proposed_button = (binding && *binding == 'b');
495
if (current_button && !proposed_button) {
496
ignore_binding = SDL_TRUE;
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 = SDL_TRUE;
504
AxisInfo current_axis_info = { 0, 0 };
505
AxisInfo proposed_axis_info = { 0, 0 };
506
SDL_bool current_axis = ParseAxisInfo(current, ¤t_axis_info);
507
SDL_bool proposed_axis = ParseAxisInfo(binding, &proposed_axis_info);
510
/* Ignore this unless the proposed binding extends the existing axis */
511
ignore_binding = SDL_TRUE;
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 */
520
ignore_binding = SDL_FALSE;
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 = SDL_FALSE;
530
SDL_bool current_hat = (current && *current == 'h');
531
SDL_bool proposed_hat = (binding && *binding == 'h');
532
if (current_hat && !proposed_hat) {
533
ignore_binding = SDL_TRUE;
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 = SDL_TRUE;
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) {
551
} else if (binding_element == action_backward) {
552
if (existing == action_forward) {
553
SDL_bool bound_backward = MappingHasElement(controller->mapping, action_backward);
554
if (bound_backward) {
555
/* Just move on to the next one */
556
ignore_binding = SDL_TRUE;
557
SetNextBindingElement();
559
/* You can't skip the backward action, go back and start over */
560
ignore_binding = SDL_TRUE;
561
SetPrevBindingElement();
563
} else if (existing == action_backward && binding_flow_direction == -1) {
564
/* Keep going backwards */
565
ignore_binding = SDL_TRUE;
566
SetPrevBindingElement();
570
} else if (existing == action_forward) {
571
/* Just move on to the next one */
572
ignore_binding = SDL_TRUE;
573
SetNextBindingElement();
574
} else if (existing == action_backward) {
575
ignore_binding = SDL_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 = SDL_TRUE;
580
SetNextBindingElement();
581
} else if (existing == action_delete) {
582
/* Clear the current binding and move to the next one */
586
} else if (binding_element != action_forward &&
587
binding_element != action_backward) {
588
/* Actually, we'll just clear the existing binding */
589
/*ignore_binding = SDL_TRUE;*/
594
if (ignore_binding) {
599
mapping = ClearMappingBinding(mapping, binding);
600
mapping = SetElementBinding(mapping, binding_element, binding);
601
SetAndFreeGamepadMapping(mapping);
606
SetNextBindingElement();
607
} else if (direction < 0) {
608
SetPrevBindingElement();
614
/* Wait to see if any more bindings come in */
615
binding_advance_time = SDL_GetTicks() + 30;
619
static void ClearBinding(void)
621
CommitBindingElement(NULL, SDL_TRUE);
624
static void SetDisplayMode(ControllerDisplayMode mode)
627
SDL_MouseButtonFlags button_state;
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);
634
mapping_controller = controller->id;
635
if (MappingHasBindings(backup_mapping)) {
636
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
638
SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_SOUTH, SDL_TRUE);
641
if (backup_mapping) {
642
SDL_free(backup_mapping);
643
backup_mapping = NULL;
645
mapping_controller = 0;
650
SetGamepadImageDisplayMode(image, mode);
651
SetGamepadDisplayDisplayMode(gamepad_elements, mode);
653
button_state = SDL_GetMouseState(&x, &y);
654
SDL_RenderCoordinatesFromWindow(screen, x, y, &x, &y);
655
UpdateButtonHighlights(x, y, button_state ? SDL_TRUE : SDL_FALSE);
658
static void CancelMapping(void)
660
SetAndFreeGamepadMapping(backup_mapping);
661
backup_mapping = NULL;
663
SetDisplayMode(CONTROLLER_MODE_TESTING);
666
static void ClearMapping(void)
668
SetAndFreeGamepadMapping(NULL);
669
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
672
static void CopyMapping(void)
674
if (controller && controller->mapping) {
675
SDL_SetClipboardText(controller->mapping);
679
static void PasteMapping(void)
682
char *mapping = SDL_GetClipboardText();
683
if (MappingHasBindings(mapping)) {
685
SDL_SetGamepadMapping(controller->id, mapping);
686
RefreshControllerName();
688
/* Not a valid mapping, ignore it */
694
static void CommitControllerName(void)
696
char *mapping = NULL;
698
if (controller->mapping) {
699
mapping = SDL_strdup(controller->mapping);
703
mapping = SetMappingName(mapping, controller_name);
704
SetAndFreeGamepadMapping(mapping);
707
static void AddControllerNameText(const char *text)
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);
714
SDL_memcpy(&name[current_length], text, text_length + 1);
715
controller_name = name;
717
CommitControllerName();
720
static void BackspaceControllerName(void)
722
size_t length = (controller_name ? SDL_strlen(controller_name) : 0);
724
controller_name[length - 1] = '\0';
726
CommitControllerName();
729
static void ClearControllerName(void)
731
if (controller_name) {
732
*controller_name = '\0';
734
CommitControllerName();
737
static void CopyControllerName(void)
739
SDL_SetClipboardText(controller_name);
742
static void PasteControllerName(void)
744
SDL_free(controller_name);
745
controller_name = SDL_GetClipboardText();
746
CommitControllerName();
749
static void CommitGamepadType(SDL_GamepadType type)
751
char *mapping = NULL;
753
if (controller->mapping) {
754
mapping = SDL_strdup(controller->mapping);
758
mapping = SetMappingType(mapping, type);
759
SetAndFreeGamepadMapping(mapping);
762
static const char *GetBindingInstruction(void)
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";
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";
861
static int FindController(SDL_JoystickID id)
865
for (i = 0; i < num_controllers; ++i) {
866
if (id == controllers[i].id) {
873
static void SetController(SDL_JoystickID id)
875
int i = FindController(id);
877
if (i < 0 && num_controllers > 0) {
882
controller = &controllers[i];
887
RefreshControllerName();
890
static void AddController(SDL_JoystickID id, SDL_bool verbose)
892
Controller *new_controllers;
893
Controller *new_controller;
894
SDL_Joystick *joystick;
896
if (FindController(id) >= 0) {
897
/* We already have this controller */
901
new_controllers = (Controller *)SDL_realloc(controllers, (num_controllers + 1) * sizeof(*controllers));
902
if (!new_controllers) {
907
controllers = new_controllers;
908
new_controller = &new_controllers[num_controllers++];
909
SDL_zerop(new_controller);
910
new_controller->id = id;
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));
916
joystick = new_controller->joystick;
918
if (verbose && !SDL_IsGamepad(id)) {
919
const char *name = SDL_GetJoystickName(joystick);
920
const char *path = SDL_GetJoystickPath(joystick);
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);
927
SDL_Log("Couldn't open joystick: %s", SDL_GetError());
930
if (mapping_controller) {
931
SetController(mapping_controller);
937
static void DelController(SDL_JoystickID id)
939
int i = FindController(id);
945
if (display_mode == CONTROLLER_MODE_BINDING && id == controller->id) {
946
SetDisplayMode(CONTROLLER_MODE_TESTING);
949
/* Reset trigger state */
950
if (controllers[i].trigger_effect != 0) {
951
controllers[i].trigger_effect = -1;
952
CyclePS5TriggerEffect(&controllers[i]);
954
SDL_assert(controllers[i].gamepad == NULL);
955
if (controllers[i].axis_state) {
956
SDL_free(controllers[i].axis_state);
958
if (controllers[i].joystick) {
959
SDL_CloseJoystick(controllers[i].joystick);
963
if (i < num_controllers) {
964
SDL_memcpy(&controllers[i], &controllers[i + 1], (num_controllers - i) * sizeof(*controllers));
967
if (mapping_controller) {
968
SetController(mapping_controller);
974
static void HandleGamepadRemapped(SDL_JoystickID id)
977
int i = FindController(id);
984
if (!controllers[i].gamepad) {
985
/* Failed to open this controller */
989
/* Get the current mapping */
990
mapping = SDL_GetGamepadMapping(controllers[i].gamepad);
992
/* Make sure the mapping has a valid name */
993
if (mapping && !MappingHasName(mapping)) {
994
mapping = SetMappingName(mapping, SDL_GetJoystickName(controllers[i].joystick));
997
SDL_free(controllers[i].mapping);
998
controllers[i].mapping = mapping;
999
controllers[i].has_bindings = MappingHasBindings(mapping);
1002
static void HandleGamepadAdded(SDL_JoystickID id, SDL_bool verbose)
1004
SDL_Gamepad *gamepad;
1005
Uint16 firmware_version;
1006
SDL_SensorType sensors[] = {
1016
i = FindController(id);
1021
SDL_assert(!controllers[i].gamepad);
1022
controllers[i].gamepad = SDL_OpenGamepad(id);
1024
gamepad = controllers[i].gamepad;
1027
SDL_PropertiesID props = SDL_GetGamepadProperties(gamepad);
1028
const char *name = SDL_GetGamepadName(gamepad);
1029
const char *path = SDL_GetGamepadPath(gamepad);
1030
SDL_Log("Opened gamepad %s%s%s\n", name, path ? ", " : "", path ? path : "");
1032
firmware_version = SDL_GetGamepadFirmwareVersion(gamepad);
1033
if (firmware_version) {
1034
SDL_Log("Firmware version: 0x%x (%d)\n", firmware_version, firmware_version);
1037
if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN, SDL_FALSE)) {
1038
SDL_Log("Has player LED");
1041
if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, SDL_FALSE)) {
1042
SDL_Log("Rumble supported");
1045
if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN, SDL_FALSE)) {
1046
SDL_Log("Trigger rumble supported");
1049
if (SDL_GetGamepadPlayerIndex(gamepad) >= 0) {
1050
SDL_Log("Player index: %d\n", SDL_GetGamepadPlayerIndex(gamepad));
1054
for (i = 0; i < SDL_arraysize(sensors); ++i) {
1055
SDL_SensorType sensor = sensors[i];
1057
if (SDL_GamepadHasSensor(gamepad, sensor)) {
1059
SDL_Log("Enabling %s at %.2f Hz\n", GetSensorName(sensor), SDL_GetGamepadSensorDataRate(gamepad, sensor));
1061
SDL_SetGamepadSensorEnabled(gamepad, sensor, SDL_TRUE);
1066
char *mapping = SDL_GetGamepadMapping(gamepad);
1068
SDL_Log("Mapping: %s\n", mapping);
1073
SDL_Log("Couldn't open gamepad: %s", SDL_GetError());
1076
HandleGamepadRemapped(id);
1079
static void HandleGamepadRemoved(SDL_JoystickID id)
1081
int i = FindController(id);
1088
if (controllers[i].mapping) {
1089
SDL_free(controllers[i].mapping);
1090
controllers[i].mapping = NULL;
1092
if (controllers[i].gamepad) {
1093
SDL_CloseGamepad(controllers[i].gamepad);
1094
controllers[i].gamepad = NULL;
1098
static Uint16 ConvertAxisToRumble(Sint16 axisval)
1100
/* Only start rumbling if the axis is past the halfway point */
1101
const Sint16 half_axis = (Sint16)SDL_ceil(SDL_JOYSTICK_AXIS_MAX / 2.0f);
1102
if (axisval > half_axis) {
1103
return (Uint16)(axisval - half_axis) * 4;
1109
static SDL_bool ShowingFront(void)
1111
SDL_bool showing_front = SDL_TRUE;
1114
/* Show the back of the gamepad if the paddles are being held or bound */
1115
for (i = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; ++i) {
1116
if (SDL_GetGamepadButton(controller->gamepad, (SDL_GamepadButton)i) == SDL_PRESSED ||
1117
binding_element == i) {
1118
showing_front = SDL_FALSE;
1122
if ((SDL_GetModState() & SDL_KMOD_SHIFT) && binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
1123
showing_front = SDL_FALSE;
1125
return showing_front;
1128
static void SDLCALL VirtualGamepadSetPlayerIndex(void *userdata, int player_index)
1130
SDL_Log("Virtual Gamepad: player index set to %d\n", player_index);
1133
static int SDLCALL VirtualGamepadRumble(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1135
SDL_Log("Virtual Gamepad: rumble set to %d/%d\n", low_frequency_rumble, high_frequency_rumble);
1139
static int SDLCALL VirtualGamepadRumbleTriggers(void *userdata, Uint16 left_rumble, Uint16 right_rumble)
1141
SDL_Log("Virtual Gamepad: trigger rumble set to %d/%d\n", left_rumble, right_rumble);
1145
static int SDLCALL VirtualGamepadSetLED(void *userdata, Uint8 red, Uint8 green, Uint8 blue)
1147
SDL_Log("Virtual Gamepad: LED set to RGB %d,%d,%d\n", red, green, blue);
1151
static void OpenVirtualGamepad(void)
1153
SDL_VirtualJoystickTouchpadDesc virtual_touchpad = { 1, { 0, 0, 0 } };
1154
SDL_VirtualJoystickSensorDesc virtual_sensor = { SDL_SENSOR_ACCEL, 0.0f };
1155
SDL_VirtualJoystickDesc desc;
1156
SDL_JoystickID virtual_id;
1158
if (virtual_joystick) {
1163
desc.type = SDL_JOYSTICK_TYPE_GAMEPAD;
1164
desc.naxes = SDL_GAMEPAD_AXIS_MAX;
1165
desc.nbuttons = SDL_GAMEPAD_BUTTON_MAX;
1166
desc.ntouchpads = 1;
1167
desc.touchpads = &virtual_touchpad;
1169
desc.sensors = &virtual_sensor;
1170
desc.SetPlayerIndex = VirtualGamepadSetPlayerIndex;
1171
desc.Rumble = VirtualGamepadRumble;
1172
desc.RumbleTriggers = VirtualGamepadRumbleTriggers;
1173
desc.SetLED = VirtualGamepadSetLED;
1175
virtual_id = SDL_AttachVirtualJoystick(&desc);
1176
if (virtual_id == 0) {
1177
SDL_Log("Couldn't attach virtual device: %s\n", SDL_GetError());
1179
virtual_joystick = SDL_OpenJoystick(virtual_id);
1180
if (!virtual_joystick) {
1181
SDL_Log("Couldn't open virtual device: %s\n", SDL_GetError());
1186
static void CloseVirtualGamepad(void)
1189
SDL_JoystickID *joysticks = SDL_GetJoysticks(NULL);
1191
for (i = 0; joysticks[i]; ++i) {
1192
SDL_JoystickID instance_id = joysticks[i];
1193
if (SDL_IsJoystickVirtual(instance_id)) {
1194
SDL_DetachVirtualJoystick(instance_id);
1197
SDL_free(joysticks);
1200
if (virtual_joystick) {
1201
SDL_CloseJoystick(virtual_joystick);
1202
virtual_joystick = NULL;
1206
static void VirtualGamepadMouseMotion(float x, float y)
1208
if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) {
1209
if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
1210
const float MOVING_DISTANCE = 2.0f;
1211
if (SDL_fabs(x - virtual_axis_start_x) >= MOVING_DISTANCE ||
1212
SDL_fabs(y - virtual_axis_start_y) >= MOVING_DISTANCE) {
1213
SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, SDL_RELEASED);
1214
virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
1219
if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
1220
if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
1221
virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
1222
int range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN);
1223
float distance = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), 0.0f, 1.0f);
1224
Sint16 value = (Sint16)(SDL_JOYSTICK_AXIS_MIN + (distance * range));
1225
SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, value);
1227
float distanceX = SDL_clamp((x - virtual_axis_start_x) / GetGamepadImageAxisWidth(image), -1.0f, 1.0f);
1228
float distanceY = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), -1.0f, 1.0f);
1229
Sint16 valueX, valueY;
1231
if (distanceX >= 0) {
1232
valueX = (Sint16)(distanceX * SDL_JOYSTICK_AXIS_MAX);
1234
valueX = (Sint16)(distanceX * -SDL_JOYSTICK_AXIS_MIN);
1236
if (distanceY >= 0) {
1237
valueY = (Sint16)(distanceY * SDL_JOYSTICK_AXIS_MAX);
1239
valueY = (Sint16)(distanceY * -SDL_JOYSTICK_AXIS_MIN);
1241
SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, valueX);
1242
SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, valueY);
1246
if (virtual_touchpad_active) {
1248
GetGamepadTouchpadArea(image, &touchpad);
1249
virtual_touchpad_x = (x - touchpad.x) / touchpad.w;
1250
virtual_touchpad_y = (y - touchpad.y) / touchpad.h;
1251
SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, SDL_PRESSED, virtual_touchpad_x, virtual_touchpad_y, 1.0f);
1255
static void VirtualGamepadMouseDown(float x, float y)
1257
int element = GetGamepadImageElementAt(image, x, y);
1259
if (element == SDL_GAMEPAD_ELEMENT_INVALID) {
1260
SDL_FPoint point = { x, y };
1262
GetGamepadTouchpadArea(image, &touchpad);
1263
if (SDL_PointInRectFloat(&point, &touchpad)) {
1264
virtual_touchpad_active = SDL_TRUE;
1265
virtual_touchpad_x = (x - touchpad.x) / touchpad.w;
1266
virtual_touchpad_y = (y - touchpad.y) / touchpad.h;
1267
SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, SDL_PRESSED, virtual_touchpad_x, virtual_touchpad_y, 1.0f);
1272
if (element < SDL_GAMEPAD_BUTTON_MAX) {
1273
virtual_button_active = (SDL_GamepadButton)element;
1274
SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, SDL_PRESSED);
1277
case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE:
1278
case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE:
1279
case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE:
1280
case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE:
1281
virtual_axis_active = SDL_GAMEPAD_AXIS_LEFTX;
1283
case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE:
1284
case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE:
1285
case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE:
1286
case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE:
1287
virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHTX;
1289
case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER:
1290
virtual_axis_active = SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
1292
case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER:
1293
virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
1296
virtual_axis_start_x = x;
1297
virtual_axis_start_y = y;
1301
static void VirtualGamepadMouseUp(float x, float y)
1303
if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) {
1304
SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, SDL_RELEASED);
1305
virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
1308
if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
1309
if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
1310
virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
1311
SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, SDL_JOYSTICK_AXIS_MIN);
1313
SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, 0);
1314
SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, 0);
1316
virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
1319
if (virtual_touchpad_active) {
1320
SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, SDL_RELEASED, virtual_touchpad_x, virtual_touchpad_y, 0.0f);
1321
virtual_touchpad_active = SDL_FALSE;
1325
static void DrawGamepadWaiting(SDL_Renderer *renderer)
1327
const char *text = "Waiting for gamepad, press A to add a virtual controller";
1330
x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
1331
y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2;
1332
SDLTest_DrawString(renderer, x, y, text);
1335
static void DrawGamepadInfo(SDL_Renderer *renderer)
1342
if (title_highlighted) {
1345
SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
1347
if (title_pressed) {
1348
SDL_SetRenderDrawColor(renderer, PRESSED_COLOR);
1350
SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR);
1352
SDL_RenderFillRect(renderer, &title_area);
1354
SDL_SetRenderDrawColor(renderer, r, g, b, a);
1357
if (type_highlighted) {
1360
SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
1363
SDL_SetRenderDrawColor(renderer, PRESSED_COLOR);
1365
SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR);
1367
SDL_RenderFillRect(renderer, &type_area);
1369
SDL_SetRenderDrawColor(renderer, r, g, b, a);
1372
if (controller->joystick) {
1373
SDL_snprintf(text, sizeof(text), "(%" SDL_PRIu32 ")", SDL_GetJoystickID(controller->joystick));
1374
x = SCREEN_WIDTH - (FONT_CHARACTER_SIZE * SDL_strlen(text)) - 8.0f;
1376
SDLTest_DrawString(renderer, x, y, text);
1379
if (controller_name && *controller_name) {
1380
x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2;
1381
y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2;
1382
SDLTest_DrawString(renderer, x, y, controller_name);
1385
if (SDL_IsJoystickVirtual(controller->id)) {
1386
SDL_strlcpy(text, "Click on the gamepad image below to generate input", sizeof(text));
1387
x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
1388
y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2 + FONT_LINE_HEIGHT + 2.0f;
1389
SDLTest_DrawString(renderer, x, y, text);
1392
type = GetGamepadTypeString(SDL_GetGamepadType(controller->gamepad));
1393
x = type_area.x + type_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(type)) / 2;
1394
y = type_area.y + type_area.h / 2 - FONT_CHARACTER_SIZE / 2;
1395
SDLTest_DrawString(renderer, x, y, type);
1397
if (display_mode == CONTROLLER_MODE_TESTING) {
1398
Uint64 steam_handle = SDL_GetGamepadSteamHandle(controller->gamepad);
1400
SDL_snprintf(text, SDL_arraysize(text), "Steam: 0x%.16" SDL_PRIx64, steam_handle);
1401
y = SCREEN_HEIGHT - 2 * (8.0f + FONT_LINE_HEIGHT);
1402
x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
1403
SDLTest_DrawString(renderer, x, y, text);
1406
SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x",
1407
SDL_GetJoystickVendor(controller->joystick),
1408
SDL_GetJoystickProduct(controller->joystick));
1409
y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
1410
x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
1411
SDLTest_DrawString(renderer, x, y, text);
1413
serial = SDL_GetJoystickSerial(controller->joystick);
1414
if (serial && *serial) {
1415
SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial);
1416
x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
1417
y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
1418
SDLTest_DrawString(renderer, x, y, text);
1423
static const char *GetButtonLabel(SDL_GamepadType type, SDL_GamepadButton button)
1425
switch (SDL_GetGamepadButtonLabelForType(type, button)) {
1426
case SDL_GAMEPAD_BUTTON_LABEL_A:
1428
case SDL_GAMEPAD_BUTTON_LABEL_B:
1430
case SDL_GAMEPAD_BUTTON_LABEL_X:
1432
case SDL_GAMEPAD_BUTTON_LABEL_Y:
1434
case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
1436
case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
1438
case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
1440
case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
1447
static void DrawBindingTips(SDL_Renderer *renderer)
1450
SDL_FRect image_area, button_area;
1453
GetGamepadImageArea(image, &image_area);
1454
GetGamepadButtonArea(done_mapping_button, &button_area);
1455
x = image_area.x + image_area.w / 2;
1456
y = image_area.y + image_area.h;
1457
y += (button_area.y - y - FONT_CHARACTER_SIZE) / 2;
1459
text = GetBindingInstruction();
1461
if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
1462
SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
1466
SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH;
1467
SDL_bool bound_forward = MappingHasElement(controller->mapping, action_forward);
1468
SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST;
1469
SDL_bool bound_backward = MappingHasElement(controller->mapping, action_backward);
1470
SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST;
1471
SDL_bool bound_delete = MappingHasElement(controller->mapping, action_delete);
1473
y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2;
1475
rect.w = 2.0f + (FONT_CHARACTER_SIZE * SDL_strlen(text)) + 2.0f;
1476
rect.h = 2.0f + FONT_CHARACTER_SIZE + 2.0f;
1477
rect.x = x - rect.w / 2;
1480
SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
1481
SDL_SetRenderDrawColor(renderer, SELECTED_COLOR);
1482
SDL_RenderFillRect(renderer, &rect);
1483
SDL_SetRenderDrawColor(renderer, r, g, b, a);
1484
SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
1486
y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN);
1488
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1489
text = "(press RETURN to complete)";
1490
} else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE ||
1491
binding_element == action_forward ||
1492
binding_element == action_backward) {
1493
text = "(press ESC to cancel)";
1495
static char dynamic_text[128];
1496
SDL_GamepadType type = GetGamepadImageType(image);
1497
if (binding_flow && bound_forward && bound_backward) {
1498
if (binding_element != action_delete && bound_delete) {
1499
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));
1501
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));
1503
text = dynamic_text;
1505
text = "(press SPACE to delete and ESC to cancel)";
1508
SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
1512
static void UpdateGamepadEffects(void)
1514
if (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) {
1518
/* Update LED based on left thumbstick position */
1520
Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX);
1521
Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
1524
set_LED = (x < -8000 || x > 8000 || y > 8000);
1530
r = (Uint8)(((~x) * 255) / 32767);
1534
b = (Uint8)(((int)(x)*255) / 32767);
1537
g = (Uint8)(((int)(y)*255) / 32767);
1542
SDL_SetGamepadLED(controller->gamepad, r, g, b);
1546
if (controller->trigger_effect == 0) {
1547
/* Update rumble based on trigger state */
1549
Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER);
1550
Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
1551
Uint16 low_frequency_rumble = ConvertAxisToRumble(left);
1552
Uint16 high_frequency_rumble = ConvertAxisToRumble(right);
1553
SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250);
1556
/* Update trigger rumble based on thumbstick state */
1558
Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
1559
Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
1560
Uint16 left_rumble = ConvertAxisToRumble(~left);
1561
Uint16 right_rumble = ConvertAxisToRumble(~right);
1563
SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250);
1568
static void loop(void *arg)
1572
/* If we have a virtual controller, send a virtual accelerometer sensor reading */
1573
if (virtual_joystick) {
1574
float data[3] = { 0.0f, SDL_STANDARD_GRAVITY, 0.0f };
1575
SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_ACCEL, SDL_GetTicksNS(), data, SDL_arraysize(data));
1578
/* Update to get the current event state */
1581
/* Process all currently pending events */
1582
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST) == 1) {
1583
SDL_ConvertEventToRenderCoordinates(screen, &event);
1585
switch (event.type) {
1586
case SDL_EVENT_JOYSTICK_ADDED:
1587
AddController(event.jdevice.which, SDL_TRUE);
1590
case SDL_EVENT_JOYSTICK_REMOVED:
1591
DelController(event.jdevice.which);
1594
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
1595
if (display_mode == CONTROLLER_MODE_TESTING) {
1596
if (event.jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
1597
SetController(event.jaxis.which);
1599
} else if (display_mode == CONTROLLER_MODE_BINDING &&
1600
event.jaxis.which == controller->id &&
1601
event.jaxis.axis < controller->num_axes &&
1602
binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1603
const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 gamepad needed 96 */
1604
AxisState *pAxisState = &controller->axis_state[event.jaxis.axis];
1605
int nValue = event.jaxis.value;
1606
int nCurrentDistance, nFarthestDistance;
1607
if (!pAxisState->m_bMoving) {
1608
Sint16 nInitialValue;
1609
pAxisState->m_bMoving = SDL_GetJoystickAxisInitialState(controller->joystick, event.jaxis.axis, &nInitialValue);
1610
pAxisState->m_nLastValue = nValue;
1611
pAxisState->m_nStartingValue = nInitialValue;
1612
pAxisState->m_nFarthestValue = nInitialValue;
1613
} else if (SDL_abs(nValue - pAxisState->m_nLastValue) <= MAX_ALLOWED_JITTER) {
1616
pAxisState->m_nLastValue = nValue;
1618
nCurrentDistance = SDL_abs(nValue - pAxisState->m_nStartingValue);
1619
nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue);
1620
if (nCurrentDistance > nFarthestDistance) {
1621
pAxisState->m_nFarthestValue = nValue;
1622
nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue);
1625
#ifdef DEBUG_AXIS_MAPPING
1626
SDL_Log("AXIS %d nValue %d nCurrentDistance %d nFarthestDistance %d\n", event.jaxis.axis, nValue, nCurrentDistance, nFarthestDistance);
1628
/* If we've gone out far enough and started to come back, let's bind this axis */
1629
if (nFarthestDistance >= 16000 && nCurrentDistance <= 10000) {
1631
int axis_min = StandardizeAxisValue(pAxisState->m_nStartingValue);
1632
int axis_max = StandardizeAxisValue(pAxisState->m_nFarthestValue);
1634
if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MIN) {
1635
/* The negative half axis */
1636
(void)SDL_snprintf(binding, sizeof(binding), "-a%d", event.jaxis.axis);
1637
} else if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MAX) {
1638
/* The positive half axis */
1639
(void)SDL_snprintf(binding, sizeof(binding), "+a%d", event.jaxis.axis);
1641
(void)SDL_snprintf(binding, sizeof(binding), "a%d", event.jaxis.axis);
1642
if (axis_min > axis_max) {
1643
/* Invert the axis */
1644
SDL_strlcat(binding, "~", SDL_arraysize(binding));
1647
#ifdef DEBUG_AXIS_MAPPING
1648
SDL_Log("AXIS %d axis_min = %d, axis_max = %d, binding = %s\n", event.jaxis.axis, axis_min, axis_max, binding);
1650
CommitBindingElement(binding, SDL_FALSE);
1655
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
1656
if (display_mode == CONTROLLER_MODE_TESTING) {
1657
SetController(event.jbutton.which);
1661
case SDL_EVENT_JOYSTICK_BUTTON_UP:
1662
if (display_mode == CONTROLLER_MODE_BINDING &&
1663
event.jbutton.which == controller->id &&
1664
binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1667
SDL_snprintf(binding, sizeof(binding), "b%d", event.jbutton.button);
1668
CommitBindingElement(binding, SDL_FALSE);
1672
case SDL_EVENT_JOYSTICK_HAT_MOTION:
1673
if (display_mode == CONTROLLER_MODE_BINDING &&
1674
event.jhat.which == controller->id &&
1675
event.jhat.value != SDL_HAT_CENTERED &&
1676
binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1679
SDL_snprintf(binding, sizeof(binding), "h%d.%d", event.jhat.hat, event.jhat.value);
1680
CommitBindingElement(binding, SDL_FALSE);
1684
case SDL_EVENT_GAMEPAD_ADDED:
1685
HandleGamepadAdded(event.gdevice.which, SDL_TRUE);
1688
case SDL_EVENT_GAMEPAD_REMOVED:
1689
HandleGamepadRemoved(event.gdevice.which);
1692
case SDL_EVENT_GAMEPAD_REMAPPED:
1693
HandleGamepadRemapped(event.gdevice.which);
1696
case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
1697
RefreshControllerName();
1700
#ifdef VERBOSE_TOUCHPAD
1701
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
1702
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
1703
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
1704
SDL_Log("Gamepad %" SDL_PRIu32 " touchpad %" SDL_PRIs32 " finger %" SDL_PRIs32 " %s %.2f, %.2f, %.2f\n",
1705
event.gtouchpad.which,
1706
event.gtouchpad.touchpad,
1707
event.gtouchpad.finger,
1708
(event.type == SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN ? "pressed at" : (event.type == SDL_EVENT_GAMEPAD_TOUCHPAD_UP ? "released at" : "moved to")),
1711
event.gtouchpad.pressure);
1713
#endif /* VERBOSE_TOUCHPAD */
1715
#ifdef VERBOSE_SENSORS
1716
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
1717
SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")\n",
1718
event.gsensor.which,
1719
GetSensorName((SDL_SensorType) event.gsensor.sensor),
1720
event.gsensor.data[0],
1721
event.gsensor.data[1],
1722
event.gsensor.data[2],
1723
event.gsensor.sensor_timestamp);
1725
#endif /* VERBOSE_SENSORS */
1728
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
1729
if (display_mode == CONTROLLER_MODE_TESTING) {
1730
if (event.gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
1731
SetController(event.gaxis.which);
1734
SDL_Log("Gamepad %" SDL_PRIu32 " axis %s changed to %d\n",
1736
SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event.gaxis.axis),
1739
#endif /* VERBOSE_AXES */
1741
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
1742
case SDL_EVENT_GAMEPAD_BUTTON_UP:
1743
if (display_mode == CONTROLLER_MODE_TESTING) {
1744
if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
1745
SetController(event.gbutton.which);
1748
#ifdef VERBOSE_BUTTONS
1749
SDL_Log("Gamepad %" SDL_PRIu32 " button %s %s\n",
1750
event.gbutton.which,
1751
SDL_GetGamepadStringForButton((SDL_GamepadButton) event.gbutton.button),
1752
event.gbutton.state ? "pressed" : "released");
1753
#endif /* VERBOSE_BUTTONS */
1755
if (display_mode == CONTROLLER_MODE_TESTING) {
1756
if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
1757
controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5) {
1758
/* Cycle PS5 audio routing when the microphone button is pressed */
1759
if (event.gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) {
1760
CyclePS5AudioRoute(controller);
1763
/* Cycle PS5 trigger effects when the triangle button is pressed */
1764
if (event.gbutton.button == SDL_GAMEPAD_BUTTON_NORTH) {
1765
CyclePS5TriggerEffect(controller);
1771
case SDL_EVENT_MOUSE_BUTTON_DOWN:
1772
if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
1773
VirtualGamepadMouseDown(event.button.x, event.button.y);
1775
UpdateButtonHighlights(event.button.x, event.button.y, event.button.state ? SDL_TRUE : SDL_FALSE);
1778
case SDL_EVENT_MOUSE_BUTTON_UP:
1779
if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
1780
VirtualGamepadMouseUp(event.button.x, event.button.y);
1783
if (display_mode == CONTROLLER_MODE_TESTING) {
1784
if (GamepadButtonContains(setup_mapping_button, event.button.x, event.button.y)) {
1785
SetDisplayMode(CONTROLLER_MODE_BINDING);
1787
} else if (display_mode == CONTROLLER_MODE_BINDING) {
1788
if (GamepadButtonContains(done_mapping_button, event.button.x, event.button.y)) {
1789
if (controller->mapping) {
1790
SDL_Log("Mapping complete:\n");
1791
SDL_Log("%s\n", controller->mapping);
1793
SetDisplayMode(CONTROLLER_MODE_TESTING);
1794
} else if (GamepadButtonContains(cancel_button, event.button.x, event.button.y)) {
1796
} else if (GamepadButtonContains(clear_button, event.button.x, event.button.y)) {
1798
} else if (controller->has_bindings &&
1799
GamepadButtonContains(copy_button, event.button.x, event.button.y)) {
1801
} else if (GamepadButtonContains(paste_button, event.button.x, event.button.y)) {
1803
} else if (title_pressed) {
1804
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, SDL_FALSE);
1805
} else if (type_pressed) {
1806
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_TYPE, SDL_FALSE);
1807
} else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
1808
int type = GetGamepadTypeDisplayAt(gamepad_type, event.button.x, event.button.y);
1809
if (type != SDL_GAMEPAD_TYPE_UNSELECTED) {
1810
CommitGamepadType((SDL_GamepadType)type);
1814
int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID;
1815
char *joystick_element;
1817
if (controller->joystick != virtual_joystick) {
1818
gamepad_element = GetGamepadImageElementAt(image, event.button.x, event.button.y);
1820
if (gamepad_element == SDL_GAMEPAD_ELEMENT_INVALID) {
1821
gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event.button.x, event.button.y);
1823
if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1824
/* Set this to SDL_FALSE if you don't want to start the binding flow at this point */
1825
const SDL_bool should_start_flow = SDL_TRUE;
1826
SetCurrentBindingElement(gamepad_element, should_start_flow);
1829
joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event.button.x, event.button.y);
1830
if (joystick_element) {
1831
CommitBindingElement(joystick_element, SDL_TRUE);
1832
SDL_free(joystick_element);
1836
UpdateButtonHighlights(event.button.x, event.button.y, event.button.state ? SDL_TRUE : SDL_FALSE);
1839
case SDL_EVENT_MOUSE_MOTION:
1840
if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
1841
VirtualGamepadMouseMotion(event.motion.x, event.motion.y);
1843
UpdateButtonHighlights(event.motion.x, event.motion.y, event.motion.state ? SDL_TRUE : SDL_FALSE);
1846
case SDL_EVENT_KEY_DOWN:
1847
if (display_mode == CONTROLLER_MODE_TESTING) {
1848
if (event.key.key >= SDLK_0 && event.key.key <= SDLK_9) {
1849
if (controller && controller->gamepad) {
1850
int player_index = (event.key.key - SDLK_0);
1852
SDL_SetGamepadPlayerIndex(controller->gamepad, player_index);
1855
} else if (event.key.key == SDLK_A) {
1856
OpenVirtualGamepad();
1857
} else if (event.key.key == SDLK_D) {
1858
CloseVirtualGamepad();
1859
} else if (event.key.key == SDLK_R && (event.key.mod & SDL_KMOD_CTRL)) {
1860
SDL_ReloadGamepadMappings();
1861
} else if (event.key.key == SDLK_ESCAPE) {
1864
} else if (display_mode == CONTROLLER_MODE_BINDING) {
1865
if (event.key.key == SDLK_C && (event.key.mod & SDL_KMOD_CTRL)) {
1866
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1867
CopyControllerName();
1871
} else if (event.key.key == SDLK_V && (event.key.mod & SDL_KMOD_CTRL)) {
1872
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1873
ClearControllerName();
1874
PasteControllerName();
1878
} else if (event.key.key == SDLK_X && (event.key.mod & SDL_KMOD_CTRL)) {
1879
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1880
CopyControllerName();
1881
ClearControllerName();
1886
} else if (event.key.key == SDLK_SPACE) {
1887
if (binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
1890
} else if (event.key.key == SDLK_BACKSPACE) {
1891
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1892
BackspaceControllerName();
1894
} else if (event.key.key == SDLK_RETURN) {
1895
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1898
} else if (event.key.key == SDLK_ESCAPE) {
1899
if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1907
case SDL_EVENT_TEXT_INPUT:
1908
if (display_mode == CONTROLLER_MODE_BINDING) {
1909
if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1910
AddControllerNameText(event.text.text);
1914
case SDL_EVENT_QUIT:
1922
/* Wait 30 ms for joystick events to stop coming in,
1923
in case a gamepad sends multiple events for a single control (e.g. axis and button for trigger)
1925
if (binding_advance_time && SDL_GetTicks() > (binding_advance_time + 30)) {
1927
SetNextBindingElement();
1933
/* blank screen, set up for drawing this frame. */
1934
SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE);
1935
SDL_RenderClear(screen);
1936
SDL_SetRenderDrawColor(screen, 0x10, 0x10, 0x10, SDL_ALPHA_OPAQUE);
1939
SetGamepadImageShowingFront(image, ShowingFront());
1940
UpdateGamepadImageFromGamepad(image, controller->gamepad);
1941
if (display_mode == CONTROLLER_MODE_BINDING &&
1942
binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1943
SetGamepadImageElement(image, binding_element, SDL_TRUE);
1945
RenderGamepadImage(image);
1947
if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
1948
SetGamepadTypeDisplayRealType(gamepad_type, SDL_GetRealGamepadType(controller->gamepad));
1949
RenderGamepadTypeDisplay(gamepad_type);
1951
RenderGamepadDisplay(gamepad_elements, controller->gamepad);
1953
RenderJoystickDisplay(joystick_elements, controller->joystick);
1955
if (display_mode == CONTROLLER_MODE_TESTING) {
1956
RenderGamepadButton(setup_mapping_button);
1957
} else if (display_mode == CONTROLLER_MODE_BINDING) {
1958
DrawBindingTips(screen);
1959
RenderGamepadButton(done_mapping_button);
1960
RenderGamepadButton(cancel_button);
1961
RenderGamepadButton(clear_button);
1962
if (controller->has_bindings) {
1963
RenderGamepadButton(copy_button);
1965
RenderGamepadButton(paste_button);
1968
DrawGamepadInfo(screen);
1970
UpdateGamepadEffects();
1972
DrawGamepadWaiting(screen);
1975
SDL_RenderPresent(screen);
1977
#ifdef SDL_PLATFORM_EMSCRIPTEN
1979
emscripten_cancel_main_loop();
1984
int main(int argc, char *argv[])
1986
SDL_bool show_mappings = SDL_FALSE;
1988
float content_scale;
1989
int screen_width, screen_height;
1991
int gamepad_index = -1;
1992
SDLTest_CommonState *state;
1994
/* Initialize test framework */
1995
state = SDLTest_CommonCreateState(argv, 0);
2000
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1");
2001
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
2002
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
2003
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
2004
SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1");
2005
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
2006
SDL_SetHint(SDL_HINT_JOYSTICK_LINUX_DEADZONES, "1");
2008
/* Enable standard application logging */
2009
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
2011
/* Enable input debug logging */
2012
SDL_SetLogPriority(SDL_LOG_CATEGORY_INPUT, SDL_LOG_PRIORITY_DEBUG);
2014
/* Parse commandline */
2015
for (i = 1; i < argc;) {
2018
consumed = SDLTest_CommonArg(state, i);
2020
if (SDL_strcmp(argv[i], "--mappings") == 0) {
2021
show_mappings = SDL_TRUE;
2023
} else if (SDL_strcmp(argv[i], "--virtual") == 0) {
2024
OpenVirtualGamepad();
2026
} else if (gamepad_index < 0) {
2027
char *endptr = NULL;
2028
gamepad_index = (int)SDL_strtol(argv[i], &endptr, 0);
2029
if (endptr != argv[i] && *endptr == '\0' && gamepad_index >= 0) {
2034
if (consumed <= 0) {
2035
static const char *options[] = { "[--mappings]", "[--virtual]", "[index]", NULL };
2036
SDLTest_CommonLogUsage(state, argv[0], options);
2042
if (gamepad_index < 0) {
2046
/* Initialize SDL (Note: video is required to start event loop) */
2047
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD) < 0) {
2048
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
2052
SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt");
2054
if (show_mappings) {
2056
char **mappings = SDL_GetGamepadMappings(&count);
2058
SDL_Log("Supported mappings:\n");
2059
for (map_i = 0; map_i < count; ++map_i) {
2060
SDL_Log("\t%s\n", mappings[map_i]);
2066
/* Create a window to display gamepad state */
2067
content_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
2068
if (content_scale == 0.0f) {
2069
content_scale = 1.0f;
2071
screen_width = (int)SDL_ceilf(SCREEN_WIDTH * content_scale);
2072
screen_height = (int)SDL_ceilf(SCREEN_HEIGHT * content_scale);
2073
window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, 0);
2075
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError());
2079
screen = SDL_CreateRenderer(window, NULL);
2081
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError());
2082
SDL_DestroyWindow(window);
2086
SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE);
2087
SDL_RenderClear(screen);
2088
SDL_RenderPresent(screen);
2090
/* scale for platforms that don't give you the window size you asked for. */
2091
SDL_SetRenderLogicalPresentation(screen, (int)SCREEN_WIDTH, (int)SCREEN_HEIGHT,
2092
SDL_LOGICAL_PRESENTATION_LETTERBOX,
2093
SDL_SCALEMODE_LINEAR);
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;
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;
2106
image = CreateGamepadImage(screen);
2108
SDL_DestroyRenderer(screen);
2109
SDL_DestroyWindow(window);
2112
SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT);
2114
gamepad_elements = CreateGamepadDisplay(screen);
2116
area.y = TITLE_HEIGHT;
2117
area.w = PANEL_WIDTH;
2118
area.h = GAMEPAD_HEIGHT;
2119
SetGamepadDisplayArea(gamepad_elements, &area);
2121
gamepad_type = CreateGamepadTypeDisplay(screen);
2123
area.y = TITLE_HEIGHT;
2124
area.w = PANEL_WIDTH;
2125
area.h = GAMEPAD_HEIGHT;
2126
SetGamepadTypeDisplayArea(gamepad_type, &area);
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);
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);
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);
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);
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);
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);
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);
2177
/* Process the initial gamepad list */
2180
if (gamepad_index < num_controllers) {
2181
SetController(controllers[gamepad_index].id);
2182
} else if (num_controllers > 0) {
2183
SetController(controllers[0].id);
2186
/* Loop, getting gamepad events! */
2187
#ifdef SDL_PLATFORM_EMSCRIPTEN
2188
emscripten_set_main_loop_arg(loop, NULL, 0, 1);
2195
CloseVirtualGamepad();
2196
while (num_controllers > 0) {
2197
HandleGamepadRemoved(controllers[0].id);
2198
DelController(controllers[0].id);
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);
2216
SDLTest_CommonDestroyState(state);