summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/joystick
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/joystick
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/joystick')
-rw-r--r--contrib/SDL-3.2.8/src/joystick/SDL_gamepad.c3907
-rw-r--r--contrib/SDL-3.2.8/src/joystick/SDL_gamepad_c.h50
-rw-r--r--contrib/SDL-3.2.8/src/joystick/SDL_gamepad_db.h904
-rw-r--r--contrib/SDL-3.2.8/src/joystick/SDL_joystick.c3675
-rw-r--r--contrib/SDL-3.2.8/src/joystick/SDL_joystick_c.h270
-rw-r--r--contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.c254
-rw-r--r--contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.h36
-rw-r--r--contrib/SDL-3.2.8/src/joystick/SDL_sysjoystick.h269
-rw-r--r--contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick.c681
-rw-r--r--contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick_c.h57
-rw-r--r--contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick.m1946
-rw-r--r--contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick_c.h73
-rw-r--r--contrib/SDL-3.2.8/src/joystick/bsd/SDL_bsdjoystick.c868
-rwxr-xr-xcontrib/SDL-3.2.8/src/joystick/check_8bitdo.sh15
-rw-r--r--contrib/SDL-3.2.8/src/joystick/controller_list.h609
-rw-r--r--contrib/SDL-3.2.8/src/joystick/controller_type.c140
-rw-r--r--contrib/SDL-3.2.8/src/joystick/controller_type.h78
-rw-r--r--contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick.c1089
-rw-r--r--contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick_c.h80
-rw-r--r--contrib/SDL-3.2.8/src/joystick/dummy/SDL_sysjoystick.c156
-rw-r--r--contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick.c445
-rw-r--r--contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick_c.h49
-rw-r--r--contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c828
-rw-r--r--contrib/SDL-3.2.8/src/joystick/haiku/SDL_haikujoystick.cc315
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_combined.c236
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_gamecube.c534
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_luna.c421
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_nintendo.h49
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps3.c1446
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps4.c1390
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps5.c1624
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.c285
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.h42
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_shield.c578
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_stadia.c324
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam.c1534
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam_hori.c415
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steamdeck.c451
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_switch.c2859
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_wii.c1617
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360.c379
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360w.c388
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xboxone.c1675
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick.c1730
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick_c.h195
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_constants.h582
-rw-r--r--contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_structs.h463
-rw-r--r--contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick.c2730
-rw-r--r--contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick_c.h117
-rw-r--r--contrib/SDL-3.2.8/src/joystick/n3ds/SDL_sysjoystick.c298
-rw-r--r--contrib/SDL-3.2.8/src/joystick/ps2/SDL_sysjoystick.c366
-rw-r--r--contrib/SDL-3.2.8/src/joystick/psp/SDL_sysjoystick.c277
-rwxr-xr-xcontrib/SDL-3.2.8/src/joystick/sort_controllers.py164
-rw-r--r--contrib/SDL-3.2.8/src/joystick/usb_ids.h194
-rw-r--r--contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick.c990
-rw-r--r--contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick_c.h84
-rw-r--r--contrib/SDL-3.2.8/src/joystick/vita/SDL_sysjoystick.c400
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c1210
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h40
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c2238
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h32
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c1039
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c693
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h103
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c473
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h44
66 files changed, 47503 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_gamepad.c b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad.c
new file mode 100644
index 0000000..8486865
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad.c
@@ -0,0 +1,3907 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// This is the gamepad API for Simple DirectMedia Layer
24
25#include "SDL_sysjoystick.h"
26#include "SDL_joystick_c.h"
27#include "SDL_steam_virtual_gamepad.h"
28#include "SDL_gamepad_c.h"
29#include "SDL_gamepad_db.h"
30#include "controller_type.h"
31#include "usb_ids.h"
32#include "hidapi/SDL_hidapi_nintendo.h"
33#include "../events/SDL_events_c.h"
34
35
36#ifdef SDL_PLATFORM_ANDROID
37#endif
38
39// Many gamepads turn the center button into an instantaneous button press
40#define SDL_MINIMUM_GUIDE_BUTTON_DELAY_MS 250
41
42#define SDL_GAMEPAD_CRC_FIELD "crc:"
43#define SDL_GAMEPAD_CRC_FIELD_SIZE 4 // hard-coded for speed
44#define SDL_GAMEPAD_TYPE_FIELD "type:"
45#define SDL_GAMEPAD_TYPE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_TYPE_FIELD)
46#define SDL_GAMEPAD_FACE_FIELD "face:"
47#define SDL_GAMEPAD_FACE_FIELD_SIZE 5 // hard-coded for speed
48#define SDL_GAMEPAD_PLATFORM_FIELD "platform:"
49#define SDL_GAMEPAD_PLATFORM_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_PLATFORM_FIELD)
50#define SDL_GAMEPAD_HINT_FIELD "hint:"
51#define SDL_GAMEPAD_HINT_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_HINT_FIELD)
52#define SDL_GAMEPAD_SDKGE_FIELD "sdk>=:"
53#define SDL_GAMEPAD_SDKGE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKGE_FIELD)
54#define SDL_GAMEPAD_SDKLE_FIELD "sdk<=:"
55#define SDL_GAMEPAD_SDKLE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKLE_FIELD)
56
57static bool SDL_gamepads_initialized;
58static SDL_Gamepad *SDL_gamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
59
60// The face button style of a gamepad
61typedef enum
62{
63 SDL_GAMEPAD_FACE_STYLE_UNKNOWN,
64 SDL_GAMEPAD_FACE_STYLE_ABXY,
65 SDL_GAMEPAD_FACE_STYLE_BAYX,
66 SDL_GAMEPAD_FACE_STYLE_SONY,
67} SDL_GamepadFaceStyle;
68
69// our hard coded list of mapping support
70typedef enum
71{
72 SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT,
73 SDL_GAMEPAD_MAPPING_PRIORITY_API,
74 SDL_GAMEPAD_MAPPING_PRIORITY_USER,
75} SDL_GamepadMappingPriority;
76
77#define _guarded SDL_GUARDED_BY(SDL_joystick_lock)
78
79typedef struct GamepadMapping_t
80{
81 SDL_GUID guid _guarded;
82 char *name _guarded;
83 char *mapping _guarded;
84 SDL_GamepadMappingPriority priority _guarded;
85 struct GamepadMapping_t *next _guarded;
86} GamepadMapping_t;
87
88typedef struct
89{
90 int refcount _guarded;
91 SDL_JoystickID *joysticks _guarded;
92 GamepadMapping_t **joystick_mappings _guarded;
93
94 int num_changed_mappings _guarded;
95 GamepadMapping_t **changed_mappings _guarded;
96
97} MappingChangeTracker;
98
99#undef _guarded
100
101static SDL_GUID s_zeroGUID;
102static GamepadMapping_t *s_pSupportedGamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
103static GamepadMapping_t *s_pDefaultMapping SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
104static GamepadMapping_t *s_pXInputMapping SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
105static MappingChangeTracker *s_mappingChangeTracker SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
106static SDL_HashTable *s_gamepadInstanceIDs SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
107
108#define _guarded SDL_GUARDED_BY(SDL_joystick_lock)
109
110// The SDL gamepad structure
111struct SDL_Gamepad
112{
113 SDL_Joystick *joystick _guarded; // underlying joystick device
114 int ref_count _guarded;
115
116 const char *name _guarded;
117 SDL_GamepadType type _guarded;
118 SDL_GamepadFaceStyle face_style _guarded;
119 GamepadMapping_t *mapping _guarded;
120 int num_bindings _guarded;
121 SDL_GamepadBinding *bindings _guarded;
122 SDL_GamepadBinding **last_match_axis _guarded;
123 Uint8 *last_hat_mask _guarded;
124 Uint64 guide_button_down _guarded;
125
126 struct SDL_Gamepad *next _guarded; // pointer to next gamepad we have allocated
127};
128
129#undef _guarded
130
131#define CHECK_GAMEPAD_MAGIC(gamepad, result) \
132 if (!SDL_ObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD) || \
133 !SDL_IsJoystickValid(gamepad->joystick)) { \
134 SDL_InvalidParamError("gamepad"); \
135 SDL_UnlockJoysticks(); \
136 return result; \
137 }
138
139static SDL_vidpid_list SDL_allowed_gamepads = {
140 SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, 0, 0, NULL,
141 NULL, 0, 0, NULL,
142 0, NULL,
143 false
144};
145static SDL_vidpid_list SDL_ignored_gamepads = {
146 SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, 0, 0, NULL,
147 NULL, 0, 0, NULL,
148 0, NULL,
149 false
150};
151
152static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_GUID jGUID, const char *mappingString, bool *existing, SDL_GamepadMappingPriority priority);
153static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t *pGamepadMapping);
154static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id, bool create_mapping);
155static void SDL_SendGamepadAxis(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadAxis axis, Sint16 value);
156static void SDL_SendGamepadButton(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadButton button, bool down);
157
158static bool HasSameOutput(SDL_GamepadBinding *a, SDL_GamepadBinding *b)
159{
160 if (a->output_type != b->output_type) {
161 return false;
162 }
163
164 if (a->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {
165 return a->output.axis.axis == b->output.axis.axis;
166 } else {
167 return a->output.button == b->output.button;
168 }
169}
170
171static void ResetOutput(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadBinding *bind)
172{
173 if (bind->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {
174 SDL_SendGamepadAxis(timestamp, gamepad, bind->output.axis.axis, 0);
175 } else {
176 SDL_SendGamepadButton(timestamp, gamepad, bind->output.button, false);
177 }
178}
179
180static void HandleJoystickAxis(Uint64 timestamp, SDL_Gamepad *gamepad, int axis, int value)
181{
182 int i;
183 SDL_GamepadBinding *last_match;
184 SDL_GamepadBinding *match = NULL;
185
186 SDL_AssertJoysticksLocked();
187
188 last_match = gamepad->last_match_axis[axis];
189 for (i = 0; i < gamepad->num_bindings; ++i) {
190 SDL_GamepadBinding *binding = &gamepad->bindings[i];
191 if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS &&
192 axis == binding->input.axis.axis) {
193 if (binding->input.axis.axis_min < binding->input.axis.axis_max) {
194 if (value >= binding->input.axis.axis_min &&
195 value <= binding->input.axis.axis_max) {
196 match = binding;
197 break;
198 }
199 } else {
200 if (value >= binding->input.axis.axis_max &&
201 value <= binding->input.axis.axis_min) {
202 match = binding;
203 break;
204 }
205 }
206 }
207 }
208
209 if (last_match && (!match || !HasSameOutput(last_match, match))) {
210 // Clear the last input that this axis generated
211 ResetOutput(timestamp, gamepad, last_match);
212 }
213
214 if (match) {
215 if (match->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {
216 if (match->input.axis.axis_min != match->output.axis.axis_min || match->input.axis.axis_max != match->output.axis.axis_max) {
217 float normalized_value = (float)(value - match->input.axis.axis_min) / (match->input.axis.axis_max - match->input.axis.axis_min);
218 value = match->output.axis.axis_min + (int)(normalized_value * (match->output.axis.axis_max - match->output.axis.axis_min));
219 }
220 SDL_SendGamepadAxis(timestamp, gamepad, match->output.axis.axis, (Sint16)value);
221 } else {
222 bool down;
223 int threshold = match->input.axis.axis_min + (match->input.axis.axis_max - match->input.axis.axis_min) / 2;
224 if (match->input.axis.axis_max < match->input.axis.axis_min) {
225 down = (value <= threshold);
226 } else {
227 down = (value >= threshold);
228 }
229 SDL_SendGamepadButton(timestamp, gamepad, match->output.button, down);
230 }
231 }
232 gamepad->last_match_axis[axis] = match;
233}
234
235static void HandleJoystickButton(Uint64 timestamp, SDL_Gamepad *gamepad, int button, bool down)
236{
237 int i;
238
239 SDL_AssertJoysticksLocked();
240
241 for (i = 0; i < gamepad->num_bindings; ++i) {
242 SDL_GamepadBinding *binding = &gamepad->bindings[i];
243 if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON &&
244 button == binding->input.button) {
245 if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {
246 int value = down ? binding->output.axis.axis_max : binding->output.axis.axis_min;
247 SDL_SendGamepadAxis(timestamp, gamepad, binding->output.axis.axis, (Sint16)value);
248 } else {
249 SDL_SendGamepadButton(timestamp, gamepad, binding->output.button, down);
250 }
251 break;
252 }
253 }
254}
255
256static void HandleJoystickHat(Uint64 timestamp, SDL_Gamepad *gamepad, int hat, Uint8 value)
257{
258 int i;
259 Uint8 last_mask, changed_mask;
260
261 SDL_AssertJoysticksLocked();
262
263 last_mask = gamepad->last_hat_mask[hat];
264 changed_mask = (last_mask ^ value);
265 for (i = 0; i < gamepad->num_bindings; ++i) {
266 SDL_GamepadBinding *binding = &gamepad->bindings[i];
267 if (binding->input_type == SDL_GAMEPAD_BINDTYPE_HAT && hat == binding->input.hat.hat) {
268 if ((changed_mask & binding->input.hat.hat_mask) != 0) {
269 if (value & binding->input.hat.hat_mask) {
270 if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {
271 SDL_SendGamepadAxis(timestamp, gamepad, binding->output.axis.axis, (Sint16)binding->output.axis.axis_max);
272 } else {
273 SDL_SendGamepadButton(timestamp, gamepad, binding->output.button, true);
274 }
275 } else {
276 ResetOutput(timestamp, gamepad, binding);
277 }
278 }
279 }
280 }
281 gamepad->last_hat_mask[hat] = value;
282}
283
284/* The joystick layer will _also_ send events to recenter before disconnect,
285 but it has to make (sometimes incorrect) guesses at what being "centered"
286 is. The gamepad layer, however, can set a definite logical idle
287 position, so set them all here. If we happened to already be at the
288 center thanks to the joystick layer or idle hands, this won't generate
289 duplicate events. */
290static void RecenterGamepad(SDL_Gamepad *gamepad)
291{
292 int i;
293 Uint64 timestamp = SDL_GetTicksNS();
294
295 for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {
296 SDL_GamepadButton button = (SDL_GamepadButton)i;
297 if (SDL_GetGamepadButton(gamepad, button)) {
298 SDL_SendGamepadButton(timestamp, gamepad, button, false);
299 }
300 }
301
302 for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) {
303 SDL_GamepadAxis axis = (SDL_GamepadAxis)i;
304 if (SDL_GetGamepadAxis(gamepad, axis) != 0) {
305 SDL_SendGamepadAxis(timestamp, gamepad, axis, 0);
306 }
307 }
308}
309
310void SDL_PrivateGamepadAdded(SDL_JoystickID instance_id)
311{
312 SDL_Event event;
313
314 if (!SDL_gamepads_initialized) {
315 return;
316 }
317
318 event.type = SDL_EVENT_GAMEPAD_ADDED;
319 event.common.timestamp = 0;
320 event.gdevice.which = instance_id;
321 SDL_PushEvent(&event);
322}
323
324void SDL_PrivateGamepadRemoved(SDL_JoystickID instance_id)
325{
326 SDL_Event event;
327 SDL_Gamepad *gamepad;
328
329 SDL_AssertJoysticksLocked();
330
331 if (!SDL_gamepads_initialized) {
332 return;
333 }
334
335 for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
336 if (gamepad->joystick->instance_id == instance_id) {
337 RecenterGamepad(gamepad);
338 break;
339 }
340 }
341
342 event.type = SDL_EVENT_GAMEPAD_REMOVED;
343 event.common.timestamp = 0;
344 event.gdevice.which = instance_id;
345 SDL_PushEvent(&event);
346}
347
348static void SDL_PrivateGamepadRemapped(SDL_JoystickID instance_id)
349{
350 SDL_Event event;
351
352 if (!SDL_gamepads_initialized || SDL_IsJoystickBeingAdded()) {
353 return;
354 }
355
356 event.type = SDL_EVENT_GAMEPAD_REMAPPED;
357 event.common.timestamp = 0;
358 event.gdevice.which = instance_id;
359 SDL_PushEvent(&event);
360}
361
362/*
363 * Event filter to fire gamepad events from joystick ones
364 */
365static bool SDLCALL SDL_GamepadEventWatcher(void *userdata, SDL_Event *event)
366{
367 SDL_Gamepad *gamepad;
368
369 switch (event->type) {
370 case SDL_EVENT_JOYSTICK_AXIS_MOTION:
371 {
372 SDL_AssertJoysticksLocked();
373
374 for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
375 if (gamepad->joystick->instance_id == event->jaxis.which) {
376 HandleJoystickAxis(event->common.timestamp, gamepad, event->jaxis.axis, event->jaxis.value);
377 break;
378 }
379 }
380 } break;
381 case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
382 case SDL_EVENT_JOYSTICK_BUTTON_UP:
383 {
384 SDL_AssertJoysticksLocked();
385
386 for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
387 if (gamepad->joystick->instance_id == event->jbutton.which) {
388 HandleJoystickButton(event->common.timestamp, gamepad, event->jbutton.button, event->jbutton.down);
389 break;
390 }
391 }
392 } break;
393 case SDL_EVENT_JOYSTICK_HAT_MOTION:
394 {
395 SDL_AssertJoysticksLocked();
396
397 for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
398 if (gamepad->joystick->instance_id == event->jhat.which) {
399 HandleJoystickHat(event->common.timestamp, gamepad, event->jhat.hat, event->jhat.value);
400 break;
401 }
402 }
403 } break;
404 case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
405 {
406 SDL_AssertJoysticksLocked();
407
408 for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
409 if (gamepad->joystick->instance_id == event->jdevice.which) {
410 SDL_Event deviceevent;
411
412 deviceevent.type = SDL_EVENT_GAMEPAD_UPDATE_COMPLETE;
413 deviceevent.common.timestamp = event->jdevice.timestamp;
414 deviceevent.gdevice.which = event->jdevice.which;
415 SDL_PushEvent(&deviceevent);
416 break;
417 }
418 }
419 } break;
420 default:
421 break;
422 }
423
424 return true;
425}
426
427/* SDL defines sensor orientation relative to the device natural
428 orientation, so when it's changed orientation to be used as a
429 gamepad, change the sensor orientation to match.
430 */
431static void AdjustSensorOrientation(const SDL_Joystick *joystick, const float *src, float *dst)
432{
433 unsigned int i, j;
434
435 SDL_AssertJoysticksLocked();
436
437 for (i = 0; i < 3; ++i) {
438 dst[i] = 0.0f;
439 for (j = 0; j < 3; ++j) {
440 dst[i] += joystick->sensor_transform[i][j] * src[j];
441 }
442 }
443}
444
445/*
446 * Event filter to fire gamepad sensor events from system sensor events
447 *
448 * We don't use SDL_GamepadEventWatcher() for this because we want to
449 * deliver gamepad sensor events when system sensor events are disabled,
450 * and we also need to avoid a potential deadlock where joystick event
451 * delivery locks the joysticks and then the event queue, but sensor
452 * event delivery would lock the event queue and then from within the
453 * event watcher function lock the joysticks.
454 */
455void SDL_GamepadSensorWatcher(Uint64 timestamp, SDL_SensorID sensor, Uint64 sensor_timestamp, float *data, int num_values)
456{
457 SDL_Gamepad *gamepad;
458
459 SDL_LockJoysticks();
460 for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
461 if (gamepad->joystick->accel && gamepad->joystick->accel_sensor == sensor) {
462 float gamepad_data[3];
463 AdjustSensorOrientation(gamepad->joystick, data, gamepad_data);
464 SDL_SendJoystickSensor(timestamp, gamepad->joystick, SDL_SENSOR_ACCEL, sensor_timestamp, gamepad_data, SDL_arraysize(gamepad_data));
465 }
466 if (gamepad->joystick->gyro && gamepad->joystick->gyro_sensor == sensor) {
467 float gamepad_data[3];
468 AdjustSensorOrientation(gamepad->joystick, data, gamepad_data);
469 SDL_SendJoystickSensor(timestamp, gamepad->joystick, SDL_SENSOR_GYRO, sensor_timestamp, gamepad_data, SDL_arraysize(gamepad_data));
470 }
471 }
472 SDL_UnlockJoysticks();
473}
474
475static void PushMappingChangeTracking(void)
476{
477 MappingChangeTracker *tracker;
478 int i, num_joysticks;
479
480 SDL_AssertJoysticksLocked();
481
482 if (s_mappingChangeTracker) {
483 ++s_mappingChangeTracker->refcount;
484 return;
485 }
486 s_mappingChangeTracker = (MappingChangeTracker *)SDL_calloc(1, sizeof(*tracker));
487 s_mappingChangeTracker->refcount = 1;
488
489 // Save the list of joysticks and associated mappings
490 tracker = s_mappingChangeTracker;
491 tracker->joysticks = SDL_GetJoysticks(&num_joysticks);
492 if (!tracker->joysticks) {
493 return;
494 }
495 if (num_joysticks == 0) {
496 return;
497 }
498 tracker->joystick_mappings = (GamepadMapping_t **)SDL_malloc(num_joysticks * sizeof(*tracker->joystick_mappings));
499 if (!tracker->joystick_mappings) {
500 return;
501 }
502 for (i = 0; i < num_joysticks; ++i) {
503 tracker->joystick_mappings[i] = SDL_PrivateGetGamepadMapping(tracker->joysticks[i], false);
504 }
505}
506
507static void AddMappingChangeTracking(GamepadMapping_t *mapping)
508{
509 MappingChangeTracker *tracker;
510 int num_mappings;
511 GamepadMapping_t **new_mappings;
512
513 SDL_AssertJoysticksLocked();
514
515 SDL_assert(s_mappingChangeTracker != NULL);
516 tracker = s_mappingChangeTracker;
517 num_mappings = tracker->num_changed_mappings;
518 new_mappings = (GamepadMapping_t **)SDL_realloc(tracker->changed_mappings, (num_mappings + 1) * sizeof(*new_mappings));
519 if (new_mappings) {
520 tracker->changed_mappings = new_mappings;
521 tracker->changed_mappings[num_mappings] = mapping;
522 tracker->num_changed_mappings = (num_mappings + 1);
523 }
524}
525
526static bool HasMappingChangeTracking(MappingChangeTracker *tracker, GamepadMapping_t *mapping)
527{
528 int i;
529
530 SDL_AssertJoysticksLocked();
531
532 for (i = 0; i < tracker->num_changed_mappings; ++i) {
533 if (tracker->changed_mappings[i] == mapping) {
534 return true;
535 }
536 }
537 return false;
538}
539
540static void PopMappingChangeTracking(void)
541{
542 int i;
543 MappingChangeTracker *tracker;
544
545 SDL_AssertJoysticksLocked();
546
547 SDL_assert(s_mappingChangeTracker != NULL);
548 tracker = s_mappingChangeTracker;
549 --tracker->refcount;
550 if (tracker->refcount > 0) {
551 return;
552 }
553 s_mappingChangeTracker = NULL;
554
555 // Now check to see what gamepads changed because of the mapping changes
556 if (tracker->joysticks && tracker->joystick_mappings) {
557 for (i = 0; tracker->joysticks[i]; ++i) {
558 // Looking up the new mapping might create one and associate it with the gamepad (and generate events)
559 SDL_JoystickID joystick = tracker->joysticks[i];
560 SDL_Gamepad *gamepad = SDL_GetGamepadFromID(joystick);
561 GamepadMapping_t *new_mapping = SDL_PrivateGetGamepadMapping(joystick, false);
562 GamepadMapping_t *old_mapping = gamepad ? gamepad->mapping : tracker->joystick_mappings[i];
563
564 if (new_mapping && !old_mapping) {
565 SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)true, true);
566 SDL_PrivateGamepadAdded(joystick);
567 } else if (old_mapping && !new_mapping) {
568 SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)false, true);
569 SDL_PrivateGamepadRemoved(joystick);
570 } else if (old_mapping != new_mapping || HasMappingChangeTracking(tracker, new_mapping)) {
571 if (gamepad) {
572 SDL_PrivateLoadButtonMapping(gamepad, new_mapping);
573 }
574 SDL_PrivateGamepadRemapped(joystick);
575 }
576 }
577 }
578
579 SDL_free(tracker->joysticks);
580 SDL_free(tracker->joystick_mappings);
581 SDL_free(tracker->changed_mappings);
582 SDL_free(tracker);
583}
584
585#ifdef SDL_PLATFORM_ANDROID
586/*
587 * Helper function to guess at a mapping based on the elements reported for this gamepad
588 */
589static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_GUID guid)
590{
591 const int face_button_mask = ((1 << SDL_GAMEPAD_BUTTON_SOUTH) |
592 (1 << SDL_GAMEPAD_BUTTON_EAST) |
593 (1 << SDL_GAMEPAD_BUTTON_WEST) |
594 (1 << SDL_GAMEPAD_BUTTON_NORTH));
595 bool existing;
596 char mapping_string[1024];
597 int button_mask;
598 int axis_mask;
599
600 button_mask = SDL_Swap16LE(*(Uint16 *)(&guid.data[sizeof(guid.data) - 4]));
601 axis_mask = SDL_Swap16LE(*(Uint16 *)(&guid.data[sizeof(guid.data) - 2]));
602 if (!button_mask && !axis_mask) {
603 // Accelerometer, shouldn't have a gamepad mapping
604 return NULL;
605 }
606 if (!(button_mask & face_button_mask)) {
607 // We don't know what buttons or axes are supported, don't make up a mapping
608 return NULL;
609 }
610
611 SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));
612
613 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_SOUTH)) {
614 SDL_strlcat(mapping_string, "a:b0,", sizeof(mapping_string));
615 }
616 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_EAST)) {
617 SDL_strlcat(mapping_string, "b:b1,", sizeof(mapping_string));
618 } else if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {
619 // Use the back button as "B" for easy UI navigation with TV remotes
620 SDL_strlcat(mapping_string, "b:b4,", sizeof(mapping_string));
621 button_mask &= ~(1 << SDL_GAMEPAD_BUTTON_BACK);
622 }
623 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_WEST)) {
624 SDL_strlcat(mapping_string, "x:b2,", sizeof(mapping_string));
625 }
626 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_NORTH)) {
627 SDL_strlcat(mapping_string, "y:b3,", sizeof(mapping_string));
628 }
629 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {
630 SDL_strlcat(mapping_string, "back:b4,", sizeof(mapping_string));
631 }
632 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_GUIDE)) {
633 // The guide button generally isn't functional (or acts as a home button) on most Android gamepads before Android 11
634 if (SDL_GetAndroidSDKVersion() >= 30 /* Android 11 */) {
635 SDL_strlcat(mapping_string, "guide:b5,", sizeof(mapping_string));
636 }
637 }
638 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) {
639 SDL_strlcat(mapping_string, "start:b6,", sizeof(mapping_string));
640 }
641 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) {
642 SDL_strlcat(mapping_string, "leftstick:b7,", sizeof(mapping_string));
643 }
644 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) {
645 SDL_strlcat(mapping_string, "rightstick:b8,", sizeof(mapping_string));
646 }
647 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {
648 SDL_strlcat(mapping_string, "leftshoulder:b9,", sizeof(mapping_string));
649 }
650 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {
651 SDL_strlcat(mapping_string, "rightshoulder:b10,", sizeof(mapping_string));
652 }
653 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_UP)) {
654 SDL_strlcat(mapping_string, "dpup:b11,", sizeof(mapping_string));
655 }
656 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN)) {
657 SDL_strlcat(mapping_string, "dpdown:b12,", sizeof(mapping_string));
658 }
659 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT)) {
660 SDL_strlcat(mapping_string, "dpleft:b13,", sizeof(mapping_string));
661 }
662 if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) {
663 SDL_strlcat(mapping_string, "dpright:b14,", sizeof(mapping_string));
664 }
665 if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTX)) {
666 SDL_strlcat(mapping_string, "leftx:a0,", sizeof(mapping_string));
667 }
668 if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTY)) {
669 SDL_strlcat(mapping_string, "lefty:a1,", sizeof(mapping_string));
670 }
671 if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTX)) {
672 SDL_strlcat(mapping_string, "rightx:a2,", sizeof(mapping_string));
673 }
674 if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTY)) {
675 SDL_strlcat(mapping_string, "righty:a3,", sizeof(mapping_string));
676 }
677 if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER)) {
678 SDL_strlcat(mapping_string, "lefttrigger:a4,", sizeof(mapping_string));
679 }
680 if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) {
681 SDL_strlcat(mapping_string, "righttrigger:a5,", sizeof(mapping_string));
682 }
683
684 return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);
685}
686#endif // SDL_PLATFORM_ANDROID
687
688/*
689 * Helper function to guess at a mapping for HIDAPI gamepads
690 */
691static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
692{
693 bool existing;
694 char mapping_string[1024];
695 Uint16 vendor;
696 Uint16 product;
697
698 SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));
699
700 SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
701
702 if ((vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) ||
703 (vendor == USB_VENDOR_DRAGONRISE &&
704 (product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 ||
705 product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2))) {
706 // GameCube driver has 12 buttons and 6 axes
707 SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b2,y:b3,", sizeof(mapping_string));
708 } else if (vendor == USB_VENDOR_NINTENDO &&
709 (guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft ||
710 guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight ||
711 guid.data[15] == k_eSwitchDeviceInfoControllerType_NESLeft ||
712 guid.data[15] == k_eSwitchDeviceInfoControllerType_NESRight ||
713 guid.data[15] == k_eSwitchDeviceInfoControllerType_SNES ||
714 guid.data[15] == k_eSwitchDeviceInfoControllerType_N64 ||
715 guid.data[15] == k_eSwitchDeviceInfoControllerType_SEGA_Genesis ||
716 guid.data[15] == k_eWiiExtensionControllerType_None ||
717 guid.data[15] == k_eWiiExtensionControllerType_Nunchuk ||
718 guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft ||
719 guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConRight)) {
720 switch (guid.data[15]) {
721 case k_eSwitchDeviceInfoControllerType_HVCLeft:
722 SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string));
723 break;
724 case k_eSwitchDeviceInfoControllerType_HVCRight:
725 SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,", sizeof(mapping_string));
726 break;
727 case k_eSwitchDeviceInfoControllerType_NESLeft:
728 case k_eSwitchDeviceInfoControllerType_NESRight:
729 SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string));
730 break;
731 case k_eSwitchDeviceInfoControllerType_SNES:
732 SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,", sizeof(mapping_string));
733 break;
734 case k_eSwitchDeviceInfoControllerType_N64:
735 SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string));
736 break;
737 case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:
738 SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string));
739 break;
740 case k_eWiiExtensionControllerType_None:
741 SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,start:b6,x:b2,y:b3,", sizeof(mapping_string));
742 break;
743 case k_eWiiExtensionControllerType_Nunchuk:
744 {
745 // FIXME: Should we map this to the left or right side?
746 const bool map_nunchuck_left_side = true;
747
748 if (map_nunchuck_left_side) {
749 SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,start:b6,x:b2,y:b3,", sizeof(mapping_string));
750 } else {
751 SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,rightshoulder:b9,righttrigger:a4,rightx:a0,righty:a1,start:b6,x:b2,y:b3,", sizeof(mapping_string));
752 }
753 } break;
754 default:
755 if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false)) {
756 // Vertical mode
757 if (guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
758 SDL_strlcat(mapping_string, "back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,paddle2:b13,paddle4:b15,", sizeof(mapping_string));
759 } else {
760 SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,paddle1:b12,paddle3:b14,", sizeof(mapping_string));
761 }
762 } else {
763 // Mini gamepad mode
764 if (guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
765 SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,paddle2:b13,paddle4:b15,", sizeof(mapping_string));
766 } else {
767 SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,paddle1:b12,paddle3:b14,", sizeof(mapping_string));
768 }
769 }
770 break;
771 }
772 } else {
773 // All other gamepads have the standard set of 19 buttons and 6 axes
774 SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", sizeof(mapping_string));
775
776 if (SDL_IsJoystickXboxSeriesX(vendor, product)) {
777 // XBox Series X Controllers have a share button under the guide button
778 SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
779 } else if (SDL_IsJoystickXboxOneElite(vendor, product)) {
780 // XBox One Elite Controllers have 4 back paddle buttons
781 SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,", sizeof(mapping_string));
782 } else if (SDL_IsJoystickSteamController(vendor, product)) {
783 // Steam controllers have 2 back paddle buttons
784 SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,", sizeof(mapping_string));
785 } else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) ||
786 SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) {
787 // Nintendo Switch Pro controllers have a screenshot button
788 SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
789 } else if (SDL_IsJoystickNintendoSwitchJoyConPair(vendor, product)) {
790 // The Nintendo Switch Joy-Con combined controllers has a share button and paddles
791 SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,", sizeof(mapping_string));
792 } else if (SDL_IsJoystickAmazonLunaController(vendor, product)) {
793 // Amazon Luna Controller has a mic button under the guide button
794 SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
795 } else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) {
796 // The Google Stadia controller has a share button and a Google Assistant button
797 SDL_strlcat(mapping_string, "misc1:b11,misc2:b12", sizeof(mapping_string));
798 } else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) {
799 // The NVIDIA SHIELD controller has a share button between back and start buttons
800 SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
801
802 if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) {
803 // The original SHIELD controller has a touchpad and plus/minus buttons as well
804 SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14", sizeof(mapping_string));
805 }
806 } else if (SDL_IsJoystickHoriSteamController(vendor, product)) {
807 /* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */
808 SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17", sizeof(mapping_string));
809 } else {
810 switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) {
811 case SDL_GAMEPAD_TYPE_PS4:
812 // PS4 controllers have an additional touchpad button
813 SDL_strlcat(mapping_string, "touchpad:b11,", sizeof(mapping_string));
814 break;
815 case SDL_GAMEPAD_TYPE_PS5:
816 // PS5 controllers have a microphone button and an additional touchpad button
817 SDL_strlcat(mapping_string, "touchpad:b11,misc1:b12,", sizeof(mapping_string));
818 // DualSense Edge controllers have paddles
819 if (SDL_IsJoystickDualSenseEdge(vendor, product)) {
820 SDL_strlcat(mapping_string, "paddle1:b16,paddle2:b15,paddle3:b14,paddle4:b13,", sizeof(mapping_string));
821 }
822 break;
823 default:
824 if (vendor == 0 && product == 0) {
825 // This is a Bluetooth Nintendo Switch Pro controller
826 SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
827 }
828 break;
829 }
830 }
831 }
832
833 return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);
834}
835
836/*
837 * Helper function to guess at a mapping for RAWINPUT gamepads
838 */
839static GamepadMapping_t *SDL_CreateMappingForRAWINPUTGamepad(SDL_GUID guid)
840{
841 bool existing;
842 char mapping_string[1024];
843
844 SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));
845 SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,back:b6,guide:b10,start:b7,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,", sizeof(mapping_string));
846
847 return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);
848}
849
850/*
851 * Helper function to guess at a mapping for WGI gamepads
852 */
853static GamepadMapping_t *SDL_CreateMappingForWGIGamepad(SDL_GUID guid)
854{
855 bool existing;
856 char mapping_string[1024];
857
858 if (guid.data[15] != SDL_JOYSTICK_TYPE_GAMEPAD) {
859 return NULL;
860 }
861
862 SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));
863 SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:b10,dpdown:b12,dpleft:b13,dpright:b11,leftx:a1,lefty:a0~,rightx:a3,righty:a2~,lefttrigger:a4,righttrigger:a5,", sizeof(mapping_string));
864
865 return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);
866}
867
868/*
869 * Helper function to scan the mappings database for a gamepad with the specified GUID
870 */
871static GamepadMapping_t *SDL_PrivateMatchGamepadMappingForGUID(SDL_GUID guid, bool match_version, bool exact_match_crc)
872{
873 GamepadMapping_t *mapping, *best_match = NULL;
874 Uint16 crc = 0;
875
876 SDL_AssertJoysticksLocked();
877
878 SDL_GetJoystickGUIDInfo(guid, NULL, NULL, NULL, &crc);
879
880 // Clear the CRC from the GUID for matching, the mappings never include it in the GUID
881 SDL_SetJoystickGUIDCRC(&guid, 0);
882
883 if (!match_version) {
884 SDL_SetJoystickGUIDVersion(&guid, 0);
885 }
886
887 for (mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) {
888 SDL_GUID mapping_guid;
889
890 if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) {
891 continue;
892 }
893
894 SDL_memcpy(&mapping_guid, &mapping->guid, sizeof(mapping_guid));
895 if (!match_version) {
896 SDL_SetJoystickGUIDVersion(&mapping_guid, 0);
897 }
898
899 if (SDL_memcmp(&guid, &mapping_guid, sizeof(guid)) == 0) {
900 const char *crc_string = SDL_strstr(mapping->mapping, SDL_GAMEPAD_CRC_FIELD);
901 if (crc_string) {
902 Uint16 mapping_crc = (Uint16)SDL_strtol(crc_string + SDL_GAMEPAD_CRC_FIELD_SIZE, NULL, 16);
903 if (mapping_crc != crc) {
904 // This mapping specified a CRC and they don't match
905 continue;
906 }
907
908 // An exact match, including CRC
909 return mapping;
910 } else if (crc && exact_match_crc) {
911 return NULL;
912 }
913
914 if (!best_match) {
915 best_match = mapping;
916 }
917 }
918 }
919 return best_match;
920}
921
922/*
923 * Helper function to scan the mappings database for a gamepad with the specified GUID
924 */
925static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_GUID guid, bool adding_mapping)
926{
927 GamepadMapping_t *mapping;
928
929 mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, adding_mapping);
930 if (mapping) {
931 return mapping;
932 }
933
934 if (adding_mapping) {
935 // We didn't find an existing mapping
936 return NULL;
937 }
938
939 // Try harder to get the best match, or create a mapping
940
941 if (SDL_JoystickGUIDUsesVersion(guid)) {
942 // Try again, ignoring the version
943 mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, false, false);
944 if (mapping) {
945 return mapping;
946 }
947 }
948
949#ifdef SDL_JOYSTICK_XINPUT
950 if (SDL_IsJoystickXInput(guid)) {
951 // This is an XInput device
952 return s_pXInputMapping;
953 }
954#endif
955 if (SDL_IsJoystickHIDAPI(guid)) {
956 mapping = SDL_CreateMappingForHIDAPIGamepad(guid);
957 } else if (SDL_IsJoystickRAWINPUT(guid)) {
958 mapping = SDL_CreateMappingForRAWINPUTGamepad(guid);
959 } else if (SDL_IsJoystickWGI(guid)) {
960 mapping = SDL_CreateMappingForWGIGamepad(guid);
961 } else if (SDL_IsJoystickVIRTUAL(guid)) {
962 // We'll pick up a robust mapping in VIRTUAL_JoystickGetGamepadMapping
963#ifdef SDL_PLATFORM_ANDROID
964 } else {
965 mapping = SDL_CreateMappingForAndroidGamepad(guid);
966#endif
967 }
968 return mapping;
969}
970
971static const char *map_StringForGamepadType[] = {
972 "unknown",
973 "standard",
974 "xbox360",
975 "xboxone",
976 "ps3",
977 "ps4",
978 "ps5",
979 "switchpro",
980 "joyconleft",
981 "joyconright",
982 "joyconpair"
983};
984SDL_COMPILE_TIME_ASSERT(map_StringForGamepadType, SDL_arraysize(map_StringForGamepadType) == SDL_GAMEPAD_TYPE_COUNT);
985
986/*
987 * convert a string to its enum equivalent
988 */
989SDL_GamepadType SDL_GetGamepadTypeFromString(const char *str)
990{
991 int i;
992
993 if (!str || str[0] == '\0') {
994 return SDL_GAMEPAD_TYPE_UNKNOWN;
995 }
996
997 if (*str == '+' || *str == '-') {
998 ++str;
999 }
1000
1001 for (i = 0; i < SDL_arraysize(map_StringForGamepadType); ++i) {
1002 if (SDL_strcasecmp(str, map_StringForGamepadType[i]) == 0) {
1003 return (SDL_GamepadType)i;
1004 }
1005 }
1006 return SDL_GAMEPAD_TYPE_UNKNOWN;
1007}
1008
1009/*
1010 * convert an enum to its string equivalent
1011 */
1012const char *SDL_GetGamepadStringForType(SDL_GamepadType type)
1013{
1014 if (type >= SDL_GAMEPAD_TYPE_STANDARD && type < SDL_GAMEPAD_TYPE_COUNT) {
1015 return map_StringForGamepadType[type];
1016 }
1017 return NULL;
1018}
1019
1020static const char *map_StringForGamepadAxis[] = {
1021 "leftx",
1022 "lefty",
1023 "rightx",
1024 "righty",
1025 "lefttrigger",
1026 "righttrigger"
1027};
1028SDL_COMPILE_TIME_ASSERT(map_StringForGamepadAxis, SDL_arraysize(map_StringForGamepadAxis) == SDL_GAMEPAD_AXIS_COUNT);
1029
1030/*
1031 * convert a string to its enum equivalent
1032 */
1033SDL_GamepadAxis SDL_GetGamepadAxisFromString(const char *str)
1034{
1035 int i;
1036
1037 if (!str || str[0] == '\0') {
1038 return SDL_GAMEPAD_AXIS_INVALID;
1039 }
1040
1041 if (*str == '+' || *str == '-') {
1042 ++str;
1043 }
1044
1045 for (i = 0; i < SDL_arraysize(map_StringForGamepadAxis); ++i) {
1046 if (SDL_strcasecmp(str, map_StringForGamepadAxis[i]) == 0) {
1047 return (SDL_GamepadAxis)i;
1048 }
1049 }
1050 return SDL_GAMEPAD_AXIS_INVALID;
1051}
1052
1053/*
1054 * convert an enum to its string equivalent
1055 */
1056const char *SDL_GetGamepadStringForAxis(SDL_GamepadAxis axis)
1057{
1058 if (axis > SDL_GAMEPAD_AXIS_INVALID && axis < SDL_GAMEPAD_AXIS_COUNT) {
1059 return map_StringForGamepadAxis[axis];
1060 }
1061 return NULL;
1062}
1063
1064static const char *map_StringForGamepadButton[] = {
1065 "a",
1066 "b",
1067 "x",
1068 "y",
1069 "back",
1070 "guide",
1071 "start",
1072 "leftstick",
1073 "rightstick",
1074 "leftshoulder",
1075 "rightshoulder",
1076 "dpup",
1077 "dpdown",
1078 "dpleft",
1079 "dpright",
1080 "misc1",
1081 "paddle1",
1082 "paddle2",
1083 "paddle3",
1084 "paddle4",
1085 "touchpad",
1086 "misc2",
1087 "misc3",
1088 "misc4",
1089 "misc5",
1090 "misc6"
1091};
1092SDL_COMPILE_TIME_ASSERT(map_StringForGamepadButton, SDL_arraysize(map_StringForGamepadButton) == SDL_GAMEPAD_BUTTON_COUNT);
1093
1094/*
1095 * convert a string to its enum equivalent
1096 */
1097static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, bool baxy)
1098{
1099 int i;
1100
1101 if (!str || str[0] == '\0') {
1102 return SDL_GAMEPAD_BUTTON_INVALID;
1103 }
1104
1105 for (i = 0; i < SDL_arraysize(map_StringForGamepadButton); ++i) {
1106 if (SDL_strcasecmp(str, map_StringForGamepadButton[i]) == 0) {
1107 if (baxy) {
1108 // Need to swap face buttons
1109 switch (i) {
1110 case SDL_GAMEPAD_BUTTON_SOUTH:
1111 return SDL_GAMEPAD_BUTTON_EAST;
1112 case SDL_GAMEPAD_BUTTON_EAST:
1113 return SDL_GAMEPAD_BUTTON_SOUTH;
1114 case SDL_GAMEPAD_BUTTON_WEST:
1115 return SDL_GAMEPAD_BUTTON_NORTH;
1116 case SDL_GAMEPAD_BUTTON_NORTH:
1117 return SDL_GAMEPAD_BUTTON_WEST;
1118 default:
1119 break;
1120 }
1121 }
1122 return (SDL_GamepadButton)i;
1123 }
1124 }
1125 return SDL_GAMEPAD_BUTTON_INVALID;
1126}
1127SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str)
1128{
1129 return SDL_PrivateGetGamepadButtonFromString(str, false);
1130}
1131
1132/*
1133 * convert an enum to its string equivalent
1134 */
1135const char *SDL_GetGamepadStringForButton(SDL_GamepadButton button)
1136{
1137 if (button > SDL_GAMEPAD_BUTTON_INVALID && button < SDL_GAMEPAD_BUTTON_COUNT) {
1138 return map_StringForGamepadButton[button];
1139 }
1140 return NULL;
1141}
1142
1143/*
1144 * given a gamepad button name and a joystick name update our mapping structure with it
1145 */
1146static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGameButton, const char *szJoystickButton)
1147{
1148 SDL_GamepadBinding bind;
1149 SDL_GamepadButton button;
1150 SDL_GamepadAxis axis;
1151 bool invert_input = false;
1152 char half_axis_input = 0;
1153 char half_axis_output = 0;
1154 int i;
1155 SDL_GamepadBinding *new_bindings;
1156 bool baxy_mapping = false;
1157
1158 SDL_AssertJoysticksLocked();
1159
1160 SDL_zero(bind);
1161
1162 if (*szGameButton == '+' || *szGameButton == '-') {
1163 half_axis_output = *szGameButton++;
1164 }
1165
1166 if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) {
1167 baxy_mapping = true;
1168 }
1169
1170 axis = SDL_GetGamepadAxisFromString(szGameButton);
1171 button = SDL_PrivateGetGamepadButtonFromString(szGameButton, baxy_mapping);
1172 if (axis != SDL_GAMEPAD_AXIS_INVALID) {
1173 bind.output_type = SDL_GAMEPAD_BINDTYPE_AXIS;
1174 bind.output.axis.axis = axis;
1175 if (axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
1176 bind.output.axis.axis_min = 0;
1177 bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX;
1178 } else {
1179 if (half_axis_output == '+') {
1180 bind.output.axis.axis_min = 0;
1181 bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX;
1182 } else if (half_axis_output == '-') {
1183 bind.output.axis.axis_min = 0;
1184 bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MIN;
1185 } else {
1186 bind.output.axis.axis_min = SDL_JOYSTICK_AXIS_MIN;
1187 bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX;
1188 }
1189 }
1190 } else if (button != SDL_GAMEPAD_BUTTON_INVALID) {
1191 bind.output_type = SDL_GAMEPAD_BINDTYPE_BUTTON;
1192 bind.output.button = button;
1193 } else {
1194 return false;
1195 }
1196
1197 if (*szJoystickButton == '+' || *szJoystickButton == '-') {
1198 half_axis_input = *szJoystickButton++;
1199 }
1200 if (szJoystickButton[SDL_strlen(szJoystickButton) - 1] == '~') {
1201 invert_input = true;
1202 }
1203
1204 if (szJoystickButton[0] == 'a' && SDL_isdigit((unsigned char)szJoystickButton[1])) {
1205 bind.input_type = SDL_GAMEPAD_BINDTYPE_AXIS;
1206 bind.input.axis.axis = SDL_atoi(&szJoystickButton[1]);
1207 if (half_axis_input == '+') {
1208 bind.input.axis.axis_min = 0;
1209 bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MAX;
1210 } else if (half_axis_input == '-') {
1211 bind.input.axis.axis_min = 0;
1212 bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MIN;
1213 } else {
1214 bind.input.axis.axis_min = SDL_JOYSTICK_AXIS_MIN;
1215 bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MAX;
1216 }
1217 if (invert_input) {
1218 int tmp = bind.input.axis.axis_min;
1219 bind.input.axis.axis_min = bind.input.axis.axis_max;
1220 bind.input.axis.axis_max = tmp;
1221 }
1222 } else if (szJoystickButton[0] == 'b' && SDL_isdigit((unsigned char)szJoystickButton[1])) {
1223 bind.input_type = SDL_GAMEPAD_BINDTYPE_BUTTON;
1224 bind.input.button = SDL_atoi(&szJoystickButton[1]);
1225 } else if (szJoystickButton[0] == 'h' && SDL_isdigit((unsigned char)szJoystickButton[1]) &&
1226 szJoystickButton[2] == '.' && SDL_isdigit((unsigned char)szJoystickButton[3])) {
1227 int hat = SDL_atoi(&szJoystickButton[1]);
1228 int mask = SDL_atoi(&szJoystickButton[3]);
1229 bind.input_type = SDL_GAMEPAD_BINDTYPE_HAT;
1230 bind.input.hat.hat = hat;
1231 bind.input.hat.hat_mask = mask;
1232 } else {
1233 return false;
1234 }
1235
1236 for (i = 0; i < gamepad->num_bindings; ++i) {
1237 if (SDL_memcmp(&gamepad->bindings[i], &bind, sizeof(bind)) == 0) {
1238 // We already have this binding, could be different face button names?
1239 return true;
1240 }
1241 }
1242
1243 ++gamepad->num_bindings;
1244 new_bindings = (SDL_GamepadBinding *)SDL_realloc(gamepad->bindings, gamepad->num_bindings * sizeof(*gamepad->bindings));
1245 if (!new_bindings) {
1246 SDL_free(gamepad->bindings);
1247 gamepad->num_bindings = 0;
1248 gamepad->bindings = NULL;
1249 return false;
1250 }
1251 gamepad->bindings = new_bindings;
1252 gamepad->bindings[gamepad->num_bindings - 1] = bind;
1253 return true;
1254}
1255
1256/*
1257 * given a gamepad mapping string update our mapping object
1258 */
1259static bool SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char *pchString)
1260{
1261 char szGameButton[20];
1262 char szJoystickButton[20];
1263 bool bGameButton = true;
1264 int i = 0;
1265 const char *pchPos = pchString;
1266
1267 SDL_zeroa(szGameButton);
1268 SDL_zeroa(szJoystickButton);
1269
1270 while (pchPos && *pchPos) {
1271 if (*pchPos == ':') {
1272 i = 0;
1273 bGameButton = false;
1274 } else if (*pchPos == ' ') {
1275
1276 } else if (*pchPos == ',') {
1277 i = 0;
1278 bGameButton = true;
1279 SDL_PrivateParseGamepadElement(gamepad, szGameButton, szJoystickButton);
1280 SDL_zeroa(szGameButton);
1281 SDL_zeroa(szJoystickButton);
1282
1283 } else if (bGameButton) {
1284 if (i >= sizeof(szGameButton)) {
1285 szGameButton[sizeof(szGameButton) - 1] = '\0';
1286 return SDL_SetError("Button name too large: %s", szGameButton);
1287 }
1288 szGameButton[i] = *pchPos;
1289 i++;
1290 } else {
1291 if (i >= sizeof(szJoystickButton)) {
1292 szJoystickButton[sizeof(szJoystickButton) - 1] = '\0';
1293 return SDL_SetError("Joystick button name too large: %s", szJoystickButton);
1294 }
1295 szJoystickButton[i] = *pchPos;
1296 i++;
1297 }
1298 pchPos++;
1299 }
1300
1301 // No more values if the string was terminated by a comma. Don't report an error.
1302 if (szGameButton[0] != '\0' || szJoystickButton[0] != '\0') {
1303 SDL_PrivateParseGamepadElement(gamepad, szGameButton, szJoystickButton);
1304 }
1305 return true;
1306}
1307
1308static void SDL_UpdateGamepadType(SDL_Gamepad *gamepad)
1309{
1310 char *type_string, *comma;
1311
1312 SDL_AssertJoysticksLocked();
1313
1314 gamepad->type = SDL_GAMEPAD_TYPE_UNKNOWN;
1315
1316 type_string = SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_TYPE_FIELD);
1317 if (type_string) {
1318 type_string += SDL_GAMEPAD_TYPE_FIELD_SIZE;
1319 comma = SDL_strchr(type_string, ',');
1320 if (comma) {
1321 *comma = '\0';
1322 gamepad->type = SDL_GetGamepadTypeFromString(type_string);
1323 *comma = ',';
1324 } else {
1325 gamepad->type = SDL_GetGamepadTypeFromString(type_string);
1326 }
1327 }
1328 if (gamepad->type == SDL_GAMEPAD_TYPE_UNKNOWN) {
1329 gamepad->type = SDL_GetRealGamepadTypeForID(gamepad->joystick->instance_id);
1330 }
1331}
1332
1333static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleFromString(const char *string)
1334{
1335 if (SDL_strcmp(string, "abxy") == 0) {
1336 return SDL_GAMEPAD_FACE_STYLE_ABXY;
1337 } else if (SDL_strcmp(string, "bayx") == 0) {
1338 return SDL_GAMEPAD_FACE_STYLE_BAYX;
1339 } else if (SDL_strcmp(string, "sony") == 0) {
1340 return SDL_GAMEPAD_FACE_STYLE_SONY;
1341 } else {
1342 return SDL_GAMEPAD_FACE_STYLE_UNKNOWN;
1343 }
1344}
1345
1346static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleForGamepadType(SDL_GamepadType type)
1347{
1348 switch (type) {
1349 case SDL_GAMEPAD_TYPE_PS3:
1350 case SDL_GAMEPAD_TYPE_PS4:
1351 case SDL_GAMEPAD_TYPE_PS5:
1352 return SDL_GAMEPAD_FACE_STYLE_SONY;
1353 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
1354 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
1355 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
1356 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
1357 return SDL_GAMEPAD_FACE_STYLE_BAYX;
1358 default:
1359 return SDL_GAMEPAD_FACE_STYLE_ABXY;
1360 }
1361}
1362
1363static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad)
1364{
1365 char *face_string, *comma;
1366
1367 SDL_AssertJoysticksLocked();
1368
1369 gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_UNKNOWN;
1370
1371 face_string = SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_FACE_FIELD);
1372 if (face_string) {
1373 face_string += SDL_GAMEPAD_TYPE_FIELD_SIZE;
1374 comma = SDL_strchr(face_string, ',');
1375 if (comma) {
1376 *comma = '\0';
1377 gamepad->face_style = SDL_GetGamepadFaceStyleFromString(face_string);
1378 *comma = ',';
1379 } else {
1380 gamepad->face_style = SDL_GetGamepadFaceStyleFromString(face_string);
1381 }
1382 }
1383
1384 if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN &&
1385 SDL_strstr(gamepad->mapping->mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") != NULL) {
1386 // This controller uses Nintendo button style
1387 gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_BAYX;
1388 }
1389 if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN) {
1390 gamepad->face_style = SDL_GetGamepadFaceStyleForGamepadType(gamepad->type);
1391 }
1392}
1393
1394static void SDL_FixupHIDAPIMapping(SDL_Gamepad *gamepad)
1395{
1396 // Check to see if we need fixup
1397 bool need_fixup = false;
1398 for (int i = 0; i < gamepad->num_bindings; ++i) {
1399 SDL_GamepadBinding *binding = &gamepad->bindings[i];
1400 if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON &&
1401 binding->output.button >= SDL_GAMEPAD_BUTTON_DPAD_UP) {
1402 if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON &&
1403 binding->input.button == binding->output.button) {
1404 // Old style binding
1405 need_fixup = true;
1406 }
1407 break;
1408 }
1409 }
1410 if (!need_fixup) {
1411 return;
1412 }
1413
1414 for (int i = 0; i < gamepad->num_bindings; ++i) {
1415 SDL_GamepadBinding *binding = &gamepad->bindings[i];
1416 if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON &&
1417 binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON) {
1418 switch (binding->output.button) {
1419 case SDL_GAMEPAD_BUTTON_DPAD_UP:
1420 binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT;
1421 binding->input.hat.hat = 0;
1422 binding->input.hat.hat_mask = SDL_HAT_UP;
1423 break;
1424 case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
1425 binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT;
1426 binding->input.hat.hat = 0;
1427 binding->input.hat.hat_mask = SDL_HAT_DOWN;
1428 break;
1429 case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
1430 binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT;
1431 binding->input.hat.hat = 0;
1432 binding->input.hat.hat_mask = SDL_HAT_LEFT;
1433 break;
1434 case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
1435 binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT;
1436 binding->input.hat.hat = 0;
1437 binding->input.hat.hat_mask = SDL_HAT_RIGHT;
1438 break;
1439 default:
1440 if (binding->output.button > SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
1441 binding->input.button -= 4;
1442 }
1443 break;
1444 }
1445 }
1446 }
1447}
1448
1449/*
1450 * Make a new button mapping struct
1451 */
1452static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t *pGamepadMapping)
1453{
1454 int i;
1455
1456 SDL_AssertJoysticksLocked();
1457
1458 gamepad->name = pGamepadMapping->name;
1459 gamepad->num_bindings = 0;
1460 gamepad->mapping = pGamepadMapping;
1461 if (gamepad->joystick->naxes != 0 && gamepad->last_match_axis) {
1462 SDL_memset(gamepad->last_match_axis, 0, gamepad->joystick->naxes * sizeof(*gamepad->last_match_axis));
1463 }
1464
1465 SDL_UpdateGamepadType(gamepad);
1466 SDL_UpdateGamepadFaceStyle(gamepad);
1467
1468 SDL_PrivateParseGamepadConfigString(gamepad, pGamepadMapping->mapping);
1469
1470 if (SDL_IsJoystickHIDAPI(pGamepadMapping->guid)) {
1471 SDL_FixupHIDAPIMapping(gamepad);
1472 }
1473
1474 // Set the zero point for triggers
1475 for (i = 0; i < gamepad->num_bindings; ++i) {
1476 SDL_GamepadBinding *binding = &gamepad->bindings[i];
1477 if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS &&
1478 binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS &&
1479 (binding->output.axis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
1480 binding->output.axis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) {
1481 if (binding->input.axis.axis < gamepad->joystick->naxes) {
1482 gamepad->joystick->axes[binding->input.axis.axis].value =
1483 gamepad->joystick->axes[binding->input.axis.axis].zero = (Sint16)binding->input.axis.axis_min;
1484 }
1485 }
1486 }
1487}
1488
1489/*
1490 * grab the guid string from a mapping string
1491 */
1492static char *SDL_PrivateGetGamepadGUIDFromMappingString(const char *pMapping)
1493{
1494 const char *pFirstComma = SDL_strchr(pMapping, ',');
1495 if (pFirstComma) {
1496 char *pchGUID = (char *)SDL_malloc(pFirstComma - pMapping + 1);
1497 if (!pchGUID) {
1498 return NULL;
1499 }
1500 SDL_memcpy(pchGUID, pMapping, pFirstComma - pMapping);
1501 pchGUID[pFirstComma - pMapping] = '\0';
1502
1503 // Convert old style GUIDs to the new style in 2.0.5
1504#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)
1505 if (SDL_strlen(pchGUID) == 32 &&
1506 SDL_memcmp(&pchGUID[20], "504944564944", 12) == 0) {
1507 SDL_memcpy(&pchGUID[20], "000000000000", 12);
1508 SDL_memcpy(&pchGUID[16], &pchGUID[4], 4);
1509 SDL_memcpy(&pchGUID[8], &pchGUID[0], 4);
1510 SDL_memcpy(&pchGUID[0], "03000000", 8);
1511 }
1512#elif defined(SDL_PLATFORM_MACOS)
1513 if (SDL_strlen(pchGUID) == 32 &&
1514 SDL_memcmp(&pchGUID[4], "000000000000", 12) == 0 &&
1515 SDL_memcmp(&pchGUID[20], "000000000000", 12) == 0) {
1516 SDL_memcpy(&pchGUID[20], "000000000000", 12);
1517 SDL_memcpy(&pchGUID[8], &pchGUID[0], 4);
1518 SDL_memcpy(&pchGUID[0], "03000000", 8);
1519 }
1520#endif
1521 return pchGUID;
1522 }
1523 return NULL;
1524}
1525
1526/*
1527 * grab the name string from a mapping string
1528 */
1529static char *SDL_PrivateGetGamepadNameFromMappingString(const char *pMapping)
1530{
1531 const char *pFirstComma, *pSecondComma;
1532 char *pchName;
1533
1534 pFirstComma = SDL_strchr(pMapping, ',');
1535 if (!pFirstComma) {
1536 return NULL;
1537 }
1538
1539 pSecondComma = SDL_strchr(pFirstComma + 1, ',');
1540 if (!pSecondComma) {
1541 return NULL;
1542 }
1543
1544 pchName = (char *)SDL_malloc(pSecondComma - pFirstComma);
1545 if (!pchName) {
1546 return NULL;
1547 }
1548 SDL_memcpy(pchName, pFirstComma + 1, pSecondComma - pFirstComma);
1549 pchName[pSecondComma - pFirstComma - 1] = 0;
1550 return pchName;
1551}
1552
1553/*
1554 * grab the button mapping string from a mapping string
1555 */
1556static char *SDL_PrivateGetGamepadMappingFromMappingString(const char *pMapping)
1557{
1558 const char *pFirstComma, *pSecondComma;
1559 char *result;
1560 size_t length;
1561
1562 pFirstComma = SDL_strchr(pMapping, ',');
1563 if (!pFirstComma) {
1564 return NULL;
1565 }
1566
1567 pSecondComma = SDL_strchr(pFirstComma + 1, ',');
1568 if (!pSecondComma) {
1569 return NULL;
1570 }
1571
1572 // Skip whitespace
1573 while (SDL_isspace(pSecondComma[1])) {
1574 ++pSecondComma;
1575 }
1576
1577 result = SDL_strdup(pSecondComma + 1); // mapping is everything after the 3rd comma
1578
1579 // Trim whitespace
1580 length = SDL_strlen(result);
1581 while (length > 0 && SDL_isspace(result[length - 1])) {
1582 --length;
1583 }
1584 result[length] = '\0';
1585
1586 return result;
1587}
1588
1589/*
1590 * Helper function to add a mapping for a guid
1591 */
1592static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_GUID jGUID, const char *mappingString, bool *existing, SDL_GamepadMappingPriority priority)
1593{
1594 char *pchName;
1595 char *pchMapping;
1596 GamepadMapping_t *pGamepadMapping;
1597 Uint16 crc;
1598
1599 SDL_AssertJoysticksLocked();
1600
1601 pchName = SDL_PrivateGetGamepadNameFromMappingString(mappingString);
1602 if (!pchName) {
1603 SDL_SetError("Couldn't parse name from %s", mappingString);
1604 return NULL;
1605 }
1606
1607 pchMapping = SDL_PrivateGetGamepadMappingFromMappingString(mappingString);
1608 if (!pchMapping) {
1609 SDL_free(pchName);
1610 SDL_SetError("Couldn't parse %s", mappingString);
1611 return NULL;
1612 }
1613
1614 // Fix up the GUID and the mapping with the CRC, if needed
1615 SDL_GetJoystickGUIDInfo(jGUID, NULL, NULL, NULL, &crc);
1616 if (crc) {
1617 // Make sure the mapping has the CRC
1618 char *new_mapping;
1619 const char *optional_comma;
1620 size_t mapping_length;
1621 char *crc_end = "";
1622 char *crc_string = SDL_strstr(pchMapping, SDL_GAMEPAD_CRC_FIELD);
1623 if (crc_string) {
1624 crc_end = SDL_strchr(crc_string, ',');
1625 if (crc_end) {
1626 ++crc_end;
1627 } else {
1628 crc_end = "";
1629 }
1630 *crc_string = '\0';
1631 }
1632
1633 // Make sure there's a comma before the CRC
1634 mapping_length = SDL_strlen(pchMapping);
1635 if (mapping_length == 0 || pchMapping[mapping_length - 1] == ',') {
1636 optional_comma = "";
1637 } else {
1638 optional_comma = ",";
1639 }
1640
1641 if (SDL_asprintf(&new_mapping, "%s%s%s%.4x,%s", pchMapping, optional_comma, SDL_GAMEPAD_CRC_FIELD, crc, crc_end) >= 0) {
1642 SDL_free(pchMapping);
1643 pchMapping = new_mapping;
1644 }
1645 } else {
1646 // Make sure the GUID has the CRC, for matching purposes
1647 char *crc_string = SDL_strstr(pchMapping, SDL_GAMEPAD_CRC_FIELD);
1648 if (crc_string) {
1649 crc = (Uint16)SDL_strtol(crc_string + SDL_GAMEPAD_CRC_FIELD_SIZE, NULL, 16);
1650 if (crc) {
1651 SDL_SetJoystickGUIDCRC(&jGUID, crc);
1652 }
1653 }
1654 }
1655
1656 PushMappingChangeTracking();
1657
1658 pGamepadMapping = SDL_PrivateGetGamepadMappingForGUID(jGUID, true);
1659 if (pGamepadMapping) {
1660 // Only overwrite the mapping if the priority is the same or higher.
1661 if (pGamepadMapping->priority <= priority) {
1662 // Update existing mapping
1663 SDL_free(pGamepadMapping->name);
1664 pGamepadMapping->name = pchName;
1665 SDL_free(pGamepadMapping->mapping);
1666 pGamepadMapping->mapping = pchMapping;
1667 pGamepadMapping->priority = priority;
1668 } else {
1669 SDL_free(pchName);
1670 SDL_free(pchMapping);
1671 }
1672 if (existing) {
1673 *existing = true;
1674 }
1675 AddMappingChangeTracking(pGamepadMapping);
1676 } else {
1677 pGamepadMapping = (GamepadMapping_t *)SDL_malloc(sizeof(*pGamepadMapping));
1678 if (!pGamepadMapping) {
1679 PopMappingChangeTracking();
1680 SDL_free(pchName);
1681 SDL_free(pchMapping);
1682 return NULL;
1683 }
1684 // Clear the CRC, we've already added it to the mapping
1685 if (crc) {
1686 SDL_SetJoystickGUIDCRC(&jGUID, 0);
1687 }
1688 pGamepadMapping->guid = jGUID;
1689 pGamepadMapping->name = pchName;
1690 pGamepadMapping->mapping = pchMapping;
1691 pGamepadMapping->next = NULL;
1692 pGamepadMapping->priority = priority;
1693
1694 if (s_pSupportedGamepads) {
1695 // Add the mapping to the end of the list
1696 GamepadMapping_t *pCurrMapping, *pPrevMapping;
1697
1698 for (pPrevMapping = s_pSupportedGamepads, pCurrMapping = pPrevMapping->next;
1699 pCurrMapping;
1700 pPrevMapping = pCurrMapping, pCurrMapping = pCurrMapping->next) {
1701 // continue;
1702 }
1703 pPrevMapping->next = pGamepadMapping;
1704 } else {
1705 s_pSupportedGamepads = pGamepadMapping;
1706 }
1707 if (existing) {
1708 *existing = false;
1709 }
1710 }
1711
1712 PopMappingChangeTracking();
1713
1714 return pGamepadMapping;
1715}
1716
1717/*
1718 * Helper function to determine pre-calculated offset to certain joystick mappings
1719 */
1720static GamepadMapping_t *SDL_PrivateGetGamepadMappingForNameAndGUID(const char *name, SDL_GUID guid)
1721{
1722 GamepadMapping_t *mapping;
1723
1724 SDL_AssertJoysticksLocked();
1725
1726 mapping = SDL_PrivateGetGamepadMappingForGUID(guid, false);
1727#ifdef SDL_PLATFORM_LINUX
1728 if (!mapping && name) {
1729 if (SDL_strstr(name, "Xbox 360 Wireless Receiver")) {
1730 // The Linux driver xpad.c maps the wireless dpad to buttons
1731 bool existing;
1732 mapping = SDL_PrivateAddMappingForGUID(guid,
1733 "none,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
1734 &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);
1735 }
1736 }
1737#endif // SDL_PLATFORM_LINUX
1738
1739 return mapping;
1740}
1741
1742static void SDL_PrivateAppendToMappingString(char *mapping_string,
1743 size_t mapping_string_len,
1744 const char *input_name,
1745 SDL_InputMapping *mapping)
1746{
1747 char buffer[16];
1748 if (mapping->kind == EMappingKind_None) {
1749 return;
1750 }
1751
1752 SDL_strlcat(mapping_string, input_name, mapping_string_len);
1753 SDL_strlcat(mapping_string, ":", mapping_string_len);
1754 switch (mapping->kind) {
1755 case EMappingKind_Button:
1756 (void)SDL_snprintf(buffer, sizeof(buffer), "b%u", mapping->target);
1757 break;
1758 case EMappingKind_Axis:
1759 (void)SDL_snprintf(buffer, sizeof(buffer), "%sa%u%s",
1760 mapping->half_axis_positive ? "+" :
1761 mapping->half_axis_negative ? "-" : "",
1762 mapping->target,
1763 mapping->axis_reversed ? "~" : "");
1764 break;
1765 case EMappingKind_Hat:
1766 (void)SDL_snprintf(buffer, sizeof(buffer), "h%i.%i", mapping->target >> 4, mapping->target & 0x0F);
1767 break;
1768 default:
1769 SDL_assert(false);
1770 }
1771
1772 SDL_strlcat(mapping_string, buffer, mapping_string_len);
1773 SDL_strlcat(mapping_string, ",", mapping_string_len);
1774}
1775
1776static GamepadMapping_t *SDL_PrivateGenerateAutomaticGamepadMapping(const char *name,
1777 SDL_GUID guid,
1778 SDL_GamepadMapping *raw_map)
1779{
1780 bool existing;
1781 char name_string[128];
1782 char mapping[1024];
1783
1784 // Remove any commas in the name
1785 SDL_strlcpy(name_string, name, sizeof(name_string));
1786 {
1787 char *spot;
1788 for (spot = name_string; *spot; ++spot) {
1789 if (*spot == ',') {
1790 *spot = ' ';
1791 }
1792 }
1793 }
1794 (void)SDL_snprintf(mapping, sizeof(mapping), "none,%s,", name_string);
1795 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "a", &raw_map->a);
1796 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "b", &raw_map->b);
1797 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "x", &raw_map->x);
1798 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "y", &raw_map->y);
1799 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "back", &raw_map->back);
1800 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "guide", &raw_map->guide);
1801 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "start", &raw_map->start);
1802 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftstick", &raw_map->leftstick);
1803 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightstick", &raw_map->rightstick);
1804 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftshoulder", &raw_map->leftshoulder);
1805 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightshoulder", &raw_map->rightshoulder);
1806 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpup", &raw_map->dpup);
1807 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpdown", &raw_map->dpdown);
1808 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpleft", &raw_map->dpleft);
1809 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpright", &raw_map->dpright);
1810 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc1", &raw_map->misc1);
1811 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc2", &raw_map->misc2);
1812 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc3", &raw_map->misc3);
1813 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc4", &raw_map->misc4);
1814 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc5", &raw_map->misc5);
1815 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc6", &raw_map->misc6);
1816 /* Keep using paddle1-4 in the generated mapping so that it can be
1817 * reused with SDL2 */
1818 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle1", &raw_map->right_paddle1);
1819 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle2", &raw_map->left_paddle1);
1820 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle3", &raw_map->right_paddle2);
1821 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle4", &raw_map->left_paddle2);
1822 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftx", &raw_map->leftx);
1823 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "lefty", &raw_map->lefty);
1824 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightx", &raw_map->rightx);
1825 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "righty", &raw_map->righty);
1826 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "lefttrigger", &raw_map->lefttrigger);
1827 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "righttrigger", &raw_map->righttrigger);
1828 SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "touchpad", &raw_map->touchpad);
1829
1830 return SDL_PrivateAddMappingForGUID(guid, mapping, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);
1831}
1832
1833static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id, bool create_mapping)
1834{
1835 const char *name;
1836 SDL_GUID guid;
1837 GamepadMapping_t *mapping;
1838
1839 SDL_AssertJoysticksLocked();
1840
1841 name = SDL_GetJoystickNameForID(instance_id);
1842 guid = SDL_GetJoystickGUIDForID(instance_id);
1843 mapping = SDL_PrivateGetGamepadMappingForNameAndGUID(name, guid);
1844 if (!mapping && create_mapping) {
1845 SDL_GamepadMapping raw_map;
1846
1847 SDL_zero(raw_map);
1848 if (SDL_PrivateJoystickGetAutoGamepadMapping(instance_id, &raw_map)) {
1849 mapping = SDL_PrivateGenerateAutomaticGamepadMapping(name, guid, &raw_map);
1850 }
1851 }
1852
1853 if (!mapping) {
1854 mapping = s_pDefaultMapping;
1855 }
1856 return mapping;
1857}
1858
1859/*
1860 * Add or update an entry into the Mappings Database
1861 */
1862int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio)
1863{
1864 const char *platform = SDL_GetPlatform();
1865 int gamepads = 0;
1866 char *buf, *line, *line_end, *tmp, *comma, line_platform[64];
1867 size_t db_size;
1868 size_t platform_len;
1869
1870 buf = (char *)SDL_LoadFile_IO(src, &db_size, closeio);
1871 if (!buf) {
1872 SDL_SetError("Could not allocate space to read DB into memory");
1873 return -1;
1874 }
1875 line = buf;
1876
1877 SDL_LockJoysticks();
1878
1879 PushMappingChangeTracking();
1880
1881 while (line < buf + db_size) {
1882 line_end = SDL_strchr(line, '\n');
1883 if (line_end) {
1884 *line_end = '\0';
1885 } else {
1886 line_end = buf + db_size;
1887 }
1888
1889 // Extract and verify the platform
1890 tmp = SDL_strstr(line, SDL_GAMEPAD_PLATFORM_FIELD);
1891 if (tmp) {
1892 tmp += SDL_GAMEPAD_PLATFORM_FIELD_SIZE;
1893 comma = SDL_strchr(tmp, ',');
1894 if (comma) {
1895 platform_len = comma - tmp + 1;
1896 if (platform_len + 1 < SDL_arraysize(line_platform)) {
1897 SDL_strlcpy(line_platform, tmp, platform_len);
1898 if (SDL_strncasecmp(line_platform, platform, platform_len) == 0 &&
1899 SDL_AddGamepadMapping(line) > 0) {
1900 gamepads++;
1901 }
1902 }
1903 }
1904 }
1905
1906 line = line_end + 1;
1907 }
1908
1909 PopMappingChangeTracking();
1910
1911 SDL_UnlockJoysticks();
1912
1913 SDL_free(buf);
1914 return gamepads;
1915}
1916
1917int SDL_AddGamepadMappingsFromFile(const char *file)
1918{
1919 return SDL_AddGamepadMappingsFromIO(SDL_IOFromFile(file, "rb"), true);
1920}
1921
1922bool SDL_ReloadGamepadMappings(void)
1923{
1924 SDL_Gamepad *gamepad;
1925
1926 SDL_LockJoysticks();
1927
1928 PushMappingChangeTracking();
1929
1930 for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
1931 AddMappingChangeTracking(gamepad->mapping);
1932 }
1933
1934 SDL_QuitGamepadMappings();
1935 SDL_InitGamepadMappings();
1936
1937 PopMappingChangeTracking();
1938
1939 SDL_UnlockJoysticks();
1940
1941 return true;
1942}
1943
1944static char *SDL_ConvertMappingToPositional(const char *mapping)
1945{
1946 // Add space for '!' and null terminator
1947 size_t length = SDL_strlen(mapping) + 1 + 1;
1948 char *remapped = (char *)SDL_malloc(length);
1949 if (remapped) {
1950 char *button_A;
1951 char *button_B;
1952 char *button_X;
1953 char *button_Y;
1954 char *hint;
1955
1956 SDL_strlcpy(remapped, mapping, length);
1957 button_A = SDL_strstr(remapped, "a:");
1958 button_B = SDL_strstr(remapped, "b:");
1959 button_X = SDL_strstr(remapped, "x:");
1960 button_Y = SDL_strstr(remapped, "y:");
1961 hint = SDL_strstr(remapped, "hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS");
1962
1963 if (button_A) {
1964 *button_A = 'b';
1965 }
1966 if (button_B) {
1967 *button_B = 'a';
1968 }
1969 if (button_X) {
1970 *button_X = 'y';
1971 }
1972 if (button_Y) {
1973 *button_Y = 'x';
1974 }
1975 if (hint) {
1976 hint += 5;
1977 SDL_memmove(hint + 1, hint, SDL_strlen(hint) + 1);
1978 *hint = '!';
1979 }
1980 }
1981 return remapped;
1982}
1983
1984/*
1985 * Add or update an entry into the Mappings Database with a priority
1986 */
1987static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMappingPriority priority)
1988{
1989 char *remapped = NULL;
1990 char *pchGUID;
1991 SDL_GUID jGUID;
1992 bool is_default_mapping = false;
1993 bool is_xinput_mapping = false;
1994 bool existing = false;
1995 GamepadMapping_t *pGamepadMapping;
1996 int result = -1;
1997
1998 SDL_AssertJoysticksLocked();
1999
2000 if (!mappingString) {
2001 SDL_InvalidParamError("mappingString");
2002 return -1;
2003 }
2004
2005 { // Extract and verify the hint field
2006 const char *tmp;
2007
2008 tmp = SDL_strstr(mappingString, SDL_GAMEPAD_HINT_FIELD);
2009 if (tmp) {
2010 bool default_value, value, negate;
2011 int len;
2012 char hint[128];
2013
2014 tmp += SDL_GAMEPAD_HINT_FIELD_SIZE;
2015
2016 if (*tmp == '!') {
2017 negate = true;
2018 ++tmp;
2019 } else {
2020 negate = false;
2021 }
2022
2023 len = 0;
2024 while (*tmp && *tmp != ',' && *tmp != ':' && len < (sizeof(hint) - 1)) {
2025 hint[len++] = *tmp++;
2026 }
2027 hint[len] = '\0';
2028
2029 if (tmp[0] == ':' && tmp[1] == '=') {
2030 tmp += 2;
2031 default_value = SDL_atoi(tmp);
2032 } else {
2033 default_value = false;
2034 }
2035
2036 if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") == 0) {
2037 // This hint is used to signal whether the mapping uses positional buttons or not
2038 if (negate) {
2039 // This mapping uses positional buttons, we can use it as-is
2040 } else {
2041 // This mapping uses labeled buttons, we need to swap them to positional
2042 remapped = SDL_ConvertMappingToPositional(mappingString);
2043 if (!remapped) {
2044 goto done;
2045 }
2046 mappingString = remapped;
2047 }
2048 } else {
2049 value = SDL_GetHintBoolean(hint, default_value);
2050 if (negate) {
2051 value = !value;
2052 }
2053 if (!value) {
2054 result = 0;
2055 goto done;
2056 }
2057 }
2058 }
2059 }
2060
2061#ifdef ANDROID
2062 { // Extract and verify the SDK version
2063 const char *tmp;
2064
2065 tmp = SDL_strstr(mappingString, SDL_GAMEPAD_SDKGE_FIELD);
2066 if (tmp) {
2067 tmp += SDL_GAMEPAD_SDKGE_FIELD_SIZE;
2068 if (!(SDL_GetAndroidSDKVersion() >= SDL_atoi(tmp))) {
2069 SDL_SetError("SDK version %d < minimum version %d", SDL_GetAndroidSDKVersion(), SDL_atoi(tmp));
2070 goto done;
2071 }
2072 }
2073 tmp = SDL_strstr(mappingString, SDL_GAMEPAD_SDKLE_FIELD);
2074 if (tmp) {
2075 tmp += SDL_GAMEPAD_SDKLE_FIELD_SIZE;
2076 if (!(SDL_GetAndroidSDKVersion() <= SDL_atoi(tmp))) {
2077 SDL_SetError("SDK version %d > maximum version %d", SDL_GetAndroidSDKVersion(), SDL_atoi(tmp));
2078 goto done;
2079 }
2080 }
2081 }
2082#endif
2083
2084 pchGUID = SDL_PrivateGetGamepadGUIDFromMappingString(mappingString);
2085 if (!pchGUID) {
2086 SDL_SetError("Couldn't parse GUID from %s", mappingString);
2087 goto done;
2088 }
2089 if (!SDL_strcasecmp(pchGUID, "default")) {
2090 is_default_mapping = true;
2091 } else if (!SDL_strcasecmp(pchGUID, "xinput")) {
2092 is_xinput_mapping = true;
2093 }
2094 jGUID = SDL_StringToGUID(pchGUID);
2095 SDL_free(pchGUID);
2096
2097 pGamepadMapping = SDL_PrivateAddMappingForGUID(jGUID, mappingString, &existing, priority);
2098 if (!pGamepadMapping) {
2099 goto done;
2100 }
2101
2102 if (existing) {
2103 result = 0;
2104 } else {
2105 if (is_default_mapping) {
2106 s_pDefaultMapping = pGamepadMapping;
2107 } else if (is_xinput_mapping) {
2108 s_pXInputMapping = pGamepadMapping;
2109 }
2110 result = 1;
2111 }
2112done:
2113 if (remapped) {
2114 SDL_free(remapped);
2115 }
2116 return result;
2117}
2118
2119/*
2120 * Add or update an entry into the Mappings Database
2121 */
2122int SDL_AddGamepadMapping(const char *mapping)
2123{
2124 int result;
2125
2126 SDL_LockJoysticks();
2127 {
2128 result = SDL_PrivateAddGamepadMapping(mapping, SDL_GAMEPAD_MAPPING_PRIORITY_API);
2129 }
2130 SDL_UnlockJoysticks();
2131
2132 return result;
2133}
2134
2135/*
2136 * Create a mapping string for a mapping
2137 */
2138static char *CreateMappingString(GamepadMapping_t *mapping, SDL_GUID guid)
2139{
2140 char *pMappingString, *pPlatformString;
2141 char pchGUID[33];
2142 size_t needed;
2143 bool need_platform = false;
2144 const char *platform = NULL;
2145
2146 SDL_AssertJoysticksLocked();
2147
2148 SDL_GUIDToString(guid, pchGUID, sizeof(pchGUID));
2149
2150 // allocate enough memory for GUID + ',' + name + ',' + mapping + \0
2151 needed = SDL_strlen(pchGUID) + 1 + SDL_strlen(mapping->name) + 1 + SDL_strlen(mapping->mapping) + 1;
2152
2153 if (!SDL_strstr(mapping->mapping, SDL_GAMEPAD_PLATFORM_FIELD)) {
2154 // add memory for ',' + platform:PLATFORM
2155 need_platform = true;
2156 if (mapping->mapping[SDL_strlen(mapping->mapping) - 1] != ',') {
2157 needed += 1;
2158 }
2159 platform = SDL_GetPlatform();
2160 needed += SDL_GAMEPAD_PLATFORM_FIELD_SIZE + SDL_strlen(platform) + 1;
2161 }
2162
2163 pMappingString = (char *)SDL_malloc(needed);
2164 if (!pMappingString) {
2165 return NULL;
2166 }
2167
2168 (void)SDL_snprintf(pMappingString, needed, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping);
2169
2170 if (need_platform) {
2171 if (mapping->mapping[SDL_strlen(mapping->mapping) - 1] != ',') {
2172 SDL_strlcat(pMappingString, ",", needed);
2173 }
2174 SDL_strlcat(pMappingString, SDL_GAMEPAD_PLATFORM_FIELD, needed);
2175 SDL_strlcat(pMappingString, platform, needed);
2176 SDL_strlcat(pMappingString, ",", needed);
2177 }
2178
2179 // Make sure multiple platform strings haven't made their way into the mapping
2180 pPlatformString = SDL_strstr(pMappingString, SDL_GAMEPAD_PLATFORM_FIELD);
2181 if (pPlatformString) {
2182 pPlatformString = SDL_strstr(pPlatformString + 1, SDL_GAMEPAD_PLATFORM_FIELD);
2183 if (pPlatformString) {
2184 *pPlatformString = '\0';
2185 }
2186 }
2187 return pMappingString;
2188}
2189
2190char **SDL_GetGamepadMappings(int *count)
2191{
2192 int num_mappings = 0;
2193 char **result = NULL;
2194 char **mappings = NULL;
2195
2196 if (count) {
2197 *count = 0;
2198 }
2199
2200 SDL_LockJoysticks();
2201
2202 for (GamepadMapping_t *mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) {
2203 if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) {
2204 continue;
2205 }
2206 num_mappings++;
2207 }
2208
2209 size_t final_allocation = sizeof (char *); // for the NULL terminator element.
2210 bool failed = false;
2211 mappings = (char **) SDL_calloc(num_mappings + 1, sizeof (char *));
2212 if (!mappings) {
2213 failed = true;
2214 } else {
2215 int i = 0;
2216 for (GamepadMapping_t *mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) {
2217 if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) {
2218 continue;
2219 }
2220
2221 char *mappingstr = CreateMappingString(mapping, mapping->guid);
2222 if (!mappingstr) {
2223 failed = true;
2224 break; // error string is already set.
2225 }
2226
2227 SDL_assert(i < num_mappings);
2228 mappings[i++] = mappingstr;
2229
2230 final_allocation += SDL_strlen(mappingstr) + 1 + sizeof (char *);
2231 }
2232 }
2233
2234 SDL_UnlockJoysticks();
2235
2236 if (!failed) {
2237 result = (char **) SDL_malloc(final_allocation);
2238 if (result) {
2239 final_allocation -= (sizeof (char *) * num_mappings + 1);
2240 char *strptr = (char *) (result + (num_mappings + 1));
2241 for (int i = 0; i < num_mappings; i++) {
2242 result[i] = strptr;
2243 const size_t slen = SDL_strlcpy(strptr, mappings[i], final_allocation) + 1;
2244 SDL_assert(final_allocation >= slen);
2245 final_allocation -= slen;
2246 strptr += slen;
2247 }
2248 result[num_mappings] = NULL;
2249
2250 if (count) {
2251 *count = num_mappings;
2252 }
2253 }
2254 }
2255
2256 if (mappings) {
2257 for (int i = 0; i < num_mappings; i++) {
2258 SDL_free(mappings[i]);
2259 }
2260 SDL_free(mappings);
2261 }
2262
2263 return result;
2264}
2265
2266/*
2267 * Get the mapping string for this GUID
2268 */
2269char *SDL_GetGamepadMappingForGUID(SDL_GUID guid)
2270{
2271 char *result;
2272
2273 SDL_LockJoysticks();
2274 {
2275 GamepadMapping_t *mapping = SDL_PrivateGetGamepadMappingForGUID(guid, false);
2276 if (mapping) {
2277 result = CreateMappingString(mapping, guid);
2278 } else {
2279 SDL_SetError("Mapping not available");
2280 result = NULL;
2281 }
2282 }
2283 SDL_UnlockJoysticks();
2284
2285 return result;
2286}
2287
2288/*
2289 * Get the mapping string for this device
2290 */
2291char *SDL_GetGamepadMapping(SDL_Gamepad *gamepad)
2292{
2293 char *result;
2294
2295 SDL_LockJoysticks();
2296 {
2297 CHECK_GAMEPAD_MAGIC(gamepad, NULL);
2298
2299 result = CreateMappingString(gamepad->mapping, gamepad->joystick->guid);
2300 }
2301 SDL_UnlockJoysticks();
2302
2303 return result;
2304}
2305
2306/*
2307 * Set the mapping string for this device
2308 */
2309bool SDL_SetGamepadMapping(SDL_JoystickID instance_id, const char *mapping)
2310{
2311 SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id);
2312 bool result = false;
2313
2314 if (SDL_memcmp(&guid, &s_zeroGUID, sizeof(guid)) == 0) {
2315 return SDL_InvalidParamError("instance_id");
2316 }
2317
2318 if (!mapping) {
2319 mapping = "*,*,";
2320 }
2321
2322 SDL_LockJoysticks();
2323 {
2324 if (SDL_PrivateAddMappingForGUID(guid, mapping, NULL, SDL_GAMEPAD_MAPPING_PRIORITY_API)) {
2325 result = true;
2326 }
2327 }
2328 SDL_UnlockJoysticks();
2329
2330 return result;
2331}
2332
2333static void SDL_LoadGamepadHints(void)
2334{
2335 const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLERCONFIG);
2336 if (hint && hint[0]) {
2337 char *pTempMappings = SDL_strdup(hint);
2338 char *pUserMappings = pTempMappings;
2339
2340 PushMappingChangeTracking();
2341
2342 while (pUserMappings) {
2343 char *pchNewLine = NULL;
2344
2345 pchNewLine = SDL_strchr(pUserMappings, '\n');
2346 if (pchNewLine) {
2347 *pchNewLine = '\0';
2348 }
2349
2350 SDL_PrivateAddGamepadMapping(pUserMappings, SDL_GAMEPAD_MAPPING_PRIORITY_USER);
2351
2352 if (pchNewLine) {
2353 pUserMappings = pchNewLine + 1;
2354 } else {
2355 pUserMappings = NULL;
2356 }
2357 }
2358
2359 PopMappingChangeTracking();
2360
2361 SDL_free(pTempMappings);
2362 }
2363}
2364
2365/*
2366 * Fill the given buffer with the expected gamepad mapping filepath.
2367 * Usually this will just be SDL_HINT_GAMECONTROLLERCONFIG_FILE, but for
2368 * Android, we want to get the internal storage path.
2369 */
2370static bool SDL_GetGamepadMappingFilePath(char *path, size_t size)
2371{
2372 const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLERCONFIG_FILE);
2373 if (hint && *hint) {
2374 return SDL_strlcpy(path, hint, size) < size;
2375 }
2376
2377#ifdef SDL_PLATFORM_ANDROID
2378 return SDL_snprintf(path, size, "%s/gamepad_map.txt", SDL_GetAndroidInternalStoragePath()) < size;
2379#else
2380 return false;
2381#endif
2382}
2383
2384/*
2385 * Initialize the gamepad system, mostly load our DB of gamepad config mappings
2386 */
2387bool SDL_InitGamepadMappings(void)
2388{
2389 char szGamepadMapPath[1024];
2390 int i = 0;
2391 const char *pMappingString = NULL;
2392
2393 SDL_AssertJoysticksLocked();
2394
2395 PushMappingChangeTracking();
2396
2397 pMappingString = s_GamepadMappings[i];
2398 while (pMappingString) {
2399 SDL_PrivateAddGamepadMapping(pMappingString, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);
2400
2401 i++;
2402 pMappingString = s_GamepadMappings[i];
2403 }
2404
2405 if (SDL_GetGamepadMappingFilePath(szGamepadMapPath, sizeof(szGamepadMapPath))) {
2406 SDL_AddGamepadMappingsFromFile(szGamepadMapPath);
2407 }
2408
2409 // load in any user supplied config
2410 SDL_LoadGamepadHints();
2411
2412 SDL_LoadVIDPIDList(&SDL_allowed_gamepads);
2413 SDL_LoadVIDPIDList(&SDL_ignored_gamepads);
2414
2415 PopMappingChangeTracking();
2416
2417 return true;
2418}
2419
2420bool SDL_InitGamepads(void)
2421{
2422 int i;
2423 SDL_JoystickID *joysticks;
2424
2425 SDL_gamepads_initialized = true;
2426
2427 // Watch for joystick events and fire gamepad ones if needed
2428 SDL_AddEventWatch(SDL_GamepadEventWatcher, NULL);
2429
2430 // Send added events for gamepads currently attached
2431 joysticks = SDL_GetJoysticks(NULL);
2432 if (joysticks) {
2433 for (i = 0; joysticks[i]; ++i) {
2434 if (SDL_IsGamepad(joysticks[i])) {
2435 SDL_PrivateGamepadAdded(joysticks[i]);
2436 }
2437 }
2438 SDL_free(joysticks);
2439 }
2440
2441 return true;
2442}
2443
2444bool SDL_HasGamepad(void)
2445{
2446 int num_joysticks = 0;
2447 int num_gamepads = 0;
2448 SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks);
2449 if (joysticks) {
2450 int i;
2451 for (i = num_joysticks - 1; i >= 0 && num_gamepads == 0; --i) {
2452 if (SDL_IsGamepad(joysticks[i])) {
2453 ++num_gamepads;
2454 }
2455 }
2456 SDL_free(joysticks);
2457 }
2458 if (num_gamepads > 0) {
2459 return true;
2460 }
2461 return false;
2462}
2463
2464SDL_JoystickID *SDL_GetGamepads(int *count)
2465{
2466 int num_joysticks = 0;
2467 int num_gamepads = 0;
2468 SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks);
2469 if (joysticks) {
2470 int i;
2471 for (i = num_joysticks - 1; i >= 0; --i) {
2472 if (SDL_IsGamepad(joysticks[i])) {
2473 ++num_gamepads;
2474 } else {
2475 SDL_memmove(&joysticks[i], &joysticks[i+1], (num_gamepads + 1) * sizeof(joysticks[i]));
2476 }
2477 }
2478 }
2479 if (count) {
2480 *count = num_gamepads;
2481 }
2482 return joysticks;
2483}
2484
2485const char *SDL_GetGamepadNameForID(SDL_JoystickID instance_id)
2486{
2487 const char *result = NULL;
2488
2489 SDL_LockJoysticks();
2490 {
2491 GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true);
2492 if (mapping) {
2493 if (SDL_strcmp(mapping->name, "*") == 0) {
2494 result = SDL_GetJoystickNameForID(instance_id);
2495 } else {
2496 result = SDL_GetPersistentString(mapping->name);
2497 }
2498 }
2499 }
2500 SDL_UnlockJoysticks();
2501
2502 return result;
2503}
2504
2505const char *SDL_GetGamepadPathForID(SDL_JoystickID instance_id)
2506{
2507 return SDL_GetJoystickPathForID(instance_id);
2508}
2509
2510int SDL_GetGamepadPlayerIndexForID(SDL_JoystickID instance_id)
2511{
2512 return SDL_GetJoystickPlayerIndexForID(instance_id);
2513}
2514
2515SDL_GUID SDL_GetGamepadGUIDForID(SDL_JoystickID instance_id)
2516{
2517 return SDL_GetJoystickGUIDForID(instance_id);
2518}
2519
2520Uint16 SDL_GetGamepadVendorForID(SDL_JoystickID instance_id)
2521{
2522 return SDL_GetJoystickVendorForID(instance_id);
2523}
2524
2525Uint16 SDL_GetGamepadProductForID(SDL_JoystickID instance_id)
2526{
2527 return SDL_GetJoystickProductForID(instance_id);
2528}
2529
2530Uint16 SDL_GetGamepadProductVersionForID(SDL_JoystickID instance_id)
2531{
2532 return SDL_GetJoystickProductVersionForID(instance_id);
2533}
2534
2535SDL_GamepadType SDL_GetGamepadTypeForID(SDL_JoystickID instance_id)
2536{
2537 SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
2538
2539 SDL_LockJoysticks();
2540 {
2541 GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true);
2542 if (mapping) {
2543 char *type_string, *comma;
2544
2545 type_string = SDL_strstr(mapping->mapping, SDL_GAMEPAD_TYPE_FIELD);
2546 if (type_string) {
2547 type_string += SDL_GAMEPAD_TYPE_FIELD_SIZE;
2548 comma = SDL_strchr(type_string, ',');
2549 if (comma) {
2550 *comma = '\0';
2551 type = SDL_GetGamepadTypeFromString(type_string);
2552 *comma = ',';
2553 }
2554 }
2555 }
2556 }
2557 SDL_UnlockJoysticks();
2558
2559 if (type != SDL_GAMEPAD_TYPE_UNKNOWN) {
2560 return type;
2561 }
2562 return SDL_GetRealGamepadTypeForID(instance_id);
2563}
2564
2565SDL_GamepadType SDL_GetRealGamepadTypeForID(SDL_JoystickID instance_id)
2566{
2567 SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
2568 const SDL_SteamVirtualGamepadInfo *info;
2569
2570 SDL_LockJoysticks();
2571 {
2572 info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id);
2573 if (info) {
2574 type = info->type;
2575 } else {
2576 type = SDL_GetGamepadTypeFromGUID(SDL_GetJoystickGUIDForID(instance_id), SDL_GetJoystickNameForID(instance_id));
2577 }
2578 }
2579 SDL_UnlockJoysticks();
2580
2581 return type;
2582}
2583
2584char *SDL_GetGamepadMappingForID(SDL_JoystickID instance_id)
2585{
2586 char *result = NULL;
2587
2588 SDL_LockJoysticks();
2589 {
2590 GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true);
2591 if (mapping) {
2592 char pchGUID[33];
2593 SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id);
2594 SDL_GUIDToString(guid, pchGUID, sizeof(pchGUID));
2595 SDL_asprintf(&result, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping);
2596 }
2597 }
2598 SDL_UnlockJoysticks();
2599
2600 return result;
2601}
2602
2603/*
2604 * Return 1 if the joystick with this name and GUID is a supported gamepad
2605 */
2606bool SDL_IsGamepadNameAndGUID(const char *name, SDL_GUID guid)
2607{
2608 bool result;
2609
2610 SDL_LockJoysticks();
2611 {
2612 if (s_pDefaultMapping || SDL_PrivateGetGamepadMappingForNameAndGUID(name, guid) != NULL) {
2613 result = true;
2614 } else {
2615 result = false;
2616 }
2617 }
2618 SDL_UnlockJoysticks();
2619
2620 return result;
2621}
2622
2623/*
2624 * Return 1 if the joystick at this device index is a supported gamepad
2625 */
2626bool SDL_IsGamepad(SDL_JoystickID instance_id)
2627{
2628 bool result;
2629
2630 SDL_LockJoysticks();
2631 {
2632 const void *value;
2633 if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) {
2634 result = (bool)(uintptr_t)value;
2635 } else {
2636 if (SDL_PrivateGetGamepadMapping(instance_id, true) != NULL) {
2637 result = true;
2638 } else {
2639 result = false;
2640 }
2641
2642 if (!s_gamepadInstanceIDs) {
2643 s_gamepadInstanceIDs = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL);
2644 }
2645 SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, (void *)(uintptr_t)result, true);
2646 }
2647 }
2648 SDL_UnlockJoysticks();
2649
2650 return result;
2651}
2652
2653/*
2654 * Return 1 if the gamepad should be ignored by SDL
2655 */
2656bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
2657{
2658#ifdef SDL_PLATFORM_LINUX
2659 if (SDL_endswith(name, " Motion Sensors")) {
2660 // Don't treat the PS3 and PS4 motion controls as a separate gamepad
2661 return true;
2662 }
2663 if (SDL_strncmp(name, "Nintendo ", 9) == 0 && SDL_strstr(name, " IMU") != NULL) {
2664 // Don't treat the Nintendo IMU as a separate gamepad
2665 return true;
2666 }
2667 if (SDL_endswith(name, " Accelerometer") ||
2668 SDL_endswith(name, " IR") ||
2669 SDL_endswith(name, " Motion Plus") ||
2670 SDL_endswith(name, " Nunchuk")) {
2671 // Don't treat the Wii extension controls as a separate gamepad
2672 return true;
2673 }
2674#endif
2675
2676 if (name && SDL_strcmp(name, "uinput-fpc") == 0) {
2677 // The Google Pixel fingerprint sensor reports itself as a joystick
2678 return true;
2679 }
2680
2681#ifdef SDL_PLATFORM_WIN32
2682 if (SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false) &&
2683 SDL_GetHintBoolean("STEAM_COMPAT_PROTON", false)) {
2684 // We are launched by Steam and running under Proton
2685 // We can't tell whether this controller is a Steam Virtual Gamepad,
2686 // so assume that Proton is doing the appropriate filtering of controllers
2687 // and anything we see here is fine to use.
2688 return false;
2689 }
2690#endif // SDL_PLATFORM_WIN32
2691
2692 if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) {
2693 return !SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false);
2694 }
2695
2696 if (SDL_allowed_gamepads.num_included_entries > 0) {
2697 if (SDL_VIDPIDInList(vendor_id, product_id, &SDL_allowed_gamepads)) {
2698 return false;
2699 }
2700 return true;
2701 } else {
2702 if (SDL_VIDPIDInList(vendor_id, product_id, &SDL_ignored_gamepads)) {
2703 return true;
2704 }
2705 return false;
2706 }
2707}
2708
2709/*
2710 * Open a gamepad for use
2711 *
2712 * This function returns a gamepad identifier, or NULL if an error occurred.
2713 */
2714SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id)
2715{
2716 SDL_Gamepad *gamepad;
2717 SDL_Gamepad *gamepadlist;
2718 GamepadMapping_t *pSupportedGamepad = NULL;
2719
2720 SDL_LockJoysticks();
2721
2722 gamepadlist = SDL_gamepads;
2723 // If the gamepad is already open, return it
2724 while (gamepadlist) {
2725 if (instance_id == gamepadlist->joystick->instance_id) {
2726 gamepad = gamepadlist;
2727 ++gamepad->ref_count;
2728 SDL_UnlockJoysticks();
2729 return gamepad;
2730 }
2731 gamepadlist = gamepadlist->next;
2732 }
2733
2734 // Find a gamepad mapping
2735 pSupportedGamepad = SDL_PrivateGetGamepadMapping(instance_id, true);
2736 if (!pSupportedGamepad) {
2737 SDL_SetError("Couldn't find mapping for device (%" SDL_PRIu32 ")", instance_id);
2738 SDL_UnlockJoysticks();
2739 return NULL;
2740 }
2741
2742 // Create and initialize the gamepad
2743 gamepad = (SDL_Gamepad *)SDL_calloc(1, sizeof(*gamepad));
2744 if (!gamepad) {
2745 SDL_UnlockJoysticks();
2746 return NULL;
2747 }
2748 SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, true);
2749
2750 gamepad->joystick = SDL_OpenJoystick(instance_id);
2751 if (!gamepad->joystick) {
2752 SDL_free(gamepad);
2753 SDL_UnlockJoysticks();
2754 return NULL;
2755 }
2756
2757 if (gamepad->joystick->naxes) {
2758 gamepad->last_match_axis = (SDL_GamepadBinding **)SDL_calloc(gamepad->joystick->naxes, sizeof(*gamepad->last_match_axis));
2759 if (!gamepad->last_match_axis) {
2760 SDL_CloseJoystick(gamepad->joystick);
2761 SDL_free(gamepad);
2762 SDL_UnlockJoysticks();
2763 return NULL;
2764 }
2765 }
2766 if (gamepad->joystick->nhats) {
2767 gamepad->last_hat_mask = (Uint8 *)SDL_calloc(gamepad->joystick->nhats, sizeof(*gamepad->last_hat_mask));
2768 if (!gamepad->last_hat_mask) {
2769 SDL_CloseJoystick(gamepad->joystick);
2770 SDL_free(gamepad->last_match_axis);
2771 SDL_free(gamepad);
2772 SDL_UnlockJoysticks();
2773 return NULL;
2774 }
2775 }
2776
2777 SDL_PrivateLoadButtonMapping(gamepad, pSupportedGamepad);
2778
2779 // Add the gamepad to list
2780 ++gamepad->ref_count;
2781 // Link the gamepad in the list
2782 gamepad->next = SDL_gamepads;
2783 SDL_gamepads = gamepad;
2784
2785 SDL_UnlockJoysticks();
2786
2787 return gamepad;
2788}
2789
2790/*
2791 * Manually pump for gamepad updates.
2792 */
2793void SDL_UpdateGamepads(void)
2794{
2795 // Just for API completeness; the joystick API does all the work.
2796 SDL_UpdateJoysticks();
2797}
2798
2799/**
2800 * Return whether a gamepad has a given axis
2801 */
2802bool SDL_GamepadHasAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)
2803{
2804 bool result = false;
2805
2806 SDL_LockJoysticks();
2807 {
2808 int i;
2809
2810 CHECK_GAMEPAD_MAGIC(gamepad, false);
2811
2812 for (i = 0; i < gamepad->num_bindings; ++i) {
2813 const SDL_GamepadBinding *binding = &gamepad->bindings[i];
2814 if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS && binding->output.axis.axis == axis) {
2815 result = true;
2816 break;
2817 }
2818 }
2819 }
2820 SDL_UnlockJoysticks();
2821
2822 return result;
2823}
2824
2825/*
2826 * Get the current state of an axis control on a gamepad
2827 */
2828Sint16 SDL_GetGamepadAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)
2829{
2830 Sint16 result = 0;
2831
2832 SDL_LockJoysticks();
2833 {
2834 int i;
2835
2836 CHECK_GAMEPAD_MAGIC(gamepad, 0);
2837
2838 for (i = 0; i < gamepad->num_bindings; ++i) {
2839 const SDL_GamepadBinding *binding = &gamepad->bindings[i];
2840 if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS && binding->output.axis.axis == axis) {
2841 int value = 0;
2842 bool valid_input_range;
2843 bool valid_output_range;
2844
2845 if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS) {
2846 value = SDL_GetJoystickAxis(gamepad->joystick, binding->input.axis.axis);
2847 if (binding->input.axis.axis_min < binding->input.axis.axis_max) {
2848 valid_input_range = (value >= binding->input.axis.axis_min && value <= binding->input.axis.axis_max);
2849 } else {
2850 valid_input_range = (value >= binding->input.axis.axis_max && value <= binding->input.axis.axis_min);
2851 }
2852 if (valid_input_range) {
2853 if (binding->input.axis.axis_min != binding->output.axis.axis_min || binding->input.axis.axis_max != binding->output.axis.axis_max) {
2854 float normalized_value = (float)(value - binding->input.axis.axis_min) / (binding->input.axis.axis_max - binding->input.axis.axis_min);
2855 value = binding->output.axis.axis_min + (int)(normalized_value * (binding->output.axis.axis_max - binding->output.axis.axis_min));
2856 }
2857 } else {
2858 value = 0;
2859 }
2860 } else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON) {
2861 if (SDL_GetJoystickButton(gamepad->joystick, binding->input.button)) {
2862 value = binding->output.axis.axis_max;
2863 }
2864 } else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_HAT) {
2865 int hat_mask = SDL_GetJoystickHat(gamepad->joystick, binding->input.hat.hat);
2866 if (hat_mask & binding->input.hat.hat_mask) {
2867 value = binding->output.axis.axis_max;
2868 }
2869 }
2870
2871 if (binding->output.axis.axis_min < binding->output.axis.axis_max) {
2872 valid_output_range = (value >= binding->output.axis.axis_min && value <= binding->output.axis.axis_max);
2873 } else {
2874 valid_output_range = (value >= binding->output.axis.axis_max && value <= binding->output.axis.axis_min);
2875 }
2876 // If the value is zero, there might be another binding that makes it non-zero
2877 if (value != 0 && valid_output_range) {
2878 result = (Sint16)value;
2879 break;
2880 }
2881 }
2882 }
2883 }
2884 SDL_UnlockJoysticks();
2885
2886 return result;
2887}
2888
2889/**
2890 * Return whether a gamepad has a given button
2891 */
2892bool SDL_GamepadHasButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)
2893{
2894 bool result = false;
2895
2896 SDL_LockJoysticks();
2897 {
2898 int i;
2899
2900 CHECK_GAMEPAD_MAGIC(gamepad, false);
2901
2902 for (i = 0; i < gamepad->num_bindings; ++i) {
2903 const SDL_GamepadBinding *binding = &gamepad->bindings[i];
2904 if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON && binding->output.button == button) {
2905 result = true;
2906 break;
2907 }
2908 }
2909 }
2910 SDL_UnlockJoysticks();
2911
2912 return result;
2913}
2914
2915/*
2916 * Get the current state of a button on a gamepad
2917 */
2918bool SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)
2919{
2920 bool result = false;
2921
2922 SDL_LockJoysticks();
2923 {
2924 int i;
2925
2926 CHECK_GAMEPAD_MAGIC(gamepad, false);
2927
2928 for (i = 0; i < gamepad->num_bindings; ++i) {
2929 const SDL_GamepadBinding *binding = &gamepad->bindings[i];
2930 if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON && binding->output.button == button) {
2931 if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS) {
2932 bool valid_input_range;
2933
2934 int value = SDL_GetJoystickAxis(gamepad->joystick, binding->input.axis.axis);
2935 int threshold = binding->input.axis.axis_min + (binding->input.axis.axis_max - binding->input.axis.axis_min) / 2;
2936 if (binding->input.axis.axis_min < binding->input.axis.axis_max) {
2937 valid_input_range = (value >= binding->input.axis.axis_min && value <= binding->input.axis.axis_max);
2938 if (valid_input_range) {
2939 result |= (value >= threshold);
2940 }
2941 } else {
2942 valid_input_range = (value >= binding->input.axis.axis_max && value <= binding->input.axis.axis_min);
2943 if (valid_input_range) {
2944 result |= (value <= threshold);
2945 }
2946 }
2947 } else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON) {
2948 result |= SDL_GetJoystickButton(gamepad->joystick, binding->input.button);
2949 } else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_HAT) {
2950 int hat_mask = SDL_GetJoystickHat(gamepad->joystick, binding->input.hat.hat);
2951 result |= ((hat_mask & binding->input.hat.hat_mask) != 0);
2952 }
2953 }
2954 }
2955 }
2956 SDL_UnlockJoysticks();
2957
2958 return result;
2959}
2960
2961/**
2962 * Get the label of a button on a gamepad.
2963 */
2964static SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForFaceStyle(SDL_GamepadFaceStyle face_style, SDL_GamepadButton button)
2965{
2966 SDL_GamepadButtonLabel label = SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN;
2967
2968 switch (face_style) {
2969 case SDL_GAMEPAD_FACE_STYLE_ABXY:
2970 switch (button) {
2971 case SDL_GAMEPAD_BUTTON_SOUTH:
2972 label = SDL_GAMEPAD_BUTTON_LABEL_A;
2973 break;
2974 case SDL_GAMEPAD_BUTTON_EAST:
2975 label = SDL_GAMEPAD_BUTTON_LABEL_B;
2976 break;
2977 case SDL_GAMEPAD_BUTTON_WEST:
2978 label = SDL_GAMEPAD_BUTTON_LABEL_X;
2979 break;
2980 case SDL_GAMEPAD_BUTTON_NORTH:
2981 label = SDL_GAMEPAD_BUTTON_LABEL_Y;
2982 break;
2983 default:
2984 break;
2985 }
2986 break;
2987 case SDL_GAMEPAD_FACE_STYLE_BAYX:
2988 switch (button) {
2989 case SDL_GAMEPAD_BUTTON_SOUTH:
2990 label = SDL_GAMEPAD_BUTTON_LABEL_B;
2991 break;
2992 case SDL_GAMEPAD_BUTTON_EAST:
2993 label = SDL_GAMEPAD_BUTTON_LABEL_A;
2994 break;
2995 case SDL_GAMEPAD_BUTTON_WEST:
2996 label = SDL_GAMEPAD_BUTTON_LABEL_Y;
2997 break;
2998 case SDL_GAMEPAD_BUTTON_NORTH:
2999 label = SDL_GAMEPAD_BUTTON_LABEL_X;
3000 break;
3001 default:
3002 break;
3003 }
3004 break;
3005 case SDL_GAMEPAD_FACE_STYLE_SONY:
3006 switch (button) {
3007 case SDL_GAMEPAD_BUTTON_SOUTH:
3008 label = SDL_GAMEPAD_BUTTON_LABEL_CROSS;
3009 break;
3010 case SDL_GAMEPAD_BUTTON_EAST:
3011 label = SDL_GAMEPAD_BUTTON_LABEL_CIRCLE;
3012 break;
3013 case SDL_GAMEPAD_BUTTON_WEST:
3014 label = SDL_GAMEPAD_BUTTON_LABEL_SQUARE;
3015 break;
3016 case SDL_GAMEPAD_BUTTON_NORTH:
3017 label = SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE;
3018 break;
3019 default:
3020 break;
3021 }
3022 break;
3023 default:
3024 break;
3025 }
3026 return label;
3027}
3028
3029/**
3030 * Get the label of a button on a gamepad.
3031 */
3032SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForType(SDL_GamepadType type, SDL_GamepadButton button)
3033{
3034 return SDL_GetGamepadButtonLabelForFaceStyle(SDL_GetGamepadFaceStyleForGamepadType(type), button);
3035}
3036
3037/**
3038 * Get the label of a button on a gamepad.
3039 */
3040SDL_GamepadButtonLabel SDL_GetGamepadButtonLabel(SDL_Gamepad *gamepad, SDL_GamepadButton button)
3041{
3042 SDL_GamepadFaceStyle face_style;
3043
3044 SDL_LockJoysticks();
3045 {
3046 CHECK_GAMEPAD_MAGIC(gamepad, SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN);
3047
3048 face_style = gamepad->face_style;
3049 }
3050 SDL_UnlockJoysticks();
3051
3052 return SDL_GetGamepadButtonLabelForFaceStyle(face_style, button);
3053}
3054
3055/**
3056 * Get the number of touchpads on a gamepad.
3057 */
3058int SDL_GetNumGamepadTouchpads(SDL_Gamepad *gamepad)
3059{
3060 int result = 0;
3061
3062 SDL_LockJoysticks();
3063 {
3064 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3065 if (joystick) {
3066 result = joystick->ntouchpads;
3067 }
3068 }
3069 SDL_UnlockJoysticks();
3070
3071 return result;
3072}
3073
3074/**
3075 * Get the number of supported simultaneous fingers on a touchpad on a gamepad.
3076 */
3077int SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *gamepad, int touchpad)
3078{
3079 int result = 0;
3080
3081 SDL_LockJoysticks();
3082 {
3083 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3084 if (joystick) {
3085 if (touchpad >= 0 && touchpad < joystick->ntouchpads) {
3086 result = joystick->touchpads[touchpad].nfingers;
3087 }
3088 }
3089 }
3090 SDL_UnlockJoysticks();
3091
3092 return result;
3093}
3094
3095/**
3096 * Get the current state of a finger on a touchpad on a gamepad.
3097 */
3098bool SDL_GetGamepadTouchpadFinger(SDL_Gamepad *gamepad, int touchpad, int finger, bool *down, float *x, float *y, float *pressure)
3099{
3100 bool result = false;
3101
3102 SDL_LockJoysticks();
3103 {
3104 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3105 if (joystick) {
3106 if (touchpad >= 0 && touchpad < joystick->ntouchpads) {
3107 SDL_JoystickTouchpadInfo *touchpad_info = &joystick->touchpads[touchpad];
3108 if (finger >= 0 && finger < touchpad_info->nfingers) {
3109 SDL_JoystickTouchpadFingerInfo *info = &touchpad_info->fingers[finger];
3110
3111 if (down) {
3112 *down = info->down;
3113 }
3114 if (x) {
3115 *x = info->x;
3116 }
3117 if (y) {
3118 *y = info->y;
3119 }
3120 if (pressure) {
3121 *pressure = info->pressure;
3122 }
3123 result = true;
3124 } else {
3125 result = SDL_InvalidParamError("finger");
3126 }
3127 } else {
3128 result = SDL_InvalidParamError("touchpad");
3129 }
3130 }
3131 }
3132 SDL_UnlockJoysticks();
3133
3134 return result;
3135}
3136
3137/**
3138 * Return whether a gamepad has a particular sensor.
3139 */
3140bool SDL_GamepadHasSensor(SDL_Gamepad *gamepad, SDL_SensorType type)
3141{
3142 bool result = false;
3143
3144 SDL_LockJoysticks();
3145 {
3146 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3147 if (joystick) {
3148 int i;
3149 for (i = 0; i < joystick->nsensors; ++i) {
3150 if (joystick->sensors[i].type == type) {
3151 result = true;
3152 break;
3153 }
3154 }
3155 }
3156 }
3157 SDL_UnlockJoysticks();
3158
3159 return result;
3160}
3161
3162/*
3163 * Set whether data reporting for a gamepad sensor is enabled
3164 */
3165bool SDL_SetGamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type, bool enabled)
3166{
3167 SDL_LockJoysticks();
3168 {
3169 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3170 if (joystick) {
3171 int i;
3172 for (i = 0; i < joystick->nsensors; ++i) {
3173 SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];
3174
3175 if (sensor->type == type) {
3176 if (sensor->enabled == (enabled != false)) {
3177 SDL_UnlockJoysticks();
3178 return true;
3179 }
3180
3181 if (type == SDL_SENSOR_ACCEL && joystick->accel_sensor) {
3182 if (enabled) {
3183 joystick->accel = SDL_OpenSensor(joystick->accel_sensor);
3184 if (!joystick->accel) {
3185 SDL_UnlockJoysticks();
3186 return false;
3187 }
3188 } else {
3189 if (joystick->accel) {
3190 SDL_CloseSensor(joystick->accel);
3191 joystick->accel = NULL;
3192 }
3193 }
3194 } else if (type == SDL_SENSOR_GYRO && joystick->gyro_sensor) {
3195 if (enabled) {
3196 joystick->gyro = SDL_OpenSensor(joystick->gyro_sensor);
3197 if (!joystick->gyro) {
3198 SDL_UnlockJoysticks();
3199 return false;
3200 }
3201 } else {
3202 if (joystick->gyro) {
3203 SDL_CloseSensor(joystick->gyro);
3204 joystick->gyro = NULL;
3205 }
3206 }
3207 } else {
3208 if (enabled) {
3209 if (joystick->nsensors_enabled == 0) {
3210 if (!joystick->driver->SetSensorsEnabled(joystick, true)) {
3211 SDL_UnlockJoysticks();
3212 return false;
3213 }
3214 }
3215 ++joystick->nsensors_enabled;
3216 } else {
3217 if (joystick->nsensors_enabled == 1) {
3218 if (!joystick->driver->SetSensorsEnabled(joystick, false)) {
3219 SDL_UnlockJoysticks();
3220 return false;
3221 }
3222 }
3223 --joystick->nsensors_enabled;
3224 }
3225 }
3226
3227 sensor->enabled = enabled;
3228 SDL_UnlockJoysticks();
3229 return true;
3230 }
3231 }
3232 }
3233 }
3234 SDL_UnlockJoysticks();
3235
3236 return SDL_Unsupported();
3237}
3238
3239/*
3240 * Query whether sensor data reporting is enabled for a gamepad
3241 */
3242bool SDL_GamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type)
3243{
3244 bool result = false;
3245
3246 SDL_LockJoysticks();
3247 {
3248 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3249 if (joystick) {
3250 int i;
3251 for (i = 0; i < joystick->nsensors; ++i) {
3252 if (joystick->sensors[i].type == type) {
3253 result = joystick->sensors[i].enabled;
3254 break;
3255 }
3256 }
3257 }
3258 }
3259 SDL_UnlockJoysticks();
3260
3261 return result;
3262}
3263
3264/*
3265 * Get the data rate of a gamepad sensor.
3266 */
3267float SDL_GetGamepadSensorDataRate(SDL_Gamepad *gamepad, SDL_SensorType type)
3268{
3269 float result = 0.0f;
3270
3271 SDL_LockJoysticks();
3272 {
3273 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3274 if (joystick) {
3275 int i;
3276 for (i = 0; i < joystick->nsensors; ++i) {
3277 SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];
3278
3279 if (sensor->type == type) {
3280 result = sensor->rate;
3281 break;
3282 }
3283 }
3284 }
3285 }
3286 SDL_UnlockJoysticks();
3287
3288 return result;
3289}
3290
3291/*
3292 * Get the current state of a gamepad sensor.
3293 */
3294bool SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *data, int num_values)
3295{
3296 SDL_LockJoysticks();
3297 {
3298 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3299 if (joystick) {
3300 int i;
3301 for (i = 0; i < joystick->nsensors; ++i) {
3302 SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];
3303
3304 if (sensor->type == type) {
3305 num_values = SDL_min(num_values, SDL_arraysize(sensor->data));
3306 SDL_memcpy(data, sensor->data, num_values * sizeof(*data));
3307 SDL_UnlockJoysticks();
3308 return true;
3309 }
3310 }
3311 }
3312 }
3313 SDL_UnlockJoysticks();
3314
3315 return SDL_Unsupported();
3316}
3317
3318SDL_JoystickID SDL_GetGamepadID(SDL_Gamepad *gamepad)
3319{
3320 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3321
3322 if (!joystick) {
3323 return 0;
3324 }
3325 return SDL_GetJoystickID(joystick);
3326}
3327
3328SDL_PropertiesID SDL_GetGamepadProperties(SDL_Gamepad *gamepad)
3329{
3330 SDL_PropertiesID result = 0;
3331
3332 SDL_LockJoysticks();
3333 {
3334 CHECK_GAMEPAD_MAGIC(gamepad, 0);
3335
3336 result = SDL_GetJoystickProperties(gamepad->joystick);
3337 }
3338 SDL_UnlockJoysticks();
3339
3340 return result;
3341}
3342
3343const char *SDL_GetGamepadName(SDL_Gamepad *gamepad)
3344{
3345 const char *result = NULL;
3346
3347 SDL_LockJoysticks();
3348 {
3349 CHECK_GAMEPAD_MAGIC(gamepad, NULL);
3350
3351 if (SDL_strcmp(gamepad->name, "*") == 0 ||
3352 gamepad->joystick->steam_handle != 0) {
3353 result = SDL_GetJoystickName(gamepad->joystick);
3354 } else {
3355 result = SDL_GetPersistentString(gamepad->name);
3356 }
3357 }
3358 SDL_UnlockJoysticks();
3359
3360 return result;
3361}
3362
3363const char *SDL_GetGamepadPath(SDL_Gamepad *gamepad)
3364{
3365 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3366
3367 if (!joystick) {
3368 return NULL;
3369 }
3370 return SDL_GetJoystickPath(joystick);
3371}
3372
3373SDL_GamepadType SDL_GetGamepadType(SDL_Gamepad *gamepad)
3374{
3375 SDL_GamepadType type;
3376 const SDL_SteamVirtualGamepadInfo *info;
3377
3378 SDL_LockJoysticks();
3379 {
3380 CHECK_GAMEPAD_MAGIC(gamepad, SDL_GAMEPAD_TYPE_UNKNOWN);
3381
3382 info = SDL_GetJoystickVirtualGamepadInfoForID(gamepad->joystick->instance_id);
3383 if (info) {
3384 type = info->type;
3385 } else {
3386 type = gamepad->type;
3387 }
3388 }
3389 SDL_UnlockJoysticks();
3390
3391 return type;
3392}
3393
3394SDL_GamepadType SDL_GetRealGamepadType(SDL_Gamepad *gamepad)
3395{
3396 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3397
3398 if (!joystick) {
3399 return SDL_GAMEPAD_TYPE_UNKNOWN;
3400 }
3401 return SDL_GetGamepadTypeFromGUID(SDL_GetJoystickGUID(joystick), SDL_GetJoystickName(joystick));
3402}
3403
3404int SDL_GetGamepadPlayerIndex(SDL_Gamepad *gamepad)
3405{
3406 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3407
3408 if (!joystick) {
3409 return -1;
3410 }
3411 return SDL_GetJoystickPlayerIndex(joystick);
3412}
3413
3414/**
3415 * Set the player index of an opened gamepad
3416 */
3417bool SDL_SetGamepadPlayerIndex(SDL_Gamepad *gamepad, int player_index)
3418{
3419 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3420
3421 if (!joystick) {
3422 // SDL_SetError() will have been called already by SDL_GetGamepadJoystick()
3423 return false;
3424 }
3425 return SDL_SetJoystickPlayerIndex(joystick, player_index);
3426}
3427
3428Uint16 SDL_GetGamepadVendor(SDL_Gamepad *gamepad)
3429{
3430 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3431
3432 if (!joystick) {
3433 return 0;
3434 }
3435 return SDL_GetJoystickVendor(joystick);
3436}
3437
3438Uint16 SDL_GetGamepadProduct(SDL_Gamepad *gamepad)
3439{
3440 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3441
3442 if (!joystick) {
3443 return 0;
3444 }
3445 return SDL_GetJoystickProduct(joystick);
3446}
3447
3448Uint16 SDL_GetGamepadProductVersion(SDL_Gamepad *gamepad)
3449{
3450 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3451
3452 if (!joystick) {
3453 return 0;
3454 }
3455 return SDL_GetJoystickProductVersion(joystick);
3456}
3457
3458Uint16 SDL_GetGamepadFirmwareVersion(SDL_Gamepad *gamepad)
3459{
3460 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3461
3462 if (!joystick) {
3463 return 0;
3464 }
3465 return SDL_GetJoystickFirmwareVersion(joystick);
3466}
3467
3468const char * SDL_GetGamepadSerial(SDL_Gamepad *gamepad)
3469{
3470 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3471
3472 if (!joystick) {
3473 return NULL;
3474 }
3475 return SDL_GetJoystickSerial(joystick);
3476
3477}
3478
3479Uint64 SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad)
3480{
3481 Uint64 handle = 0;
3482
3483 SDL_LockJoysticks();
3484 {
3485 CHECK_GAMEPAD_MAGIC(gamepad, 0);
3486
3487 handle = gamepad->joystick->steam_handle;
3488 }
3489 SDL_UnlockJoysticks();
3490
3491 return handle;
3492}
3493
3494SDL_JoystickConnectionState SDL_GetGamepadConnectionState(SDL_Gamepad *gamepad)
3495{
3496 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3497
3498 if (!joystick) {
3499 return SDL_JOYSTICK_CONNECTION_INVALID;
3500 }
3501 return SDL_GetJoystickConnectionState(joystick);
3502}
3503
3504SDL_PowerState SDL_GetGamepadPowerInfo(SDL_Gamepad *gamepad, int *percent)
3505{
3506 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3507
3508 if (percent) {
3509 *percent = -1;
3510 }
3511 if (!joystick) {
3512 return SDL_POWERSTATE_ERROR;
3513 }
3514 return SDL_GetJoystickPowerInfo(joystick, percent);
3515}
3516
3517/*
3518 * Return if the gamepad in question is currently attached to the system,
3519 * \return 0 if not plugged in, 1 if still present.
3520 */
3521bool SDL_GamepadConnected(SDL_Gamepad *gamepad)
3522{
3523 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3524
3525 if (!joystick) {
3526 return false;
3527 }
3528 return SDL_JoystickConnected(joystick);
3529}
3530
3531/*
3532 * Get the joystick for this gamepad
3533 */
3534SDL_Joystick *SDL_GetGamepadJoystick(SDL_Gamepad *gamepad)
3535{
3536 SDL_Joystick *joystick;
3537
3538 SDL_LockJoysticks();
3539 {
3540 CHECK_GAMEPAD_MAGIC(gamepad, NULL);
3541
3542 joystick = gamepad->joystick;
3543 }
3544 SDL_UnlockJoysticks();
3545
3546 return joystick;
3547}
3548
3549/*
3550 * Return the SDL_Gamepad associated with an instance id.
3551 */
3552SDL_Gamepad *SDL_GetGamepadFromID(SDL_JoystickID joyid)
3553{
3554 SDL_Gamepad *gamepad;
3555
3556 SDL_LockJoysticks();
3557 gamepad = SDL_gamepads;
3558 while (gamepad) {
3559 if (gamepad->joystick->instance_id == joyid) {
3560 SDL_UnlockJoysticks();
3561 return gamepad;
3562 }
3563 gamepad = gamepad->next;
3564 }
3565 SDL_UnlockJoysticks();
3566 return NULL;
3567}
3568
3569/**
3570 * Return the SDL_Gamepad associated with a player index.
3571 */
3572SDL_Gamepad *SDL_GetGamepadFromPlayerIndex(int player_index)
3573{
3574 SDL_Gamepad *result = NULL;
3575
3576 SDL_LockJoysticks();
3577 {
3578 SDL_Joystick *joystick = SDL_GetJoystickFromPlayerIndex(player_index);
3579 if (joystick) {
3580 result = SDL_GetGamepadFromID(joystick->instance_id);
3581 }
3582 }
3583 SDL_UnlockJoysticks();
3584
3585 return result;
3586}
3587
3588/*
3589 * Get the SDL joystick layer bindings for this gamepad
3590 */
3591SDL_GamepadBinding **SDL_GetGamepadBindings(SDL_Gamepad *gamepad, int *count)
3592{
3593 SDL_GamepadBinding **bindings = NULL;
3594
3595 if (count) {
3596 *count = 0;
3597 }
3598
3599 SDL_LockJoysticks();
3600 {
3601 CHECK_GAMEPAD_MAGIC(gamepad, NULL);
3602
3603 size_t pointers_size = ((gamepad->num_bindings + 1) * sizeof(SDL_GamepadBinding *));
3604 size_t elements_size = (gamepad->num_bindings * sizeof(SDL_GamepadBinding));
3605 bindings = (SDL_GamepadBinding **)SDL_malloc(pointers_size + elements_size);
3606 if (bindings) {
3607 SDL_GamepadBinding *binding = (SDL_GamepadBinding *)((Uint8 *)bindings + pointers_size);
3608 int i;
3609 for (i = 0; i < gamepad->num_bindings; ++i, ++binding) {
3610 bindings[i] = binding;
3611 SDL_copyp(binding, &gamepad->bindings[i]);
3612 }
3613 bindings[i] = NULL;
3614
3615 if (count) {
3616 *count = gamepad->num_bindings;
3617 }
3618 }
3619 }
3620 SDL_UnlockJoysticks();
3621
3622 return bindings;
3623}
3624
3625bool SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
3626{
3627 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3628
3629 if (!joystick) {
3630 return false;
3631 }
3632 return SDL_RumbleJoystick(joystick, low_frequency_rumble, high_frequency_rumble, duration_ms);
3633}
3634
3635bool SDL_RumbleGamepadTriggers(SDL_Gamepad *gamepad, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms)
3636{
3637 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3638
3639 if (!joystick) {
3640 return false;
3641 }
3642 return SDL_RumbleJoystickTriggers(joystick, left_rumble, right_rumble, duration_ms);
3643}
3644
3645bool SDL_SetGamepadLED(SDL_Gamepad *gamepad, Uint8 red, Uint8 green, Uint8 blue)
3646{
3647 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3648
3649 if (!joystick) {
3650 return false;
3651 }
3652 return SDL_SetJoystickLED(joystick, red, green, blue);
3653}
3654
3655bool SDL_SendGamepadEffect(SDL_Gamepad *gamepad, const void *data, int size)
3656{
3657 SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
3658
3659 if (!joystick) {
3660 return false;
3661 }
3662 return SDL_SendJoystickEffect(joystick, data, size);
3663}
3664
3665void SDL_CloseGamepad(SDL_Gamepad *gamepad)
3666{
3667 SDL_Gamepad *gamepadlist, *gamepadlistprev;
3668
3669 SDL_LockJoysticks();
3670
3671 if (!SDL_ObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD)) {
3672 SDL_UnlockJoysticks();
3673 return;
3674 }
3675
3676 // First decrement ref count
3677 if (--gamepad->ref_count > 0) {
3678 SDL_UnlockJoysticks();
3679 return;
3680 }
3681
3682 SDL_CloseJoystick(gamepad->joystick);
3683
3684 gamepadlist = SDL_gamepads;
3685 gamepadlistprev = NULL;
3686 while (gamepadlist) {
3687 if (gamepad == gamepadlist) {
3688 if (gamepadlistprev) {
3689 // unlink this entry
3690 gamepadlistprev->next = gamepadlist->next;
3691 } else {
3692 SDL_gamepads = gamepad->next;
3693 }
3694 break;
3695 }
3696 gamepadlistprev = gamepadlist;
3697 gamepadlist = gamepadlist->next;
3698 }
3699
3700 SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false);
3701 SDL_free(gamepad->bindings);
3702 SDL_free(gamepad->last_match_axis);
3703 SDL_free(gamepad->last_hat_mask);
3704 SDL_free(gamepad);
3705
3706 SDL_UnlockJoysticks();
3707}
3708
3709/*
3710 * Quit the gamepad subsystem
3711 */
3712void SDL_QuitGamepads(void)
3713{
3714 SDL_Gamepad *gamepad;
3715
3716 SDL_LockJoysticks();
3717
3718 for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
3719 SDL_PrivateGamepadRemoved(gamepad->joystick->instance_id);
3720 }
3721
3722 SDL_gamepads_initialized = false;
3723
3724 SDL_RemoveEventWatch(SDL_GamepadEventWatcher, NULL);
3725
3726 while (SDL_gamepads) {
3727 SDL_gamepads->ref_count = 1;
3728 SDL_CloseGamepad(SDL_gamepads);
3729 }
3730
3731 SDL_UnlockJoysticks();
3732}
3733
3734void SDL_QuitGamepadMappings(void)
3735{
3736 GamepadMapping_t *pGamepadMap;
3737
3738 SDL_AssertJoysticksLocked();
3739
3740 while (s_pSupportedGamepads) {
3741 pGamepadMap = s_pSupportedGamepads;
3742 s_pSupportedGamepads = s_pSupportedGamepads->next;
3743 SDL_free(pGamepadMap->name);
3744 SDL_free(pGamepadMap->mapping);
3745 SDL_free(pGamepadMap);
3746 }
3747
3748 SDL_FreeVIDPIDList(&SDL_allowed_gamepads);
3749 SDL_FreeVIDPIDList(&SDL_ignored_gamepads);
3750
3751 if (s_gamepadInstanceIDs) {
3752 SDL_DestroyHashTable(s_gamepadInstanceIDs);
3753 s_gamepadInstanceIDs = NULL;
3754 }
3755}
3756
3757/*
3758 * Event filter to transform joystick events into appropriate gamepad ones
3759 */
3760static void SDL_SendGamepadAxis(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadAxis axis, Sint16 value)
3761{
3762 SDL_AssertJoysticksLocked();
3763
3764 // translate the event, if desired
3765 if (SDL_EventEnabled(SDL_EVENT_GAMEPAD_AXIS_MOTION)) {
3766 SDL_Event event;
3767 event.type = SDL_EVENT_GAMEPAD_AXIS_MOTION;
3768 event.common.timestamp = timestamp;
3769 event.gaxis.which = gamepad->joystick->instance_id;
3770 event.gaxis.axis = axis;
3771 event.gaxis.value = value;
3772 SDL_PushEvent(&event);
3773 }
3774}
3775
3776static void SDL_SendGamepadButton(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadButton button, bool down)
3777{
3778 SDL_Event event;
3779
3780 SDL_AssertJoysticksLocked();
3781
3782 if (button == SDL_GAMEPAD_BUTTON_INVALID) {
3783 return;
3784 }
3785
3786 if (down) {
3787 event.type = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
3788 } else {
3789 event.type = SDL_EVENT_GAMEPAD_BUTTON_UP;
3790 }
3791
3792 if (button == SDL_GAMEPAD_BUTTON_GUIDE) {
3793 Uint64 now = SDL_GetTicks();
3794 if (down) {
3795 gamepad->guide_button_down = now;
3796
3797 if (gamepad->joystick->delayed_guide_button) {
3798 // Skip duplicate press
3799 return;
3800 }
3801 } else {
3802 if (now < (gamepad->guide_button_down + SDL_MINIMUM_GUIDE_BUTTON_DELAY_MS)) {
3803 gamepad->joystick->delayed_guide_button = true;
3804 return;
3805 }
3806 gamepad->joystick->delayed_guide_button = false;
3807 }
3808 }
3809
3810 // translate the event, if desired
3811 if (SDL_EventEnabled(event.type)) {
3812 event.common.timestamp = timestamp;
3813 event.gbutton.which = gamepad->joystick->instance_id;
3814 event.gbutton.button = button;
3815 event.gbutton.down = down;
3816 SDL_PushEvent(&event);
3817 }
3818}
3819
3820static const Uint32 SDL_gamepad_event_list[] = {
3821 SDL_EVENT_GAMEPAD_AXIS_MOTION,
3822 SDL_EVENT_GAMEPAD_BUTTON_DOWN,
3823 SDL_EVENT_GAMEPAD_BUTTON_UP,
3824 SDL_EVENT_GAMEPAD_ADDED,
3825 SDL_EVENT_GAMEPAD_REMOVED,
3826 SDL_EVENT_GAMEPAD_REMAPPED,
3827 SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN,
3828 SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION,
3829 SDL_EVENT_GAMEPAD_TOUCHPAD_UP,
3830 SDL_EVENT_GAMEPAD_SENSOR_UPDATE,
3831};
3832
3833void SDL_SetGamepadEventsEnabled(bool enabled)
3834{
3835 unsigned int i;
3836
3837 for (i = 0; i < SDL_arraysize(SDL_gamepad_event_list); ++i) {
3838 SDL_SetEventEnabled(SDL_gamepad_event_list[i], enabled);
3839 }
3840}
3841
3842bool SDL_GamepadEventsEnabled(void)
3843{
3844 bool enabled = false;
3845 unsigned int i;
3846
3847 for (i = 0; i < SDL_arraysize(SDL_gamepad_event_list); ++i) {
3848 enabled = SDL_EventEnabled(SDL_gamepad_event_list[i]);
3849 if (enabled) {
3850 break;
3851 }
3852 }
3853 return enabled;
3854}
3855
3856void SDL_GamepadHandleDelayedGuideButton(SDL_Joystick *joystick)
3857{
3858 SDL_Gamepad *gamepad;
3859
3860 SDL_AssertJoysticksLocked();
3861
3862 for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
3863 if (gamepad->joystick == joystick) {
3864 SDL_SendGamepadButton(0, gamepad, SDL_GAMEPAD_BUTTON_GUIDE, false);
3865
3866 // Make sure we send an update complete event for this change
3867 if (!gamepad->joystick->update_complete) {
3868 gamepad->joystick->update_complete = SDL_GetTicksNS();
3869 }
3870 break;
3871 }
3872 }
3873}
3874
3875const char *SDL_GetGamepadAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)
3876{
3877 const char *result = NULL;
3878#ifdef SDL_JOYSTICK_MFI
3879 const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button);
3880
3881 SDL_LockJoysticks();
3882 {
3883 CHECK_GAMEPAD_MAGIC(gamepad, NULL);
3884
3885 result = IOS_GetAppleSFSymbolsNameForButton(gamepad, button);
3886 }
3887 SDL_UnlockJoysticks();
3888#endif
3889 return result;
3890}
3891
3892const char *SDL_GetGamepadAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)
3893{
3894 const char *result = NULL;
3895#ifdef SDL_JOYSTICK_MFI
3896 const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis);
3897
3898 SDL_LockJoysticks();
3899 {
3900 CHECK_GAMEPAD_MAGIC(gamepad, NULL);
3901
3902 result = IOS_GetAppleSFSymbolsNameForAxis(gamepad, axis);
3903 }
3904 SDL_UnlockJoysticks();
3905#endif
3906 return result;
3907}
diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_c.h b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_c.h
new file mode 100644
index 0000000..f1b1d10
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_c.h
@@ -0,0 +1,50 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef SDL_gamepad_c_h_
23#define SDL_gamepad_c_h_
24
25#include "SDL_internal.h"
26
27// Useful functions and variables from SDL_gamepad.c
28
29// Initialization and shutdown functions
30extern bool SDL_InitGamepadMappings(void);
31extern void SDL_QuitGamepadMappings(void);
32extern bool SDL_InitGamepads(void);
33extern void SDL_QuitGamepads(void);
34
35extern void SDL_PrivateGamepadAdded(SDL_JoystickID instance_id);
36extern void SDL_PrivateGamepadRemoved(SDL_JoystickID instance_id);
37
38// Function to return whether a joystick name and GUID is a gamepad
39extern bool SDL_IsGamepadNameAndGUID(const char *name, SDL_GUID guid);
40
41// Function to return whether a gamepad should be ignored
42extern bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name);
43
44// Handle delayed guide button on a gamepad
45extern void SDL_GamepadHandleDelayedGuideButton(SDL_Joystick *joystick);
46
47// Handle system sensor data
48extern void SDL_GamepadSensorWatcher(Uint64 timestamp, SDL_SensorID sensor, Uint64 sensor_timestamp, float *data, int num_values);
49
50#endif // SDL_gamepad_c_h_
diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_db.h b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_db.h
new file mode 100644
index 0000000..d72e422
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/SDL_gamepad_db.h
@@ -0,0 +1,904 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23/* Default mappings we support
24
25 The easiest way to generate a new mapping is to start Steam in Big Picture
26 mode, configure your joystick and then look in config/config.vdf in your
27 Steam installation directory for the "SDL_GamepadBind" entry.
28
29 Alternatively, you can use the app located in test/controllermap
30 */
31static const char *s_GamepadMappings[] = {
32#ifdef SDL_JOYSTICK_PRIVATE
33 SDL_PRIVATE_GAMEPAD_DEFINITIONS
34#endif
35#ifdef SDL_JOYSTICK_XINPUT
36 "xinput,*,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
37#endif
38#ifdef SDL_JOYSTICK_WGI
39 "03000000491900001904000000007700,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,",
40 "03000000d11800000094000000007700,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
41 "030000007e0500000920000000007701,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
42 "030000004c050000c405000000007701,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
43 "030000004c050000e60c000000007700,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
44 "0300000032150000000a000000007703,Razer Atrox Arcade Stick,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b11,dpup:b10,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,x:b2,y:b3,",
45 "03000000de280000ff11000000007701,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:b12,dpleft:b13,dpright:b11,dpup:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a4,leftx:a1,lefty:a0~,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a2~,start:b7,x:b2,y:b3,",
46#endif
47#ifdef SDL_JOYSTICK_DINPUT
48 "03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,",
49 "03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
50 "03000000c82d00001038000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
51 "03000000c82d00000650000000000000,8BitDo M30 Gamepad,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,",
52 "03000000c82d00005106000000000000,8BitDo M30 Gamepad,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b10,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,",
53 "03000000c82d00002090000000000000,8BitDo Micro gamepad,a:b1,b:b0,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,guide:b12,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
54 "03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
55 "03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
56 "030000003512000012ab000000000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
57 "03000000022000000090000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
58 "03000000203800000900000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
59 "03000000c82d00002038000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
60 "03000000c82d00000660000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
61 "03000000c82d00000060000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
62 "03000000c82d00000061000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
63 "03000000102800000900000000000000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
64 "03000000c82d00001290000000000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
65 "03000000c82d00006228000000000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
66 "03000000c82d00000260000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
67 "03000000c82d00000261000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
68 "03000000c82d00000160000000000000,8BitDo SN30 Pro,crc:769e,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", /* Firmware v2.00, USB */
69 "03000000c82d00000160000000000000,8BitDo SN30 Pro,crc:fa59,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", /* Firmware v1.26, USB */
70 "03000000c82d00000161000000000000,8BitDo SN30 Pro,crc:190b,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", /* Bluetooth, firmware v1.26 uses b2 for guide, firmware v2.00 uses b12 for guide */
71 "030000003512000020ab000000000000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
72 "03000000c82d00001b30000000000000,8BitDo Ultimate 2C Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a2,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,", /* Bluetooth */
73 "03000000c82d00001130000000000000,8BitDo Ultimate Wired Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
74 "03000000c82d00001330000000000000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
75 "03000000c82d00001890000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
76 "03000000c82d00003032000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
77 "03000000a00500003232000000000000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
78 "03000000050b00000579000000000000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
79 "03000000050b00000679000000000000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
80 "030000008f0e00001200000000000000,Acme GA-02,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
81 "03000000fa190000f0ff000000000000,Acteck AGJ-3200,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
82 "03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
83 "030000006f0e00000263000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
84 "030000006f0e00001101000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
85 "030000006f0e00001401000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
86 "030000006f0e00001402000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
87 "030000006f0e00001901000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
88 "030000006f0e00001a01000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
89 "03000000d62000001d57000000000000,Airflo PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
90 "03000000491900001904000000000000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,",
91 "03000000d62000002a79000000000000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
92 "03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,",
93 "03000000d6200000e557000000000000,Batarang,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
94 "03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,",
95 "030000006f0e00003201000000000000,Battlefield 4 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
96 "03000000bc2000006012000000000000,Betop 2126F,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
97 "03000000bc2000000055000000000000,Betop BFM Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
98 "03000000bc2000006312000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
99 "03000000bc2000006412000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
100 "03000000c01100000555000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
101 "03000000c01100000655000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
102 "03000000790000000700000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,",
103 "03000000808300000300000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,",
104 "030000006b1400000055000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
105 "030000006b1400000103000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,",
106 "0300000066f700000500000000000000,BrutalLegendTest,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,",
107 "03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
108 "03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,",
109 "03000000a306000022f6000000000000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,",
110 "03000000451300000830000000000000,Defender Game Racer X7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
111 "03000000790000000600000000000000,Defender Joystick Cobra R4,crc:c77a,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2~,righty:a3~,start:b9,x:b3,y:b0,",
112 "03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
113 "03000000bd12000002e0000000000000,Dual USB Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,",
114 "030000006f0e00003001000000000000,EA SPORTS PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
115 "03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
116 "030000008f0e00000f31000000000000,EXEQ,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,",
117 "03000000b80500000410000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,",
118 "03000000b80500000610000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,",
119 "03000000852100000201000000000000,FF-GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
120 "030000000d0f00002700000000000000,FIGHTING STICK V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
121 "030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
122 "03000000300f00000b01000000000000,GGE909 Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
123 "03000000790000002201000000000000,Game Controller for PC,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
124 "0300000066f700000100000000000000,Game VIB Joystick,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,",
125 "03000000491900000204000000000000,GameSir T4 Pro,crc:1aa4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
126 "03000000ac0500003d03000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
127 "03000000ac0500004d04000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
128 "03000000ac0500001a06000000000000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
129 "03000000ffff00000000000000000000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
130 "03000000c01100000140000000000000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
131 "03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
132 "03000000280400000140000000000000,Gamepad Pro USB,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
133 "030000005c1a00003330000000000000,Genius MaxFire Grandias 12V,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
134 "030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
135 "03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
136 "030000008305000009a0000000000000,Genius,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
137 "03000000f025000021c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
138 "03000000f0250000c383000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
139 "03000000f0250000c483000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
140 "03000000f0250000c283000000000000,Gioteck,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
141 "03000000d11800000094000000000000,Google Stadia Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b11,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
142 "03000000632500002605000000000000,HJD-X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
143 "030000000d0f00008400000000000000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
144 "030000000d0f00008500000000000000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
145 "030000000d0f00008800000000000000,HORI Fighting Stick mini 4 (PS3),a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,",
146 "030000000d0f00008700000000000000,HORI Fighting Stick mini 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
147 "030000000d0f00006e00000000000000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
148 "030000000d0f00006600000000000000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
149 "030000000d0f0000ee00000000000000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
150 "03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,",
151 "03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
152 "030000000d0f00004900000000000000,Hatsune Miku Sho Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
153 "03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,",
154 "030000000d0f00005f00000000000000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
155 "030000000d0f00005e00000000000000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
156 "030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,",
157 "030000000d0f00000900000000000000,Hori Pad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
158 "030000000d0f00005400000000000000,Hori Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
159 "030000000d0f00004d00000000000000,Hori Pad A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
160 "030000000d0f00009200000000000000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
161 "030000000d0f0000c100000000000000,Horipad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
162 "030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,",
163 "030000006f0e00002401000000000000,INJUSTICE FightStick PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
164 "03000000ac0500002c02000000000000,IPEGA,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
165 "03000000b50700001403000000000000,Impact Black,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
166 "03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
167 "030000006e0500000520000000000000,JC-P301U,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,",
168 "030000006e0500000320000000000000,JC-U3613M (DInput),a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,",
169 "030000006e0500000720000000000000,JC-W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,",
170 "03000000790000000200000000000000,King PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,",
171 "030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
172 "030000006d040000d2ca000000000000,Logitech Cordless Precision,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
173 "030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,",
174 "030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
175 "030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
176 "030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", /* Guide button doesn't seem to be sent in DInput mode. */
177 "030000006d0400001ac2000000000000,Logitech Precision Gamepad,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
178 "03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
179 "03000000380700006382000000000000,MLG Gamepad PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
180 "03000000c62400002a89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
181 "03000000c62400002b89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
182 "03000000c62400001a89000000000000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
183 "03000000c62400001b89000000000000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
184 "03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,",
185 "03000000380700006652000000000000,Mad Catz C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,",
186 "03000000380700005032000000000000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
187 "03000000380700005082000000000000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
188 "03000000380700008433000000000000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
189 "03000000380700008483000000000000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
190 "03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
191 "03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
192 "03000000380700006252000000000000,Mad Catz Micro C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,",
193 "03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
194 "03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
195 "03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
196 "03000000380700008532000000000000,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
197 "03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
198 "030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
199 "03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,",
200 "03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
201 "030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,",
202 "0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,",
203 "03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
204 "03000000efbe0000edfe000000000000,Monect Virtual Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b0,",
205 "030000006b140000010c000000000000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
206 "030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,",
207 "03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,",
208 "030000005509000000b4000000000000,NVIDIA Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
209 "030000004b120000014d000000000000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
210 "03000000790000004318000000000000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
211 "03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,",
212 "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
213 "030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,",
214 "03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
215 "03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,",
216 "03000000782300000a10000000000000,Onlive Wireless Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,",
217 "030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,",
218 "0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,start:b11,x:b3,y:b4,",
219 "03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,",
220 "030000006f0e00000901000000000000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
221 "03000000632500002306000000000000,PS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
222 "03000000e30500009605000000000000,PS to USB convert cable,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,",
223 "03000000100800000100000000000000,PS1 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
224 "030000008f0e00007530000000000000,PS1 Controller,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
225 "03000000100800000300000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,",
226 "03000000250900008888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,",
227 "03000000666600006706000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,",
228 "030000006b1400000303000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
229 "030000009d0d00001330000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
230 "03000000250900000500000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,",
231 "030000004c0500006802000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b10,lefttrigger:a3~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:a4~,rightx:a2,righty:a5,start:b8,x:b3,y:b0,",
232 "03000000632500007505000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
233 "03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,",
234 "030000008f0e00001431000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
235 "030000003807000056a8000000000000,PS3 RF pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
236 "03000000100000008200000000000000,PS360+ v1.66,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
237 "030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
238 "030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,",
239 "030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
240 "030000004c050000e60c000000000000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
241 "030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
242 "03000000d62000002640000000000000,PowerA OPS v1 Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
243 "03000000d62000003340000000000000,PowerA OPS v3 Pro Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
244 "03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
245 "03000000d62000009557000000000000,Pro Elite PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
246 "03000000d62000009f31000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
247 "03000000d6200000c757000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
248 "03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,",
249 "03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,",
250 "03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,",
251 "03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,",
252 "03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,",
253 "03000000222c00000025000000000000,Qanba Dragon Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
254 "03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
255 "03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick (PS4),a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
256 "030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,",
257 "030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,",
258 "030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
259 "03000000050b00005819000000000000,ROG Chakram Core,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,",
260 "03000000050b0000181a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,",
261 "03000000050b00001a1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,",
262 "03000000050b00001c1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,",
263 "03000000050b0000e318000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,",
264 "03000000050b0000e518000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,",
265 "030000000d0f0000ad00000000000000,RX Gamepad,a:b0,b:b4,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,rightshoulder:b6,start:b9,x:b2,y:b1,",
266 "03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
267 "03000000321500000204000000000000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
268 "03000000321500000104000000000000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
269 "03000000321500000507000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
270 "03000000321500000707000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
271 "03000000321500000011000000000000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
272 "03000000321500000009000000000000,Razer Serval,+lefty:+a2,-lefty:-a1,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,leftx:a0,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
273 "030000000d0f00006a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
274 "030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
275 "030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
276 "030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
277 "030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
278 "030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
279 "0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,",
280 "0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,",
281 "03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
282 "030000006b140000130d000000000000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
283 "030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
284 "030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
285 "030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
286 "030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
287 "03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,",
288 "03000000341a00000908000000000000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
289 "03000000790000000600000000000000,SPEEDLINK STRIKE Gamepad,crc:5811,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,",
290 "03000000790000001c18000000000000,STK-7024X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
291 "03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,",
292 "03000000457500002211000000000000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
293 "03000000a306000023f6000000000000,Saitek Cyborg V.1 Game pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,",
294 "03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,",
295 "03000000300f00001201000000000000,Saitek Dual Analog Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
296 "03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,x:b0,y:b1,",
297 "03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,",
298 "03000000300f00001001000000000000,Saitek P480 Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
299 "03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,",
300 "03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,",
301 "03000000a30600002106000000000000,Saitek PS1000,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,",
302 "03000000a306000020f6000000000000,Saitek PS2700,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,",
303 "03000000300f00001101000000000000,Saitek Rumble Pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
304 "0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,",
305 "030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,",
306 "030000008f0e00000800000000000000,SpeedLink Strike FX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
307 "03000000c01100000591000000000000,Speedlink Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
308 "030000004c0500006802000000000000,SplitFish Game Controller,crc:5628,a:b0,b:b16,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,leftshoulder:b17,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b10,",
309 "03000000de280000ff11000000000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
310 "03000000110100003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
311 "03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,",
312 "03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
313 "03000000d620000011a7000000000000,Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
314 "030000004f04000007d0000000000000,T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
315 "03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
316 "03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
317 "030000004f0400000ed0000000000000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
318 "030000004f04000015b3000000000000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,",
319 "030000004f04000023b3000000000000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
320 "030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,",
321 "030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,",
322 "03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,",
323 "03000000d62000006000000000000000,Tournament PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
324 "030000005f140000c501000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
325 "03000000b80500000210000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
326 "03000000d90400000200000000000000,TwinShock PS2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
327 "03000000300f00000701000000000000,USB 4-Axis 12-Button Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
328 "03000000341a00002308000000000000,USB Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
329 "030000006b1400000203000000000000,USB Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
330 "03000000790000000a00000000000000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,",
331 "03000000f0250000c183000000000000,USB Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
332 "03000000ff1100004133000000000000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,",
333 "03000000632500002305000000000000,USB Vibration Joystick (BM),a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
334 "03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
335 "030000006f0e00000302000000000000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
336 "030000006f0e00000702000000000000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
337 "03000000450c00002043000000000000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
338 "03000000341a00000608000000000000,Xeox,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
339 "03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,",
340 "03000000c0160000e105000000000000,Xin-Mo Dual Arcade,crc:2246,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */
341 "03000000790000004f18000000000000,ZD-T Android,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,",
342 "03000000120c0000101e000000000000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
343 "03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
344 "03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
345 "03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
346 "030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
347 "03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
348#endif
349#ifdef SDL_PLATFORM_MACOS
350 "03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
351 "03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
352 "03000000c82d00000650000001000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a5,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
353 "03000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
354 "05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
355 "03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
356 "03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
357 "030000003512000012ab000001000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
358 "03000000022000000090000001000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
359 "03000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
360 "03000000c82d00000660000000020000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
361 "03000000102800000900000000000000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
362 "03000000c82d00001290000001000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
363 "03000000c82d00000260000001000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
364 "03000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
365 "03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
366 "03000000c82d00001b30000001000000,8BitDo Ultimate 2C Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", /* Bluetooth */
367 "03000000c82d00001130000000020000,8BitDo Ultimate Wired Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
368 "03000000c82d00001330000000020000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
369 "03000000c82d00001890000001000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
370 "03000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
371 "03000000a00500003232000008010000,8BitDo Zero Gamepad,a:b1,b:b2,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
372 "03000000a00500003232000009010000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
373 "03000000050b00000579000000010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b42,paddle1:b9,paddle2:b11,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,",
374 "03000000050b00000679000000010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b23,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,",
375 "03000000491900001904000001010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,",
376 "03000000710100001904000000010000,Amazon Luna Controller,a:b0,b:b1,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
377 "03000000c62400001a89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,",
378 "03000000c62400001b89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
379 "03000000d62000002a79000000010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
380 "030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
381 "03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,",
382 "03000000a306000022f6000001030000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,",
383 "030000000d0f00008400000000010000,Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
384 "030000000d0f00008500000000010000,Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
385 "03000000ac0500001a06000002020000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
386 "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
387 "03000000c01100000140000000010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
388 "03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
389 "03000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
390 "030000000d0f00005f00000000000000,HORI Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
391 "030000000d0f00005e00000000000000,HORI Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
392 "030000000d0f00008800000000010000,HORI Fighting Stick mini 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,",
393 "030000000d0f00008700000000010000,HORI Fighting Stick mini 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,",
394 "030000000d0f00004d00000000000000,HORI Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
395 "030000000d0f0000aa00000072050000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
396 "030000000d0f00006e00000000010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
397 "030000000d0f00006600000000010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
398 "030000000d0f00006600000000000000,HORIPAD FPS PLUS 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
399 "030000000d0f00005f00000000010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
400 "030000000d0f00005e00000000010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
401 "030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,",
402 "030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
403 "030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
404 "030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
405 "030000006d04000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", /* Guide button doesn't seem to be sent in DInput mode. */
406 "030000006d04000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
407 "030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
408 "030000006d04000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", /* This includes F710 in DInput mode and the "Logitech Cordless RumblePad 2", at the very least. */
409 "03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
410 "03000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
411 "03000000c62400002b89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
412 "03000000380700005032000000010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
413 "03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
414 "03000000380700008433000000010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
415 "03000000380700008483000000010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
416 "03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
417 "0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,",
418 "03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,",
419 "030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,",
420 "03000000550900001472000025050000,NVIDIA Controller v01.04,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,",
421 "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
422 "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
423 "050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
424 "0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,start:b11,x:b3,y:b4,",
425 "030000006f0e00000901000002010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
426 "030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,",
427 "030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,",
428 "030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
429 "030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
430 "030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
431 "030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
432 "050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
433 "030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
434 "03000000222c00000225000000010000,Qanba Dragon Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
435 "030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
436 "03000000321500000204000000010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
437 "03000000321500000104000000010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
438 "03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
439 "03000000321500000507000001010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
440 "03000000321500000011000000010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
441 "03000000321500000009000000020000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
442 "0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
443 "03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,",
444 "03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
445 "030000006b140000130d000000010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
446 "030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
447 "030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,",
448 "03000000457500002211000000010000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
449 "03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,",
450 "03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,",
451 "030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
452 "030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
453 "030000005e0400008e02000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
454 "050000004e696d6275732b0000000000,SteelSeries Nimbus+,a:b0,b:b1,back:b15,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b16,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b14,x:b2,y:b3,",
455 "03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,",
456 "03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,",
457 "03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,",
458 "03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,",
459 "03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,",
460 "030000004f0400000ed0000000020000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
461 "030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,",
462 "030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,",
463 "03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,",
464 "03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,",
465 "03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,",
466 "030000006f0e00000302000025040000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
467 "030000006f0e00000702000003060000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
468 "050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,",
469 "050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,",
470 "030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
471 "03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
472 "030000005e040000050b000003090000,Xbox Elite Wireless Controller,a:b0,b:b1,back:b38,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
473 "030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
474 "030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
475 "030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
476 "030000005e040000200b000011050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
477 "030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
478 "030000005e040000e002000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
479 "030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,",
480 "030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
481 "03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,",
482 "03000000c0160000e105000000040000,Xin-Mo Dual Arcade,crc:82d5,a:b2,b:b4,back:b18,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b8,righttrigger:b10,start:b16,x:b0,y:b6,", /* Ultimate Atari Fight Stick */
483 "03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
484 "03000000120c0000101e000000010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
485 "03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
486 "03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,",
487#endif
488#if defined(SDL_JOYSTICK_LINUX) || defined(SDL_PLATFORM_OPENBSD)
489 "03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
490 "05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
491 "05000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
492 "05000000c82d00002090000000010000,8BitDo Micro gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b12,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
493 "03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
494 "05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
495 "030000003512000012ab000010010000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
496 "03000000022000000090000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
497 "03000000c82d00000190000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
498 "05000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
499 "05000000c82d00002038000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
500 "03000000c82d00000020000000000000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
501 "06000000c82d00000020000006010000,8BitDo Pro 2 Wired Controller for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
502 "03000000c82d00000660000011010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
503 "05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
504 "05000000c82d00000061000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
505 "05000000102800000900000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
506 "05000000c82d00003028000000010000,8BitDo SFC30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
507 "03000000c82d00000260000011010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
508 "05000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
509 "03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
510 "030000003512000020ab000010010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
511 "05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
512 "05000000c82d00001b30000001000000,8BitDo Ultimate 2C Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,", /* Bluetooth */
513 "03000000c82d00001130000011010000,8BitDo Ultimate Wired Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
514 "03000000c82d00001330000011010000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
515 "03000000c82d00001890000011010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
516 "05000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
517 "05000000a00500003232000001000000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
518 "05000000a00500003232000008010000,8BitDo Zero Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
519 "03000000c82d00000031000011010000,8Bitdo Receiver,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,",
520 "03000000c82d00001290000011010000,8Bitdo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
521 "05000000c82d00006228000000010000,8Bitdo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
522 "05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,",
523 "05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,",
524 "03000000050b00000579000011010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b36,paddle1:b52,paddle2:b53,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
525 "05000000050b00000679000000010000,ASUS ROG Kunai 3 Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b21,paddle1:b22,paddle2:b23,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
526 "030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
527 "030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
528 "030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
529 "03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
530 "05000000491900000204000021000000,Amazon Fire Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
531 "03000000491900001904000011010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,",
532 "05000000710100001904000000010000,Amazon Luna Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
533 "03000000790000003018000011010000,Arcade Fightstick F300,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
534 "03000000503200000110000000000000,Atari Classic Controller,a:b0,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,x:b1,",
535 "05000000503200000110000000000000,Atari Classic Controller,a:b0,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,x:b1,",
536 "03000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,",
537 "05000000503200000210000000000000,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,",
538 "05000000503200000210000000000000128804098,Atari Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,",
539 "030000005e0400008e02000047010000,Atari Xbox 360 Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
540 "03000000c62400001b89000011010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
541 "03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
542 "03000000120c0000f70e000011010000,Brook Universal Fighting Board,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:,lefty:,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:,righty:,start:b9,x:b0,y:b3,",
543 "03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,",
544 "03000000ffff0000ffff000000010000,Chinese-made Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
545 "03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
546 "03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,",
547 "03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,",
548 "050000004c050000f20d000000010000,DualSense Edge Wireless Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
549 "030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
550 "03000000790000001100000010010000,Elecom Gamepad,crc:e86c,a:b2,b:b3,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b0,y:b1,",
551 "0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
552 "0500000047532067616d657061640000,GS Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
553 "03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
554 "03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
555 "0500000049190000020400001b010000,GameSir T4 Pro,crc:8283,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b23,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
556 "03000000ac0500001a06000011010000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
557 "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
558 "03000000c01100000140000011010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
559 "030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
560 "030000008f0e00000800000010010000,Gasia Co. Ltd PS(R) Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
561 "030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
562 "03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
563 "03000000d11800000094000011010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
564 "05000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
565 "03000000280400000140000000010000,Gravis Gamepad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
566 "030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,",
567 "030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
568 "03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
569 "030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
570 "030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
571 "030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
572 "030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
573 "030000000d0f00005001000009040000,HORI Fighting Commander OCTA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
574 "030000000d0f00008400000011010000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
575 "030000000d0f00008500000010010000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
576 "030000000d0f00008800000011010000,HORI Fighting Stick mini 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,",
577 "030000000d0f00008700000011010000,HORI Fighting Stick mini 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,rightstick:b11,righttrigger:a4,start:b9,x:b0,y:b3,",
578 "030000000d0f0000d800000072056800,HORI Real Arcade Pro S,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
579 "030000000d0f0000aa00000011010000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
580 "030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
581 "030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
582 "030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
583 "06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
584 "03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,",
585 "030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
586 "030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
587 "030000000d0f00008600000002010000,Hori Fighting Commander,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
588 "03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
589 "030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,",
590 "03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
591 "03000000d80400008200000003000000,IMS PCU#0 Gamepad Interface,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,",
592 "03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,",
593 "05000000491900000204000000000000,Ipega PG-9087S,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
594 "030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,",
595 "03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
596 "03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
597 "030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
598 "030000006d040000d1ca000011010000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
599 "030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
600 "030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
601 "030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
602 "030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
603 "030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
604 "030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,", /* Guide button doesn't seem to be sent in DInput mode. */
605 "030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
606 "030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
607 "030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,",
608 "03000000c62400002b89000011010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
609 "05000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
610 "05000000c62400001a89000000010000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
611 "03000000250900006688000000010000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,",
612 "05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
613 "03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
614 "03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
615 "03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
616 "03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
617 "03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,",
618 "03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
619 "03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
620 "03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
621 "03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
622 "03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
623 "03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
624 "03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
625 "03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,",
626 "03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,",
627 "030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,",
628 "030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
629 "030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
630 "030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
631 "030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
632 "030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
633 "030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
634 "030000005e0400008902000020010000,Microsoft Xbox Controller S,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
635 "05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
636 "030000006b140000010c000010010000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
637 "030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,",
638 "03000000550900001072000011010000,NVIDIA Controller v01.03,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
639 "03000000550900001472000011010000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,",
640 "05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,",
641 "030000004b120000014d000000010000,NYKO AIRFLO EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
642 "03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
643 "03000000790000004318000010010000,Nintendo GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
644 "050000007e0500000620000001800000,Nintendo Switch Joy-Con (L),a:b16,b:b15,guide:b4,leftshoulder:b6,leftstick:b12,leftx:a1,lefty:a0~,rightshoulder:b8,start:b9,x:b14,y:b17,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
645 "060000007e0500000620000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
646 "060000007e0500000820000000000000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
647 "050000007e0500000720000001800000,Nintendo Switch Joy-Con (R),a:b1,b:b2,guide:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
648 "03000000d620000013a7000011010000,Nintendo Switch PowerA Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
649 "03000000d620000011a7000011010000,Nintendo Switch PowerA Core Plus Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
650 "030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
651 "050000004c69632050726f20436f6e00,Nintendo Switch Pro Controller,crc:15b7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
652 "050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
653 "050000007e0500000920000001800000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
654 "050000007e0500000603000000060000,Nintendo Wii Remote Classic Controller,crc:0d8a,a:b1,b:b0,back:b10,dpdown:b14,dpleft:b12,dpright:b13,dpup:b11,guide:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
655 "050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
656 "050000007e0500000603000000060000,Nintendo Wii Remote,crc:60be,a:b1,b:b0,back:b4,dpdown:b8,dpleft:b6,dpright:b7,dpup:b5,guide:b2,start:b3,x:b9,y:b10,",
657 "05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
658 "030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,",
659 "05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,",
660 "05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,",
661 "030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
662 "03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
663 "030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
664 "030000006f0e00000901000011010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
665 "03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,",
666 "03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
667 "030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,",
668 "030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
669 "030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,",
670 "030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
671 "030000006f0e00001402000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
672 "030000008f0e00000300000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
673 "050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,",
674 "050000004c0500006802000000800000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
675 "050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
676 "05000000504c415953544154494f4e00,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,",
677 "060000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,",
678 "030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
679 "030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
680 "030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
681 "030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
682 "030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
683 "030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
684 "030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
685 "050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
686 "050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
687 "050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
688 "050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
689 "050000004c050000cc09000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
690 "030000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
691 "030000004c050000e60c000011010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
692 "030000004c050000e60c000011810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
693 "050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
694 "050000004c050000e60c000000810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,",
695 "030000004c050000da0c000011010000,Playstation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,",
696 "03000000c62400003a54000001010000,PowerA XBox One Controller,a:b0,b:b1,back:b6,dpdown:h0.7,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
697 "03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
698 "03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,",
699 "03000000222c00000225000011010000,Qanba Dragon Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
700 "03000000222c00000025000011010000,Qanba Dragon Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
701 "03000000222c00001220000011010000,Qanba Drone 2 Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
702 "03000000222c00001020000011010000,Qanba Drone 2 Arcade Joystick (PS5),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
703 "03000000222c00000020000011010000,Qanba Drone Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,",
704 "03000000222c00000223000011010000,Qanba Obsidian Arcade Joystick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
705 "03000000222c00000023000011010000,Qanba Obsidian Arcade Joystick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
706 "030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
707 "03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
708 "03000000321500000104000011010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
709 "03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
710 "03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
711 "03000000321500000011000011010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
712 "030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
713 "03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
714 "03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
715 "03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
716 "050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
717 "0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
718 "0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,",
719 "03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
720 "030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
721 "030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
722 "030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
723 "030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
724 "030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
725 "03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
726 "03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
727 "03000000457500002211000010010000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
728 "03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,",
729 "03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,",
730 "03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,",
731 "03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,",
732 "03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,",
733 "03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,",
734 "03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,",
735 "03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
736 "03000000632500002305000010010000,ShanWan USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
737 "03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,",
738 "030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
739 "030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
740 "03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
741 "03000000de2800000112000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,",
742 "03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
743 "03000000de2800000211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,",
744 "03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
745 "03000000de2800004211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,",
746 "03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
747 "05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
748 "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
749 "05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
750 "03000000de2800000512000000016800,Steam Deck Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
751 "03000000de2800000512000011010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,",
752 "03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
753 "0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
754 "05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b18,leftshoulder:b6,leftstick:b13,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:+a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
755 "03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
756 "03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,",
757 "0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,",
758 "030000004f0400000ed0000011010000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
759 "030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,",
760 "030000004f04000015b3000001010000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,",
761 "030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,",
762 "030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
763 "030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,",
764 "030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
765 "030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
766 "03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,",
767 "03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,",
768 "03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
769 "03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,",
770 "03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,",
771 "05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,",
772 "030000006f0e00000302000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
773 "030000006f0e00000702000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
774 "030000000d0f0000ab01000011010000,Wireless HORIPAD For Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,misc3:b16,misc4:b17,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
775 "050000000d0f00009601000091000000,Wireless HORIPAD For Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,misc3:b16,misc4:b17,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
776 "030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
777 "030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
778 "030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
779 "030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
780 "030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
781 "030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
782 "03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
783 "0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,",
784 "030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
785 "0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,",
786 "050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
787 "050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
788 "05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,",
789 "03000000c0160000e105000010010000,Xin-Mo Dual Arcade,crc:82d5,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,", /* Ultimate Atari Fight Stick */
790 "03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
791 "03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
792 "03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,",
793 "03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
794 "050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
795 "03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
796 "030000009b2800008000000020020000,raphnet technologies 1-player WUSBMote v2.2,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,",
797 "030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,",
798#endif
799#ifdef SDL_PLATFORM_OPENBSD
800 "030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
801 "030000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
802 "030000005e0400008e02000010010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4~,start:b7,x:b2,y:b3,",
803#endif
804#ifdef SDL_PLATFORM_ANDROID
805 "05000000c82d000006500000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a4,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
806 "05000000c82d000051060000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
807 "05000000c82d000020900000ffff3f00,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b5,leftshoulder:b9,lefttrigger:a6,rightshoulder:b10,righttrigger:a7,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
808 "05000000c82d000015900000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
809 "05000000c82d000065280000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
810 "050000000220000000900000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
811 "050000002038000009000000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
812 "05000000c82d000000600000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
813 "05000000c82d000000610000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
814 "05000000c82d000012900000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
815 "05000000c82d000062280000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
816 "05000000c82d000002600000ffff0f00,8BitDo SN30 Pro+,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
817 "05000000c82d000001600000ffff3f00,8BitDo SN30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
818 "050000002028000009000000ffff3f00,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
819 "050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b21,b:b20,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b24,y:b23,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
820 "05000000c82d000018900000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
821 "05000000c82d000030320000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
822 "05000000d6020000e5890000dfff3f80,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a3,rightx:a4,righty:a5,start:b6,x:b2,y:b3,",
823 "0500000031366332860c44aadfff0f00,GS Gamepad,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
824 "05000000bc20000000550000ffff3f00,GameSir G3w,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
825 "050000005509000003720000cf7f3f00,NVIDIA Controller v01.01,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
826 "050000005509000010720000ffff3f00,NVIDIA Controller v01.03,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
827 "050000005509000014720000df7f3f80,NVIDIA Controller v01.04,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a3,rightx:a4,righty:a5,start:b6,x:b2,y:b3,",
828 "050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,sdk>=:29,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
829 "050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b2,y:b17,sdk<=:28,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", /* Extremely slow in Bluetooth mode on Android */
830 "050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
831 "050000004c050000c405000000783f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
832 "050000004c050000c4050000fffe3f80,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a3,rightx:a4,righty:a5,start:b16,x:b0,y:b2,",
833 "050000004c050000c4050000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
834 "050000004c050000cc090000fffe3f80,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a3,rightx:a4,righty:a5,start:b16,x:b0,y:b2,",
835 "050000004c050000cc090000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
836 "050000004c050000e60c0000fffe3f80,PS5 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a3,rightx:a4,righty:a5,start:b16,x:b2,y:b17,",
837 "050000004c050000e60c0000ffff3f00,PS5 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
838 "05000000f8270000bf0b0000ffff3f00,Razer Kishi,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
839 "050000003215000005070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
840 "050000003215000007070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
841 "050000003215000000090000bf7f3f00,Razer Serval,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,",
842 "050000004f0400000ed00000fffe3f00,ThrustMaster eSwap PRO Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
843 "050000005e0400008e02000000783f00,Xbox 360 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
844 "050000005e040000000b000000783f80,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
845 "050000005e040000050b0000ffff3f00,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
846 "050000005e040000e002000000783f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
847 "050000005e040000ea02000000783f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
848 "050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
849 "050000005e040000e00200000ffe3f80,Xbox One Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b17,y:b2,",
850 "050000005e040000fd020000ffff3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
851 "050000005e040000120b000000783f80,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
852 "050000005e040000130b0000ffff3f00,Xbox Series X Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
853 "050000005e04000091020000ff073f80,Xbox Wireless Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", /* The DPAD doesn't seem to work on this controller on Android TV? */
854 "050000001727000044310000ffff3f80,XiaoMi Game Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a4,righty:a5,start:b6,x:b2,y:b3,",
855 "0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
856#endif
857#ifdef SDL_JOYSTICK_MFI
858 "05000000ac050000010000004f066d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,",
859 "05000000ac05000001000000cf076d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,",
860 "05000000ac05000001000000df076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
861 "05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,",
862 "05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,",
863 "05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
864 "05000000ac050000040000003b8a6d04,8BitDo SN30 Pro+,crc:3e00,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
865 "050000008a35000003010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,",
866 "050000008a35000004010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,",
867 "050000007e050000062000000f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
868 "050000007e050000062000004f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
869 "050000007e05000008200000df070000,Nintendo Switch Joy-Con (L/R),a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
870 "050000007e050000072000000f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
871 "050000007e050000072000004f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
872 "050000007e05000009200000df870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
873 "050000007e05000009200000ff870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
874 "050000004c050000cc090000df070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
875 "050000004c050000cc090000df870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
876 "050000004c050000cc090000ff070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,",
877 "050000004c050000cc090000ff870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,",
878 "050000004c050000e60c0000df870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,touchpad:b10,x:b2,y:b3,",
879 "050000004c050000e60c0000ff870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,",
880 "05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,",
881 "050000005e040000050b0000df070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b10,paddle2:b12,paddle3:b11,paddle4:b13,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
882 "050000005e040000050b0000ff070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,",
883 "050000005e040000130b0000df870001,Xbox Series X Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
884 "050000005e040000130b0000ff870001,Xbox Series X Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,",
885 "050000005e040000e0020000df070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
886 "050000005e040000e0020000ff070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,",
887#endif
888#ifdef SDL_JOYSTICK_EMSCRIPTEN
889 "default,Standard Gamepad,a:b0,b:b1,back:b8,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b16,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
890#endif
891#ifdef SDL_JOYSTICK_PS2
892 "0000000050533220436f6e74726f6c00,PS2 Controller,crc:ed87,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,",
893#endif
894#ifdef SDL_JOYSTICK_PSP
895 "00000000505350206275696c74696e00,PSP builtin joypad,crc:bb86,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,",
896#endif
897#ifdef SDL_JOYSTICK_VITA
898 "0000000050535669746120436f6e7400,PSVita Controller,crc:d598,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftstick:b14,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,",
899#endif
900#ifdef SDL_JOYSTICK_N3DS
901 "000000004e696e74656e646f20334400,Nintendo 3DS,crc:3210,a:b0,b:b1,back:b2,dpdown:b7,dpleft:b5,dpright:b4,dpup:b6,leftshoulder:b9,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b8,righttrigger:b15,rightx:a2,righty:a3,start:b3,x:b10,y:b11,",
902#endif
903 NULL
904};
diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_joystick.c b/contrib/SDL-3.2.8/src/joystick/SDL_joystick.c
new file mode 100644
index 0000000..7574adc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/SDL_joystick.c
@@ -0,0 +1,3675 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// This is the joystick API for Simple DirectMedia Layer
24
25#include "SDL_sysjoystick.h"
26#include "../SDL_hints_c.h"
27#include "SDL_gamepad_c.h"
28#include "SDL_joystick_c.h"
29#include "SDL_steam_virtual_gamepad.h"
30
31#include "../events/SDL_events_c.h"
32#include "../video/SDL_sysvideo.h"
33#include "../sensor/SDL_sensor_c.h"
34#include "hidapi/SDL_hidapijoystick_c.h"
35
36// This is included in only one place because it has a large static list of controllers
37#include "controller_type.h"
38
39#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)
40// Needed for checking for input remapping programs
41#include "../core/windows/SDL_windows.h"
42
43#undef UNICODE // We want ASCII functions
44#include <tlhelp32.h>
45#endif
46
47#ifdef SDL_JOYSTICK_VIRTUAL
48#include "./virtual/SDL_virtualjoystick_c.h"
49#endif
50
51static SDL_JoystickDriver *SDL_joystick_drivers[] = {
52#ifdef SDL_JOYSTICK_HIDAPI // Highest priority driver for supported devices
53 &SDL_HIDAPI_JoystickDriver,
54#endif
55#ifdef SDL_JOYSTICK_PRIVATE
56 &SDL_PRIVATE_JoystickDriver,
57#endif
58#ifdef SDL_JOYSTICK_GAMEINPUT // Higher priority than other Windows drivers
59 &SDL_GAMEINPUT_JoystickDriver,
60#endif
61#ifdef SDL_JOYSTICK_RAWINPUT // Before WINDOWS driver, as WINDOWS wants to check if this driver is handling things
62 &SDL_RAWINPUT_JoystickDriver,
63#endif
64#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT) // Before WGI driver, as WGI wants to check if this driver is handling things
65 &SDL_WINDOWS_JoystickDriver,
66#endif
67#ifdef SDL_JOYSTICK_WGI
68 &SDL_WGI_JoystickDriver,
69#endif
70#ifdef SDL_JOYSTICK_WINMM
71 &SDL_WINMM_JoystickDriver,
72#endif
73#ifdef SDL_JOYSTICK_LINUX
74 &SDL_LINUX_JoystickDriver,
75#endif
76#ifdef SDL_JOYSTICK_IOKIT
77 &SDL_DARWIN_JoystickDriver,
78#endif
79#if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)) && !defined(SDL_JOYSTICK_DISABLED)
80 &SDL_IOS_JoystickDriver,
81#endif
82#ifdef SDL_JOYSTICK_ANDROID
83 &SDL_ANDROID_JoystickDriver,
84#endif
85#ifdef SDL_JOYSTICK_EMSCRIPTEN
86 &SDL_EMSCRIPTEN_JoystickDriver,
87#endif
88#ifdef SDL_JOYSTICK_HAIKU
89 &SDL_HAIKU_JoystickDriver,
90#endif
91#ifdef SDL_JOYSTICK_USBHID /* !!! FIXME: "USBHID" is a generic name, and doubly-confusing with HIDAPI next to it. This is the *BSD interface, rename this. */
92 &SDL_BSD_JoystickDriver,
93#endif
94#ifdef SDL_JOYSTICK_PS2
95 &SDL_PS2_JoystickDriver,
96#endif
97#ifdef SDL_JOYSTICK_PSP
98 &SDL_PSP_JoystickDriver,
99#endif
100#ifdef SDL_JOYSTICK_VIRTUAL
101 &SDL_VIRTUAL_JoystickDriver,
102#endif
103#ifdef SDL_JOYSTICK_VITA
104 &SDL_VITA_JoystickDriver,
105#endif
106#ifdef SDL_JOYSTICK_N3DS
107 &SDL_N3DS_JoystickDriver,
108#endif
109#if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED)
110 &SDL_DUMMY_JoystickDriver
111#endif
112};
113
114#ifndef SDL_THREAD_SAFETY_ANALYSIS
115static
116#endif
117SDL_Mutex *SDL_joystick_lock = NULL; // This needs to support recursive locks
118static SDL_AtomicInt SDL_joystick_lock_pending;
119static int SDL_joysticks_locked;
120static bool SDL_joysticks_initialized;
121static bool SDL_joysticks_quitting;
122static bool SDL_joystick_being_added;
123static SDL_Joystick *SDL_joysticks SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
124static int SDL_joystick_player_count SDL_GUARDED_BY(SDL_joystick_lock) = 0;
125static SDL_JoystickID *SDL_joystick_players SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
126static bool SDL_joystick_allows_background_events = false;
127
128static Uint32 initial_arcadestick_devices[] = {
129 MAKE_VIDPID(0x0079, 0x181a), // Venom Arcade Stick
130 MAKE_VIDPID(0x0079, 0x181b), // Venom Arcade Stick
131 MAKE_VIDPID(0x0c12, 0x0ef6), // Hitbox Arcade Stick
132 MAKE_VIDPID(0x0e6f, 0x0109), // PDP Versus Fighting Pad
133 MAKE_VIDPID(0x0f0d, 0x0016), // Hori Real Arcade Pro.EX
134 MAKE_VIDPID(0x0f0d, 0x001b), // Hori Real Arcade Pro VX
135 MAKE_VIDPID(0x0f0d, 0x0063), // Hori Real Arcade Pro Hayabusa (USA) Xbox One
136 MAKE_VIDPID(0x0f0d, 0x006a), // Real Arcade Pro 4
137 MAKE_VIDPID(0x0f0d, 0x0078), // Hori Real Arcade Pro V Kai Xbox One
138 MAKE_VIDPID(0x0f0d, 0x008a), // HORI Real Arcade Pro 4
139 MAKE_VIDPID(0x0f0d, 0x008c), // Hori Real Arcade Pro 4
140 MAKE_VIDPID(0x0f0d, 0x00aa), // HORI Real Arcade Pro V Hayabusa in Switch Mode
141 MAKE_VIDPID(0x0f0d, 0x00ed), // Hori Fighting Stick mini 4 kai
142 MAKE_VIDPID(0x0f0d, 0x011c), // Hori Fighting Stick Alpha in PS4 Mode
143 MAKE_VIDPID(0x0f0d, 0x011e), // Hori Fighting Stick Alpha in PC Mode
144 MAKE_VIDPID(0x0f0d, 0x0184), // Hori Fighting Stick Alpha in PS5 Mode
145 MAKE_VIDPID(0x146b, 0x0604), // NACON Daija Arcade Stick
146 MAKE_VIDPID(0x1532, 0x0a00), // Razer Atrox Arcade Stick
147 MAKE_VIDPID(0x1bad, 0xf03d), // Street Fighter IV Arcade Stick TE - Chun Li
148 MAKE_VIDPID(0x1bad, 0xf502), // Hori Real Arcade Pro.VX SA
149 MAKE_VIDPID(0x1bad, 0xf504), // Hori Real Arcade Pro. EX
150 MAKE_VIDPID(0x1bad, 0xf506), // Hori Real Arcade Pro.EX Premium VLX
151 MAKE_VIDPID(0x20d6, 0xa715), // PowerA Nintendo Switch Fusion Arcade Stick
152 MAKE_VIDPID(0x24c6, 0x5000), // Razer Atrox Arcade Stick
153 MAKE_VIDPID(0x24c6, 0x5501), // Hori Real Arcade Pro VX-SA
154 MAKE_VIDPID(0x24c6, 0x550e), // Hori Real Arcade Pro V Kai 360
155 MAKE_VIDPID(0x2c22, 0x2300), // Qanba Obsidian Arcade Joystick in PS4 Mode
156 MAKE_VIDPID(0x2c22, 0x2302), // Qanba Obsidian Arcade Joystick in PS3 Mode
157 MAKE_VIDPID(0x2c22, 0x2303), // Qanba Obsidian Arcade Joystick in PC Mode
158 MAKE_VIDPID(0x2c22, 0x2500), // Qanba Dragon Arcade Joystick in PS4 Mode
159 MAKE_VIDPID(0x2c22, 0x2502), // Qanba Dragon Arcade Joystick in PS3 Mode
160 MAKE_VIDPID(0x2c22, 0x2503), // Qanba Dragon Arcade Joystick in PC Mode
161};
162static SDL_vidpid_list arcadestick_devices = {
163 SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES, 0, 0, NULL,
164 SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED, 0, 0, NULL,
165 SDL_arraysize(initial_arcadestick_devices), initial_arcadestick_devices,
166 false
167};
168
169/* This list is taken from:
170 https://raw.githubusercontent.com/denilsonsa/udev-joystick-blacklist/master/generate_rules.py
171 */
172static Uint32 initial_blacklist_devices[] = {
173 // Microsoft Microsoft Wireless Optical Desktop 2.10
174 // Microsoft Wireless Desktop - Comfort Edition
175 MAKE_VIDPID(0x045e, 0x009d),
176
177 // Microsoft Microsoft Digital Media Pro Keyboard
178 // Microsoft Corp. Digital Media Pro Keyboard
179 MAKE_VIDPID(0x045e, 0x00b0),
180
181 // Microsoft Microsoft Digital Media Keyboard
182 // Microsoft Corp. Digital Media Keyboard 1.0A
183 MAKE_VIDPID(0x045e, 0x00b4),
184
185 // Microsoft Microsoft Digital Media Keyboard 3000
186 MAKE_VIDPID(0x045e, 0x0730),
187
188 // Microsoft Microsoft 2.4GHz Transceiver v6.0
189 // Microsoft Microsoft 2.4GHz Transceiver v8.0
190 // Microsoft Corp. Nano Transceiver v1.0 for Bluetooth
191 // Microsoft Wireless Mobile Mouse 1000
192 // Microsoft Wireless Desktop 3000
193 MAKE_VIDPID(0x045e, 0x0745),
194
195 // Microsoft SideWinder(TM) 2.4GHz Transceiver
196 MAKE_VIDPID(0x045e, 0x0748),
197
198 // Microsoft Corp. Wired Keyboard 600
199 MAKE_VIDPID(0x045e, 0x0750),
200
201 // Microsoft Corp. Sidewinder X4 keyboard
202 MAKE_VIDPID(0x045e, 0x0768),
203
204 // Microsoft Corp. Arc Touch Mouse Transceiver
205 MAKE_VIDPID(0x045e, 0x0773),
206
207 // Microsoft 2.4GHz Transceiver v9.0
208 // Microsoft Nano Transceiver v2.1
209 // Microsoft Sculpt Ergonomic Keyboard (5KV-00001)
210 MAKE_VIDPID(0x045e, 0x07a5),
211
212 // Microsoft Nano Transceiver v1.0
213 // Microsoft Wireless Keyboard 800
214 MAKE_VIDPID(0x045e, 0x07b2),
215
216 // Microsoft Nano Transceiver v2.0
217 MAKE_VIDPID(0x045e, 0x0800),
218
219 MAKE_VIDPID(0x046d, 0xc30a), // Logitech, Inc. iTouch Composite keyboard
220
221 MAKE_VIDPID(0x04d9, 0xa0df), // Tek Syndicate Mouse (E-Signal USB Gaming Mouse)
222
223 // List of Wacom devices at: http://linuxwacom.sourceforge.net/wiki/index.php/Device_IDs
224 MAKE_VIDPID(0x056a, 0x0010), // Wacom ET-0405 Graphire
225 MAKE_VIDPID(0x056a, 0x0011), // Wacom ET-0405A Graphire2 (4x5)
226 MAKE_VIDPID(0x056a, 0x0012), // Wacom ET-0507A Graphire2 (5x7)
227 MAKE_VIDPID(0x056a, 0x0013), // Wacom CTE-430 Graphire3 (4x5)
228 MAKE_VIDPID(0x056a, 0x0014), // Wacom CTE-630 Graphire3 (6x8)
229 MAKE_VIDPID(0x056a, 0x0015), // Wacom CTE-440 Graphire4 (4x5)
230 MAKE_VIDPID(0x056a, 0x0016), // Wacom CTE-640 Graphire4 (6x8)
231 MAKE_VIDPID(0x056a, 0x0017), // Wacom CTE-450 Bamboo Fun (4x5)
232 MAKE_VIDPID(0x056a, 0x0018), // Wacom CTE-650 Bamboo Fun 6x8
233 MAKE_VIDPID(0x056a, 0x0019), // Wacom CTE-631 Bamboo One
234 MAKE_VIDPID(0x056a, 0x00d1), // Wacom Bamboo Pen and Touch CTH-460
235 MAKE_VIDPID(0x056a, 0x030e), // Wacom Intuos Pen (S) CTL-480
236
237 MAKE_VIDPID(0x09da, 0x054f), // A4 Tech Co., G7 750 mouse
238 MAKE_VIDPID(0x09da, 0x1410), // A4 Tech Co., Ltd Bloody AL9 mouse
239 MAKE_VIDPID(0x09da, 0x3043), // A4 Tech Co., Ltd Bloody R8A Gaming Mouse
240 MAKE_VIDPID(0x09da, 0x31b5), // A4 Tech Co., Ltd Bloody TL80 Terminator Laser Gaming Mouse
241 MAKE_VIDPID(0x09da, 0x3997), // A4 Tech Co., Ltd Bloody RT7 Terminator Wireless
242 MAKE_VIDPID(0x09da, 0x3f8b), // A4 Tech Co., Ltd Bloody V8 mouse
243 MAKE_VIDPID(0x09da, 0x51f4), // Modecom MC-5006 Keyboard
244 MAKE_VIDPID(0x09da, 0x5589), // A4 Tech Co., Ltd Terminator TL9 Laser Gaming Mouse
245 MAKE_VIDPID(0x09da, 0x7b22), // A4 Tech Co., Ltd Bloody V5
246 MAKE_VIDPID(0x09da, 0x7f2d), // A4 Tech Co., Ltd Bloody R3 mouse
247 MAKE_VIDPID(0x09da, 0x8090), // A4 Tech Co., Ltd X-718BK Oscar Optical Gaming Mouse
248 MAKE_VIDPID(0x09da, 0x9033), // A4 Tech Co., X7 X-705K
249 MAKE_VIDPID(0x09da, 0x9066), // A4 Tech Co., Sharkoon Fireglider Optical
250 MAKE_VIDPID(0x09da, 0x9090), // A4 Tech Co., Ltd XL-730K / XL-750BK / XL-755BK Laser Mouse
251 MAKE_VIDPID(0x09da, 0x90c0), // A4 Tech Co., Ltd X7 G800V keyboard
252 MAKE_VIDPID(0x09da, 0xf012), // A4 Tech Co., Ltd Bloody V7 mouse
253 MAKE_VIDPID(0x09da, 0xf32a), // A4 Tech Co., Ltd Bloody B540 keyboard
254 MAKE_VIDPID(0x09da, 0xf613), // A4 Tech Co., Ltd Bloody V2 mouse
255 MAKE_VIDPID(0x09da, 0xf624), // A4 Tech Co., Ltd Bloody B120 Keyboard
256
257 MAKE_VIDPID(0x1b1c, 0x1b3c), // Corsair Harpoon RGB gaming mouse
258
259 MAKE_VIDPID(0x1d57, 0xad03), // [T3] 2.4GHz and IR Air Mouse Remote Control
260
261 MAKE_VIDPID(0x1e7d, 0x2e4a), // Roccat Tyon Mouse
262
263 MAKE_VIDPID(0x20a0, 0x422d), // Winkeyless.kr Keyboards
264
265 MAKE_VIDPID(0x2516, 0x001f), // Cooler Master Storm Mizar Mouse
266 MAKE_VIDPID(0x2516, 0x0028), // Cooler Master Storm Alcor Mouse
267
268 /*****************************************************************/
269 // Additional entries
270 /*****************************************************************/
271
272 MAKE_VIDPID(0x04d9, 0x8008), // OBINLB USB-HID Keyboard (Anne Pro II)
273 MAKE_VIDPID(0x04d9, 0x8009), // OBINLB USB-HID Keyboard (Anne Pro II)
274 MAKE_VIDPID(0x04d9, 0xa292), // OBINLB USB-HID Keyboard (Anne Pro II)
275 MAKE_VIDPID(0x04d9, 0xa293), // OBINLB USB-HID Keyboard (Anne Pro II)
276 MAKE_VIDPID(0x1532, 0x0266), // Razer Huntsman V2 Analog, non-functional DInput device
277 MAKE_VIDPID(0x1532, 0x0282), // Razer Huntsman Mini Analog, non-functional DInput device
278 MAKE_VIDPID(0x26ce, 0x01a2), // ASRock LED Controller
279 MAKE_VIDPID(0x20d6, 0x0002), // PowerA Enhanced Wireless Controller for Nintendo Switch (charging port only)
280};
281static SDL_vidpid_list blacklist_devices = {
282 SDL_HINT_JOYSTICK_BLACKLIST_DEVICES, 0, 0, NULL,
283 SDL_HINT_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED, 0, 0, NULL,
284 SDL_arraysize(initial_blacklist_devices), initial_blacklist_devices,
285 false
286};
287
288static Uint32 initial_flightstick_devices[] = {
289 MAKE_VIDPID(0x044f, 0x0402), // HOTAS Warthog Joystick
290 MAKE_VIDPID(0x044f, 0xb10a), // ThrustMaster, Inc. T.16000M Joystick
291 MAKE_VIDPID(0x046d, 0xc215), // Logitech Extreme 3D
292 MAKE_VIDPID(0x0738, 0x2221), // Saitek Pro Flight X-56 Rhino Stick
293 MAKE_VIDPID(0x231d, 0x0126), // Gunfighter Mk.III 'Space Combat Edition' (right)
294 MAKE_VIDPID(0x231d, 0x0127), // Gunfighter Mk.III 'Space Combat Edition' (left)
295 MAKE_VIDPID(0x362c, 0x0001), // Yawman Arrow
296};
297static SDL_vidpid_list flightstick_devices = {
298 SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES, 0, 0, NULL,
299 SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED, 0, 0, NULL,
300 SDL_arraysize(initial_flightstick_devices), initial_flightstick_devices,
301 false
302};
303
304static Uint32 initial_gamecube_devices[] = {
305 MAKE_VIDPID(0x0e6f, 0x0185), // PDP Wired Fight Pad Pro for Nintendo Switch
306 MAKE_VIDPID(0x20d6, 0xa711), // PowerA Wired Controller Nintendo GameCube Style
307};
308static SDL_vidpid_list gamecube_devices = {
309 SDL_HINT_JOYSTICK_GAMECUBE_DEVICES, 0, 0, NULL,
310 SDL_HINT_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED, 0, 0, NULL,
311 SDL_arraysize(initial_gamecube_devices), initial_gamecube_devices,
312 false
313};
314
315static Uint32 initial_rog_gamepad_mice[] = {
316 MAKE_VIDPID(0x0b05, 0x18e3), // ROG Chakram (wired) Mouse
317 MAKE_VIDPID(0x0b05, 0x18e5), // ROG Chakram (wireless) Mouse
318 MAKE_VIDPID(0x0b05, 0x1906), // ROG Pugio II
319 MAKE_VIDPID(0x0b05, 0x1958), // ROG Chakram Core Mouse
320 MAKE_VIDPID(0x0b05, 0x1a18), // ROG Chakram X (wired) Mouse
321 MAKE_VIDPID(0x0b05, 0x1a1a), // ROG Chakram X (wireless) Mouse
322 MAKE_VIDPID(0x0b05, 0x1a1c), // ROG Chakram X (Bluetooth) Mouse
323};
324static SDL_vidpid_list rog_gamepad_mice = {
325 SDL_HINT_ROG_GAMEPAD_MICE, 0, 0, NULL,
326 SDL_HINT_ROG_GAMEPAD_MICE_EXCLUDED, 0, 0, NULL,
327 SDL_arraysize(initial_rog_gamepad_mice), initial_rog_gamepad_mice,
328 false
329};
330
331static Uint32 initial_throttle_devices[] = {
332 MAKE_VIDPID(0x044f, 0x0404), // HOTAS Warthog Throttle
333 MAKE_VIDPID(0x0738, 0xa221), // Saitek Pro Flight X-56 Rhino Throttle
334};
335static SDL_vidpid_list throttle_devices = {
336 SDL_HINT_JOYSTICK_THROTTLE_DEVICES, 0, 0, NULL,
337 SDL_HINT_JOYSTICK_THROTTLE_DEVICES_EXCLUDED, 0, 0, NULL,
338 SDL_arraysize(initial_throttle_devices), initial_throttle_devices,
339 false
340};
341
342static Uint32 initial_wheel_devices[] = {
343 MAKE_VIDPID(0x0079, 0x1864), // DragonRise Inc. Wired Wheel (active mode) (also known as PXN V900 (PS3), Superdrive SV-750, or a Genesis Seaborg 400)
344 MAKE_VIDPID(0x044f, 0xb65d), // Thrustmaster Wheel FFB
345 MAKE_VIDPID(0x044f, 0xb65e), // Thrustmaster T500RS
346 MAKE_VIDPID(0x044f, 0xb664), // Thrustmaster TX (initial mode)
347 MAKE_VIDPID(0x044f, 0xb669), // Thrustmaster TX (active mode)
348 MAKE_VIDPID(0x044f, 0xb66d), // Thrustmaster T300RS (PS4 mode)
349 MAKE_VIDPID(0x044f, 0xb66d), // Thrustmaster Wheel FFB
350 MAKE_VIDPID(0x044f, 0xb66e), // Thrustmaster T300RS (normal mode)
351 MAKE_VIDPID(0x044f, 0xb66f), // Thrustmaster T300RS (advanced mode)
352 MAKE_VIDPID(0x044f, 0xb677), // Thrustmaster T150
353 MAKE_VIDPID(0x044f, 0xb67f), // Thrustmaster TMX
354 MAKE_VIDPID(0x044f, 0xb691), // Thrustmaster TS-XW (initial mode)
355 MAKE_VIDPID(0x044f, 0xb692), // Thrustmaster TS-XW (active mode)
356 MAKE_VIDPID(0x044f, 0xb696), // Thrustmaster T248
357 MAKE_VIDPID(0x046d, 0xc24f), // Logitech G29 (PS3)
358 MAKE_VIDPID(0x046d, 0xc260), // Logitech G29 (PS4)
359 MAKE_VIDPID(0x046d, 0xc261), // Logitech G920 (initial mode)
360 MAKE_VIDPID(0x046d, 0xc262), // Logitech G920 (active mode)
361 MAKE_VIDPID(0x046d, 0xc266), // Logitech G923 for Playstation 4 and PC (PC mode)
362 MAKE_VIDPID(0x046d, 0xc267), // Logitech G923 for Playstation 4 and PC (PS4 mode)
363 MAKE_VIDPID(0x046d, 0xc268), // Logitech PRO Racing Wheel (PC mode)
364 MAKE_VIDPID(0x046d, 0xc269), // Logitech PRO Racing Wheel (PS4/PS5 mode)
365 MAKE_VIDPID(0x046d, 0xc26d), // Logitech G923 (Xbox)
366 MAKE_VIDPID(0x046d, 0xc26e), // Logitech G923
367 MAKE_VIDPID(0x046d, 0xc272), // Logitech PRO Racing Wheel for Xbox (PC mode)
368 MAKE_VIDPID(0x046d, 0xc294), // Logitech generic wheel
369 MAKE_VIDPID(0x046d, 0xc295), // Logitech Momo Force
370 MAKE_VIDPID(0x046d, 0xc298), // Logitech Driving Force Pro
371 MAKE_VIDPID(0x046d, 0xc299), // Logitech G25
372 MAKE_VIDPID(0x046d, 0xc29a), // Logitech Driving Force GT
373 MAKE_VIDPID(0x046d, 0xc29b), // Logitech G27
374 MAKE_VIDPID(0x046d, 0xca03), // Logitech Momo Racing
375 MAKE_VIDPID(0x0483, 0x0522), // Simagic Wheelbase (including M10, Alpha Mini, Alpha, Alpha U)
376 MAKE_VIDPID(0x0483, 0xa355), // VRS DirectForce Pro Wheel Base
377 MAKE_VIDPID(0x0eb7, 0x0001), // Fanatec ClubSport Wheel Base V2
378 MAKE_VIDPID(0x0eb7, 0x0004), // Fanatec ClubSport Wheel Base V2.5
379 MAKE_VIDPID(0x0eb7, 0x0005), // Fanatec CSL Elite Wheel Base+ (PS4)
380 MAKE_VIDPID(0x0eb7, 0x0006), // Fanatec Podium Wheel Base DD1
381 MAKE_VIDPID(0x0eb7, 0x0007), // Fanatec Podium Wheel Base DD2
382 MAKE_VIDPID(0x0eb7, 0x0011), // Fanatec Forza Motorsport (CSR Wheel / CSR Elite Wheel)
383 MAKE_VIDPID(0x0eb7, 0x0020), // Fanatec generic wheel / CSL DD / GT DD Pro
384 MAKE_VIDPID(0x0eb7, 0x0197), // Fanatec Porsche Wheel (Turbo / GT3 RS / Turbo S / GT3 V2 / GT2)
385 MAKE_VIDPID(0x0eb7, 0x038e), // Fanatec ClubSport Wheel Base V1
386 MAKE_VIDPID(0x0eb7, 0x0e03), // Fanatec CSL Elite Wheel Base
387 MAKE_VIDPID(0x11ff, 0x0511), // DragonRise Inc. Wired Wheel (initial mode) (also known as PXN V900 (PS3), Superdrive SV-750, or a Genesis Seaborg 400)
388 MAKE_VIDPID(0x1209, 0xffb0), // Generic FFBoard OpenFFBoard universal forcefeedback wheel
389 MAKE_VIDPID(0x16d0, 0x0d5a), // Simucube 1 Wheelbase
390 MAKE_VIDPID(0x16d0, 0x0d5f), // Simucube 2 Ultimate Wheelbase
391 MAKE_VIDPID(0x16d0, 0x0d60), // Simucube 2 Pro Wheelbase
392 MAKE_VIDPID(0x16d0, 0x0d61), // Simucube 2 Sport Wheelbase
393 MAKE_VIDPID(0x2433, 0xf300), // Asetek SimSports Invicta Wheelbase
394 MAKE_VIDPID(0x2433, 0xf301), // Asetek SimSports Forte Wheelbase
395 MAKE_VIDPID(0x2433, 0xf303), // Asetek SimSports La Prima Wheelbase
396 MAKE_VIDPID(0x2433, 0xf306), // Asetek SimSports Tony Kannan Wheelbase
397 MAKE_VIDPID(0x3416, 0x0301), // Cammus C5 Wheelbase
398 MAKE_VIDPID(0x3416, 0x0302), // Cammus C12 Wheelbase
399 MAKE_VIDPID(0x346e, 0x0000), // Moza R16/R21 Wheelbase
400 MAKE_VIDPID(0x346e, 0x0002), // Moza R9 Wheelbase
401 MAKE_VIDPID(0x346e, 0x0004), // Moza R5 Wheelbase
402 MAKE_VIDPID(0x346e, 0x0005), // Moza R3 Wheelbase
403 MAKE_VIDPID(0x346e, 0x0006), // Moza R12 Wheelbase
404};
405static SDL_vidpid_list wheel_devices = {
406 SDL_HINT_JOYSTICK_WHEEL_DEVICES, 0, 0, NULL,
407 SDL_HINT_JOYSTICK_WHEEL_DEVICES_EXCLUDED, 0, 0, NULL,
408 SDL_arraysize(initial_wheel_devices), initial_wheel_devices,
409 false
410};
411
412static Uint32 initial_zero_centered_devices[] = {
413 MAKE_VIDPID(0x05a0, 0x3232), // 8Bitdo Zero Gamepad
414 MAKE_VIDPID(0x0e8f, 0x3013), // HuiJia SNES USB adapter
415};
416static SDL_vidpid_list zero_centered_devices = {
417 SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES, 0, 0, NULL,
418 NULL, 0, 0, NULL,
419 SDL_arraysize(initial_zero_centered_devices), initial_zero_centered_devices,
420 false
421};
422
423#define CHECK_JOYSTICK_MAGIC(joystick, result) \
424 if (!SDL_ObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK)) { \
425 SDL_InvalidParamError("joystick"); \
426 SDL_UnlockJoysticks(); \
427 return result; \
428 }
429
430bool SDL_JoysticksInitialized(void)
431{
432 return SDL_joysticks_initialized;
433}
434
435bool SDL_JoysticksQuitting(void)
436{
437 return SDL_joysticks_quitting;
438}
439
440void SDL_LockJoysticks(void)
441{
442 (void)SDL_AtomicIncRef(&SDL_joystick_lock_pending);
443 SDL_LockMutex(SDL_joystick_lock);
444 (void)SDL_AtomicDecRef(&SDL_joystick_lock_pending);
445
446 ++SDL_joysticks_locked;
447}
448
449void SDL_UnlockJoysticks(void)
450{
451 bool last_unlock = false;
452
453 --SDL_joysticks_locked;
454
455 if (!SDL_joysticks_initialized) {
456 // NOTE: There's a small window here where another thread could lock the mutex after we've checked for pending locks
457 if (!SDL_joysticks_locked && SDL_GetAtomicInt(&SDL_joystick_lock_pending) == 0) {
458 last_unlock = true;
459 }
460 }
461
462 /* The last unlock after joysticks are uninitialized will cleanup the mutex,
463 * allowing applications to lock joysticks while reinitializing the system.
464 */
465 if (last_unlock) {
466 SDL_Mutex *joystick_lock = SDL_joystick_lock;
467
468 SDL_LockMutex(joystick_lock);
469 {
470 SDL_UnlockMutex(SDL_joystick_lock);
471
472 SDL_joystick_lock = NULL;
473 }
474 SDL_UnlockMutex(joystick_lock);
475 SDL_DestroyMutex(joystick_lock);
476 } else {
477 SDL_UnlockMutex(SDL_joystick_lock);
478 }
479}
480
481bool SDL_JoysticksLocked(void)
482{
483 return (SDL_joysticks_locked > 0);
484}
485
486void SDL_AssertJoysticksLocked(void)
487{
488 SDL_assert(SDL_JoysticksLocked());
489}
490
491/*
492 * Get the driver and device index for a joystick instance ID
493 * This should be called while the joystick lock is held, to prevent another thread from updating the list
494 */
495static bool SDL_GetDriverAndJoystickIndex(SDL_JoystickID instance_id, SDL_JoystickDriver **driver, int *driver_index)
496{
497 int i, num_joysticks, device_index;
498
499 SDL_AssertJoysticksLocked();
500
501 if (instance_id > 0) {
502 for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
503 num_joysticks = SDL_joystick_drivers[i]->GetCount();
504 for (device_index = 0; device_index < num_joysticks; ++device_index) {
505 SDL_JoystickID joystick_id = SDL_joystick_drivers[i]->GetDeviceInstanceID(device_index);
506 if (joystick_id == instance_id) {
507 *driver = SDL_joystick_drivers[i];
508 *driver_index = device_index;
509 return true;
510 }
511 }
512 }
513 }
514
515 SDL_SetError("Joystick %" SDL_PRIu32 " not found", instance_id);
516 return false;
517}
518
519static int SDL_FindFreePlayerIndex(void)
520{
521 int player_index;
522
523 SDL_AssertJoysticksLocked();
524
525 for (player_index = 0; player_index < SDL_joystick_player_count; ++player_index) {
526 if (SDL_joystick_players[player_index] == 0) {
527 break;
528 }
529 }
530 return player_index;
531}
532
533static int SDL_GetPlayerIndexForJoystickID(SDL_JoystickID instance_id)
534{
535 int player_index;
536
537 SDL_AssertJoysticksLocked();
538
539 for (player_index = 0; player_index < SDL_joystick_player_count; ++player_index) {
540 if (instance_id == SDL_joystick_players[player_index]) {
541 break;
542 }
543 }
544 if (player_index == SDL_joystick_player_count) {
545 player_index = -1;
546 }
547 return player_index;
548}
549
550static SDL_JoystickID SDL_GetJoystickIDForPlayerIndex(int player_index)
551{
552 SDL_AssertJoysticksLocked();
553
554 if (player_index < 0 || player_index >= SDL_joystick_player_count) {
555 return 0;
556 }
557 return SDL_joystick_players[player_index];
558}
559
560static bool SDL_SetJoystickIDForPlayerIndex(int player_index, SDL_JoystickID instance_id)
561{
562 SDL_JoystickID existing_instance = SDL_GetJoystickIDForPlayerIndex(player_index);
563 SDL_JoystickDriver *driver;
564 int device_index;
565 int existing_player_index;
566
567 SDL_AssertJoysticksLocked();
568
569 if (player_index >= SDL_joystick_player_count) {
570 SDL_JoystickID *new_players = (SDL_JoystickID *)SDL_realloc(SDL_joystick_players, (player_index + 1) * sizeof(*SDL_joystick_players));
571 if (!new_players) {
572 return false;
573 }
574
575 SDL_joystick_players = new_players;
576 SDL_memset(&SDL_joystick_players[SDL_joystick_player_count], 0, (player_index - SDL_joystick_player_count + 1) * sizeof(SDL_joystick_players[0]));
577 SDL_joystick_player_count = player_index + 1;
578 } else if (player_index >= 0 && SDL_joystick_players[player_index] == instance_id) {
579 // Joystick is already assigned the requested player index
580 return true;
581 }
582
583 // Clear the old player index
584 existing_player_index = SDL_GetPlayerIndexForJoystickID(instance_id);
585 if (existing_player_index >= 0) {
586 SDL_joystick_players[existing_player_index] = 0;
587 }
588
589 if (player_index >= 0) {
590 SDL_joystick_players[player_index] = instance_id;
591 }
592
593 // Update the driver with the new index
594 if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
595 driver->SetDevicePlayerIndex(device_index, player_index);
596 }
597
598 // Move any existing joystick to another slot
599 if (existing_instance > 0) {
600 SDL_SetJoystickIDForPlayerIndex(SDL_FindFreePlayerIndex(), existing_instance);
601 }
602 return true;
603}
604
605static void SDLCALL SDL_JoystickAllowBackgroundEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
606{
607 if (SDL_GetStringBoolean(hint, false)) {
608 SDL_joystick_allows_background_events = true;
609 } else {
610 SDL_joystick_allows_background_events = false;
611 }
612}
613
614bool SDL_InitJoysticks(void)
615{
616 int i;
617 bool result = false;
618
619 // Create the joystick list lock
620 if (SDL_joystick_lock == NULL) {
621 SDL_joystick_lock = SDL_CreateMutex();
622 }
623
624 if (!SDL_InitSubSystem(SDL_INIT_EVENTS)) {
625 return false;
626 }
627
628 SDL_LockJoysticks();
629
630 SDL_joysticks_initialized = true;
631
632 SDL_InitGamepadMappings();
633
634 SDL_LoadVIDPIDList(&arcadestick_devices);
635 SDL_LoadVIDPIDList(&blacklist_devices);
636 SDL_LoadVIDPIDList(&flightstick_devices);
637 SDL_LoadVIDPIDList(&gamecube_devices);
638 SDL_LoadVIDPIDList(&rog_gamepad_mice);
639 SDL_LoadVIDPIDList(&throttle_devices);
640 SDL_LoadVIDPIDList(&wheel_devices);
641 SDL_LoadVIDPIDList(&zero_centered_devices);
642
643 // See if we should allow joystick events while in the background
644 SDL_AddHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
645 SDL_JoystickAllowBackgroundEventsChanged, NULL);
646
647 SDL_InitSteamVirtualGamepadInfo();
648
649 for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
650 if (SDL_joystick_drivers[i]->Init()) {
651 result = true;
652 }
653 }
654 SDL_UnlockJoysticks();
655
656 if (!result) {
657 SDL_QuitJoysticks();
658 }
659
660 return result;
661}
662
663bool SDL_JoysticksOpened(void)
664{
665 bool opened;
666
667 SDL_LockJoysticks();
668 {
669 if (SDL_joysticks != NULL) {
670 opened = true;
671 } else {
672 opened = false;
673 }
674 }
675 SDL_UnlockJoysticks();
676
677 return opened;
678}
679
680bool SDL_JoystickHandledByAnotherDriver(struct SDL_JoystickDriver *driver, Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
681{
682 int i;
683 bool result = false;
684
685 SDL_LockJoysticks();
686 {
687 for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
688 if (driver == SDL_joystick_drivers[i]) {
689 // Higher priority drivers do not have this device
690 break;
691 }
692 if (SDL_joystick_drivers[i]->IsDevicePresent(vendor_id, product_id, version, name)) {
693 result = true;
694 break;
695 }
696 }
697 }
698 SDL_UnlockJoysticks();
699
700 return result;
701}
702
703bool SDL_HasJoystick(void)
704{
705 int i;
706 int total_joysticks = 0;
707
708 SDL_LockJoysticks();
709 {
710 for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
711 total_joysticks += SDL_joystick_drivers[i]->GetCount();
712 }
713 }
714 SDL_UnlockJoysticks();
715
716 if (total_joysticks > 0) {
717 return true;
718 }
719 return false;
720}
721
722SDL_JoystickID *SDL_GetJoysticks(int *count)
723{
724 int i, num_joysticks, device_index;
725 int joystick_index = 0, total_joysticks = 0;
726 SDL_JoystickID *joysticks;
727
728 SDL_LockJoysticks();
729 {
730 for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
731 total_joysticks += SDL_joystick_drivers[i]->GetCount();
732 }
733
734 joysticks = (SDL_JoystickID *)SDL_malloc((total_joysticks + 1) * sizeof(*joysticks));
735 if (joysticks) {
736 if (count) {
737 *count = total_joysticks;
738 }
739
740 for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
741 num_joysticks = SDL_joystick_drivers[i]->GetCount();
742 for (device_index = 0; device_index < num_joysticks; ++device_index) {
743 SDL_assert(joystick_index < total_joysticks);
744 joysticks[joystick_index] = SDL_joystick_drivers[i]->GetDeviceInstanceID(device_index);
745 SDL_assert(joysticks[joystick_index] > 0);
746 ++joystick_index;
747 }
748 }
749 SDL_assert(joystick_index == total_joysticks);
750 joysticks[joystick_index] = 0;
751 } else {
752 if (count) {
753 *count = 0;
754 }
755 }
756 }
757 SDL_UnlockJoysticks();
758
759 return joysticks;
760}
761
762const SDL_SteamVirtualGamepadInfo *SDL_GetJoystickVirtualGamepadInfoForID(SDL_JoystickID instance_id)
763{
764 SDL_JoystickDriver *driver;
765 int device_index;
766 const SDL_SteamVirtualGamepadInfo *info = NULL;
767
768 if (SDL_SteamVirtualGamepadEnabled() &&
769 SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
770 info = SDL_GetSteamVirtualGamepadInfo(driver->GetDeviceSteamVirtualGamepadSlot(device_index));
771 }
772 return info;
773}
774
775/*
776 * Get the implementation dependent name of a joystick
777 */
778const char *SDL_GetJoystickNameForID(SDL_JoystickID instance_id)
779{
780 SDL_JoystickDriver *driver;
781 int device_index;
782 const char *name = NULL;
783 const SDL_SteamVirtualGamepadInfo *info;
784
785 SDL_LockJoysticks();
786 info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id);
787 if (info) {
788 name = SDL_GetPersistentString(info->name);
789 } else if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
790 name = SDL_GetPersistentString(driver->GetDeviceName(device_index));
791 }
792 SDL_UnlockJoysticks();
793
794 return name;
795}
796
797/*
798 * Get the implementation dependent path of a joystick
799 */
800const char *SDL_GetJoystickPathForID(SDL_JoystickID instance_id)
801{
802 SDL_JoystickDriver *driver;
803 int device_index;
804 const char *path = NULL;
805
806 SDL_LockJoysticks();
807 if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
808 path = SDL_GetPersistentString(driver->GetDevicePath(device_index));
809 }
810 SDL_UnlockJoysticks();
811
812 if (!path) {
813 SDL_Unsupported();
814 }
815 return path;
816}
817
818/*
819 * Get the player index of a joystick, or -1 if it's not available
820 */
821int SDL_GetJoystickPlayerIndexForID(SDL_JoystickID instance_id)
822{
823 int player_index;
824
825 SDL_LockJoysticks();
826 player_index = SDL_GetPlayerIndexForJoystickID(instance_id);
827 SDL_UnlockJoysticks();
828
829 return player_index;
830}
831
832/*
833 * Return true if this joystick is known to have all axes centered at zero
834 * This isn't generally needed unless the joystick never generates an initial axis value near zero,
835 * e.g. it's emulating axes with digital buttons
836 */
837static bool SDL_JoystickAxesCenteredAtZero(SDL_Joystick *joystick)
838{
839 // printf("JOYSTICK '%s' VID/PID 0x%.4x/0x%.4x AXES: %d\n", joystick->name, vendor, product, joystick->naxes);
840
841 if (joystick->naxes == 2) {
842 // Assume D-pad or thumbstick style axes are centered at 0
843 return true;
844 }
845
846 return SDL_VIDPIDInList(SDL_GetJoystickVendor(joystick), SDL_GetJoystickProduct(joystick), &zero_centered_devices);
847}
848
849static bool IsROGAlly(SDL_Joystick *joystick)
850{
851 Uint16 vendor, product;
852 SDL_GUID guid = SDL_GetJoystickGUID(joystick);
853
854 // The ROG Ally controller spoofs an Xbox 360 controller
855 SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
856 if (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX360_WIRED_CONTROLLER) {
857 // Check to see if this system has the expected sensors
858 bool has_ally_accel = false;
859 bool has_ally_gyro = false;
860
861 if (SDL_InitSubSystem(SDL_INIT_SENSOR)) {
862 SDL_SensorID *sensors = SDL_GetSensors(NULL);
863 if (sensors) {
864 int i;
865 for (i = 0; sensors[i]; ++i) {
866 SDL_SensorID sensor = sensors[i];
867
868 if (!has_ally_accel && SDL_GetSensorTypeForID(sensor) == SDL_SENSOR_ACCEL) {
869 const char *sensor_name = SDL_GetSensorNameForID(sensor);
870 if (sensor_name && SDL_strcmp(sensor_name, "Sensor BMI320 Acc") == 0) {
871 has_ally_accel = true;
872 }
873 }
874 if (!has_ally_gyro && SDL_GetSensorTypeForID(sensor) == SDL_SENSOR_GYRO) {
875 const char *sensor_name = SDL_GetSensorNameForID(sensor);
876 if (sensor_name && SDL_strcmp(sensor_name, "Sensor BMI320 Gyr") == 0) {
877 has_ally_gyro = true;
878 }
879 }
880 }
881 SDL_free(sensors);
882 }
883 SDL_QuitSubSystem(SDL_INIT_SENSOR);
884 }
885 if (has_ally_accel && has_ally_gyro) {
886 return true;
887 }
888 }
889 return false;
890}
891
892static bool ShouldAttemptSensorFusion(SDL_Joystick *joystick, bool *invert_sensors)
893{
894 SDL_AssertJoysticksLocked();
895
896 *invert_sensors = false;
897
898 // The SDL controller sensor API is only available for gamepads (at the moment)
899 if (!SDL_IsGamepad(joystick->instance_id)) {
900 return false;
901 }
902
903 // If the controller already has sensors, use those
904 if (joystick->nsensors > 0) {
905 return false;
906 }
907
908 const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION);
909 if (hint && *hint) {
910 if (*hint == '@' || SDL_strncmp(hint, "0x", 2) == 0) {
911 SDL_vidpid_list gamepads;
912 SDL_GUID guid;
913 Uint16 vendor, product;
914 bool enabled;
915 SDL_zero(gamepads);
916
917 // See if the gamepad is in our list of devices to enable
918 guid = SDL_GetJoystickGUID(joystick);
919 SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
920 SDL_LoadVIDPIDListFromHints(&gamepads, hint, NULL);
921 enabled = SDL_VIDPIDInList(vendor, product, &gamepads);
922 SDL_FreeVIDPIDList(&gamepads);
923 if (enabled) {
924 return true;
925 }
926 } else {
927 return SDL_GetStringBoolean(hint, false);
928 }
929 }
930
931 // See if this is another known wraparound gamepad
932 if (joystick->name &&
933 (SDL_strstr(joystick->name, "Backbone One") ||
934 SDL_strstr(joystick->name, "Kishi"))) {
935 return true;
936 }
937 if (IsROGAlly(joystick)) {
938 /* I'm not sure if this is a Windows thing, or a quirk for ROG Ally,
939 * but we need to invert the sensor data on all axes.
940 */
941 *invert_sensors = true;
942 return true;
943 }
944 return false;
945}
946
947static void AttemptSensorFusion(SDL_Joystick *joystick, bool invert_sensors)
948{
949 SDL_SensorID *sensors;
950 unsigned int i, j;
951
952 SDL_AssertJoysticksLocked();
953
954 if (!SDL_InitSubSystem(SDL_INIT_SENSOR)) {
955 return;
956 }
957
958 sensors = SDL_GetSensors(NULL);
959 if (sensors) {
960 for (i = 0; sensors[i]; ++i) {
961 SDL_SensorID sensor = sensors[i];
962
963 if (!joystick->accel_sensor && SDL_GetSensorTypeForID(sensor) == SDL_SENSOR_ACCEL) {
964 // Increment the sensor subsystem reference count
965 SDL_InitSubSystem(SDL_INIT_SENSOR);
966
967 joystick->accel_sensor = sensor;
968 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f);
969 }
970 if (!joystick->gyro_sensor && SDL_GetSensorTypeForID(sensor) == SDL_SENSOR_GYRO) {
971 // Increment the sensor subsystem reference count
972 SDL_InitSubSystem(SDL_INIT_SENSOR);
973
974 joystick->gyro_sensor = sensor;
975 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f);
976 }
977 }
978 SDL_free(sensors);
979 }
980 SDL_QuitSubSystem(SDL_INIT_SENSOR);
981
982 /* SDL defines sensor orientation for phones relative to the natural
983 orientation, and for gamepads relative to being held in front of you.
984 When a phone is being used as a gamepad, its orientation changes,
985 so adjust sensor axes to match.
986 */
987 if (SDL_GetNaturalDisplayOrientation(SDL_GetPrimaryDisplay()) == SDL_ORIENTATION_LANDSCAPE) {
988 /* When a device in landscape orientation is laid flat, the axes change
989 orientation as follows:
990 -X to +X becomes -X to +X
991 -Y to +Y becomes +Z to -Z
992 -Z to +Z becomes -Y to +Y
993 */
994 joystick->sensor_transform[0][0] = 1.0f;
995 joystick->sensor_transform[1][2] = 1.0f;
996 joystick->sensor_transform[2][1] = -1.0f;
997 } else {
998 /* When a device in portrait orientation is rotated left and laid flat,
999 the axes change orientation as follows:
1000 -X to +X becomes +Z to -Z
1001 -Y to +Y becomes +X to -X
1002 -Z to +Z becomes -Y to +Y
1003 */
1004 joystick->sensor_transform[0][1] = -1.0f;
1005 joystick->sensor_transform[1][2] = 1.0f;
1006 joystick->sensor_transform[2][0] = -1.0f;
1007 }
1008
1009 if (invert_sensors) {
1010 for (i = 0; i < SDL_arraysize(joystick->sensor_transform); ++i) {
1011 for (j = 0; j < SDL_arraysize(joystick->sensor_transform[i]); ++j) {
1012 joystick->sensor_transform[i][j] *= -1.0f;
1013 }
1014 }
1015 }
1016}
1017
1018static void CleanupSensorFusion(SDL_Joystick *joystick)
1019{
1020 SDL_AssertJoysticksLocked();
1021
1022 if (joystick->accel_sensor || joystick->gyro_sensor) {
1023 if (joystick->accel_sensor) {
1024 if (joystick->accel) {
1025 SDL_CloseSensor(joystick->accel);
1026 joystick->accel = NULL;
1027 }
1028 joystick->accel_sensor = 0;
1029
1030 // Decrement the sensor subsystem reference count
1031 SDL_QuitSubSystem(SDL_INIT_SENSOR);
1032 }
1033 if (joystick->gyro_sensor) {
1034 if (joystick->gyro) {
1035 SDL_CloseSensor(joystick->gyro);
1036 joystick->gyro = NULL;
1037 }
1038 joystick->gyro_sensor = 0;
1039
1040 // Decrement the sensor subsystem reference count
1041 SDL_QuitSubSystem(SDL_INIT_SENSOR);
1042 }
1043 }
1044}
1045
1046/*
1047 * Open a joystick for use - the index passed as an argument refers to
1048 * the N'th joystick on the system. This index is the value which will
1049 * identify this joystick in future joystick events.
1050 *
1051 * This function returns a joystick identifier, or NULL if an error occurred.
1052 */
1053SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
1054{
1055 SDL_JoystickDriver *driver;
1056 int device_index;
1057 SDL_Joystick *joystick;
1058 SDL_Joystick *joysticklist;
1059 const char *joystickname = NULL;
1060 const char *joystickpath = NULL;
1061 bool invert_sensors = false;
1062 const SDL_SteamVirtualGamepadInfo *info;
1063
1064 SDL_LockJoysticks();
1065
1066 if (!SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
1067 SDL_UnlockJoysticks();
1068 return NULL;
1069 }
1070
1071 joysticklist = SDL_joysticks;
1072 /* If the joystick is already open, return it
1073 * it is important that we have a single joystick for each instance id
1074 */
1075 while (joysticklist) {
1076 if (instance_id == joysticklist->instance_id) {
1077 joystick = joysticklist;
1078 ++joystick->ref_count;
1079 SDL_UnlockJoysticks();
1080 return joystick;
1081 }
1082 joysticklist = joysticklist->next;
1083 }
1084
1085 // Create and initialize the joystick
1086 joystick = (SDL_Joystick *)SDL_calloc(1, sizeof(*joystick));
1087 if (!joystick) {
1088 SDL_UnlockJoysticks();
1089 return NULL;
1090 }
1091 SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, true);
1092 joystick->driver = driver;
1093 joystick->instance_id = instance_id;
1094 joystick->attached = true;
1095 joystick->led_expiration = SDL_GetTicks();
1096 joystick->battery_percent = -1;
1097
1098 if (!driver->Open(joystick, device_index)) {
1099 SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, false);
1100 SDL_free(joystick);
1101 SDL_UnlockJoysticks();
1102 return NULL;
1103 }
1104
1105 joystickname = driver->GetDeviceName(device_index);
1106 if (joystickname) {
1107 joystick->name = SDL_strdup(joystickname);
1108 }
1109
1110 joystickpath = driver->GetDevicePath(device_index);
1111 if (joystickpath) {
1112 joystick->path = SDL_strdup(joystickpath);
1113 }
1114
1115 joystick->guid = driver->GetDeviceGUID(device_index);
1116
1117 if (joystick->naxes > 0) {
1118 joystick->axes = (SDL_JoystickAxisInfo *)SDL_calloc(joystick->naxes, sizeof(*joystick->axes));
1119 }
1120 if (joystick->nballs > 0) {
1121 joystick->balls = (SDL_JoystickBallData *)SDL_calloc(joystick->nballs, sizeof(*joystick->balls));
1122 }
1123 if (joystick->nhats > 0) {
1124 joystick->hats = (Uint8 *)SDL_calloc(joystick->nhats, sizeof(*joystick->hats));
1125 }
1126 if (joystick->nbuttons > 0) {
1127 joystick->buttons = (bool *)SDL_calloc(joystick->nbuttons, sizeof(*joystick->buttons));
1128 }
1129 if (((joystick->naxes > 0) && !joystick->axes) ||
1130 ((joystick->nballs > 0) && !joystick->balls) ||
1131 ((joystick->nhats > 0) && !joystick->hats) ||
1132 ((joystick->nbuttons > 0) && !joystick->buttons)) {
1133 SDL_CloseJoystick(joystick);
1134 SDL_UnlockJoysticks();
1135 return NULL;
1136 }
1137
1138 // If this joystick is known to have all zero centered axes, skip the auto-centering code
1139 if (SDL_JoystickAxesCenteredAtZero(joystick)) {
1140 int i;
1141
1142 for (i = 0; i < joystick->naxes; ++i) {
1143 joystick->axes[i].has_initial_value = true;
1144 }
1145 }
1146
1147 // Get the Steam Input API handle
1148 info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id);
1149 if (info) {
1150 joystick->steam_handle = info->handle;
1151 }
1152
1153 // Use system gyro and accelerometer if the gamepad doesn't have built-in sensors
1154 if (ShouldAttemptSensorFusion(joystick, &invert_sensors)) {
1155 AttemptSensorFusion(joystick, invert_sensors);
1156 }
1157
1158 // Add joystick to list
1159 ++joystick->ref_count;
1160 // Link the joystick in the list
1161 joystick->next = SDL_joysticks;
1162 SDL_joysticks = joystick;
1163
1164 driver->Update(joystick);
1165
1166 SDL_UnlockJoysticks();
1167
1168 return joystick;
1169}
1170
1171SDL_JoystickID SDL_AttachVirtualJoystick(const SDL_VirtualJoystickDesc *desc)
1172{
1173#ifdef SDL_JOYSTICK_VIRTUAL
1174 SDL_JoystickID result;
1175
1176 SDL_LockJoysticks();
1177 result = SDL_JoystickAttachVirtualInner(desc);
1178 SDL_UnlockJoysticks();
1179 return result;
1180#else
1181 SDL_SetError("SDL not built with virtual-joystick support");
1182 return 0;
1183#endif
1184}
1185
1186bool SDL_DetachVirtualJoystick(SDL_JoystickID instance_id)
1187{
1188#ifdef SDL_JOYSTICK_VIRTUAL
1189 bool result;
1190
1191 SDL_LockJoysticks();
1192 result = SDL_JoystickDetachVirtualInner(instance_id);
1193 SDL_UnlockJoysticks();
1194 return result;
1195#else
1196 return SDL_SetError("SDL not built with virtual-joystick support");
1197#endif
1198}
1199
1200bool SDL_IsJoystickVirtual(SDL_JoystickID instance_id)
1201{
1202#ifdef SDL_JOYSTICK_VIRTUAL
1203 SDL_JoystickDriver *driver;
1204 int device_index;
1205 bool is_virtual = false;
1206
1207 SDL_LockJoysticks();
1208 if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
1209 if (driver == &SDL_VIRTUAL_JoystickDriver) {
1210 is_virtual = true;
1211 }
1212 }
1213 SDL_UnlockJoysticks();
1214
1215 return is_virtual;
1216#else
1217 return false;
1218#endif
1219}
1220
1221bool SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value)
1222{
1223 bool result;
1224
1225 SDL_LockJoysticks();
1226 {
1227 CHECK_JOYSTICK_MAGIC(joystick, false);
1228
1229#ifdef SDL_JOYSTICK_VIRTUAL
1230 result = SDL_SetJoystickVirtualAxisInner(joystick, axis, value);
1231#else
1232 result = SDL_SetError("SDL not built with virtual-joystick support");
1233#endif
1234 }
1235 SDL_UnlockJoysticks();
1236
1237 return result;
1238}
1239
1240bool SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel)
1241{
1242 bool result;
1243
1244 SDL_LockJoysticks();
1245 {
1246 CHECK_JOYSTICK_MAGIC(joystick, false);
1247
1248#ifdef SDL_JOYSTICK_VIRTUAL
1249 result = SDL_SetJoystickVirtualBallInner(joystick, ball, xrel, yrel);
1250#else
1251 result = SDL_SetError("SDL not built with virtual-joystick support");
1252#endif
1253 }
1254 SDL_UnlockJoysticks();
1255
1256 return result;
1257}
1258
1259bool SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, bool down)
1260{
1261 bool result;
1262
1263 SDL_LockJoysticks();
1264 {
1265 CHECK_JOYSTICK_MAGIC(joystick, false);
1266
1267#ifdef SDL_JOYSTICK_VIRTUAL
1268 result = SDL_SetJoystickVirtualButtonInner(joystick, button, down);
1269#else
1270 result = SDL_SetError("SDL not built with virtual-joystick support");
1271#endif
1272 }
1273 SDL_UnlockJoysticks();
1274
1275 return result;
1276}
1277
1278bool SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value)
1279{
1280 bool result;
1281
1282 SDL_LockJoysticks();
1283 {
1284 CHECK_JOYSTICK_MAGIC(joystick, false);
1285
1286#ifdef SDL_JOYSTICK_VIRTUAL
1287 result = SDL_SetJoystickVirtualHatInner(joystick, hat, value);
1288#else
1289 result = SDL_SetError("SDL not built with virtual-joystick support");
1290#endif
1291 }
1292 SDL_UnlockJoysticks();
1293
1294 return result;
1295}
1296
1297bool SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure)
1298{
1299 bool result;
1300
1301 SDL_LockJoysticks();
1302 {
1303 CHECK_JOYSTICK_MAGIC(joystick, false);
1304
1305#ifdef SDL_JOYSTICK_VIRTUAL
1306 result = SDL_SetJoystickVirtualTouchpadInner(joystick, touchpad, finger, down, x, y, pressure);
1307#else
1308 result = SDL_SetError("SDL not built with virtual-joystick support");
1309#endif
1310 }
1311 SDL_UnlockJoysticks();
1312
1313 return result;
1314}
1315
1316bool SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values)
1317{
1318 bool result;
1319
1320 SDL_LockJoysticks();
1321 {
1322 CHECK_JOYSTICK_MAGIC(joystick, false);
1323
1324#ifdef SDL_JOYSTICK_VIRTUAL
1325 result = SDL_SendJoystickVirtualSensorDataInner(joystick, type, sensor_timestamp, data, num_values);
1326#else
1327 result = SDL_SetError("SDL not built with virtual-joystick support");
1328#endif
1329 }
1330 SDL_UnlockJoysticks();
1331
1332 return result;
1333}
1334
1335/*
1336 * Checks to make sure the joystick is valid.
1337 */
1338bool SDL_IsJoystickValid(SDL_Joystick *joystick)
1339{
1340 SDL_AssertJoysticksLocked();
1341 return SDL_ObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK);
1342}
1343
1344bool SDL_PrivateJoystickGetAutoGamepadMapping(SDL_JoystickID instance_id, SDL_GamepadMapping *out)
1345{
1346 SDL_JoystickDriver *driver;
1347 int device_index;
1348 bool is_ok = false;
1349
1350 SDL_LockJoysticks();
1351 if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
1352 is_ok = driver->GetGamepadMapping(device_index, out);
1353 }
1354 SDL_UnlockJoysticks();
1355
1356 return is_ok;
1357}
1358
1359/*
1360 * Get the number of multi-dimensional axis controls on a joystick
1361 */
1362int SDL_GetNumJoystickAxes(SDL_Joystick *joystick)
1363{
1364 int result;
1365
1366 SDL_LockJoysticks();
1367 {
1368 CHECK_JOYSTICK_MAGIC(joystick, -1);
1369
1370 result = joystick->naxes;
1371 }
1372 SDL_UnlockJoysticks();
1373
1374 return result;
1375}
1376
1377/*
1378 * Get the number of hats on a joystick
1379 */
1380int SDL_GetNumJoystickHats(SDL_Joystick *joystick)
1381{
1382 int result;
1383
1384 SDL_LockJoysticks();
1385 {
1386 CHECK_JOYSTICK_MAGIC(joystick, -1);
1387
1388 result = joystick->nhats;
1389 }
1390 SDL_UnlockJoysticks();
1391
1392 return result;
1393}
1394
1395/*
1396 * Get the number of trackballs on a joystick
1397 */
1398int SDL_GetNumJoystickBalls(SDL_Joystick *joystick)
1399{
1400 CHECK_JOYSTICK_MAGIC(joystick, -1);
1401
1402 return joystick->nballs;
1403}
1404
1405/*
1406 * Get the number of buttons on a joystick
1407 */
1408int SDL_GetNumJoystickButtons(SDL_Joystick *joystick)
1409{
1410 int result;
1411
1412 SDL_LockJoysticks();
1413 {
1414 CHECK_JOYSTICK_MAGIC(joystick, -1);
1415
1416 result = joystick->nbuttons;
1417 }
1418 SDL_UnlockJoysticks();
1419
1420 return result;
1421}
1422
1423/*
1424 * Get the current state of an axis control on a joystick
1425 */
1426Sint16 SDL_GetJoystickAxis(SDL_Joystick *joystick, int axis)
1427{
1428 Sint16 state;
1429
1430 SDL_LockJoysticks();
1431 {
1432 CHECK_JOYSTICK_MAGIC(joystick, 0);
1433
1434 if (axis < joystick->naxes) {
1435 state = joystick->axes[axis].value;
1436 } else {
1437 SDL_SetError("Joystick only has %d axes", joystick->naxes);
1438 state = 0;
1439 }
1440 }
1441 SDL_UnlockJoysticks();
1442
1443 return state;
1444}
1445
1446/*
1447 * Get the initial state of an axis control on a joystick
1448 */
1449bool SDL_GetJoystickAxisInitialState(SDL_Joystick *joystick, int axis, Sint16 *state)
1450{
1451 bool result;
1452
1453 SDL_LockJoysticks();
1454 {
1455 CHECK_JOYSTICK_MAGIC(joystick, false);
1456
1457 if (axis >= joystick->naxes) {
1458 SDL_SetError("Joystick only has %d axes", joystick->naxes);
1459 result = false;
1460 } else {
1461 if (state) {
1462 *state = joystick->axes[axis].initial_value;
1463 }
1464 result = joystick->axes[axis].has_initial_value;
1465 }
1466 }
1467 SDL_UnlockJoysticks();
1468
1469 return result;
1470}
1471
1472/*
1473 * Get the current state of a hat on a joystick
1474 */
1475Uint8 SDL_GetJoystickHat(SDL_Joystick *joystick, int hat)
1476{
1477 Uint8 state;
1478
1479 SDL_LockJoysticks();
1480 {
1481 CHECK_JOYSTICK_MAGIC(joystick, 0);
1482
1483 if (hat < joystick->nhats) {
1484 state = joystick->hats[hat];
1485 } else {
1486 SDL_SetError("Joystick only has %d hats", joystick->nhats);
1487 state = 0;
1488 }
1489 }
1490 SDL_UnlockJoysticks();
1491
1492 return state;
1493}
1494
1495/*
1496 * Get the ball axis change since the last poll
1497 */
1498bool SDL_GetJoystickBall(SDL_Joystick *joystick, int ball, int *dx, int *dy)
1499{
1500 bool result;
1501
1502 SDL_LockJoysticks();
1503 {
1504 CHECK_JOYSTICK_MAGIC(joystick, false);
1505
1506 if (ball < joystick->nballs) {
1507 if (dx) {
1508 *dx = joystick->balls[ball].dx;
1509 }
1510 if (dy) {
1511 *dy = joystick->balls[ball].dy;
1512 }
1513 joystick->balls[ball].dx = 0;
1514 joystick->balls[ball].dy = 0;
1515 result = true;
1516 } else {
1517 result = SDL_SetError("Joystick only has %d balls", joystick->nballs);
1518 }
1519 }
1520 SDL_UnlockJoysticks();
1521
1522 return result;
1523}
1524
1525/*
1526 * Get the current state of a button on a joystick
1527 */
1528bool SDL_GetJoystickButton(SDL_Joystick *joystick, int button)
1529{
1530 bool down = false;
1531
1532 SDL_LockJoysticks();
1533 {
1534 CHECK_JOYSTICK_MAGIC(joystick, false);
1535
1536 if (button < joystick->nbuttons) {
1537 down = joystick->buttons[button];
1538 } else {
1539 SDL_SetError("Joystick only has %d buttons", joystick->nbuttons);
1540 }
1541 }
1542 SDL_UnlockJoysticks();
1543
1544 return down;
1545}
1546
1547/*
1548 * Return if the joystick in question is currently attached to the system,
1549 * \return false if not plugged in, true if still present.
1550 */
1551bool SDL_JoystickConnected(SDL_Joystick *joystick)
1552{
1553 bool result;
1554
1555 SDL_LockJoysticks();
1556 {
1557 CHECK_JOYSTICK_MAGIC(joystick, false);
1558
1559 result = joystick->attached;
1560 }
1561 SDL_UnlockJoysticks();
1562
1563 return result;
1564}
1565
1566/*
1567 * Get the instance id for this opened joystick
1568 */
1569SDL_JoystickID SDL_GetJoystickID(SDL_Joystick *joystick)
1570{
1571 SDL_JoystickID result;
1572
1573 SDL_LockJoysticks();
1574 {
1575 CHECK_JOYSTICK_MAGIC(joystick, 0);
1576
1577 result = joystick->instance_id;
1578 }
1579 SDL_UnlockJoysticks();
1580
1581 return result;
1582}
1583
1584/*
1585 * Return the SDL_Joystick associated with an instance id.
1586 */
1587SDL_Joystick *SDL_GetJoystickFromID(SDL_JoystickID instance_id)
1588{
1589 SDL_Joystick *joystick;
1590
1591 SDL_LockJoysticks();
1592 for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
1593 if (joystick->instance_id == instance_id) {
1594 break;
1595 }
1596 }
1597 SDL_UnlockJoysticks();
1598 return joystick;
1599}
1600
1601/**
1602 * Return the SDL_Joystick associated with a player index.
1603 */
1604SDL_Joystick *SDL_GetJoystickFromPlayerIndex(int player_index)
1605{
1606 SDL_JoystickID instance_id;
1607 SDL_Joystick *joystick;
1608
1609 SDL_LockJoysticks();
1610 instance_id = SDL_GetJoystickIDForPlayerIndex(player_index);
1611 for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
1612 if (joystick->instance_id == instance_id) {
1613 break;
1614 }
1615 }
1616 SDL_UnlockJoysticks();
1617 return joystick;
1618}
1619
1620/*
1621 * Get the properties associated with a joystick
1622 */
1623SDL_PropertiesID SDL_GetJoystickProperties(SDL_Joystick *joystick)
1624{
1625 SDL_PropertiesID result;
1626
1627 SDL_LockJoysticks();
1628 {
1629 CHECK_JOYSTICK_MAGIC(joystick, 0);
1630
1631 if (joystick->props == 0) {
1632 joystick->props = SDL_CreateProperties();
1633 }
1634 result = joystick->props;
1635 }
1636 SDL_UnlockJoysticks();
1637
1638 return result;
1639}
1640
1641/*
1642 * Get the friendly name of this joystick
1643 */
1644const char *SDL_GetJoystickName(SDL_Joystick *joystick)
1645{
1646 const char *result;
1647 const SDL_SteamVirtualGamepadInfo *info;
1648
1649 SDL_LockJoysticks();
1650 {
1651 CHECK_JOYSTICK_MAGIC(joystick, NULL);
1652
1653 info = SDL_GetJoystickVirtualGamepadInfoForID(joystick->instance_id);
1654 if (info) {
1655 result = SDL_GetPersistentString(info->name);
1656 } else {
1657 result = SDL_GetPersistentString(joystick->name);
1658 }
1659 }
1660 SDL_UnlockJoysticks();
1661
1662 return result;
1663}
1664
1665/*
1666 * Get the implementation dependent path of this joystick
1667 */
1668const char *SDL_GetJoystickPath(SDL_Joystick *joystick)
1669{
1670 const char *result;
1671
1672 SDL_LockJoysticks();
1673 {
1674 CHECK_JOYSTICK_MAGIC(joystick, NULL);
1675
1676 if (joystick->path) {
1677 result = SDL_GetPersistentString(joystick->path);
1678 } else {
1679 SDL_Unsupported();
1680 result = NULL;
1681 }
1682 }
1683 SDL_UnlockJoysticks();
1684
1685 return result;
1686}
1687
1688/**
1689 * Get the player index of an opened joystick, or -1 if it's not available
1690 */
1691int SDL_GetJoystickPlayerIndex(SDL_Joystick *joystick)
1692{
1693 int result;
1694
1695 SDL_LockJoysticks();
1696 {
1697 CHECK_JOYSTICK_MAGIC(joystick, -1);
1698
1699 result = SDL_GetPlayerIndexForJoystickID(joystick->instance_id);
1700 }
1701 SDL_UnlockJoysticks();
1702
1703 return result;
1704}
1705
1706/**
1707 * Set the player index of an opened joystick
1708 */
1709bool SDL_SetJoystickPlayerIndex(SDL_Joystick *joystick, int player_index)
1710{
1711 bool result;
1712
1713 SDL_LockJoysticks();
1714 {
1715 CHECK_JOYSTICK_MAGIC(joystick, false);
1716
1717 result = SDL_SetJoystickIDForPlayerIndex(player_index, joystick->instance_id);
1718 }
1719 SDL_UnlockJoysticks();
1720
1721 return result;
1722}
1723
1724bool SDL_RumbleJoystick(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
1725{
1726 bool result;
1727
1728 SDL_LockJoysticks();
1729 {
1730 CHECK_JOYSTICK_MAGIC(joystick, false);
1731
1732 if (low_frequency_rumble == joystick->low_frequency_rumble &&
1733 high_frequency_rumble == joystick->high_frequency_rumble) {
1734 // Just update the expiration
1735 result = true;
1736 } else {
1737 result = joystick->driver->Rumble(joystick, low_frequency_rumble, high_frequency_rumble);
1738 if (result) {
1739 joystick->rumble_resend = SDL_GetTicks() + SDL_RUMBLE_RESEND_MS;
1740 if (joystick->rumble_resend == 0) {
1741 joystick->rumble_resend = 1;
1742 }
1743 } else {
1744 joystick->rumble_resend = 0;
1745 }
1746 }
1747
1748 if (result) {
1749 joystick->low_frequency_rumble = low_frequency_rumble;
1750 joystick->high_frequency_rumble = high_frequency_rumble;
1751
1752 if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) {
1753 joystick->rumble_expiration = SDL_GetTicks() + SDL_min(duration_ms, SDL_MAX_RUMBLE_DURATION_MS);
1754 if (!joystick->rumble_expiration) {
1755 joystick->rumble_expiration = 1;
1756 }
1757 } else {
1758 joystick->rumble_expiration = 0;
1759 joystick->rumble_resend = 0;
1760 }
1761 }
1762 }
1763 SDL_UnlockJoysticks();
1764
1765 return result;
1766}
1767
1768bool SDL_RumbleJoystickTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms)
1769{
1770 bool result;
1771
1772 SDL_LockJoysticks();
1773 {
1774 CHECK_JOYSTICK_MAGIC(joystick, false);
1775
1776 if (left_rumble == joystick->left_trigger_rumble && right_rumble == joystick->right_trigger_rumble) {
1777 // Just update the expiration
1778 result = true;
1779 } else {
1780 result = joystick->driver->RumbleTriggers(joystick, left_rumble, right_rumble);
1781 }
1782
1783 if (result) {
1784 joystick->left_trigger_rumble = left_rumble;
1785 joystick->right_trigger_rumble = right_rumble;
1786
1787 if ((left_rumble || right_rumble) && duration_ms) {
1788 joystick->trigger_rumble_expiration = SDL_GetTicks() + SDL_min(duration_ms, SDL_MAX_RUMBLE_DURATION_MS);
1789 } else {
1790 joystick->trigger_rumble_expiration = 0;
1791 }
1792 }
1793 }
1794 SDL_UnlockJoysticks();
1795
1796 return result;
1797}
1798
1799bool SDL_SetJoystickLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1800{
1801 bool result;
1802 bool isfreshvalue;
1803
1804 SDL_LockJoysticks();
1805 {
1806 CHECK_JOYSTICK_MAGIC(joystick, false);
1807
1808 isfreshvalue = red != joystick->led_red ||
1809 green != joystick->led_green ||
1810 blue != joystick->led_blue;
1811
1812 if (isfreshvalue || SDL_GetTicks() >= joystick->led_expiration) {
1813 result = joystick->driver->SetLED(joystick, red, green, blue);
1814 joystick->led_expiration = SDL_GetTicks() + SDL_LED_MIN_REPEAT_MS;
1815 } else {
1816 // Avoid spamming the driver
1817 result = true;
1818 }
1819
1820 // Save the LED value regardless of success, so we don't spam the driver
1821 joystick->led_red = red;
1822 joystick->led_green = green;
1823 joystick->led_blue = blue;
1824 }
1825 SDL_UnlockJoysticks();
1826
1827 return result;
1828}
1829
1830bool SDL_SendJoystickEffect(SDL_Joystick *joystick, const void *data, int size)
1831{
1832 bool result;
1833
1834 SDL_LockJoysticks();
1835 {
1836 CHECK_JOYSTICK_MAGIC(joystick, false);
1837
1838 result = joystick->driver->SendEffect(joystick, data, size);
1839 }
1840 SDL_UnlockJoysticks();
1841
1842 return result;
1843}
1844
1845/*
1846 * Close a joystick previously opened with SDL_OpenJoystick()
1847 */
1848void SDL_CloseJoystick(SDL_Joystick *joystick)
1849{
1850 SDL_Joystick *joysticklist;
1851 SDL_Joystick *joysticklistprev;
1852 int i;
1853
1854 SDL_LockJoysticks();
1855 {
1856 CHECK_JOYSTICK_MAGIC(joystick,);
1857
1858 // First decrement ref count
1859 if (--joystick->ref_count > 0) {
1860 SDL_UnlockJoysticks();
1861 return;
1862 }
1863
1864 SDL_DestroyProperties(joystick->props);
1865
1866 if (joystick->rumble_expiration) {
1867 SDL_RumbleJoystick(joystick, 0, 0, 0);
1868 }
1869 if (joystick->trigger_rumble_expiration) {
1870 SDL_RumbleJoystickTriggers(joystick, 0, 0, 0);
1871 }
1872
1873 CleanupSensorFusion(joystick);
1874
1875 joystick->driver->Close(joystick);
1876 joystick->hwdata = NULL;
1877 SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, false);
1878
1879 joysticklist = SDL_joysticks;
1880 joysticklistprev = NULL;
1881 while (joysticklist) {
1882 if (joystick == joysticklist) {
1883 if (joysticklistprev) {
1884 // unlink this entry
1885 joysticklistprev->next = joysticklist->next;
1886 } else {
1887 SDL_joysticks = joystick->next;
1888 }
1889 break;
1890 }
1891 joysticklistprev = joysticklist;
1892 joysticklist = joysticklist->next;
1893 }
1894
1895 // Free the data associated with this joystick
1896 SDL_free(joystick->name);
1897 SDL_free(joystick->path);
1898 SDL_free(joystick->serial);
1899 SDL_free(joystick->axes);
1900 SDL_free(joystick->balls);
1901 SDL_free(joystick->hats);
1902 SDL_free(joystick->buttons);
1903 for (i = 0; i < joystick->ntouchpads; i++) {
1904 SDL_JoystickTouchpadInfo *touchpad = &joystick->touchpads[i];
1905 SDL_free(touchpad->fingers);
1906 }
1907 SDL_free(joystick->touchpads);
1908 SDL_free(joystick->sensors);
1909 SDL_free(joystick);
1910 }
1911 SDL_UnlockJoysticks();
1912}
1913
1914void SDL_QuitJoysticks(void)
1915{
1916 int i;
1917 SDL_JoystickID *joysticks;
1918
1919 SDL_LockJoysticks();
1920
1921 SDL_joysticks_quitting = true;
1922
1923 joysticks = SDL_GetJoysticks(NULL);
1924 if (joysticks) {
1925 for (i = 0; joysticks[i]; ++i) {
1926 SDL_PrivateJoystickRemoved(joysticks[i]);
1927 }
1928 SDL_free(joysticks);
1929 }
1930
1931 while (SDL_joysticks) {
1932 SDL_joysticks->ref_count = 1;
1933 SDL_CloseJoystick(SDL_joysticks);
1934 }
1935
1936 // Quit drivers in reverse order to avoid breaking dependencies between drivers
1937 for (i = SDL_arraysize(SDL_joystick_drivers) - 1; i >= 0; --i) {
1938 SDL_joystick_drivers[i]->Quit();
1939 }
1940
1941 if (SDL_joystick_players) {
1942 SDL_free(SDL_joystick_players);
1943 SDL_joystick_players = NULL;
1944 SDL_joystick_player_count = 0;
1945 }
1946
1947 SDL_QuitSubSystem(SDL_INIT_EVENTS);
1948
1949 SDL_QuitSteamVirtualGamepadInfo();
1950
1951 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
1952 SDL_JoystickAllowBackgroundEventsChanged, NULL);
1953
1954 SDL_FreeVIDPIDList(&arcadestick_devices);
1955 SDL_FreeVIDPIDList(&blacklist_devices);
1956 SDL_FreeVIDPIDList(&flightstick_devices);
1957 SDL_FreeVIDPIDList(&gamecube_devices);
1958 SDL_FreeVIDPIDList(&rog_gamepad_mice);
1959 SDL_FreeVIDPIDList(&throttle_devices);
1960 SDL_FreeVIDPIDList(&wheel_devices);
1961 SDL_FreeVIDPIDList(&zero_centered_devices);
1962
1963 SDL_QuitGamepadMappings();
1964
1965 SDL_joysticks_quitting = false;
1966 SDL_joysticks_initialized = false;
1967
1968 SDL_UnlockJoysticks();
1969}
1970
1971static bool SDL_PrivateJoystickShouldIgnoreEvent(void)
1972{
1973 if (SDL_joystick_allows_background_events) {
1974 return false;
1975 }
1976
1977 if (SDL_HasWindows() && SDL_GetKeyboardFocus() == NULL) {
1978 // We have windows but we don't have focus, ignore the event.
1979 return true;
1980 }
1981 return false;
1982}
1983
1984// These are global for SDL_sysjoystick.c and SDL_events.c
1985
1986void SDL_PrivateJoystickAddTouchpad(SDL_Joystick *joystick, int nfingers)
1987{
1988 int ntouchpads;
1989 SDL_JoystickTouchpadInfo *touchpads;
1990
1991 SDL_AssertJoysticksLocked();
1992
1993 ntouchpads = joystick->ntouchpads + 1;
1994 touchpads = (SDL_JoystickTouchpadInfo *)SDL_realloc(joystick->touchpads, (ntouchpads * sizeof(SDL_JoystickTouchpadInfo)));
1995 if (touchpads) {
1996 SDL_JoystickTouchpadInfo *touchpad = &touchpads[ntouchpads - 1];
1997 SDL_JoystickTouchpadFingerInfo *fingers = (SDL_JoystickTouchpadFingerInfo *)SDL_calloc(nfingers, sizeof(SDL_JoystickTouchpadFingerInfo));
1998
1999 if (fingers) {
2000 touchpad->nfingers = nfingers;
2001 touchpad->fingers = fingers;
2002 } else {
2003 // Out of memory, this touchpad won't be active
2004 touchpad->nfingers = 0;
2005 touchpad->fingers = NULL;
2006 }
2007
2008 joystick->ntouchpads = ntouchpads;
2009 joystick->touchpads = touchpads;
2010 }
2011}
2012
2013void SDL_PrivateJoystickAddSensor(SDL_Joystick *joystick, SDL_SensorType type, float rate)
2014{
2015 int nsensors;
2016 SDL_JoystickSensorInfo *sensors;
2017
2018 SDL_AssertJoysticksLocked();
2019
2020 nsensors = joystick->nsensors + 1;
2021 sensors = (SDL_JoystickSensorInfo *)SDL_realloc(joystick->sensors, (nsensors * sizeof(SDL_JoystickSensorInfo)));
2022 if (sensors) {
2023 SDL_JoystickSensorInfo *sensor = &sensors[nsensors - 1];
2024
2025 SDL_zerop(sensor);
2026 sensor->type = type;
2027 sensor->rate = rate;
2028
2029 joystick->nsensors = nsensors;
2030 joystick->sensors = sensors;
2031 }
2032}
2033
2034void SDL_PrivateJoystickSensorRate(SDL_Joystick *joystick, SDL_SensorType type, float rate)
2035{
2036 int i;
2037 SDL_AssertJoysticksLocked();
2038
2039 for (i = 0; i < joystick->nsensors; ++i) {
2040 if (joystick->sensors[i].type == type) {
2041 joystick->sensors[i].rate = rate;
2042 }
2043 }
2044}
2045
2046void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id)
2047{
2048 SDL_JoystickDriver *driver;
2049 int device_index;
2050 int player_index = -1;
2051
2052 SDL_AssertJoysticksLocked();
2053
2054 if (SDL_JoysticksQuitting()) {
2055 return;
2056 }
2057
2058 SDL_joystick_being_added = true;
2059
2060 if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
2061 player_index = driver->GetDeviceSteamVirtualGamepadSlot(device_index);
2062 if (player_index < 0) {
2063 player_index = driver->GetDevicePlayerIndex(device_index);
2064 }
2065 }
2066 if (player_index < 0 && SDL_IsGamepad(instance_id)) {
2067 player_index = SDL_FindFreePlayerIndex();
2068 }
2069 if (player_index >= 0) {
2070 SDL_SetJoystickIDForPlayerIndex(player_index, instance_id);
2071 }
2072
2073 {
2074 SDL_Event event;
2075
2076 event.type = SDL_EVENT_JOYSTICK_ADDED;
2077 event.common.timestamp = 0;
2078
2079 if (SDL_EventEnabled(event.type)) {
2080 event.jdevice.which = instance_id;
2081 SDL_PushEvent(&event);
2082 }
2083 }
2084
2085 SDL_joystick_being_added = false;
2086
2087 if (SDL_IsGamepad(instance_id)) {
2088 SDL_PrivateGamepadAdded(instance_id);
2089 }
2090}
2091
2092bool SDL_IsJoystickBeingAdded(void)
2093{
2094 return SDL_joystick_being_added;
2095}
2096
2097void SDL_PrivateJoystickForceRecentering(SDL_Joystick *joystick)
2098{
2099 Uint8 i, j;
2100 Uint64 timestamp = SDL_GetTicksNS();
2101
2102 SDL_AssertJoysticksLocked();
2103
2104 // Tell the app that everything is centered/unpressed...
2105 for (i = 0; i < joystick->naxes; i++) {
2106 if (joystick->axes[i].has_initial_value) {
2107 SDL_SendJoystickAxis(timestamp, joystick, i, joystick->axes[i].zero);
2108 }
2109 }
2110
2111 for (i = 0; i < joystick->nbuttons; i++) {
2112 SDL_SendJoystickButton(timestamp, joystick, i, false);
2113 }
2114
2115 for (i = 0; i < joystick->nhats; i++) {
2116 SDL_SendJoystickHat(timestamp, joystick, i, SDL_HAT_CENTERED);
2117 }
2118
2119 for (i = 0; i < joystick->ntouchpads; i++) {
2120 SDL_JoystickTouchpadInfo *touchpad = &joystick->touchpads[i];
2121
2122 for (j = 0; j < touchpad->nfingers; ++j) {
2123 SDL_SendJoystickTouchpad(timestamp, joystick, i, j, false, 0.0f, 0.0f, 0.0f);
2124 }
2125 }
2126}
2127
2128void SDL_PrivateJoystickRemoved(SDL_JoystickID instance_id)
2129{
2130 SDL_Joystick *joystick = NULL;
2131 int player_index;
2132 SDL_Event event;
2133
2134 SDL_AssertJoysticksLocked();
2135
2136 // Find this joystick...
2137 for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
2138 if (joystick->instance_id == instance_id) {
2139 SDL_PrivateJoystickForceRecentering(joystick);
2140 joystick->attached = false;
2141 break;
2142 }
2143 }
2144
2145 if (SDL_IsGamepad(instance_id)) {
2146 SDL_PrivateGamepadRemoved(instance_id);
2147 }
2148
2149 event.type = SDL_EVENT_JOYSTICK_REMOVED;
2150 event.common.timestamp = 0;
2151
2152 if (SDL_EventEnabled(event.type)) {
2153 event.jdevice.which = instance_id;
2154 SDL_PushEvent(&event);
2155 }
2156
2157 player_index = SDL_GetPlayerIndexForJoystickID(instance_id);
2158 if (player_index >= 0) {
2159 SDL_joystick_players[player_index] = 0;
2160 }
2161}
2162
2163void SDL_SendJoystickAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, Sint16 value)
2164{
2165 SDL_JoystickAxisInfo *info;
2166
2167 SDL_AssertJoysticksLocked();
2168
2169 // Make sure we're not getting garbage or duplicate events
2170 if (axis >= joystick->naxes) {
2171 return;
2172 }
2173
2174 info = &joystick->axes[axis];
2175 if (!info->has_initial_value ||
2176 (!info->has_second_value && (info->initial_value <= -32767 || info->initial_value == 32767) && SDL_abs(value) < (SDL_JOYSTICK_AXIS_MAX / 4))) {
2177 info->initial_value = value;
2178 info->value = value;
2179 info->zero = value;
2180 info->has_initial_value = true;
2181 } else if (value == info->value && !info->sending_initial_value) {
2182 return;
2183 } else {
2184 info->has_second_value = true;
2185 }
2186 if (!info->sent_initial_value) {
2187 // Make sure we don't send motion until there's real activity on this axis
2188 const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; // ShanWan PS3 controller needed 96
2189 if (SDL_abs(value - info->value) <= MAX_ALLOWED_JITTER &&
2190 !SDL_IsJoystickVIRTUAL(joystick->guid)) {
2191 return;
2192 }
2193 info->sent_initial_value = true;
2194 info->sending_initial_value = true;
2195 SDL_SendJoystickAxis(timestamp, joystick, axis, info->initial_value);
2196 info->sending_initial_value = false;
2197 }
2198
2199 /* We ignore events if we don't have keyboard focus, except for centering
2200 * events.
2201 */
2202 if (SDL_PrivateJoystickShouldIgnoreEvent()) {
2203 if (info->sending_initial_value ||
2204 (value > info->zero && value >= info->value) ||
2205 (value < info->zero && value <= info->value)) {
2206 return;
2207 }
2208 }
2209
2210 // Update internal joystick state
2211 SDL_assert(timestamp != 0);
2212 info->value = value;
2213 joystick->update_complete = timestamp;
2214
2215 // Post the event, if desired
2216 if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_AXIS_MOTION)) {
2217 SDL_Event event;
2218 event.type = SDL_EVENT_JOYSTICK_AXIS_MOTION;
2219 event.common.timestamp = timestamp;
2220 event.jaxis.which = joystick->instance_id;
2221 event.jaxis.axis = axis;
2222 event.jaxis.value = value;
2223 SDL_PushEvent(&event);
2224 }
2225}
2226
2227void SDL_SendJoystickBall(Uint64 timestamp, SDL_Joystick *joystick, Uint8 ball, Sint16 xrel, Sint16 yrel)
2228{
2229 SDL_AssertJoysticksLocked();
2230
2231 // Make sure we're not getting garbage events
2232 if (ball >= joystick->nballs) {
2233 return;
2234 }
2235
2236 // We ignore events if we don't have keyboard focus.
2237 if (SDL_PrivateJoystickShouldIgnoreEvent()) {
2238 return;
2239 }
2240
2241 // Update internal mouse state
2242 joystick->balls[ball].dx += xrel;
2243 joystick->balls[ball].dy += yrel;
2244
2245 // Post the event, if desired
2246 if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_BALL_MOTION)) {
2247 SDL_Event event;
2248 event.type = SDL_EVENT_JOYSTICK_BALL_MOTION;
2249 event.common.timestamp = timestamp;
2250 event.jball.which = joystick->instance_id;
2251 event.jball.ball = ball;
2252 event.jball.xrel = xrel;
2253 event.jball.yrel = yrel;
2254 SDL_PushEvent(&event);
2255 }
2256}
2257
2258void SDL_SendJoystickHat(Uint64 timestamp, SDL_Joystick *joystick, Uint8 hat, Uint8 value)
2259{
2260 SDL_AssertJoysticksLocked();
2261
2262 // Make sure we're not getting garbage or duplicate events
2263 if (hat >= joystick->nhats) {
2264 return;
2265 }
2266 if (value == joystick->hats[hat]) {
2267 return;
2268 }
2269
2270 /* We ignore events if we don't have keyboard focus, except for centering
2271 * events.
2272 */
2273 if (SDL_PrivateJoystickShouldIgnoreEvent()) {
2274 if (value != SDL_HAT_CENTERED) {
2275 return;
2276 }
2277 }
2278
2279 // Update internal joystick state
2280 SDL_assert(timestamp != 0);
2281 joystick->hats[hat] = value;
2282 joystick->update_complete = timestamp;
2283
2284 // Post the event, if desired
2285 if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_HAT_MOTION)) {
2286 SDL_Event event;
2287 event.type = SDL_EVENT_JOYSTICK_HAT_MOTION;
2288 event.common.timestamp = timestamp;
2289 event.jhat.which = joystick->instance_id;
2290 event.jhat.hat = hat;
2291 event.jhat.value = value;
2292 SDL_PushEvent(&event);
2293 }
2294}
2295
2296void SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 button, bool down)
2297{
2298 SDL_Event event;
2299
2300 SDL_AssertJoysticksLocked();
2301
2302 if (down) {
2303 event.type = SDL_EVENT_JOYSTICK_BUTTON_DOWN;
2304 } else {
2305 event.type = SDL_EVENT_JOYSTICK_BUTTON_UP;
2306 }
2307
2308 // Make sure we're not getting garbage or duplicate events
2309 if (button >= joystick->nbuttons) {
2310 return;
2311 }
2312 if (down == joystick->buttons[button]) {
2313 return;
2314 }
2315
2316 /* We ignore events if we don't have keyboard focus, except for button
2317 * release. */
2318 if (SDL_PrivateJoystickShouldIgnoreEvent()) {
2319 if (down) {
2320 return;
2321 }
2322 }
2323
2324 // Update internal joystick state
2325 SDL_assert(timestamp != 0);
2326 joystick->buttons[button] = down;
2327 joystick->update_complete = timestamp;
2328
2329 // Post the event, if desired
2330 if (SDL_EventEnabled(event.type)) {
2331 event.common.timestamp = timestamp;
2332 event.jbutton.which = joystick->instance_id;
2333 event.jbutton.button = button;
2334 event.jbutton.down = down;
2335 SDL_PushEvent(&event);
2336 }
2337}
2338
2339static void SendSteamHandleUpdateEvents(void)
2340{
2341 SDL_Joystick *joystick;
2342 const SDL_SteamVirtualGamepadInfo *info;
2343
2344 // Check to see if any Steam handles changed
2345 for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
2346 bool changed = false;
2347
2348 if (!SDL_IsGamepad(joystick->instance_id)) {
2349 continue;
2350 }
2351
2352 info = SDL_GetJoystickVirtualGamepadInfoForID(joystick->instance_id);
2353 if (info) {
2354 if (joystick->steam_handle != info->handle) {
2355 joystick->steam_handle = info->handle;
2356 changed = true;
2357 }
2358 } else {
2359 if (joystick->steam_handle != 0) {
2360 joystick->steam_handle = 0;
2361 changed = true;
2362 }
2363 }
2364 if (changed) {
2365 SDL_Event event;
2366
2367 SDL_zero(event);
2368 event.type = SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED;
2369 event.common.timestamp = 0;
2370 event.gdevice.which = joystick->instance_id;
2371 SDL_PushEvent(&event);
2372 }
2373 }
2374}
2375
2376void SDL_UpdateJoysticks(void)
2377{
2378 int i;
2379 Uint64 now;
2380 SDL_Joystick *joystick;
2381
2382 if (!SDL_WasInit(SDL_INIT_JOYSTICK)) {
2383 return;
2384 }
2385
2386 SDL_LockJoysticks();
2387
2388 if (SDL_UpdateSteamVirtualGamepadInfo()) {
2389 SendSteamHandleUpdateEvents();
2390 }
2391
2392#ifdef SDL_JOYSTICK_HIDAPI
2393 // Special function for HIDAPI devices, as a single device can provide multiple SDL_Joysticks
2394 HIDAPI_UpdateDevices();
2395#endif // SDL_JOYSTICK_HIDAPI
2396
2397 for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
2398 if (!joystick->attached) {
2399 continue;
2400 }
2401
2402 joystick->driver->Update(joystick);
2403
2404 if (joystick->delayed_guide_button) {
2405 SDL_GamepadHandleDelayedGuideButton(joystick);
2406 }
2407
2408 now = SDL_GetTicks();
2409 if (joystick->rumble_expiration && now >= joystick->rumble_expiration) {
2410 SDL_RumbleJoystick(joystick, 0, 0, 0);
2411 joystick->rumble_resend = 0;
2412 }
2413
2414 if (joystick->rumble_resend && now >= joystick->rumble_resend) {
2415 joystick->driver->Rumble(joystick, joystick->low_frequency_rumble, joystick->high_frequency_rumble);
2416 joystick->rumble_resend = now + SDL_RUMBLE_RESEND_MS;
2417 if (joystick->rumble_resend == 0) {
2418 joystick->rumble_resend = 1;
2419 }
2420 }
2421
2422 if (joystick->trigger_rumble_expiration && now >= joystick->trigger_rumble_expiration) {
2423 SDL_RumbleJoystickTriggers(joystick, 0, 0, 0);
2424 }
2425 }
2426
2427 if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_UPDATE_COMPLETE)) {
2428 for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
2429 if (joystick->update_complete) {
2430 SDL_Event event;
2431
2432 event.type = SDL_EVENT_JOYSTICK_UPDATE_COMPLETE;
2433 event.common.timestamp = joystick->update_complete;
2434 event.jdevice.which = joystick->instance_id;
2435 SDL_PushEvent(&event);
2436
2437 joystick->update_complete = 0;
2438 }
2439 }
2440 }
2441
2442 /* this needs to happen AFTER walking the joystick list above, so that any
2443 dangling hardware data from removed devices can be free'd
2444 */
2445 for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
2446 SDL_joystick_drivers[i]->Detect();
2447 }
2448
2449 SDL_UnlockJoysticks();
2450}
2451
2452static const Uint32 SDL_joystick_event_list[] = {
2453 SDL_EVENT_JOYSTICK_AXIS_MOTION,
2454 SDL_EVENT_JOYSTICK_BALL_MOTION,
2455 SDL_EVENT_JOYSTICK_HAT_MOTION,
2456 SDL_EVENT_JOYSTICK_BUTTON_DOWN,
2457 SDL_EVENT_JOYSTICK_BUTTON_UP,
2458 SDL_EVENT_JOYSTICK_ADDED,
2459 SDL_EVENT_JOYSTICK_REMOVED,
2460 SDL_EVENT_JOYSTICK_BATTERY_UPDATED
2461};
2462
2463void SDL_SetJoystickEventsEnabled(bool enabled)
2464{
2465 unsigned int i;
2466
2467 for (i = 0; i < SDL_arraysize(SDL_joystick_event_list); ++i) {
2468 SDL_SetEventEnabled(SDL_joystick_event_list[i], enabled);
2469 }
2470}
2471
2472bool SDL_JoystickEventsEnabled(void)
2473{
2474 bool enabled = false;
2475 unsigned int i;
2476
2477 for (i = 0; i < SDL_arraysize(SDL_joystick_event_list); ++i) {
2478 enabled = SDL_EventEnabled(SDL_joystick_event_list[i]);
2479 if (enabled) {
2480 break;
2481 }
2482 }
2483 return enabled;
2484}
2485
2486void SDL_GetJoystickGUIDInfo(SDL_GUID guid, Uint16 *vendor, Uint16 *product, Uint16 *version, Uint16 *crc16)
2487{
2488 Uint16 *guid16 = (Uint16 *)guid.data;
2489 Uint16 bus = SDL_Swap16LE(guid16[0]);
2490
2491 if ((bus < ' ' || bus == SDL_HARDWARE_BUS_VIRTUAL) && guid16[3] == 0x0000 && guid16[5] == 0x0000) {
2492 /* This GUID fits the standard form:
2493 * 16-bit bus
2494 * 16-bit CRC16 of the joystick name (can be zero)
2495 * 16-bit vendor ID
2496 * 16-bit zero
2497 * 16-bit product ID
2498 * 16-bit zero
2499 * 16-bit version
2500 * 8-bit driver identifier ('h' for HIDAPI, 'x' for XInput, etc.)
2501 * 8-bit driver-dependent type info
2502 */
2503 if (vendor) {
2504 *vendor = SDL_Swap16LE(guid16[2]);
2505 }
2506 if (product) {
2507 *product = SDL_Swap16LE(guid16[4]);
2508 }
2509 if (version) {
2510 *version = SDL_Swap16LE(guid16[6]);
2511 }
2512 if (crc16) {
2513 *crc16 = SDL_Swap16LE(guid16[1]);
2514 }
2515 } else if (bus < ' ' || bus == SDL_HARDWARE_BUS_VIRTUAL) {
2516 /* This GUID fits the unknown VID/PID form:
2517 * 16-bit bus
2518 * 16-bit CRC16 of the joystick name (can be zero)
2519 * 11 characters of the joystick name, null terminated
2520 */
2521 if (vendor) {
2522 *vendor = 0;
2523 }
2524 if (product) {
2525 *product = 0;
2526 }
2527 if (version) {
2528 *version = 0;
2529 }
2530 if (crc16) {
2531 *crc16 = SDL_Swap16LE(guid16[1]);
2532 }
2533 } else {
2534 if (vendor) {
2535 *vendor = 0;
2536 }
2537 if (product) {
2538 *product = 0;
2539 }
2540 if (version) {
2541 *version = 0;
2542 }
2543 if (crc16) {
2544 *crc16 = 0;
2545 }
2546 }
2547}
2548
2549char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name)
2550{
2551 const char *custom_name = GuessControllerName(vendor, product);
2552 if (custom_name) {
2553 return SDL_strdup(custom_name);
2554 }
2555
2556 return SDL_CreateDeviceName(vendor, product, vendor_name, product_name, "Controller");
2557}
2558
2559SDL_GUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 product, Uint16 version, const char *vendor_name, const char *product_name, Uint8 driver_signature, Uint8 driver_data)
2560{
2561 SDL_GUID guid;
2562 Uint16 *guid16 = (Uint16 *)guid.data;
2563 Uint16 crc = 0;
2564
2565 SDL_zero(guid);
2566
2567 if (vendor_name && *vendor_name && product_name && *product_name) {
2568 crc = SDL_crc16(crc, vendor_name, SDL_strlen(vendor_name));
2569 crc = SDL_crc16(crc, " ", 1);
2570 crc = SDL_crc16(crc, product_name, SDL_strlen(product_name));
2571 } else if (product_name) {
2572 crc = SDL_crc16(crc, product_name, SDL_strlen(product_name));
2573 }
2574
2575 // We only need 16 bits for each of these; space them out to fill 128.
2576 // Byteswap so devices get same GUID on little/big endian platforms.
2577 *guid16++ = SDL_Swap16LE(bus);
2578 *guid16++ = SDL_Swap16LE(crc);
2579
2580 if (vendor) {
2581 *guid16++ = SDL_Swap16LE(vendor);
2582 *guid16++ = 0;
2583 *guid16++ = SDL_Swap16LE(product);
2584 *guid16++ = 0;
2585 *guid16++ = SDL_Swap16LE(version);
2586 guid.data[14] = driver_signature;
2587 guid.data[15] = driver_data;
2588 } else {
2589 size_t available_space = sizeof(guid.data) - 4;
2590
2591 if (driver_signature) {
2592 available_space -= 2;
2593 guid.data[14] = driver_signature;
2594 guid.data[15] = driver_data;
2595 }
2596 if (product_name) {
2597 SDL_strlcpy((char *)guid16, product_name, available_space);
2598 }
2599 }
2600 return guid;
2601}
2602
2603SDL_GUID SDL_CreateJoystickGUIDForName(const char *name)
2604{
2605 return SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_UNKNOWN, 0, 0, 0, NULL, name, 0, 0);
2606}
2607
2608void SDL_SetJoystickGUIDVendor(SDL_GUID *guid, Uint16 vendor)
2609{
2610 Uint16 *guid16 = (Uint16 *)guid->data;
2611
2612 guid16[2] = SDL_Swap16LE(vendor);
2613}
2614
2615void SDL_SetJoystickGUIDProduct(SDL_GUID *guid, Uint16 product)
2616{
2617 Uint16 *guid16 = (Uint16 *)guid->data;
2618
2619 guid16[4] = SDL_Swap16LE(product);
2620}
2621
2622void SDL_SetJoystickGUIDVersion(SDL_GUID *guid, Uint16 version)
2623{
2624 Uint16 *guid16 = (Uint16 *)guid->data;
2625
2626 guid16[6] = SDL_Swap16LE(version);
2627}
2628
2629void SDL_SetJoystickGUIDCRC(SDL_GUID *guid, Uint16 crc)
2630{
2631 Uint16 *guid16 = (Uint16 *)guid->data;
2632
2633 guid16[1] = SDL_Swap16LE(crc);
2634}
2635
2636SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, const char *name, bool forUI)
2637{
2638 SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD;
2639
2640 if (vendor == 0x0000 && product == 0x0000) {
2641 // Some devices are only identifiable by their name
2642 if (name &&
2643 (SDL_strcmp(name, "Lic Pro Controller") == 0 ||
2644 SDL_strcmp(name, "Nintendo Wireless Gamepad") == 0 ||
2645 SDL_strcmp(name, "Wireless Gamepad") == 0)) {
2646 // HORI or PowerA Switch Pro Controller clone
2647 type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
2648 }
2649
2650 } else if (vendor == 0x0001 && product == 0x0001) {
2651 type = SDL_GAMEPAD_TYPE_STANDARD;
2652
2653 } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT) {
2654 type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT;
2655
2656 } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) {
2657 if (name && SDL_strstr(name, "NES Controller") != NULL) {
2658 // We don't have a type for the Nintendo Online NES Controller
2659 type = SDL_GAMEPAD_TYPE_STANDARD;
2660 } else {
2661 type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;
2662 }
2663
2664 } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) {
2665 if (name && SDL_strstr(name, "(L)") != NULL) {
2666 type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT;
2667 } else {
2668 type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;
2669 }
2670
2671 } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) {
2672 type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR;
2673
2674 } else if (forUI && SDL_IsJoystickGameCube(vendor, product)) {
2675 // We don't have a type for the Nintendo GameCube controller
2676 type = SDL_GAMEPAD_TYPE_STANDARD;
2677
2678 } else {
2679 switch (GuessControllerType(vendor, product)) {
2680 case k_eControllerType_XBox360Controller:
2681 type = SDL_GAMEPAD_TYPE_XBOX360;
2682 break;
2683 case k_eControllerType_XBoxOneController:
2684 type = SDL_GAMEPAD_TYPE_XBOXONE;
2685 break;
2686 case k_eControllerType_PS3Controller:
2687 type = SDL_GAMEPAD_TYPE_PS3;
2688 break;
2689 case k_eControllerType_PS4Controller:
2690 type = SDL_GAMEPAD_TYPE_PS4;
2691 break;
2692 case k_eControllerType_PS5Controller:
2693 type = SDL_GAMEPAD_TYPE_PS5;
2694 break;
2695 case k_eControllerType_XInputPS4Controller:
2696 if (forUI) {
2697 type = SDL_GAMEPAD_TYPE_PS4;
2698 } else {
2699 type = SDL_GAMEPAD_TYPE_STANDARD;
2700 }
2701 break;
2702 case k_eControllerType_SwitchProController:
2703 case k_eControllerType_SwitchInputOnlyController:
2704 type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
2705 break;
2706 case k_eControllerType_XInputSwitchController:
2707 if (forUI) {
2708 type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
2709 } else {
2710 type = SDL_GAMEPAD_TYPE_STANDARD;
2711 }
2712 break;
2713 default:
2714 break;
2715 }
2716 }
2717 return type;
2718}
2719
2720SDL_GamepadType SDL_GetGamepadTypeFromGUID(SDL_GUID guid, const char *name)
2721{
2722 SDL_GamepadType type;
2723 Uint16 vendor, product;
2724
2725 SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
2726 type = SDL_GetGamepadTypeFromVIDPID(vendor, product, name, true);
2727 if (type == SDL_GAMEPAD_TYPE_STANDARD) {
2728 if (SDL_IsJoystickXInput(guid)) {
2729 // This is probably an Xbox One controller
2730 return SDL_GAMEPAD_TYPE_XBOXONE;
2731 }
2732#ifdef SDL_JOYSTICK_HIDAPI
2733 if (SDL_IsJoystickHIDAPI(guid)) {
2734 return HIDAPI_GetGamepadTypeFromGUID(guid);
2735 }
2736#endif // SDL_JOYSTICK_HIDAPI
2737 }
2738 return type;
2739}
2740
2741bool SDL_JoystickGUIDUsesVersion(SDL_GUID guid)
2742{
2743 Uint16 vendor, product;
2744
2745 if (SDL_IsJoystickMFI(guid)) {
2746 // The version bits are used as button capability mask
2747 return false;
2748 }
2749
2750 SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
2751 if (vendor && product) {
2752 return true;
2753 }
2754 return false;
2755}
2756
2757bool SDL_IsJoystickXboxOne(Uint16 vendor_id, Uint16 product_id)
2758{
2759 EControllerType eType = GuessControllerType(vendor_id, product_id);
2760 return eType == k_eControllerType_XBoxOneController;
2761}
2762
2763bool SDL_IsJoystickXboxOneElite(Uint16 vendor_id, Uint16 product_id)
2764{
2765 if (vendor_id == USB_VENDOR_MICROSOFT) {
2766 if (product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1 ||
2767 product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2 ||
2768 product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH ||
2769 product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLE) {
2770 return true;
2771 }
2772 }
2773 return false;
2774}
2775
2776bool SDL_IsJoystickXboxSeriesX(Uint16 vendor_id, Uint16 product_id)
2777{
2778 if (vendor_id == USB_VENDOR_MICROSOFT) {
2779 if (product_id == USB_PRODUCT_XBOX_SERIES_X ||
2780 product_id == USB_PRODUCT_XBOX_SERIES_X_BLE) {
2781 return true;
2782 }
2783 }
2784 if (vendor_id == USB_VENDOR_PDP) {
2785 if (product_id == USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT ||
2786 product_id == USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE ||
2787 product_id == USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW) {
2788 return true;
2789 }
2790 }
2791 if (vendor_id == USB_VENDOR_POWERA_ALT) {
2792 if ((product_id >= 0x2001 && product_id <= 0x201a) ||
2793 product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 ||
2794 product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO4 ||
2795 product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_USB ||
2796 product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_DONGLE ||
2797 product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA ||
2798 product_id == USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA) {
2799 return true;
2800 }
2801 }
2802 if (vendor_id == USB_VENDOR_HORI) {
2803 if (product_id == USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X ||
2804 product_id == USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X) {
2805 return true;
2806 }
2807 }
2808 if (vendor_id == USB_VENDOR_HP) {
2809 if (product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX ||
2810 product_id == USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB) {
2811 return true;
2812 }
2813 }
2814 if (vendor_id == USB_VENDOR_RAZER) {
2815 if (product_id == USB_PRODUCT_RAZER_WOLVERINE_V2 ||
2816 product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_CHROMA ||
2817 product_id == USB_PRODUCT_RAZER_WOLVERINE_V3_PRO) {
2818 return true;
2819 }
2820 }
2821 if (vendor_id == USB_VENDOR_THRUSTMASTER) {
2822 if (product_id == USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_SERIES_X) {
2823 return true;
2824 }
2825 }
2826 if (vendor_id == USB_VENDOR_TURTLE_BEACH) {
2827 if (product_id == USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R ||
2828 product_id == USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON) {
2829 return true;
2830 }
2831 }
2832 if (vendor_id == USB_VENDOR_8BITDO) {
2833 if (product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 ||
2834 product_id == USB_PRODUCT_8BITDO_XBOX_CONTROLLER2) {
2835 return true;
2836 }
2837 }
2838 if (vendor_id == USB_VENDOR_GAMESIR) {
2839 if (product_id == USB_PRODUCT_GAMESIR_G7) {
2840 return true;
2841 }
2842 }
2843 if (vendor_id == USB_VENDOR_ASUS) {
2844 if (product_id == USB_PRODUCT_ROG_RAIKIRI) {
2845 return true;
2846 }
2847 }
2848 return false;
2849}
2850
2851bool SDL_IsJoystickBluetoothXboxOne(Uint16 vendor_id, Uint16 product_id)
2852{
2853 if (vendor_id == USB_VENDOR_MICROSOFT) {
2854 if (product_id == USB_PRODUCT_XBOX_ONE_ADAPTIVE_BLUETOOTH ||
2855 product_id == USB_PRODUCT_XBOX_ONE_ADAPTIVE_BLE ||
2856 product_id == USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH ||
2857 product_id == USB_PRODUCT_XBOX_ONE_S_REV2_BLUETOOTH ||
2858 product_id == USB_PRODUCT_XBOX_ONE_S_REV2_BLE ||
2859 product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH ||
2860 product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLE ||
2861 product_id == USB_PRODUCT_XBOX_SERIES_X_BLE) {
2862 return true;
2863 }
2864 }
2865 return false;
2866}
2867
2868bool SDL_IsJoystickPS4(Uint16 vendor_id, Uint16 product_id)
2869{
2870 EControllerType eType = GuessControllerType(vendor_id, product_id);
2871 return eType == k_eControllerType_PS4Controller;
2872}
2873
2874bool SDL_IsJoystickPS5(Uint16 vendor_id, Uint16 product_id)
2875{
2876 EControllerType eType = GuessControllerType(vendor_id, product_id);
2877 return eType == k_eControllerType_PS5Controller;
2878}
2879
2880bool SDL_IsJoystickDualSenseEdge(Uint16 vendor_id, Uint16 product_id)
2881{
2882 if (vendor_id == USB_VENDOR_SONY) {
2883 if (product_id == USB_PRODUCT_SONY_DS5_EDGE) {
2884 return true;
2885 }
2886 }
2887 return false;
2888}
2889
2890bool SDL_IsJoystickNintendoSwitchPro(Uint16 vendor_id, Uint16 product_id)
2891{
2892 EControllerType eType = GuessControllerType(vendor_id, product_id);
2893 return eType == k_eControllerType_SwitchProController || eType == k_eControllerType_SwitchInputOnlyController;
2894}
2895
2896bool SDL_IsJoystickNintendoSwitchProInputOnly(Uint16 vendor_id, Uint16 product_id)
2897{
2898 EControllerType eType = GuessControllerType(vendor_id, product_id);
2899 return eType == k_eControllerType_SwitchInputOnlyController;
2900}
2901
2902bool SDL_IsJoystickNintendoSwitchJoyCon(Uint16 vendor_id, Uint16 product_id)
2903{
2904 EControllerType eType = GuessControllerType(vendor_id, product_id);
2905 return eType == k_eControllerType_SwitchJoyConLeft || eType == k_eControllerType_SwitchJoyConRight;
2906}
2907
2908bool SDL_IsJoystickNintendoSwitchJoyConLeft(Uint16 vendor_id, Uint16 product_id)
2909{
2910 EControllerType eType = GuessControllerType(vendor_id, product_id);
2911 return eType == k_eControllerType_SwitchJoyConLeft;
2912}
2913
2914bool SDL_IsJoystickNintendoSwitchJoyConRight(Uint16 vendor_id, Uint16 product_id)
2915{
2916 EControllerType eType = GuessControllerType(vendor_id, product_id);
2917 return eType == k_eControllerType_SwitchJoyConRight;
2918}
2919
2920bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id)
2921{
2922 return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP;
2923}
2924
2925bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id)
2926{
2927 return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
2928}
2929
2930bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id)
2931{
2932 return SDL_VIDPIDInList(vendor_id, product_id, &gamecube_devices);
2933}
2934
2935bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id)
2936{
2937 return ((vendor_id == USB_VENDOR_AMAZON && product_id == USB_PRODUCT_AMAZON_LUNA_CONTROLLER) ||
2938 (vendor_id == BLUETOOTH_VENDOR_AMAZON && product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER));
2939}
2940
2941bool SDL_IsJoystickGoogleStadiaController(Uint16 vendor_id, Uint16 product_id)
2942{
2943 return vendor_id == USB_VENDOR_GOOGLE && product_id == USB_PRODUCT_GOOGLE_STADIA_CONTROLLER;
2944}
2945
2946bool SDL_IsJoystickNVIDIASHIELDController(Uint16 vendor_id, Uint16 product_id)
2947{
2948 return (vendor_id == USB_VENDOR_NVIDIA &&
2949 (product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 ||
2950 product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104));
2951}
2952
2953bool SDL_IsJoystickSteamVirtualGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version)
2954{
2955#ifdef SDL_PLATFORM_MACOS
2956 return (vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX360_WIRED_CONTROLLER && version == 0);
2957#else
2958 return (vendor_id == USB_VENDOR_VALVE && product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD);
2959#endif
2960}
2961
2962bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id)
2963{
2964 EControllerType eType = GuessControllerType(vendor_id, product_id);
2965 return eType == k_eControllerType_SteamController || eType == k_eControllerType_SteamControllerV2;
2966}
2967
2968bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id)
2969{
2970 return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT);
2971}
2972
2973bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id)
2974{
2975 EControllerType eType = GuessControllerType(vendor_id, product_id);
2976 return eType == k_eControllerType_SteamControllerNeptune;
2977}
2978
2979bool SDL_IsJoystickXInput(SDL_GUID guid)
2980{
2981 return (guid.data[14] == 'x') ? true : false;
2982}
2983
2984bool SDL_IsJoystickWGI(SDL_GUID guid)
2985{
2986 return (guid.data[14] == 'w') ? true : false;
2987}
2988
2989bool SDL_IsJoystickHIDAPI(SDL_GUID guid)
2990{
2991 return (guid.data[14] == 'h') ? true : false;
2992}
2993
2994bool SDL_IsJoystickMFI(SDL_GUID guid)
2995{
2996 return (guid.data[14] == 'm') ? true : false;
2997}
2998
2999bool SDL_IsJoystickRAWINPUT(SDL_GUID guid)
3000{
3001 return (guid.data[14] == 'r') ? true : false;
3002}
3003
3004bool SDL_IsJoystickVIRTUAL(SDL_GUID guid)
3005{
3006 return (guid.data[14] == 'v') ? true : false;
3007}
3008
3009static bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id)
3010{
3011 return SDL_VIDPIDInList(vendor_id, product_id, &wheel_devices);
3012}
3013
3014static bool SDL_IsJoystickArcadeStick(Uint16 vendor_id, Uint16 product_id)
3015{
3016 return SDL_VIDPIDInList(vendor_id, product_id, &arcadestick_devices);
3017}
3018
3019static bool SDL_IsJoystickFlightStick(Uint16 vendor_id, Uint16 product_id)
3020{
3021 return SDL_VIDPIDInList(vendor_id, product_id, &flightstick_devices);
3022}
3023
3024static bool SDL_IsJoystickThrottle(Uint16 vendor_id, Uint16 product_id)
3025{
3026 return SDL_VIDPIDInList(vendor_id, product_id, &throttle_devices);
3027}
3028
3029static SDL_JoystickType SDL_GetJoystickGUIDType(SDL_GUID guid)
3030{
3031 Uint16 vendor;
3032 Uint16 product;
3033
3034 SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
3035
3036 if (SDL_IsJoystickWheel(vendor, product)) {
3037 return SDL_JOYSTICK_TYPE_WHEEL;
3038 }
3039
3040 if (SDL_IsJoystickArcadeStick(vendor, product)) {
3041 return SDL_JOYSTICK_TYPE_ARCADE_STICK;
3042 }
3043
3044 if (SDL_IsJoystickFlightStick(vendor, product)) {
3045 return SDL_JOYSTICK_TYPE_FLIGHT_STICK;
3046 }
3047
3048 if (SDL_IsJoystickThrottle(vendor, product)) {
3049 return SDL_JOYSTICK_TYPE_THROTTLE;
3050 }
3051
3052 if (SDL_IsJoystickXInput(guid)) {
3053 // XInput GUID, get the type based on the XInput device subtype
3054 switch (guid.data[15]) {
3055 case 0x01: // XINPUT_DEVSUBTYPE_GAMEPAD
3056 return SDL_JOYSTICK_TYPE_GAMEPAD;
3057 case 0x02: // XINPUT_DEVSUBTYPE_WHEEL
3058 return SDL_JOYSTICK_TYPE_WHEEL;
3059 case 0x03: // XINPUT_DEVSUBTYPE_ARCADE_STICK
3060 return SDL_JOYSTICK_TYPE_ARCADE_STICK;
3061 case 0x04: // XINPUT_DEVSUBTYPE_FLIGHT_STICK
3062 return SDL_JOYSTICK_TYPE_FLIGHT_STICK;
3063 case 0x05: // XINPUT_DEVSUBTYPE_DANCE_PAD
3064 return SDL_JOYSTICK_TYPE_DANCE_PAD;
3065 case 0x06: // XINPUT_DEVSUBTYPE_GUITAR
3066 case 0x07: // XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE
3067 case 0x0B: // XINPUT_DEVSUBTYPE_GUITAR_BASS
3068 return SDL_JOYSTICK_TYPE_GUITAR;
3069 case 0x08: // XINPUT_DEVSUBTYPE_DRUM_KIT
3070 return SDL_JOYSTICK_TYPE_DRUM_KIT;
3071 case 0x13: // XINPUT_DEVSUBTYPE_ARCADE_PAD
3072 return SDL_JOYSTICK_TYPE_ARCADE_PAD;
3073 default:
3074 return SDL_JOYSTICK_TYPE_UNKNOWN;
3075 }
3076 }
3077
3078 if (SDL_IsJoystickWGI(guid)) {
3079 return (SDL_JoystickType)guid.data[15];
3080 }
3081
3082 if (SDL_IsJoystickVIRTUAL(guid)) {
3083 return (SDL_JoystickType)guid.data[15];
3084 }
3085
3086#ifdef SDL_JOYSTICK_HIDAPI
3087 if (SDL_IsJoystickHIDAPI(guid)) {
3088 return HIDAPI_GetJoystickTypeFromGUID(guid);
3089 }
3090#endif // SDL_JOYSTICK_HIDAPI
3091
3092 if (GuessControllerType(vendor, product) != k_eControllerType_UnknownNonSteamController) {
3093 return SDL_JOYSTICK_TYPE_GAMEPAD;
3094 }
3095
3096 return SDL_JOYSTICK_TYPE_UNKNOWN;
3097}
3098
3099bool SDL_ShouldIgnoreJoystick(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
3100{
3101 // Check the joystick blacklist
3102 if (SDL_VIDPIDInList(vendor_id, product_id, &blacklist_devices)) {
3103 return true;
3104 }
3105 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_ROG_CHAKRAM, false)) {
3106 if (SDL_VIDPIDInList(vendor_id, product_id, &rog_gamepad_mice)) {
3107 return true;
3108 }
3109 }
3110
3111 if (SDL_ShouldIgnoreGamepad(vendor_id, product_id, version, name)) {
3112 return true;
3113 }
3114
3115 return false;
3116}
3117
3118// return the guid for this index
3119SDL_GUID SDL_GetJoystickGUIDForID(SDL_JoystickID instance_id)
3120{
3121 SDL_JoystickDriver *driver;
3122 int device_index;
3123 SDL_GUID guid;
3124
3125 SDL_LockJoysticks();
3126 if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
3127 guid = driver->GetDeviceGUID(device_index);
3128 } else {
3129 SDL_zero(guid);
3130 }
3131 SDL_UnlockJoysticks();
3132
3133 return guid;
3134}
3135
3136Uint16 SDL_GetJoystickVendorForID(SDL_JoystickID instance_id)
3137{
3138 Uint16 vendor;
3139 const SDL_SteamVirtualGamepadInfo *info;
3140
3141 SDL_LockJoysticks();
3142 info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id);
3143 if (info) {
3144 vendor = info->vendor_id;
3145 } else {
3146 SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id);
3147
3148 SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL);
3149 }
3150 SDL_UnlockJoysticks();
3151
3152 return vendor;
3153}
3154
3155Uint16 SDL_GetJoystickProductForID(SDL_JoystickID instance_id)
3156{
3157 Uint16 product;
3158 const SDL_SteamVirtualGamepadInfo *info;
3159
3160 SDL_LockJoysticks();
3161 info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id);
3162 if (info) {
3163 product = info->product_id;
3164 } else {
3165 SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id);
3166
3167 SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL);
3168 }
3169 SDL_UnlockJoysticks();
3170
3171 return product;
3172}
3173
3174Uint16 SDL_GetJoystickProductVersionForID(SDL_JoystickID instance_id)
3175{
3176 Uint16 version;
3177 SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id);
3178
3179 SDL_GetJoystickGUIDInfo(guid, NULL, NULL, &version, NULL);
3180 return version;
3181}
3182
3183SDL_JoystickType SDL_GetJoystickTypeForID(SDL_JoystickID instance_id)
3184{
3185 SDL_JoystickType type;
3186 SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id);
3187
3188 type = SDL_GetJoystickGUIDType(guid);
3189 if (type == SDL_JOYSTICK_TYPE_UNKNOWN) {
3190 if (SDL_IsGamepad(instance_id)) {
3191 type = SDL_JOYSTICK_TYPE_GAMEPAD;
3192 }
3193 }
3194 return type;
3195}
3196
3197SDL_GUID SDL_GetJoystickGUID(SDL_Joystick *joystick)
3198{
3199 SDL_GUID result;
3200
3201 SDL_LockJoysticks();
3202 {
3203 static SDL_GUID emptyGUID;
3204
3205 CHECK_JOYSTICK_MAGIC(joystick, emptyGUID);
3206
3207 result = joystick->guid;
3208 }
3209 SDL_UnlockJoysticks();
3210
3211 return result;
3212}
3213
3214Uint16 SDL_GetJoystickVendor(SDL_Joystick *joystick)
3215{
3216 Uint16 vendor;
3217 const SDL_SteamVirtualGamepadInfo *info;
3218
3219 SDL_LockJoysticks();
3220 {
3221 CHECK_JOYSTICK_MAGIC(joystick, 0);
3222
3223 info = SDL_GetJoystickVirtualGamepadInfoForID(joystick->instance_id);
3224 if (info) {
3225 vendor = info->vendor_id;
3226 } else {
3227 SDL_GUID guid = SDL_GetJoystickGUID(joystick);
3228
3229 SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL);
3230 }
3231 }
3232 SDL_UnlockJoysticks();
3233
3234 return vendor;
3235}
3236
3237Uint16 SDL_GetJoystickProduct(SDL_Joystick *joystick)
3238{
3239 Uint16 product;
3240 const SDL_SteamVirtualGamepadInfo *info;
3241
3242 SDL_LockJoysticks();
3243 {
3244 CHECK_JOYSTICK_MAGIC(joystick, 0);
3245
3246 info = SDL_GetJoystickVirtualGamepadInfoForID(joystick->instance_id);
3247 if (info) {
3248 product = info->product_id;
3249 } else {
3250 SDL_GUID guid = SDL_GetJoystickGUID(joystick);
3251
3252 SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL);
3253 }
3254 }
3255 SDL_UnlockJoysticks();
3256
3257 return product;
3258}
3259
3260Uint16 SDL_GetJoystickProductVersion(SDL_Joystick *joystick)
3261{
3262 Uint16 version;
3263 SDL_GUID guid = SDL_GetJoystickGUID(joystick);
3264
3265 SDL_GetJoystickGUIDInfo(guid, NULL, NULL, &version, NULL);
3266 return version;
3267}
3268
3269Uint16 SDL_GetJoystickFirmwareVersion(SDL_Joystick *joystick)
3270{
3271 Uint16 result;
3272
3273 SDL_LockJoysticks();
3274 {
3275 CHECK_JOYSTICK_MAGIC(joystick, 0);
3276
3277 result = joystick->firmware_version;
3278 }
3279 SDL_UnlockJoysticks();
3280
3281 return result;
3282}
3283
3284const char *SDL_GetJoystickSerial(SDL_Joystick *joystick)
3285{
3286 const char *result;
3287
3288 SDL_LockJoysticks();
3289 {
3290 CHECK_JOYSTICK_MAGIC(joystick, NULL);
3291
3292 result = SDL_GetPersistentString(joystick->serial);
3293 }
3294 SDL_UnlockJoysticks();
3295
3296 return result;
3297}
3298
3299SDL_JoystickType SDL_GetJoystickType(SDL_Joystick *joystick)
3300{
3301 SDL_JoystickType type;
3302 SDL_GUID guid = SDL_GetJoystickGUID(joystick);
3303
3304 type = SDL_GetJoystickGUIDType(guid);
3305 if (type == SDL_JOYSTICK_TYPE_UNKNOWN) {
3306 SDL_LockJoysticks();
3307 {
3308 CHECK_JOYSTICK_MAGIC(joystick, SDL_JOYSTICK_TYPE_UNKNOWN);
3309
3310 if (SDL_IsGamepad(joystick->instance_id)) {
3311 type = SDL_JOYSTICK_TYPE_GAMEPAD;
3312 }
3313 }
3314 SDL_UnlockJoysticks();
3315 }
3316 return type;
3317}
3318
3319void SDL_SendJoystickPowerInfo(SDL_Joystick *joystick, SDL_PowerState state, int percent)
3320{
3321 SDL_AssertJoysticksLocked();
3322
3323 if (state != joystick->battery_state || percent != joystick->battery_percent) {
3324 joystick->battery_state = state;
3325 joystick->battery_percent = percent;
3326
3327 if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_BATTERY_UPDATED)) {
3328 SDL_Event event;
3329 event.type = SDL_EVENT_JOYSTICK_BATTERY_UPDATED;
3330 event.common.timestamp = 0;
3331 event.jbattery.which = joystick->instance_id;
3332 event.jbattery.state = state;
3333 event.jbattery.percent = percent;
3334 SDL_PushEvent(&event);
3335 }
3336 }
3337}
3338
3339SDL_JoystickConnectionState SDL_GetJoystickConnectionState(SDL_Joystick *joystick)
3340{
3341 SDL_JoystickConnectionState result;
3342
3343 SDL_LockJoysticks();
3344 {
3345 CHECK_JOYSTICK_MAGIC(joystick, SDL_JOYSTICK_CONNECTION_INVALID);
3346
3347 result = joystick->connection_state;
3348 }
3349 SDL_UnlockJoysticks();
3350
3351 return result;
3352}
3353
3354SDL_PowerState SDL_GetJoystickPowerInfo(SDL_Joystick *joystick, int *percent)
3355{
3356 SDL_PowerState result;
3357
3358 if (percent) {
3359 *percent = -1;
3360 }
3361
3362 SDL_LockJoysticks();
3363 {
3364 CHECK_JOYSTICK_MAGIC(joystick, SDL_POWERSTATE_ERROR);
3365
3366 result = joystick->battery_state;
3367
3368 if (percent) {
3369 *percent = joystick->battery_percent;
3370 }
3371 }
3372 SDL_UnlockJoysticks();
3373
3374 return result;
3375}
3376
3377void SDL_SendJoystickTouchpad(Uint64 timestamp, SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure)
3378{
3379 SDL_JoystickTouchpadInfo *touchpad_info;
3380 SDL_JoystickTouchpadFingerInfo *finger_info;
3381 Uint32 event_type;
3382
3383 SDL_AssertJoysticksLocked();
3384
3385 if (touchpad < 0 || touchpad >= joystick->ntouchpads) {
3386 return;
3387 }
3388
3389 touchpad_info = &joystick->touchpads[touchpad];
3390 if (finger < 0 || finger >= touchpad_info->nfingers) {
3391 return;
3392 }
3393
3394 finger_info = &touchpad_info->fingers[finger];
3395
3396 if (!down) {
3397 if (x == 0.0f && y == 0.0f) {
3398 x = finger_info->x;
3399 y = finger_info->y;
3400 }
3401 pressure = 0.0f;
3402 }
3403
3404 if (x < 0.0f) {
3405 x = 0.0f;
3406 } else if (x > 1.0f) {
3407 x = 1.0f;
3408 }
3409 if (y < 0.0f) {
3410 y = 0.0f;
3411 } else if (y > 1.0f) {
3412 y = 1.0f;
3413 }
3414 if (pressure < 0.0f) {
3415 pressure = 0.0f;
3416 } else if (pressure > 1.0f) {
3417 pressure = 1.0f;
3418 }
3419
3420 if (down == finger_info->down) {
3421 if (!down ||
3422 (x == finger_info->x && y == finger_info->y && pressure == finger_info->pressure)) {
3423 return;
3424 }
3425 }
3426
3427 if (down == finger_info->down) {
3428 event_type = SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION;
3429 } else if (down) {
3430 event_type = SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN;
3431 } else {
3432 event_type = SDL_EVENT_GAMEPAD_TOUCHPAD_UP;
3433 }
3434
3435 // We ignore events if we don't have keyboard focus, except for touch release
3436 if (SDL_PrivateJoystickShouldIgnoreEvent()) {
3437 if (event_type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP) {
3438 return;
3439 }
3440 }
3441
3442 // Update internal joystick state
3443 SDL_assert(timestamp != 0);
3444 finger_info->down = down;
3445 finger_info->x = x;
3446 finger_info->y = y;
3447 finger_info->pressure = pressure;
3448 joystick->update_complete = timestamp;
3449
3450 // Post the event, if desired
3451 if (SDL_EventEnabled(event_type)) {
3452 SDL_Event event;
3453 event.type = event_type;
3454 event.common.timestamp = timestamp;
3455 event.gtouchpad.which = joystick->instance_id;
3456 event.gtouchpad.touchpad = touchpad;
3457 event.gtouchpad.finger = finger;
3458 event.gtouchpad.x = x;
3459 event.gtouchpad.y = y;
3460 event.gtouchpad.pressure = pressure;
3461 SDL_PushEvent(&event);
3462 }
3463}
3464
3465void SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values)
3466{
3467 SDL_AssertJoysticksLocked();
3468
3469 // We ignore events if we don't have keyboard focus
3470 if (SDL_PrivateJoystickShouldIgnoreEvent()) {
3471 return;
3472 }
3473
3474 for (int i = 0; i < joystick->nsensors; ++i) {
3475 SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];
3476
3477 if (sensor->type == type) {
3478 if (sensor->enabled) {
3479 num_values = SDL_min(num_values, SDL_arraysize(sensor->data));
3480
3481 // Update internal sensor state
3482 SDL_memcpy(sensor->data, data, num_values * sizeof(*data));
3483 joystick->update_complete = timestamp;
3484
3485 // Post the event, if desired
3486 if (SDL_EventEnabled(SDL_EVENT_GAMEPAD_SENSOR_UPDATE)) {
3487 SDL_Event event;
3488 event.type = SDL_EVENT_GAMEPAD_SENSOR_UPDATE;
3489 event.common.timestamp = timestamp;
3490 event.gsensor.which = joystick->instance_id;
3491 event.gsensor.sensor = type;
3492 num_values = SDL_min(num_values,
3493 SDL_arraysize(event.gsensor.data));
3494 SDL_memset(event.gsensor.data, 0,
3495 sizeof(event.gsensor.data));
3496 SDL_memcpy(event.gsensor.data, data,
3497 num_values * sizeof(*data));
3498 event.gsensor.sensor_timestamp = sensor_timestamp;
3499 SDL_PushEvent(&event);
3500 }
3501 }
3502 break;
3503 }
3504 }
3505}
3506
3507static void SDL_LoadVIDPIDListFromHint(const char *hint, int *num_entries, int *max_entries, Uint32 **entries)
3508{
3509 Uint32 entry;
3510 char *spot;
3511 char *file = NULL;
3512
3513 if (hint && *hint == '@') {
3514 spot = file = (char *)SDL_LoadFile(hint + 1, NULL);
3515 } else {
3516 spot = (char *)hint;
3517 }
3518
3519 if (!spot) {
3520 return;
3521 }
3522
3523 while ((spot = SDL_strstr(spot, "0x")) != NULL) {
3524 entry = (Uint16)SDL_strtol(spot, &spot, 0);
3525 entry <<= 16;
3526 spot = SDL_strstr(spot, "0x");
3527 if (!spot) {
3528 break;
3529 }
3530 entry |= (Uint16)SDL_strtol(spot, &spot, 0);
3531
3532 if (*num_entries == *max_entries) {
3533 int new_max_entries = *max_entries + 16;
3534 Uint32 *new_entries = (Uint32 *)SDL_realloc(*entries, new_max_entries * sizeof(**entries));
3535 if (!new_entries) {
3536 // Out of memory, go with what we have already
3537 break;
3538 }
3539 *entries = new_entries;
3540 *max_entries = new_max_entries;
3541 }
3542 (*entries)[(*num_entries)++] = entry;
3543 }
3544
3545 if (file) {
3546 SDL_free(file);
3547 }
3548}
3549
3550void SDL_LoadVIDPIDListFromHints(SDL_vidpid_list *list, const char *included_list, const char *excluded_list)
3551{
3552 // Empty the list
3553 list->num_included_entries = 0;
3554 list->num_excluded_entries = 0;
3555
3556 // Add the initial entries
3557 if (list->num_initial_entries > 0) {
3558 if (list->num_included_entries < list->num_initial_entries) {
3559 Uint32 *entries = (Uint32 *)SDL_malloc(list->num_initial_entries * sizeof(*entries));
3560 if (entries) {
3561 SDL_memcpy(entries, list->initial_entries, list->num_initial_entries * sizeof(*entries));
3562 list->included_entries = entries;
3563 list->num_included_entries = list->num_initial_entries;
3564 list->max_included_entries = list->num_initial_entries;
3565 }
3566 }
3567 }
3568
3569 // Add the included entries from the hint
3570 SDL_LoadVIDPIDListFromHint(included_list, &list->num_included_entries, &list->max_included_entries, &list->included_entries);
3571
3572 // Add the excluded entries from the hint
3573 SDL_LoadVIDPIDListFromHint(excluded_list, &list->num_excluded_entries, &list->max_excluded_entries, &list->excluded_entries);
3574}
3575
3576static void SDLCALL SDL_VIDPIDIncludedHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
3577{
3578 SDL_vidpid_list *list = (SDL_vidpid_list *)userdata;
3579 const char *included_list = hint;
3580 const char *excluded_list = NULL;
3581
3582 if (!list->initialized) {
3583 return;
3584 }
3585
3586 if (list->excluded_hint_name) {
3587 excluded_list = SDL_GetHint(list->excluded_hint_name);
3588 }
3589 SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list);
3590}
3591
3592static void SDLCALL SDL_VIDPIDExcludedHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
3593{
3594 SDL_vidpid_list *list = (SDL_vidpid_list *)userdata;
3595 const char *included_list = NULL;
3596 const char *excluded_list = hint;
3597
3598 if (!list->initialized) {
3599 return;
3600 }
3601
3602 if (list->included_hint_name) {
3603 included_list = SDL_GetHint(list->included_hint_name);
3604 }
3605 SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list);
3606}
3607
3608void SDL_LoadVIDPIDList(SDL_vidpid_list *list)
3609{
3610 const char *included_list = NULL;
3611 const char *excluded_list = NULL;
3612
3613 if (list->included_hint_name) {
3614 SDL_AddHintCallback(list->included_hint_name, SDL_VIDPIDIncludedHintChanged, list);
3615 }
3616
3617 if (list->excluded_hint_name) {
3618 SDL_AddHintCallback(list->excluded_hint_name, SDL_VIDPIDExcludedHintChanged, list);
3619 }
3620
3621 list->initialized = true;
3622
3623 if (list->included_hint_name) {
3624 included_list = SDL_GetHint(list->included_hint_name);
3625 }
3626 if (list->excluded_hint_name) {
3627 excluded_list = SDL_GetHint(list->excluded_hint_name);
3628 }
3629 SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list);
3630}
3631
3632bool SDL_VIDPIDInList(Uint16 vendor_id, Uint16 product_id, const SDL_vidpid_list *list)
3633{
3634 int i;
3635 Uint32 vidpid = MAKE_VIDPID(vendor_id, product_id);
3636
3637 for (i = 0; i < list->num_excluded_entries; ++i) {
3638 if (vidpid == list->excluded_entries[i]) {
3639 return false;
3640 }
3641 }
3642 for (i = 0; i < list->num_included_entries; ++i) {
3643 if (vidpid == list->included_entries[i]) {
3644 return true;
3645 }
3646 }
3647 return false;
3648}
3649
3650void SDL_FreeVIDPIDList(SDL_vidpid_list *list)
3651{
3652 if (list->included_hint_name) {
3653 SDL_RemoveHintCallback(list->included_hint_name, SDL_VIDPIDIncludedHintChanged, list);
3654 }
3655
3656 if (list->excluded_hint_name) {
3657 SDL_RemoveHintCallback(list->excluded_hint_name, SDL_VIDPIDExcludedHintChanged, list);
3658 }
3659
3660 if (list->included_entries) {
3661 SDL_free(list->included_entries);
3662 list->included_entries = NULL;
3663 list->num_included_entries = 0;
3664 list->max_included_entries = 0;
3665 }
3666
3667 if (list->excluded_entries) {
3668 SDL_free(list->excluded_entries);
3669 list->excluded_entries = NULL;
3670 list->num_excluded_entries = 0;
3671 list->max_excluded_entries = 0;
3672 }
3673
3674 list->initialized = false;
3675}
diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_joystick_c.h b/contrib/SDL-3.2.8/src/joystick/SDL_joystick_c.h
new file mode 100644
index 0000000..d931cf7
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/SDL_joystick_c.h
@@ -0,0 +1,270 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef SDL_joystick_c_h_
23#define SDL_joystick_c_h_
24
25#include "SDL_internal.h"
26
27// Useful functions and variables from SDL_joystick.c
28
29// Set up for C function definitions, even when using C++
30#ifdef __cplusplus
31extern "C" {
32#endif
33
34struct SDL_JoystickDriver;
35struct SDL_SteamVirtualGamepadInfo;
36
37// Initialization and shutdown functions
38extern bool SDL_InitJoysticks(void);
39extern void SDL_QuitJoysticks(void);
40
41// Return whether the joystick system is currently initialized
42extern bool SDL_JoysticksInitialized(void);
43
44// Return whether the joystick system is shutting down
45extern bool SDL_JoysticksQuitting(void);
46
47// Return whether the joysticks are currently locked
48extern bool SDL_JoysticksLocked(void);
49
50// Make sure we currently have the joysticks locked
51extern void SDL_AssertJoysticksLocked(void) SDL_ASSERT_CAPABILITY(SDL_joystick_lock);
52
53// Function to return whether there are any joysticks opened by the application
54extern bool SDL_JoysticksOpened(void);
55
56// Function to determine whether a device is currently detected by this driver
57extern bool SDL_JoystickHandledByAnotherDriver(struct SDL_JoystickDriver *driver, Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name);
58
59/* Function to standardize the name for a controller
60 This should be freed with SDL_free() when no longer needed
61 */
62extern char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name);
63
64// Function to create a GUID for a joystick based on the VID/PID and name
65extern SDL_GUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 product, Uint16 version, const char *vendor_name, const char *product_name, Uint8 driver_signature, Uint8 driver_data);
66
67// Function to create a GUID for a joystick based on the name, with no VID/PID information
68extern SDL_GUID SDL_CreateJoystickGUIDForName(const char *name);
69
70// Function to set the vendor field of a joystick GUID
71extern void SDL_SetJoystickGUIDVendor(SDL_GUID *guid, Uint16 vendor);
72
73// Function to set the product field of a joystick GUID
74extern void SDL_SetJoystickGUIDProduct(SDL_GUID *guid, Uint16 product);
75
76// Function to set the version field of a joystick GUID
77extern void SDL_SetJoystickGUIDVersion(SDL_GUID *guid, Uint16 version);
78
79// Function to set the CRC field of a joystick GUID
80extern void SDL_SetJoystickGUIDCRC(SDL_GUID *guid, Uint16 crc);
81
82// Function to return the type of a controller
83extern SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, const char *name, bool forUI);
84extern SDL_GamepadType SDL_GetGamepadTypeFromGUID(SDL_GUID guid, const char *name);
85
86// Function to return whether a joystick GUID uses the version field
87extern bool SDL_JoystickGUIDUsesVersion(SDL_GUID guid);
88
89// Function to return whether a joystick is an Xbox One controller
90extern bool SDL_IsJoystickXboxOne(Uint16 vendor_id, Uint16 product_id);
91
92// Function to return whether a joystick is an Xbox One Elite controller
93extern bool SDL_IsJoystickXboxOneElite(Uint16 vendor_id, Uint16 product_id);
94
95// Function to return whether a joystick is an Xbox Series X controller
96extern bool SDL_IsJoystickXboxSeriesX(Uint16 vendor_id, Uint16 product_id);
97
98// Function to return whether a joystick is an Xbox One controller connected via Bluetooth
99extern bool SDL_IsJoystickBluetoothXboxOne(Uint16 vendor_id, Uint16 product_id);
100
101// Function to return whether a joystick is a PS4 controller
102extern bool SDL_IsJoystickPS4(Uint16 vendor_id, Uint16 product_id);
103
104// Function to return whether a joystick is a PS5 controller
105extern bool SDL_IsJoystickPS5(Uint16 vendor_id, Uint16 product_id);
106extern bool SDL_IsJoystickDualSenseEdge(Uint16 vendor_id, Uint16 product_id);
107
108// Function to return whether a joystick is a Nintendo Switch Pro controller
109extern bool SDL_IsJoystickNintendoSwitchPro(Uint16 vendor_id, Uint16 product_id);
110extern bool SDL_IsJoystickNintendoSwitchProInputOnly(Uint16 vendor_id, Uint16 product_id);
111extern bool SDL_IsJoystickNintendoSwitchJoyCon(Uint16 vendor_id, Uint16 product_id);
112extern bool SDL_IsJoystickNintendoSwitchJoyConLeft(Uint16 vendor_id, Uint16 product_id);
113extern bool SDL_IsJoystickNintendoSwitchJoyConRight(Uint16 vendor_id, Uint16 product_id);
114extern bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id);
115extern bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id);
116
117// Function to return whether a joystick is a Nintendo GameCube style controller
118extern bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id);
119
120// Function to return whether a joystick is an Amazon Luna controller
121extern bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id);
122
123// Function to return whether a joystick is a Google Stadia controller
124extern bool SDL_IsJoystickGoogleStadiaController(Uint16 vendor_id, Uint16 product_id);
125
126// Function to return whether a joystick is an NVIDIA SHIELD controller
127extern bool SDL_IsJoystickNVIDIASHIELDController(Uint16 vendor_id, Uint16 product_id);
128
129// Function to return whether a joystick is a Steam Virtual Gamepad
130extern bool SDL_IsJoystickSteamVirtualGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version);
131
132// Function to return whether a joystick is a Steam Controller
133extern bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id);
134
135// Function to return whether a joystick is a HORI Steam controller
136extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id);
137
138// Function to return whether a joystick is a Steam Deck
139extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id);
140
141// Function to return whether a joystick guid comes from the XInput driver
142extern bool SDL_IsJoystickXInput(SDL_GUID guid);
143
144// Function to return whether a joystick guid comes from the WGI driver
145extern bool SDL_IsJoystickWGI(SDL_GUID guid);
146
147// Function to return whether a joystick guid comes from the HIDAPI driver
148extern bool SDL_IsJoystickHIDAPI(SDL_GUID guid);
149
150// Function to return whether a joystick guid comes from the MFI driver
151extern bool SDL_IsJoystickMFI(SDL_GUID guid);
152
153// Function to return whether a joystick guid comes from the RAWINPUT driver
154extern bool SDL_IsJoystickRAWINPUT(SDL_GUID guid);
155
156// Function to return whether a joystick guid comes from the Virtual driver
157extern bool SDL_IsJoystickVIRTUAL(SDL_GUID guid);
158
159// Function to return whether a joystick should be ignored
160extern bool SDL_ShouldIgnoreJoystick(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name);
161
162// Internal event queueing functions
163extern void SDL_PrivateJoystickAddTouchpad(SDL_Joystick *joystick, int nfingers);
164extern void SDL_PrivateJoystickAddSensor(SDL_Joystick *joystick, SDL_SensorType type, float rate);
165extern void SDL_PrivateJoystickSensorRate(SDL_Joystick *joystick, SDL_SensorType type, float rate);
166extern void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id);
167extern bool SDL_IsJoystickBeingAdded(void);
168extern void SDL_PrivateJoystickRemoved(SDL_JoystickID instance_id);
169extern void SDL_PrivateJoystickForceRecentering(SDL_Joystick *joystick);
170extern void SDL_SendJoystickAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, Sint16 value);
171extern void SDL_SendJoystickBall(Uint64 timestamp, SDL_Joystick *joystick, Uint8 ball, Sint16 xrel, Sint16 yrel);
172extern void SDL_SendJoystickHat(Uint64 timestamp, SDL_Joystick *joystick, Uint8 hat, Uint8 value);
173extern void SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 button, bool down);
174extern void SDL_SendJoystickTouchpad(Uint64 timestamp, SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure);
175extern void SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values);
176extern void SDL_SendJoystickPowerInfo(SDL_Joystick *joystick, SDL_PowerState state, int percent);
177
178// Function to get the Steam virtual gamepad info for a joystick
179extern const struct SDL_SteamVirtualGamepadInfo *SDL_GetJoystickVirtualGamepadInfoForID(SDL_JoystickID instance_id);
180
181// Internal sanity checking functions
182extern bool SDL_IsJoystickValid(SDL_Joystick *joystick);
183
184typedef enum
185{
186 EMappingKind_None,
187 EMappingKind_Button,
188 EMappingKind_Axis,
189 EMappingKind_Hat,
190} EMappingKind;
191
192typedef struct SDL_InputMapping
193{
194 EMappingKind kind;
195 Uint8 target;
196 bool axis_reversed;
197 bool half_axis_positive;
198 bool half_axis_negative;
199} SDL_InputMapping;
200
201typedef struct SDL_GamepadMapping
202{
203 SDL_InputMapping a;
204 SDL_InputMapping b;
205 SDL_InputMapping x;
206 SDL_InputMapping y;
207 SDL_InputMapping back;
208 SDL_InputMapping guide;
209 SDL_InputMapping start;
210 SDL_InputMapping leftstick;
211 SDL_InputMapping rightstick;
212 SDL_InputMapping leftshoulder;
213 SDL_InputMapping rightshoulder;
214 SDL_InputMapping dpup;
215 SDL_InputMapping dpdown;
216 SDL_InputMapping dpleft;
217 SDL_InputMapping dpright;
218 SDL_InputMapping misc1;
219 SDL_InputMapping misc2;
220 SDL_InputMapping misc3;
221 SDL_InputMapping misc4;
222 SDL_InputMapping misc5;
223 SDL_InputMapping misc6;
224 SDL_InputMapping right_paddle1;
225 SDL_InputMapping left_paddle1;
226 SDL_InputMapping right_paddle2;
227 SDL_InputMapping left_paddle2;
228 SDL_InputMapping leftx;
229 SDL_InputMapping lefty;
230 SDL_InputMapping rightx;
231 SDL_InputMapping righty;
232 SDL_InputMapping lefttrigger;
233 SDL_InputMapping righttrigger;
234 SDL_InputMapping touchpad;
235} SDL_GamepadMapping;
236
237// Function to get autodetected gamepad controller mapping from the driver
238extern bool SDL_PrivateJoystickGetAutoGamepadMapping(SDL_JoystickID instance_id,
239 SDL_GamepadMapping *out);
240
241
242typedef struct
243{
244 const char *included_hint_name;
245 int num_included_entries;
246 int max_included_entries;
247 Uint32 *included_entries;
248
249 const char *excluded_hint_name;
250 int num_excluded_entries;
251 int max_excluded_entries;
252 Uint32 *excluded_entries;
253
254 int num_initial_entries;
255 Uint32 *initial_entries;
256
257 bool initialized;
258} SDL_vidpid_list;
259
260extern void SDL_LoadVIDPIDList(SDL_vidpid_list *list);
261extern void SDL_LoadVIDPIDListFromHints(SDL_vidpid_list *list, const char *included_list, const char *excluded_list);
262extern bool SDL_VIDPIDInList(Uint16 vendor_id, Uint16 product_id, const SDL_vidpid_list *list);
263extern void SDL_FreeVIDPIDList(SDL_vidpid_list *list);
264
265// Ends C function definitions when using C++
266#ifdef __cplusplus
267}
268#endif
269
270#endif // SDL_joystick_c_h_
diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.c b/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.c
new file mode 100644
index 0000000..6b253de
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.c
@@ -0,0 +1,254 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_joystick_c.h"
24#include "SDL_steam_virtual_gamepad.h"
25
26#ifdef SDL_PLATFORM_WIN32
27#include "../core/windows/SDL_windows.h"
28#else
29#include <sys/types.h>
30#include <sys/stat.h>
31#endif
32
33#define SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE "SteamVirtualGamepadInfo"
34
35static char *SDL_steam_virtual_gamepad_info_file SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
36static Uint64 SDL_steam_virtual_gamepad_info_file_mtime SDL_GUARDED_BY(SDL_joystick_lock) = 0;
37static Uint64 SDL_steam_virtual_gamepad_info_check_time SDL_GUARDED_BY(SDL_joystick_lock) = 0;
38static SDL_SteamVirtualGamepadInfo **SDL_steam_virtual_gamepad_info SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
39static int SDL_steam_virtual_gamepad_info_count SDL_GUARDED_BY(SDL_joystick_lock) = 0;
40
41
42static Uint64 GetFileModificationTime(const char *file)
43{
44 Uint64 modification_time = 0;
45
46#ifdef SDL_PLATFORM_WIN32
47 WCHAR *wFile = WIN_UTF8ToStringW(file);
48 if (wFile) {
49 HANDLE hFile = CreateFileW(wFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
50 if (hFile != INVALID_HANDLE_VALUE) {
51 FILETIME last_write_time;
52 if (GetFileTime(hFile, NULL, NULL, &last_write_time)) {
53 modification_time = last_write_time.dwHighDateTime;
54 modification_time <<= 32;
55 modification_time |= last_write_time.dwLowDateTime;
56 }
57 CloseHandle(hFile);
58 }
59 SDL_free(wFile);
60 }
61#else
62 struct stat sb;
63
64 if (stat(file, &sb) == 0) {
65 modification_time = (Uint64)sb.st_mtime;
66 }
67#endif
68 return modification_time;
69}
70
71static void SDL_FreeSteamVirtualGamepadInfo(void)
72{
73 int i;
74
75 SDL_AssertJoysticksLocked();
76
77 for (i = 0; i < SDL_steam_virtual_gamepad_info_count; ++i) {
78 SDL_SteamVirtualGamepadInfo *entry = SDL_steam_virtual_gamepad_info[i];
79 if (entry) {
80 SDL_free(entry->name);
81 SDL_free(entry);
82 }
83 }
84 SDL_free(SDL_steam_virtual_gamepad_info);
85 SDL_steam_virtual_gamepad_info = NULL;
86 SDL_steam_virtual_gamepad_info_count = 0;
87}
88
89static void AddVirtualGamepadInfo(int slot, SDL_SteamVirtualGamepadInfo *info)
90{
91 SDL_SteamVirtualGamepadInfo *new_info;
92
93 SDL_AssertJoysticksLocked();
94
95 if (slot < 0) {
96 return;
97 }
98
99 if (slot >= SDL_steam_virtual_gamepad_info_count) {
100 SDL_SteamVirtualGamepadInfo **slots = (SDL_SteamVirtualGamepadInfo **)SDL_realloc(SDL_steam_virtual_gamepad_info, (slot + 1)*sizeof(*SDL_steam_virtual_gamepad_info));
101 if (!slots) {
102 return;
103 }
104 while (SDL_steam_virtual_gamepad_info_count <= slot) {
105 slots[SDL_steam_virtual_gamepad_info_count++] = NULL;
106 }
107 SDL_steam_virtual_gamepad_info = slots;
108 }
109
110 if (SDL_steam_virtual_gamepad_info[slot]) {
111 // We already have this slot info
112 return;
113 }
114
115 new_info = (SDL_SteamVirtualGamepadInfo *)SDL_malloc(sizeof(*new_info));
116 if (!new_info) {
117 return;
118 }
119 SDL_copyp(new_info, info);
120 SDL_steam_virtual_gamepad_info[slot] = new_info;
121 SDL_zerop(info);
122}
123
124void SDL_InitSteamVirtualGamepadInfo(void)
125{
126 const char *file;
127
128 SDL_AssertJoysticksLocked();
129
130 // The file isn't available inside the macOS sandbox
131 if (SDL_GetSandbox() == SDL_SANDBOX_MACOS) {
132 return;
133 }
134
135 file = SDL_GetHint(SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE);
136 if (file && *file) {
137 SDL_steam_virtual_gamepad_info_file = SDL_strdup(file);
138 }
139 SDL_UpdateSteamVirtualGamepadInfo();
140}
141
142bool SDL_SteamVirtualGamepadEnabled(void)
143{
144 SDL_AssertJoysticksLocked();
145
146 return (SDL_steam_virtual_gamepad_info != NULL);
147}
148
149bool SDL_UpdateSteamVirtualGamepadInfo(void)
150{
151 const int UPDATE_CHECK_INTERVAL_MS = 3000;
152 Uint64 now;
153 Uint64 mtime;
154 char *data, *end, *next, *line, *value;
155 size_t size;
156 int slot, new_slot;
157 SDL_SteamVirtualGamepadInfo info;
158
159 SDL_AssertJoysticksLocked();
160
161 if (!SDL_steam_virtual_gamepad_info_file) {
162 return false;
163 }
164
165 now = SDL_GetTicks();
166 if (SDL_steam_virtual_gamepad_info_check_time &&
167 now < (SDL_steam_virtual_gamepad_info_check_time + UPDATE_CHECK_INTERVAL_MS)) {
168 return false;
169 }
170 SDL_steam_virtual_gamepad_info_check_time = now;
171
172 mtime = GetFileModificationTime(SDL_steam_virtual_gamepad_info_file);
173 if (mtime == 0 || mtime == SDL_steam_virtual_gamepad_info_file_mtime) {
174 return false;
175 }
176
177 data = (char *)SDL_LoadFile(SDL_steam_virtual_gamepad_info_file, &size);
178 if (!data) {
179 return false;
180 }
181
182 SDL_FreeSteamVirtualGamepadInfo();
183
184 slot = -1;
185 SDL_zero(info);
186
187 for (next = data, end = data + size; next < end; ) {
188 while (next < end && (*next == '\0' || *next == '\r' || *next == '\n')) {
189 ++next;
190 }
191
192 line = next;
193
194 while (next < end && (*next != '\r' && *next != '\n')) {
195 ++next;
196 }
197 *next = '\0';
198
199 if (SDL_sscanf(line, "[slot %d]", &new_slot) == 1) {
200 if (slot >= 0) {
201 AddVirtualGamepadInfo(slot, &info);
202 }
203 slot = new_slot;
204 } else {
205 value = SDL_strchr(line, '=');
206 if (value) {
207 *value++ = '\0';
208
209 if (SDL_strcmp(line, "name") == 0) {
210 SDL_free(info.name);
211 info.name = SDL_strdup(value);
212 } else if (SDL_strcmp(line, "VID") == 0) {
213 info.vendor_id = (Uint16)SDL_strtoul(value, NULL, 0);
214 } else if (SDL_strcmp(line, "PID") == 0) {
215 info.product_id = (Uint16)SDL_strtoul(value, NULL, 0);
216 } else if (SDL_strcmp(line, "type") == 0) {
217 info.type = SDL_GetGamepadTypeFromString(value);
218 } else if (SDL_strcmp(line, "handle") == 0) {
219 info.handle = (Uint64)SDL_strtoull(value, NULL, 0);
220 }
221 }
222 }
223 }
224 if (slot >= 0) {
225 AddVirtualGamepadInfo(slot, &info);
226 }
227 SDL_free(info.name);
228 SDL_free(data);
229
230 SDL_steam_virtual_gamepad_info_file_mtime = mtime;
231
232 return true;
233}
234
235const SDL_SteamVirtualGamepadInfo *SDL_GetSteamVirtualGamepadInfo(int slot)
236{
237 SDL_AssertJoysticksLocked();
238
239 if (slot < 0 || slot >= SDL_steam_virtual_gamepad_info_count) {
240 return NULL;
241 }
242 return SDL_steam_virtual_gamepad_info[slot];
243}
244
245void SDL_QuitSteamVirtualGamepadInfo(void)
246{
247 SDL_AssertJoysticksLocked();
248
249 if (SDL_steam_virtual_gamepad_info_file) {
250 SDL_FreeSteamVirtualGamepadInfo();
251 SDL_free(SDL_steam_virtual_gamepad_info_file);
252 SDL_steam_virtual_gamepad_info_file = NULL;
253 }
254}
diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.h b/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.h
new file mode 100644
index 0000000..65696ea
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/SDL_steam_virtual_gamepad.h
@@ -0,0 +1,36 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23typedef struct SDL_SteamVirtualGamepadInfo
24{
25 Uint64 handle;
26 char *name;
27 Uint16 vendor_id;
28 Uint16 product_id;
29 SDL_GamepadType type;
30} SDL_SteamVirtualGamepadInfo;
31
32void SDL_InitSteamVirtualGamepadInfo(void);
33bool SDL_SteamVirtualGamepadEnabled(void);
34bool SDL_UpdateSteamVirtualGamepadInfo(void);
35const SDL_SteamVirtualGamepadInfo *SDL_GetSteamVirtualGamepadInfo(int slot);
36void SDL_QuitSteamVirtualGamepadInfo(void);
diff --git a/contrib/SDL-3.2.8/src/joystick/SDL_sysjoystick.h b/contrib/SDL-3.2.8/src/joystick/SDL_sysjoystick.h
new file mode 100644
index 0000000..343d206
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/SDL_sysjoystick.h
@@ -0,0 +1,269 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_sysjoystick_h_
24#define SDL_sysjoystick_h_
25
26// This is the system specific header for the SDL joystick API
27#include "SDL_joystick_c.h"
28
29// Set up for C function definitions, even when using C++
30#ifdef __cplusplus
31extern "C" {
32#endif
33
34// The SDL joystick structure
35
36typedef struct SDL_JoystickAxisInfo
37{
38 Sint16 initial_value; // Initial axis state
39 Sint16 value; // Current axis state
40 Sint16 zero; // Zero point on the axis (-32768 for triggers)
41 bool has_initial_value; // Whether we've seen a value on the axis yet
42 bool has_second_value; // Whether we've seen a second value on the axis yet
43 bool sent_initial_value; // Whether we've sent the initial axis value
44 bool sending_initial_value; // Whether we are sending the initial axis value
45} SDL_JoystickAxisInfo;
46
47typedef struct SDL_JoystickBallData
48{
49 int dx;
50 int dy;
51} SDL_JoystickBallData;
52
53typedef struct SDL_JoystickTouchpadFingerInfo
54{
55 bool down;
56 float x;
57 float y;
58 float pressure;
59} SDL_JoystickTouchpadFingerInfo;
60
61typedef struct SDL_JoystickTouchpadInfo
62{
63 int nfingers;
64 SDL_JoystickTouchpadFingerInfo *fingers;
65} SDL_JoystickTouchpadInfo;
66
67typedef struct SDL_JoystickSensorInfo
68{
69 SDL_SensorType type;
70 bool enabled;
71 float rate;
72 float data[3]; // If this needs to expand, update SDL_GamepadSensorEvent
73} SDL_JoystickSensorInfo;
74
75#define _guarded SDL_GUARDED_BY(SDL_joystick_lock)
76
77struct SDL_Joystick
78{
79 SDL_JoystickID instance_id _guarded; // Device instance, monotonically increasing from 0
80 char *name _guarded; // Joystick name - system dependent
81 char *path _guarded; // Joystick path - system dependent
82 char *serial _guarded; // Joystick serial
83 SDL_GUID guid _guarded; // Joystick guid
84 Uint16 firmware_version _guarded; // Firmware version, if available
85 Uint64 steam_handle _guarded; // Steam controller API handle
86
87 int naxes _guarded; // Number of axis controls on the joystick
88 SDL_JoystickAxisInfo *axes _guarded;
89
90 int nballs _guarded; // Number of trackballs on the joystick
91 SDL_JoystickBallData *balls _guarded; // Current ball motion deltas
92
93 int nhats _guarded; // Number of hats on the joystick
94 Uint8 *hats _guarded; // Current hat states
95
96 int nbuttons _guarded; // Number of buttons on the joystick
97 bool *buttons _guarded; // Current button states
98
99 int ntouchpads _guarded; // Number of touchpads on the joystick
100 SDL_JoystickTouchpadInfo *touchpads _guarded; // Current touchpad states
101
102 int nsensors _guarded; // Number of sensors on the joystick
103 int nsensors_enabled _guarded;
104 SDL_JoystickSensorInfo *sensors _guarded;
105
106 Uint16 low_frequency_rumble _guarded;
107 Uint16 high_frequency_rumble _guarded;
108 Uint64 rumble_expiration _guarded;
109 Uint64 rumble_resend _guarded;
110
111 Uint16 left_trigger_rumble _guarded;
112 Uint16 right_trigger_rumble _guarded;
113 Uint64 trigger_rumble_expiration _guarded;
114
115 Uint8 led_red _guarded;
116 Uint8 led_green _guarded;
117 Uint8 led_blue _guarded;
118 Uint64 led_expiration _guarded;
119
120 bool attached _guarded;
121 SDL_JoystickConnectionState connection_state _guarded;
122 SDL_PowerState battery_state _guarded;
123 int battery_percent _guarded;
124
125 bool delayed_guide_button _guarded; // true if this device has the guide button event delayed
126
127 SDL_SensorID accel_sensor _guarded;
128 SDL_Sensor *accel _guarded;
129 SDL_SensorID gyro_sensor _guarded;
130 SDL_Sensor *gyro _guarded;
131 float sensor_transform[3][3] _guarded;
132
133 Uint64 update_complete _guarded;
134
135 struct SDL_JoystickDriver *driver _guarded;
136
137 struct joystick_hwdata *hwdata _guarded; // Driver dependent information
138
139 SDL_PropertiesID props _guarded;
140
141 int ref_count _guarded; // Reference count for multiple opens
142
143 struct SDL_Joystick *next _guarded; // pointer to next joystick we have allocated
144};
145
146#undef _guarded
147
148// Device bus definitions
149#define SDL_HARDWARE_BUS_UNKNOWN 0x00
150#define SDL_HARDWARE_BUS_USB 0x03
151#define SDL_HARDWARE_BUS_BLUETOOTH 0x05
152#define SDL_HARDWARE_BUS_VIRTUAL 0xFF
153
154// Macro to combine a USB vendor ID and product ID into a single Uint32 value
155#define MAKE_VIDPID(VID, PID) (((Uint32)(VID)) << 16 | (PID))
156
157typedef struct SDL_JoystickDriver
158{
159 /* Function to scan the system for joysticks.
160 * Joystick 0 should be the system default joystick.
161 * This function should return 0, or -1 on an unrecoverable error.
162 */
163 bool (*Init)(void);
164
165 // Function to return the number of joystick devices plugged in right now
166 int (*GetCount)(void);
167
168 // Function to cause any queued joystick insertions to be processed
169 void (*Detect)(void);
170
171 // Function to determine whether a device is currently detected by this driver
172 bool (*IsDevicePresent)(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name);
173
174 // Function to get the device-dependent name of a joystick
175 const char *(*GetDeviceName)(int device_index);
176
177 // Function to get the device-dependent path of a joystick
178 const char *(*GetDevicePath)(int device_index);
179
180 // Function to get the Steam virtual gamepad slot of a joystick
181 int (*GetDeviceSteamVirtualGamepadSlot)(int device_index);
182
183 // Function to get the player index of a joystick
184 int (*GetDevicePlayerIndex)(int device_index);
185
186 // Function to set the player index of a joystick
187 void (*SetDevicePlayerIndex)(int device_index, int player_index);
188
189 // Function to return the stable GUID for a plugged in device
190 SDL_GUID (*GetDeviceGUID)(int device_index);
191
192 // Function to get the current instance id of the joystick located at device_index
193 SDL_JoystickID (*GetDeviceInstanceID)(int device_index);
194
195 /* Function to open a joystick for use.
196 The joystick to open is specified by the device index.
197 This should fill the nbuttons and naxes fields of the joystick structure.
198 It returns 0, or -1 if there is an error.
199 */
200 bool (*Open)(SDL_Joystick *joystick, int device_index);
201
202 // Rumble functionality
203 bool (*Rumble)(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble);
204 bool (*RumbleTriggers)(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble);
205
206 // LED functionality
207 bool (*SetLED)(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue);
208
209 // General effects
210 bool (*SendEffect)(SDL_Joystick *joystick, const void *data, int size);
211
212 // Sensor functionality
213 bool (*SetSensorsEnabled)(SDL_Joystick *joystick, bool enabled);
214
215 /* Function to update the state of a joystick - called as a device poll.
216 * This function shouldn't update the joystick structure directly,
217 * but instead should call SDL_PrivateJoystick*() to deliver events
218 * and update joystick device state.
219 */
220 void (*Update)(SDL_Joystick *joystick);
221
222 // Function to close a joystick after use
223 void (*Close)(SDL_Joystick *joystick);
224
225 // Function to perform any system-specific joystick related cleanup
226 void (*Quit)(void);
227
228 // Function to get the autodetected controller mapping; returns false if there isn't any.
229 bool (*GetGamepadMapping)(int device_index, SDL_GamepadMapping *out);
230
231} SDL_JoystickDriver;
232
233// Windows and Mac OSX has a limit of MAX_DWORD / 1000, Linux kernel has a limit of 0xFFFF
234#define SDL_MAX_RUMBLE_DURATION_MS 0xFFFF
235
236/* Dualshock4 only rumbles for about 5 seconds max, resend rumble command every 2 seconds
237 * to make long rumble work. */
238#define SDL_RUMBLE_RESEND_MS 2000
239
240#define SDL_LED_MIN_REPEAT_MS 5000
241
242// The available joystick drivers
243extern SDL_JoystickDriver SDL_PRIVATE_JoystickDriver;
244extern SDL_JoystickDriver SDL_ANDROID_JoystickDriver;
245extern SDL_JoystickDriver SDL_BSD_JoystickDriver;
246extern SDL_JoystickDriver SDL_DARWIN_JoystickDriver;
247extern SDL_JoystickDriver SDL_DUMMY_JoystickDriver;
248extern SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver;
249extern SDL_JoystickDriver SDL_HAIKU_JoystickDriver;
250extern SDL_JoystickDriver SDL_HIDAPI_JoystickDriver;
251extern SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver;
252extern SDL_JoystickDriver SDL_IOS_JoystickDriver;
253extern SDL_JoystickDriver SDL_LINUX_JoystickDriver;
254extern SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver;
255extern SDL_JoystickDriver SDL_WGI_JoystickDriver;
256extern SDL_JoystickDriver SDL_WINDOWS_JoystickDriver;
257extern SDL_JoystickDriver SDL_WINMM_JoystickDriver;
258extern SDL_JoystickDriver SDL_PS2_JoystickDriver;
259extern SDL_JoystickDriver SDL_PSP_JoystickDriver;
260extern SDL_JoystickDriver SDL_VITA_JoystickDriver;
261extern SDL_JoystickDriver SDL_N3DS_JoystickDriver;
262extern SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver;
263
264// Ends C function definitions when using C++
265#ifdef __cplusplus
266}
267#endif
268
269#endif // SDL_sysjoystick_h_
diff --git a/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick.c
new file mode 100644
index 0000000..9a3402e
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick.c
@@ -0,0 +1,681 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_ANDROID
24
25#include <stdio.h> // For the definition of NULL
26
27#include "SDL_sysjoystick_c.h"
28#include "../SDL_joystick_c.h"
29#include "../../events/SDL_keyboard_c.h"
30#include "../../core/android/SDL_android.h"
31#include "../hidapi/SDL_hidapijoystick_c.h"
32
33#include "android/keycodes.h"
34
35// As of platform android-14, android/keycodes.h is missing these defines
36#ifndef AKEYCODE_BUTTON_1
37#define AKEYCODE_BUTTON_1 188
38#define AKEYCODE_BUTTON_2 189
39#define AKEYCODE_BUTTON_3 190
40#define AKEYCODE_BUTTON_4 191
41#define AKEYCODE_BUTTON_5 192
42#define AKEYCODE_BUTTON_6 193
43#define AKEYCODE_BUTTON_7 194
44#define AKEYCODE_BUTTON_8 195
45#define AKEYCODE_BUTTON_9 196
46#define AKEYCODE_BUTTON_10 197
47#define AKEYCODE_BUTTON_11 198
48#define AKEYCODE_BUTTON_12 199
49#define AKEYCODE_BUTTON_13 200
50#define AKEYCODE_BUTTON_14 201
51#define AKEYCODE_BUTTON_15 202
52#define AKEYCODE_BUTTON_16 203
53#endif
54
55#define ANDROID_MAX_NBUTTONS 36
56
57static SDL_joylist_item *JoystickByDeviceId(int device_id);
58
59static SDL_joylist_item *SDL_joylist = NULL;
60static SDL_joylist_item *SDL_joylist_tail = NULL;
61static int numjoysticks = 0;
62
63/* Function to convert Android keyCodes into SDL ones.
64 * This code manipulation is done to get a sequential list of codes.
65 * FIXME: This is only suited for the case where we use a fixed number of buttons determined by ANDROID_MAX_NBUTTONS
66 */
67static int keycode_to_SDL(int keycode)
68{
69 // FIXME: If this function gets too unwieldy in the future, replace with a lookup table
70 int button = 0;
71 switch (keycode) {
72 // Some gamepad buttons (API 9)
73 case AKEYCODE_BUTTON_A:
74 button = SDL_GAMEPAD_BUTTON_SOUTH;
75 break;
76 case AKEYCODE_BUTTON_B:
77 button = SDL_GAMEPAD_BUTTON_EAST;
78 break;
79 case AKEYCODE_BUTTON_X:
80 button = SDL_GAMEPAD_BUTTON_WEST;
81 break;
82 case AKEYCODE_BUTTON_Y:
83 button = SDL_GAMEPAD_BUTTON_NORTH;
84 break;
85 case AKEYCODE_BUTTON_L1:
86 button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER;
87 break;
88 case AKEYCODE_BUTTON_R1:
89 button = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER;
90 break;
91 case AKEYCODE_BUTTON_THUMBL:
92 button = SDL_GAMEPAD_BUTTON_LEFT_STICK;
93 break;
94 case AKEYCODE_BUTTON_THUMBR:
95 button = SDL_GAMEPAD_BUTTON_RIGHT_STICK;
96 break;
97 case AKEYCODE_MENU:
98 case AKEYCODE_BUTTON_START:
99 button = SDL_GAMEPAD_BUTTON_START;
100 break;
101 case AKEYCODE_BACK:
102 case AKEYCODE_BUTTON_SELECT:
103 button = SDL_GAMEPAD_BUTTON_BACK;
104 break;
105 case AKEYCODE_BUTTON_MODE:
106 button = SDL_GAMEPAD_BUTTON_GUIDE;
107 break;
108 case AKEYCODE_BUTTON_L2:
109 button = 15;
110 break;
111 case AKEYCODE_BUTTON_R2:
112 button = 16;
113 break;
114 case AKEYCODE_BUTTON_C:
115 button = 17;
116 break;
117 case AKEYCODE_BUTTON_Z:
118 button = 18;
119 break;
120
121 // D-Pad key codes (API 1)
122 case AKEYCODE_DPAD_UP:
123 button = SDL_GAMEPAD_BUTTON_DPAD_UP;
124 break;
125 case AKEYCODE_DPAD_DOWN:
126 button = SDL_GAMEPAD_BUTTON_DPAD_DOWN;
127 break;
128 case AKEYCODE_DPAD_LEFT:
129 button = SDL_GAMEPAD_BUTTON_DPAD_LEFT;
130 break;
131 case AKEYCODE_DPAD_RIGHT:
132 button = SDL_GAMEPAD_BUTTON_DPAD_RIGHT;
133 break;
134 case AKEYCODE_DPAD_CENTER:
135 // This is handled better by applications as the A button
136 // button = 19;
137 button = SDL_GAMEPAD_BUTTON_SOUTH;
138 break;
139
140 // More gamepad buttons (API 12), these get mapped to 20...35
141 case AKEYCODE_BUTTON_1:
142 case AKEYCODE_BUTTON_2:
143 case AKEYCODE_BUTTON_3:
144 case AKEYCODE_BUTTON_4:
145 case AKEYCODE_BUTTON_5:
146 case AKEYCODE_BUTTON_6:
147 case AKEYCODE_BUTTON_7:
148 case AKEYCODE_BUTTON_8:
149 case AKEYCODE_BUTTON_9:
150 case AKEYCODE_BUTTON_10:
151 case AKEYCODE_BUTTON_11:
152 case AKEYCODE_BUTTON_12:
153 case AKEYCODE_BUTTON_13:
154 case AKEYCODE_BUTTON_14:
155 case AKEYCODE_BUTTON_15:
156 case AKEYCODE_BUTTON_16:
157 button = 20 + (keycode - AKEYCODE_BUTTON_1);
158 break;
159
160 default:
161 return -1;
162 // break; -Wunreachable-code-break
163 }
164
165 /* This is here in case future generations, probably with six fingers per hand,
166 * happily add new cases up above and forget to update the max number of buttons.
167 */
168 SDL_assert(button < ANDROID_MAX_NBUTTONS);
169 return button;
170}
171
172static SDL_Scancode button_to_scancode(int button)
173{
174 switch (button) {
175 case SDL_GAMEPAD_BUTTON_SOUTH:
176 return SDL_SCANCODE_RETURN;
177 case SDL_GAMEPAD_BUTTON_EAST:
178 return SDL_SCANCODE_ESCAPE;
179 case SDL_GAMEPAD_BUTTON_BACK:
180 return SDL_SCANCODE_ESCAPE;
181 case SDL_GAMEPAD_BUTTON_START:
182 return SDL_SCANCODE_MENU;
183 case SDL_GAMEPAD_BUTTON_DPAD_UP:
184 return SDL_SCANCODE_UP;
185 case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
186 return SDL_SCANCODE_DOWN;
187 case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
188 return SDL_SCANCODE_LEFT;
189 case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
190 return SDL_SCANCODE_RIGHT;
191 }
192
193 // Unsupported button
194 return SDL_SCANCODE_UNKNOWN;
195}
196
197bool Android_OnPadDown(int device_id, int keycode)
198{
199 Uint64 timestamp = SDL_GetTicksNS();
200 SDL_joylist_item *item;
201 int button = keycode_to_SDL(keycode);
202 if (button >= 0) {
203 SDL_LockJoysticks();
204 item = JoystickByDeviceId(device_id);
205 if (item && item->joystick) {
206 SDL_SendJoystickButton(timestamp, item->joystick, button, true);
207 } else {
208 SDL_SendKeyboardKey(timestamp, SDL_GLOBAL_KEYBOARD_ID, keycode, button_to_scancode(button), true);
209 }
210 SDL_UnlockJoysticks();
211 return true;
212 }
213
214 return false;
215}
216
217bool Android_OnPadUp(int device_id, int keycode)
218{
219 Uint64 timestamp = SDL_GetTicksNS();
220 SDL_joylist_item *item;
221 int button = keycode_to_SDL(keycode);
222 if (button >= 0) {
223 SDL_LockJoysticks();
224 item = JoystickByDeviceId(device_id);
225 if (item && item->joystick) {
226 SDL_SendJoystickButton(timestamp, item->joystick, button, false);
227 } else {
228 SDL_SendKeyboardKey(timestamp, SDL_GLOBAL_KEYBOARD_ID, keycode, button_to_scancode(button), false);
229 }
230 SDL_UnlockJoysticks();
231 return true;
232 }
233
234 return false;
235}
236
237bool Android_OnJoy(int device_id, int axis, float value)
238{
239 Uint64 timestamp = SDL_GetTicksNS();
240 // Android gives joy info normalized as [-1.0, 1.0] or [0.0, 1.0]
241 SDL_joylist_item *item;
242
243 SDL_LockJoysticks();
244 item = JoystickByDeviceId(device_id);
245 if (item && item->joystick) {
246 SDL_SendJoystickAxis(timestamp, item->joystick, axis, (Sint16)(32767. * value));
247 }
248 SDL_UnlockJoysticks();
249
250 return true;
251}
252
253bool Android_OnHat(int device_id, int hat_id, int x, int y)
254{
255 Uint64 timestamp = SDL_GetTicksNS();
256 const int DPAD_UP_MASK = (1 << SDL_GAMEPAD_BUTTON_DPAD_UP);
257 const int DPAD_DOWN_MASK = (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN);
258 const int DPAD_LEFT_MASK = (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT);
259 const int DPAD_RIGHT_MASK = (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
260
261 if (x >= -1 && x <= 1 && y >= -1 && y <= 1) {
262 SDL_joylist_item *item;
263
264 SDL_LockJoysticks();
265 item = JoystickByDeviceId(device_id);
266 if (item && item->joystick) {
267 int dpad_state = 0;
268 int dpad_delta;
269 if (x < 0) {
270 dpad_state |= DPAD_LEFT_MASK;
271 } else if (x > 0) {
272 dpad_state |= DPAD_RIGHT_MASK;
273 }
274 if (y < 0) {
275 dpad_state |= DPAD_UP_MASK;
276 } else if (y > 0) {
277 dpad_state |= DPAD_DOWN_MASK;
278 }
279
280 dpad_delta = (dpad_state ^ item->dpad_state);
281 if (dpad_delta) {
282 if (dpad_delta & DPAD_UP_MASK) {
283 bool down = ((dpad_state & DPAD_UP_MASK) != 0);
284 SDL_SendJoystickButton(timestamp, item->joystick, SDL_GAMEPAD_BUTTON_DPAD_UP, down);
285 }
286 if (dpad_delta & DPAD_DOWN_MASK) {
287 bool down = ((dpad_state & DPAD_DOWN_MASK) != 0);
288 SDL_SendJoystickButton(timestamp, item->joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN, down);
289 }
290 if (dpad_delta & DPAD_LEFT_MASK) {
291 bool down = ((dpad_state & DPAD_LEFT_MASK) != 0);
292 SDL_SendJoystickButton(timestamp, item->joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT, down);
293 }
294 if (dpad_delta & DPAD_RIGHT_MASK) {
295 bool down = ((dpad_state & DPAD_RIGHT_MASK) != 0);
296 SDL_SendJoystickButton(timestamp, item->joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT, down);
297 }
298 item->dpad_state = dpad_state;
299 }
300 }
301 SDL_UnlockJoysticks();
302 return true;
303 }
304
305 return false;
306}
307
308void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble)
309{
310 SDL_joylist_item *item;
311 SDL_GUID guid;
312 int i;
313
314 SDL_LockJoysticks();
315
316 if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, true)) {
317 // Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input
318 if (naxes < 2 && nhats < 1) {
319 goto done;
320 }
321 }
322
323 if (JoystickByDeviceId(device_id) != NULL || !name) {
324 goto done;
325 }
326
327 if (SDL_JoystickHandledByAnotherDriver(&SDL_ANDROID_JoystickDriver, vendor_id, product_id, 0, name)) {
328 goto done;
329 }
330
331#ifdef DEBUG_JOYSTICK
332 SDL_Log("Joystick: %s, descriptor %s, vendor = 0x%.4x, product = 0x%.4x, %d axes, %d hats", name, desc, vendor_id, product_id, naxes, nhats);
333#endif
334
335 if (nhats > 0) {
336 // Hat is translated into DPAD buttons
337 button_mask |= ((1 << SDL_GAMEPAD_BUTTON_DPAD_UP) |
338 (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) |
339 (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT) |
340 (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT));
341 nhats = 0;
342 }
343
344 guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor_id, product_id, 0, NULL, desc, 0, 0);
345
346 // Update the GUID with capability bits
347 {
348 Uint16 *guid16 = (Uint16 *)guid.data;
349 guid16[6] = SDL_Swap16LE(button_mask);
350 guid16[7] = SDL_Swap16LE(axis_mask);
351 }
352
353 item = (SDL_joylist_item *)SDL_malloc(sizeof(SDL_joylist_item));
354 if (!item) {
355 goto done;
356 }
357
358 SDL_zerop(item);
359 item->guid = guid;
360 item->device_id = device_id;
361 item->name = SDL_CreateJoystickName(vendor_id, product_id, NULL, name);
362 if (!item->name) {
363 SDL_free(item);
364 goto done;
365 }
366
367 if (button_mask == 0xFFFFFFFF) {
368 item->nbuttons = ANDROID_MAX_NBUTTONS;
369 } else {
370 for (i = 0; i < sizeof(button_mask) * 8; ++i) {
371 if (button_mask & (1 << i)) {
372 item->nbuttons = i + 1;
373 }
374 }
375 }
376 item->naxes = naxes;
377 item->nhats = nhats;
378 item->can_rumble = can_rumble;
379 item->device_instance = SDL_GetNextObjectID();
380 if (!SDL_joylist_tail) {
381 SDL_joylist = SDL_joylist_tail = item;
382 } else {
383 SDL_joylist_tail->next = item;
384 SDL_joylist_tail = item;
385 }
386
387 // Need to increment the joystick count before we post the event
388 ++numjoysticks;
389
390 SDL_PrivateJoystickAdded(item->device_instance);
391
392#ifdef DEBUG_JOYSTICK
393 SDL_Log("Added joystick %s with device_id %d", item->name, device_id);
394#endif
395
396done:
397 SDL_UnlockJoysticks();
398}
399
400void Android_RemoveJoystick(int device_id)
401{
402 SDL_joylist_item *item = SDL_joylist;
403 SDL_joylist_item *prev = NULL;
404
405 SDL_LockJoysticks();
406
407 // Don't call JoystickByDeviceId here or there'll be an infinite loop!
408 while (item) {
409 if (item->device_id == device_id) {
410 break;
411 }
412 prev = item;
413 item = item->next;
414 }
415
416 if (!item) {
417 goto done;
418 }
419
420 if (item->joystick) {
421 item->joystick->hwdata = NULL;
422 }
423
424 if (prev) {
425 prev->next = item->next;
426 } else {
427 SDL_assert(SDL_joylist == item);
428 SDL_joylist = item->next;
429 }
430 if (item == SDL_joylist_tail) {
431 SDL_joylist_tail = prev;
432 }
433
434 // Need to decrement the joystick count before we post the event
435 --numjoysticks;
436
437 SDL_PrivateJoystickRemoved(item->device_instance);
438
439#ifdef DEBUG_JOYSTICK
440 SDL_Log("Removed joystick with device_id %d", device_id);
441#endif
442
443 SDL_free(item->name);
444 SDL_free(item);
445
446done:
447 SDL_UnlockJoysticks();
448}
449
450static void ANDROID_JoystickDetect(void);
451
452static bool ANDROID_JoystickInit(void)
453{
454 ANDROID_JoystickDetect();
455 return true;
456}
457
458static int ANDROID_JoystickGetCount(void)
459{
460 return numjoysticks;
461}
462
463static void ANDROID_JoystickDetect(void)
464{
465 /* Support for device connect/disconnect is API >= 16 only,
466 * so we poll every three seconds
467 * Ref: http://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
468 */
469 static Uint64 timeout = 0;
470 Uint64 now = SDL_GetTicks();
471 if (!timeout || now >= timeout) {
472 timeout = now + 3000;
473 Android_JNI_PollInputDevices();
474 }
475}
476
477static bool ANDROID_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
478{
479 // We don't override any other drivers
480 return false;
481}
482
483static SDL_joylist_item *GetJoystickByDevIndex(int device_index)
484{
485 SDL_joylist_item *item = SDL_joylist;
486
487 if ((device_index < 0) || (device_index >= numjoysticks)) {
488 return NULL;
489 }
490
491 while (device_index > 0) {
492 SDL_assert(item != NULL);
493 device_index--;
494 item = item->next;
495 }
496
497 return item;
498}
499
500static SDL_joylist_item *JoystickByDeviceId(int device_id)
501{
502 SDL_joylist_item *item = SDL_joylist;
503
504 while (item) {
505 if (item->device_id == device_id) {
506 return item;
507 }
508 item = item->next;
509 }
510
511 // Joystick not found, try adding it
512 ANDROID_JoystickDetect();
513
514 while (item) {
515 if (item->device_id == device_id) {
516 return item;
517 }
518 item = item->next;
519 }
520
521 return NULL;
522}
523
524static const char *ANDROID_JoystickGetDeviceName(int device_index)
525{
526 return GetJoystickByDevIndex(device_index)->name;
527}
528
529static const char *ANDROID_JoystickGetDevicePath(int device_index)
530{
531 return NULL;
532}
533
534static int ANDROID_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
535{
536 return -1;
537}
538
539static int ANDROID_JoystickGetDevicePlayerIndex(int device_index)
540{
541 return -1;
542}
543
544static void ANDROID_JoystickSetDevicePlayerIndex(int device_index, int player_index)
545{
546}
547
548static SDL_GUID ANDROID_JoystickGetDeviceGUID(int device_index)
549{
550 return GetJoystickByDevIndex(device_index)->guid;
551}
552
553static SDL_JoystickID ANDROID_JoystickGetDeviceInstanceID(int device_index)
554{
555 return GetJoystickByDevIndex(device_index)->device_instance;
556}
557
558static bool ANDROID_JoystickOpen(SDL_Joystick *joystick, int device_index)
559{
560 SDL_joylist_item *item = GetJoystickByDevIndex(device_index);
561
562 if (!item) {
563 return SDL_SetError("No such device");
564 }
565
566 if (item->joystick) {
567 return SDL_SetError("Joystick already opened");
568 }
569
570 joystick->hwdata = (struct joystick_hwdata *)item;
571 item->joystick = joystick;
572 joystick->nhats = item->nhats;
573 joystick->nbuttons = item->nbuttons;
574 joystick->naxes = item->naxes;
575
576 if (item->can_rumble) {
577 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
578 }
579
580 return true;
581}
582
583static bool ANDROID_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
584{
585 SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
586 if (!item) {
587 return SDL_SetError("Rumble failed, device disconnected");
588 }
589 if (!item->can_rumble) {
590 return SDL_Unsupported();
591 }
592
593 float low_frequency_intensity = (float)low_frequency_rumble / SDL_MAX_UINT16;
594 float high_frequency_intensity = (float)high_frequency_rumble / SDL_MAX_UINT16;
595 Android_JNI_HapticRumble(item->device_id, low_frequency_intensity, high_frequency_intensity, 5000);
596 return true;
597}
598
599static bool ANDROID_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
600{
601 return SDL_Unsupported();
602}
603
604static bool ANDROID_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
605{
606 return SDL_Unsupported();
607}
608
609static bool ANDROID_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
610{
611 return SDL_Unsupported();
612}
613
614static bool ANDROID_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
615{
616 return SDL_Unsupported();
617}
618
619static void ANDROID_JoystickUpdate(SDL_Joystick *joystick)
620{
621}
622
623static void ANDROID_JoystickClose(SDL_Joystick *joystick)
624{
625 SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
626 if (item) {
627 item->joystick = NULL;
628 }
629}
630
631static void ANDROID_JoystickQuit(void)
632{
633/* We don't have any way to scan for joysticks at init, so don't wipe the list
634 * of joysticks here in case this is a reinit.
635 */
636#if 0
637 SDL_joylist_item *item = NULL;
638 SDL_joylist_item *next = NULL;
639
640 for (item = SDL_joylist; item; item = next) {
641 next = item->next;
642 SDL_free(item->name);
643 SDL_free(item);
644 }
645
646 SDL_joylist = SDL_joylist_tail = NULL;
647
648 numjoysticks = 0;
649#endif // 0
650}
651
652static bool ANDROID_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
653{
654 return false;
655}
656
657SDL_JoystickDriver SDL_ANDROID_JoystickDriver = {
658 ANDROID_JoystickInit,
659 ANDROID_JoystickGetCount,
660 ANDROID_JoystickDetect,
661 ANDROID_JoystickIsDevicePresent,
662 ANDROID_JoystickGetDeviceName,
663 ANDROID_JoystickGetDevicePath,
664 ANDROID_JoystickGetDeviceSteamVirtualGamepadSlot,
665 ANDROID_JoystickGetDevicePlayerIndex,
666 ANDROID_JoystickSetDevicePlayerIndex,
667 ANDROID_JoystickGetDeviceGUID,
668 ANDROID_JoystickGetDeviceInstanceID,
669 ANDROID_JoystickOpen,
670 ANDROID_JoystickRumble,
671 ANDROID_JoystickRumbleTriggers,
672 ANDROID_JoystickSetLED,
673 ANDROID_JoystickSendEffect,
674 ANDROID_JoystickSetSensorsEnabled,
675 ANDROID_JoystickUpdate,
676 ANDROID_JoystickClose,
677 ANDROID_JoystickQuit,
678 ANDROID_JoystickGetGamepadMapping
679};
680
681#endif // SDL_JOYSTICK_ANDROID
diff --git a/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick_c.h
new file mode 100644
index 0000000..febb228
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/android/SDL_sysjoystick_c.h
@@ -0,0 +1,57 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_JOYSTICK_ANDROID
25
26#ifndef SDL_sysjoystick_c_h_
27#define SDL_sysjoystick_c_h_
28
29#include "../SDL_sysjoystick.h"
30
31extern bool Android_OnPadDown(int device_id, int keycode);
32extern bool Android_OnPadUp(int device_id, int keycode);
33extern bool Android_OnJoy(int device_id, int axisnum, float value);
34extern bool Android_OnHat(int device_id, int hat_id, int x, int y);
35extern void Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, bool can_rumble);
36extern void Android_RemoveJoystick(int device_id);
37
38// A linked list of available joysticks
39typedef struct SDL_joylist_item
40{
41 int device_instance;
42 int device_id; // Android's device id
43 char *name; // "SideWinder 3D Pro" or whatever
44 SDL_GUID guid;
45 SDL_Joystick *joystick;
46 int nbuttons, naxes, nhats;
47 int dpad_state;
48 bool can_rumble;
49
50 struct SDL_joylist_item *next;
51} SDL_joylist_item;
52
53typedef SDL_joylist_item joystick_hwdata;
54
55#endif // SDL_sysjoystick_c_h_
56
57#endif // SDL_JOYSTICK_ANDROID
diff --git a/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick.m b/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick.m
new file mode 100644
index 0000000..811a9f1
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick.m
@@ -0,0 +1,1946 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// This is the iOS implementation of the SDL joystick API
24#include "../SDL_sysjoystick.h"
25#include "../SDL_joystick_c.h"
26#include "../hidapi/SDL_hidapijoystick_c.h"
27#include "../usb_ids.h"
28#include "../../events/SDL_events_c.h"
29
30#include "SDL_mfijoystick_c.h"
31
32
33#if defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_TVOS)
34#import <CoreMotion/CoreMotion.h>
35#endif
36
37#ifdef SDL_PLATFORM_MACOS
38#include <IOKit/hid/IOHIDManager.h>
39#include <AppKit/NSApplication.h>
40#ifndef NSAppKitVersionNumber10_15
41#define NSAppKitVersionNumber10_15 1894
42#endif
43#endif // SDL_PLATFORM_MACOS
44
45#import <GameController/GameController.h>
46
47#ifdef SDL_JOYSTICK_MFI
48static id connectObserver = nil;
49static id disconnectObserver = nil;
50
51#include <objc/message.h>
52
53// Fix build errors when using an older SDK by defining these selectors
54@interface GCController (SDL)
55#if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 140500) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140500) || (__MAC_OS_X_VERSION_MAX_ALLOWED >= 110300))
56@property(class, nonatomic, readwrite) BOOL shouldMonitorBackgroundEvents;
57#endif
58@end
59
60#import <CoreHaptics/CoreHaptics.h>
61
62#endif // SDL_JOYSTICK_MFI
63
64static SDL_JoystickDeviceItem *deviceList = NULL;
65
66static int numjoysticks = 0;
67int SDL_AppleTVRemoteOpenedAsJoystick = 0;
68
69static SDL_JoystickDeviceItem *GetDeviceForIndex(int device_index)
70{
71 SDL_JoystickDeviceItem *device = deviceList;
72 int i = 0;
73
74 while (i < device_index) {
75 if (device == NULL) {
76 return NULL;
77 }
78 device = device->next;
79 i++;
80 }
81
82 return device;
83}
84
85#ifdef SDL_JOYSTICK_MFI
86static bool IsControllerPS4(GCController *controller)
87{
88 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
89 if ([controller.productCategory isEqualToString:@"DualShock 4"]) {
90 return true;
91 }
92 } else {
93 if ([controller.vendorName containsString:@"DUALSHOCK"]) {
94 return true;
95 }
96 }
97 return false;
98}
99static bool IsControllerPS5(GCController *controller)
100{
101 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
102 if ([controller.productCategory isEqualToString:@"DualSense"]) {
103 return true;
104 }
105 } else {
106 if ([controller.vendorName containsString:@"DualSense"]) {
107 return true;
108 }
109 }
110 return false;
111}
112static bool IsControllerXbox(GCController *controller)
113{
114 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
115 if ([controller.productCategory isEqualToString:@"Xbox One"]) {
116 return true;
117 }
118 } else {
119 if ([controller.vendorName containsString:@"Xbox"]) {
120 return true;
121 }
122 }
123 return false;
124}
125static bool IsControllerSwitchPro(GCController *controller)
126{
127 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
128 if ([controller.productCategory isEqualToString:@"Switch Pro Controller"]) {
129 return true;
130 }
131 }
132 return false;
133}
134static bool IsControllerSwitchJoyConL(GCController *controller)
135{
136 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
137 if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L)"]) {
138 return true;
139 }
140 }
141 return false;
142}
143static bool IsControllerSwitchJoyConR(GCController *controller)
144{
145 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
146 if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (R)"]) {
147 return true;
148 }
149 }
150 return false;
151}
152static bool IsControllerSwitchJoyConPair(GCController *controller)
153{
154 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
155 if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L/R)"]) {
156 return true;
157 }
158 }
159 return false;
160}
161static bool IsControllerStadia(GCController *controller)
162{
163 if ([controller.vendorName hasPrefix:@"Stadia"]) {
164 return true;
165 }
166 return false;
167}
168static bool IsControllerBackboneOne(GCController *controller)
169{
170 if ([controller.vendorName hasPrefix:@"Backbone One"]) {
171 return true;
172 }
173 return false;
174}
175static void CheckControllerSiriRemote(GCController *controller, int *is_siri_remote)
176{
177 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
178 if ([controller.productCategory hasPrefix:@"Siri Remote"]) {
179 *is_siri_remote = 1;
180 SDL_sscanf(controller.productCategory.UTF8String, "Siri Remote (%i%*s Generation)", is_siri_remote);
181 return;
182 }
183 }
184 *is_siri_remote = 0;
185}
186
187static bool ElementAlreadyHandled(SDL_JoystickDeviceItem *device, NSString *element, NSDictionary<NSString *, GCControllerElement *> *elements)
188{
189 if ([element isEqualToString:@"Left Thumbstick Left"] ||
190 [element isEqualToString:@"Left Thumbstick Right"]) {
191 if (elements[@"Left Thumbstick X Axis"]) {
192 return true;
193 }
194 }
195 if ([element isEqualToString:@"Left Thumbstick Up"] ||
196 [element isEqualToString:@"Left Thumbstick Down"]) {
197 if (elements[@"Left Thumbstick Y Axis"]) {
198 return true;
199 }
200 }
201 if ([element isEqualToString:@"Right Thumbstick Left"] ||
202 [element isEqualToString:@"Right Thumbstick Right"]) {
203 if (elements[@"Right Thumbstick X Axis"]) {
204 return true;
205 }
206 }
207 if ([element isEqualToString:@"Right Thumbstick Up"] ||
208 [element isEqualToString:@"Right Thumbstick Down"]) {
209 if (elements[@"Right Thumbstick Y Axis"]) {
210 return true;
211 }
212 }
213 if (device->is_siri_remote) {
214 if ([element isEqualToString:@"Direction Pad Left"] ||
215 [element isEqualToString:@"Direction Pad Right"]) {
216 if (elements[@"Direction Pad X Axis"]) {
217 return true;
218 }
219 }
220 if ([element isEqualToString:@"Direction Pad Up"] ||
221 [element isEqualToString:@"Direction Pad Down"]) {
222 if (elements[@"Direction Pad Y Axis"]) {
223 return true;
224 }
225 }
226 } else {
227 if ([element isEqualToString:@"Direction Pad X Axis"]) {
228 if (elements[@"Direction Pad Left"] &&
229 elements[@"Direction Pad Right"]) {
230 return true;
231 }
232 }
233 if ([element isEqualToString:@"Direction Pad Y Axis"]) {
234 if (elements[@"Direction Pad Up"] &&
235 elements[@"Direction Pad Down"]) {
236 return true;
237 }
238 }
239 }
240 if ([element isEqualToString:@"Cardinal Direction Pad X Axis"]) {
241 if (elements[@"Cardinal Direction Pad Left"] &&
242 elements[@"Cardinal Direction Pad Right"]) {
243 return true;
244 }
245 }
246 if ([element isEqualToString:@"Cardinal Direction Pad Y Axis"]) {
247 if (elements[@"Cardinal Direction Pad Up"] &&
248 elements[@"Cardinal Direction Pad Down"]) {
249 return true;
250 }
251 }
252 if ([element isEqualToString:@"Touchpad 1 X Axis"] ||
253 [element isEqualToString:@"Touchpad 1 Y Axis"] ||
254 [element isEqualToString:@"Touchpad 1 Left"] ||
255 [element isEqualToString:@"Touchpad 1 Right"] ||
256 [element isEqualToString:@"Touchpad 1 Up"] ||
257 [element isEqualToString:@"Touchpad 1 Down"] ||
258 [element isEqualToString:@"Touchpad 2 X Axis"] ||
259 [element isEqualToString:@"Touchpad 2 Y Axis"] ||
260 [element isEqualToString:@"Touchpad 2 Left"] ||
261 [element isEqualToString:@"Touchpad 2 Right"] ||
262 [element isEqualToString:@"Touchpad 2 Up"] ||
263 [element isEqualToString:@"Touchpad 2 Down"]) {
264 // The touchpad is handled separately
265 return true;
266 }
267 if ([element isEqualToString:@"Button Home"]) {
268 if (device->is_switch_joycon_pair) {
269 // The Nintendo Switch JoyCon home button doesn't ever show as being held down
270 return true;
271 }
272#ifdef SDL_PLATFORM_TVOS
273 // The OS uses the home button, it's not available to apps
274 return true;
275#endif
276 }
277 if ([element isEqualToString:@"Button Share"]) {
278 if (device->is_backbone_one) {
279 // The Backbone app uses share button
280 return true;
281 }
282 }
283 return false;
284}
285
286static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
287{
288 Uint16 vendor = 0;
289 Uint16 product = 0;
290 Uint8 subtype = 0;
291 const char *name = NULL;
292
293 if (@available(macOS 11.3, iOS 14.5, tvOS 14.5, *)) {
294 if (!GCController.shouldMonitorBackgroundEvents) {
295 GCController.shouldMonitorBackgroundEvents = YES;
296 }
297 }
298
299 /* Explicitly retain the controller because SDL_JoystickDeviceItem is a
300 * struct, and ARC doesn't work with structs. */
301 device->controller = (__bridge GCController *)CFBridgingRetain(controller);
302
303 if (controller.vendorName) {
304 name = controller.vendorName.UTF8String;
305 }
306
307 if (!name) {
308 name = "MFi Gamepad";
309 }
310
311 device->name = SDL_CreateJoystickName(0, 0, NULL, name);
312
313#ifdef DEBUG_CONTROLLER_PROFILE
314 NSLog(@"Product name: %@\n", controller.vendorName);
315 NSLog(@"Product category: %@\n", controller.productCategory);
316 NSLog(@"Elements available:\n");
317 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
318 NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
319 for (id key in controller.physicalInputProfile.buttons) {
320 NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital");
321 }
322 for (id key in controller.physicalInputProfile.axes) {
323 NSLog(@"\tAxis: %@\n", key);
324 }
325 for (id key in controller.physicalInputProfile.dpads) {
326 NSLog(@"\tHat: %@\n", key);
327 }
328 }
329#endif // DEBUG_CONTROLLER_PROFILE
330
331 device->is_xbox = IsControllerXbox(controller);
332 device->is_ps4 = IsControllerPS4(controller);
333 device->is_ps5 = IsControllerPS5(controller);
334 device->is_switch_pro = IsControllerSwitchPro(controller);
335 device->is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller);
336 device->is_stadia = IsControllerStadia(controller);
337 device->is_backbone_one = IsControllerBackboneOne(controller);
338 device->is_switch_joyconL = IsControllerSwitchJoyConL(controller);
339 device->is_switch_joyconR = IsControllerSwitchJoyConR(controller);
340#ifdef SDL_JOYSTICK_HIDAPI
341 if ((device->is_xbox && (HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOXONE) ||
342 HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOX360))) ||
343 (device->is_ps4 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS4)) ||
344 (device->is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) ||
345 (device->is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) ||
346 (device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) ||
347 (device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) ||
348 (device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) ||
349 (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) {
350 // The HIDAPI driver is taking care of this device
351 return false;
352 }
353#endif
354 if (device->is_xbox && SDL_strncmp(name, "GamePad-", 8) == 0) {
355 // This is a Steam Virtual Gamepad, which isn't supported by GCController
356 return false;
357 }
358 CheckControllerSiriRemote(controller, &device->is_siri_remote);
359
360 if (device->is_siri_remote && !SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, true)) {
361 // Ignore remotes, they'll be handled as keyboard input
362 return false;
363 }
364
365 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
366 if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) {
367 device->has_dualshock_touchpad = TRUE;
368 }
369 if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) {
370 device->has_xbox_paddles = TRUE;
371 }
372 if (controller.physicalInputProfile.buttons[@"Button Share"] != nil) {
373 device->has_xbox_share_button = TRUE;
374 }
375 }
376
377 if (device->is_backbone_one) {
378 vendor = USB_VENDOR_BACKBONE;
379 if (device->is_ps5) {
380 product = USB_PRODUCT_BACKBONE_ONE_IOS_PS5;
381 } else {
382 product = USB_PRODUCT_BACKBONE_ONE_IOS;
383 }
384 } else if (device->is_xbox) {
385 vendor = USB_VENDOR_MICROSOFT;
386 if (device->has_xbox_paddles) {
387 // Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID
388 product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH;
389 } else if (device->has_xbox_share_button) {
390 // Assume Xbox Series X Controller unless/until GCController flows VID/PID
391 product = USB_PRODUCT_XBOX_SERIES_X_BLE;
392 } else {
393 // Assume Xbox One S Bluetooth Controller unless/until GCController flows VID/PID
394 product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH;
395 }
396 } else if (device->is_ps4) {
397 // Assume DS4 Slim unless/until GCController flows VID/PID
398 vendor = USB_VENDOR_SONY;
399 product = USB_PRODUCT_SONY_DS4_SLIM;
400 if (device->has_dualshock_touchpad) {
401 subtype = 1;
402 }
403 } else if (device->is_ps5) {
404 vendor = USB_VENDOR_SONY;
405 product = USB_PRODUCT_SONY_DS5;
406 } else if (device->is_switch_pro) {
407 vendor = USB_VENDOR_NINTENDO;
408 product = USB_PRODUCT_NINTENDO_SWITCH_PRO;
409 device->has_nintendo_buttons = TRUE;
410 } else if (device->is_switch_joycon_pair) {
411 vendor = USB_VENDOR_NINTENDO;
412 product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
413 device->has_nintendo_buttons = TRUE;
414 } else if (device->is_switch_joyconL) {
415 vendor = USB_VENDOR_NINTENDO;
416 product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT;
417 } else if (device->is_switch_joyconR) {
418 vendor = USB_VENDOR_NINTENDO;
419 product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT;
420 } else if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
421 vendor = USB_VENDOR_APPLE;
422 product = 4;
423 subtype = 4;
424 } else if (controller.extendedGamepad) {
425 vendor = USB_VENDOR_APPLE;
426 product = 1;
427 subtype = 1;
428#ifdef SDL_PLATFORM_TVOS
429 } else if (controller.microGamepad) {
430 vendor = USB_VENDOR_APPLE;
431 product = 3;
432 subtype = 3;
433#endif
434 } else {
435 // We don't know how to get input events from this device
436 return false;
437 }
438
439 if (SDL_ShouldIgnoreJoystick(vendor, product, 0, name)) {
440 return false;
441 }
442
443 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
444 NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
445
446 // Provide both axes and analog buttons as SDL axes
447 NSArray *axes = [[[elements allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
448 filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
449 if (ElementAlreadyHandled(device, (NSString *)object, elements)) {
450 return false;
451 }
452
453 GCControllerElement *element = elements[object];
454 if (element.analog) {
455 if ([element isKindOfClass:[GCControllerAxisInput class]] ||
456 [element isKindOfClass:[GCControllerButtonInput class]]) {
457 return true;
458 }
459 }
460 return false;
461 }]];
462 NSArray *buttons = [[[elements allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
463 filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
464 if (ElementAlreadyHandled(device, (NSString *)object, elements)) {
465 return false;
466 }
467
468 GCControllerElement *element = elements[object];
469 if ([element isKindOfClass:[GCControllerButtonInput class]]) {
470 return true;
471 }
472 return false;
473 }]];
474 /* Explicitly retain the arrays because SDL_JoystickDeviceItem is a
475 * struct, and ARC doesn't work with structs. */
476 device->naxes = (int)axes.count;
477 device->axes = (__bridge NSArray *)CFBridgingRetain(axes);
478 device->nbuttons = (int)buttons.count;
479 device->buttons = (__bridge NSArray *)CFBridgingRetain(buttons);
480 subtype = 4;
481
482#ifdef DEBUG_CONTROLLER_PROFILE
483 NSLog(@"Elements used:\n", controller.vendorName);
484 for (id key in device->buttons) {
485 NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital");
486 }
487 for (id key in device->axes) {
488 NSLog(@"\tAxis: %@\n", key);
489 }
490#endif // DEBUG_CONTROLLER_PROFILE
491
492#ifdef SDL_PLATFORM_TVOS
493 // tvOS turns the menu button into a system gesture, so we grab it here instead
494 if (elements[GCInputButtonMenu] && !elements[@"Button Home"]) {
495 device->pause_button_index = (int)[device->buttons indexOfObject:GCInputButtonMenu];
496 }
497#endif
498 } else if (controller.extendedGamepad) {
499 GCExtendedGamepad *gamepad = controller.extendedGamepad;
500 int nbuttons = 0;
501 BOOL has_direct_menu = FALSE;
502
503 // These buttons are part of the original MFi spec
504 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH);
505 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST);
506 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST);
507 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_NORTH);
508 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
509 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
510 nbuttons += 6;
511
512 // These buttons are available on some newer controllers
513 if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) {
514 if (gamepad.leftThumbstickButton) {
515 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK);
516 ++nbuttons;
517 }
518 if (gamepad.rightThumbstickButton) {
519 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK);
520 ++nbuttons;
521 }
522 }
523 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
524 if (gamepad.buttonOptions) {
525 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_BACK);
526 ++nbuttons;
527 }
528 }
529 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_START);
530 ++nbuttons;
531
532 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
533 if (gamepad.buttonMenu) {
534 has_direct_menu = TRUE;
535 }
536 }
537#ifdef SDL_PLATFORM_TVOS
538 // The single menu button isn't very reliable, at least as of tvOS 16.1
539 if ((device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) == 0) {
540 has_direct_menu = FALSE;
541 }
542#endif
543 if (!has_direct_menu) {
544 device->pause_button_index = (nbuttons - 1);
545 }
546
547 device->naxes = 6; // 2 thumbsticks and 2 triggers
548 device->nhats = 1; // d-pad
549 device->nbuttons = nbuttons;
550 }
551#ifdef SDL_PLATFORM_TVOS
552 else if (controller.microGamepad) {
553 int nbuttons = 0;
554
555 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH);
556 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST); // Button X on microGamepad
557 device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST);
558 nbuttons += 3;
559 device->pause_button_index = (nbuttons - 1);
560
561 device->naxes = 2; // treat the touch surface as two axes
562 device->nhats = 0; // apparently the touch surface-as-dpad is buggy
563 device->nbuttons = nbuttons;
564
565 controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, false);
566 }
567#endif
568 else {
569 // We don't know how to get input events from this device
570 return false;
571 }
572
573 Uint16 signature;
574 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
575 signature = 0;
576 signature = SDL_crc16(signature, device->name, SDL_strlen(device->name));
577 for (id key in device->axes) {
578 const char *string = ((NSString *)key).UTF8String;
579 signature = SDL_crc16(signature, string, SDL_strlen(string));
580 }
581 for (id key in device->buttons) {
582 const char *string = ((NSString *)key).UTF8String;
583 signature = SDL_crc16(signature, string, SDL_strlen(string));
584 }
585 } else {
586 signature = device->button_mask;
587 }
588 device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, signature, NULL, name, 'm', subtype);
589
590 /* This will be set when the first button press of the controller is
591 * detected. */
592 controller.playerIndex = -1;
593 return true;
594}
595#endif // SDL_JOYSTICK_MFI
596
597#ifdef SDL_JOYSTICK_MFI
598static void IOS_AddJoystickDevice(GCController *controller)
599{
600 SDL_JoystickDeviceItem *device = deviceList;
601
602 while (device != NULL) {
603 if (device->controller == controller) {
604 return;
605 }
606 device = device->next;
607 }
608
609 device = (SDL_JoystickDeviceItem *)SDL_calloc(1, sizeof(SDL_JoystickDeviceItem));
610 if (device == NULL) {
611 return;
612 }
613
614 device->instance_id = SDL_GetNextObjectID();
615 device->pause_button_index = -1;
616
617 if (controller) {
618#ifdef SDL_JOYSTICK_MFI
619 if (!IOS_AddMFIJoystickDevice(device, controller)) {
620 SDL_free(device->name);
621 SDL_free(device);
622 return;
623 }
624#else
625 SDL_free(device);
626 return;
627#endif // SDL_JOYSTICK_MFI
628 }
629
630 if (deviceList == NULL) {
631 deviceList = device;
632 } else {
633 SDL_JoystickDeviceItem *lastdevice = deviceList;
634 while (lastdevice->next != NULL) {
635 lastdevice = lastdevice->next;
636 }
637 lastdevice->next = device;
638 }
639
640 ++numjoysticks;
641
642 SDL_PrivateJoystickAdded(device->instance_id);
643}
644#endif // SDL_JOYSTICK_MFI
645
646static SDL_JoystickDeviceItem *IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
647{
648 SDL_JoystickDeviceItem *prev = NULL;
649 SDL_JoystickDeviceItem *next = NULL;
650 SDL_JoystickDeviceItem *item = deviceList;
651
652 if (device == NULL) {
653 return NULL;
654 }
655
656 next = device->next;
657
658 while (item != NULL) {
659 if (item == device) {
660 break;
661 }
662 prev = item;
663 item = item->next;
664 }
665
666 // Unlink the device item from the device list.
667 if (prev) {
668 prev->next = device->next;
669 } else if (device == deviceList) {
670 deviceList = device->next;
671 }
672
673 if (device->joystick) {
674 device->joystick->hwdata = NULL;
675 }
676
677#ifdef SDL_JOYSTICK_MFI
678 @autoreleasepool {
679 // These were explicitly retained in the struct, so they should be explicitly released before freeing the struct.
680 if (device->controller) {
681 GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
682 controller.controllerPausedHandler = nil;
683 device->controller = nil;
684 }
685 if (device->axes) {
686 CFRelease((__bridge CFTypeRef)device->axes);
687 device->axes = nil;
688 }
689 if (device->buttons) {
690 CFRelease((__bridge CFTypeRef)device->buttons);
691 device->buttons = nil;
692 }
693 }
694#endif // SDL_JOYSTICK_MFI
695
696 --numjoysticks;
697
698 SDL_PrivateJoystickRemoved(device->instance_id);
699
700 SDL_free(device->name);
701 SDL_free(device);
702
703 return next;
704}
705
706#ifdef SDL_PLATFORM_TVOS
707static void SDLCALL SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue)
708{
709 BOOL allowRotation = newValue != NULL && *newValue != '0';
710
711 @autoreleasepool {
712 for (GCController *controller in [GCController controllers]) {
713 if (controller.microGamepad) {
714 controller.microGamepad.allowsRotation = allowRotation;
715 }
716 }
717 }
718}
719#endif // SDL_PLATFORM_TVOS
720
721static bool IOS_JoystickInit(void)
722{
723 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, true)) {
724 return true;
725 }
726
727#ifdef SDL_PLATFORM_MACOS
728 if (@available(macOS 10.16, *)) {
729 // Continue with initialization on macOS 11+
730 } else {
731 return true;
732 }
733#endif
734
735 @autoreleasepool {
736#ifdef SDL_JOYSTICK_MFI
737 NSNotificationCenter *center;
738#endif
739
740#ifdef SDL_JOYSTICK_MFI
741 // GameController.framework was added in iOS 7.
742 if (![GCController class]) {
743 return true;
744 }
745
746 /* For whatever reason, this always returns an empty array on
747 macOS 11.0.1 */
748 for (GCController *controller in [GCController controllers]) {
749 IOS_AddJoystickDevice(controller);
750 }
751
752#ifdef SDL_PLATFORM_TVOS
753 SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
754 SDL_AppleTVRemoteRotationHintChanged, NULL);
755#endif // SDL_PLATFORM_TVOS
756
757 center = [NSNotificationCenter defaultCenter];
758
759 connectObserver = [center addObserverForName:GCControllerDidConnectNotification
760 object:nil
761 queue:nil
762 usingBlock:^(NSNotification *note) {
763 GCController *controller = note.object;
764 SDL_LockJoysticks();
765 IOS_AddJoystickDevice(controller);
766 SDL_UnlockJoysticks();
767 }];
768
769 disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
770 object:nil
771 queue:nil
772 usingBlock:^(NSNotification *note) {
773 GCController *controller = note.object;
774 SDL_JoystickDeviceItem *device;
775 SDL_LockJoysticks();
776 for (device = deviceList; device != NULL; device = device->next) {
777 if (device->controller == controller) {
778 IOS_RemoveJoystickDevice(device);
779 break;
780 }
781 }
782 SDL_UnlockJoysticks();
783 }];
784#endif // SDL_JOYSTICK_MFI
785 }
786
787 return true;
788}
789
790static int IOS_JoystickGetCount(void)
791{
792 return numjoysticks;
793}
794
795static void IOS_JoystickDetect(void)
796{
797}
798
799static bool IOS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
800{
801 // We don't override any other drivers through this method
802 return false;
803}
804
805static const char *IOS_JoystickGetDeviceName(int device_index)
806{
807 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
808 return device ? device->name : "Unknown";
809}
810
811static const char *IOS_JoystickGetDevicePath(int device_index)
812{
813 return NULL;
814}
815
816static int IOS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
817{
818 return -1;
819}
820
821static int IOS_JoystickGetDevicePlayerIndex(int device_index)
822{
823#ifdef SDL_JOYSTICK_MFI
824 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
825 if (device && device->controller) {
826 return (int)device->controller.playerIndex;
827 }
828#endif
829 return -1;
830}
831
832static void IOS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
833{
834#ifdef SDL_JOYSTICK_MFI
835 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
836 if (device && device->controller) {
837 device->controller.playerIndex = player_index;
838 }
839#endif
840}
841
842static SDL_GUID IOS_JoystickGetDeviceGUID(int device_index)
843{
844 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
845 SDL_GUID guid;
846 if (device) {
847 guid = device->guid;
848 } else {
849 SDL_zero(guid);
850 }
851 return guid;
852}
853
854static SDL_JoystickID IOS_JoystickGetDeviceInstanceID(int device_index)
855{
856 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
857 return device ? device->instance_id : 0;
858}
859
860static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index)
861{
862 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
863 if (device == NULL) {
864 return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
865 }
866
867 joystick->hwdata = device;
868
869 joystick->naxes = device->naxes;
870 joystick->nhats = device->nhats;
871 joystick->nbuttons = device->nbuttons;
872
873 if (device->has_dualshock_touchpad) {
874 SDL_PrivateJoystickAddTouchpad(joystick, 2);
875 }
876
877 device->joystick = joystick;
878
879 @autoreleasepool {
880#ifdef SDL_JOYSTICK_MFI
881 if (device->pause_button_index >= 0) {
882 GCController *controller = device->controller;
883 controller.controllerPausedHandler = ^(GCController *c) {
884 if (joystick->hwdata) {
885 joystick->hwdata->pause_button_pressed = SDL_GetTicks();
886 }
887 };
888 }
889
890 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
891 GCController *controller = joystick->hwdata->controller;
892 GCMotion *motion = controller.motion;
893 if (motion && motion.hasRotationRate) {
894 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f);
895 }
896 if (motion && motion.hasGravityAndUserAcceleration) {
897 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f);
898 }
899 }
900
901 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
902 GCController *controller = joystick->hwdata->controller;
903 for (id key in controller.physicalInputProfile.buttons) {
904 GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
905 if ([button isBoundToSystemGesture]) {
906 button.preferredSystemGestureState = GCSystemGestureStateDisabled;
907 }
908 }
909 }
910
911 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
912 GCController *controller = device->controller;
913 if (controller.light) {
914 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true);
915 }
916
917 if (controller.haptics) {
918 for (GCHapticsLocality locality in controller.haptics.supportedLocalities) {
919 if ([locality isEqualToString:GCHapticsLocalityHandles]) {
920 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
921 } else if ([locality isEqualToString:GCHapticsLocalityTriggers]) {
922 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
923 }
924 }
925 }
926 }
927#endif // SDL_JOYSTICK_MFI
928 }
929 if (device->is_siri_remote) {
930 ++SDL_AppleTVRemoteOpenedAsJoystick;
931 }
932
933 return true;
934}
935
936#ifdef SDL_JOYSTICK_MFI
937static Uint8 IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
938{
939 Uint8 hat = 0;
940
941 if (dpad.up.isPressed) {
942 hat |= SDL_HAT_UP;
943 } else if (dpad.down.isPressed) {
944 hat |= SDL_HAT_DOWN;
945 }
946
947 if (dpad.left.isPressed) {
948 hat |= SDL_HAT_LEFT;
949 } else if (dpad.right.isPressed) {
950 hat |= SDL_HAT_RIGHT;
951 }
952
953 if (hat == 0) {
954 return SDL_HAT_CENTERED;
955 }
956
957 return hat;
958}
959#endif
960
961static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
962{
963#ifdef SDL_JOYSTICK_MFI
964 @autoreleasepool {
965 SDL_JoystickDeviceItem *device = joystick->hwdata;
966 GCController *controller = device->controller;
967 Uint8 hatstate = SDL_HAT_CENTERED;
968 int i;
969 Uint64 timestamp = SDL_GetTicksNS();
970
971#ifdef DEBUG_CONTROLLER_STATE
972 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
973 if (controller.physicalInputProfile) {
974 for (id key in controller.physicalInputProfile.buttons) {
975 GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
976 if (button.isPressed)
977 NSLog(@"Button %@ = %s\n", key, button.isPressed ? "pressed" : "released");
978 }
979 for (id key in controller.physicalInputProfile.axes) {
980 GCControllerAxisInput *axis = controller.physicalInputProfile.axes[key];
981 if (axis.value != 0.0f)
982 NSLog(@"Axis %@ = %g\n", key, axis.value);
983 }
984 for (id key in controller.physicalInputProfile.dpads) {
985 GCControllerDirectionPad *dpad = controller.physicalInputProfile.dpads[key];
986 if (dpad.up.isPressed || dpad.down.isPressed || dpad.left.isPressed || dpad.right.isPressed) {
987 NSLog(@"Hat %@ =%s%s%s%s\n", key,
988 dpad.up.isPressed ? " UP" : "",
989 dpad.down.isPressed ? " DOWN" : "",
990 dpad.left.isPressed ? " LEFT" : "",
991 dpad.right.isPressed ? " RIGHT" : "");
992 }
993 }
994 }
995 }
996#endif // DEBUG_CONTROLLER_STATE
997
998 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
999 NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
1000 NSDictionary<NSString *, GCControllerButtonInput *> *buttons = controller.physicalInputProfile.buttons;
1001
1002 int axis = 0;
1003 for (id key in device->axes) {
1004 Sint16 value;
1005 GCControllerElement *element = elements[key];
1006 if ([element isKindOfClass:[GCControllerAxisInput class]]) {
1007 value = (Sint16)([(GCControllerAxisInput *)element value] * 32767);
1008 } else {
1009 value = (Sint16)([(GCControllerButtonInput *)element value] * 32767);
1010 }
1011 SDL_SendJoystickAxis(timestamp, joystick, axis++, value);
1012 }
1013
1014 int button = 0;
1015 for (id key in device->buttons) {
1016 bool down;
1017 if (button == device->pause_button_index) {
1018 down = (device->pause_button_pressed > 0);
1019 } else {
1020 down = buttons[key].isPressed;
1021 }
1022 SDL_SendJoystickButton(timestamp, joystick, button++, down);
1023 }
1024 } else if (controller.extendedGamepad) {
1025 bool isstack;
1026 GCExtendedGamepad *gamepad = controller.extendedGamepad;
1027
1028 // Axis order matches the XInput Windows mappings.
1029 Sint16 axes[] = {
1030 (Sint16)(gamepad.leftThumbstick.xAxis.value * 32767),
1031 (Sint16)(gamepad.leftThumbstick.yAxis.value * -32767),
1032 (Sint16)((gamepad.leftTrigger.value * 65535) - 32768),
1033 (Sint16)(gamepad.rightThumbstick.xAxis.value * 32767),
1034 (Sint16)(gamepad.rightThumbstick.yAxis.value * -32767),
1035 (Sint16)((gamepad.rightTrigger.value * 65535) - 32768),
1036 };
1037
1038 // Button order matches the XInput Windows mappings.
1039 bool *buttons = SDL_small_alloc(bool, joystick->nbuttons, &isstack);
1040 int button_count = 0;
1041
1042 if (buttons == NULL) {
1043 return;
1044 }
1045
1046 // These buttons are part of the original MFi spec
1047 buttons[button_count++] = gamepad.buttonA.isPressed;
1048 buttons[button_count++] = gamepad.buttonB.isPressed;
1049 buttons[button_count++] = gamepad.buttonX.isPressed;
1050 buttons[button_count++] = gamepad.buttonY.isPressed;
1051 buttons[button_count++] = gamepad.leftShoulder.isPressed;
1052 buttons[button_count++] = gamepad.rightShoulder.isPressed;
1053
1054 // These buttons are available on some newer controllers
1055 if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) {
1056 if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) {
1057 buttons[button_count++] = gamepad.leftThumbstickButton.isPressed;
1058 }
1059 if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) {
1060 buttons[button_count++] = gamepad.rightThumbstickButton.isPressed;
1061 }
1062 }
1063 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
1064 if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {
1065 buttons[button_count++] = gamepad.buttonOptions.isPressed;
1066 }
1067 }
1068 if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) {
1069 if (device->pause_button_index >= 0) {
1070 // Guaranteed if buttonMenu is not supported on this OS
1071 buttons[button_count++] = (device->pause_button_pressed > 0);
1072 } else {
1073 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
1074 buttons[button_count++] = gamepad.buttonMenu.isPressed;
1075 }
1076 }
1077 }
1078
1079 hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
1080
1081 for (i = 0; i < SDL_arraysize(axes); i++) {
1082 SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]);
1083 }
1084
1085 for (i = 0; i < button_count; i++) {
1086 SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]);
1087 }
1088
1089 SDL_small_free(buttons, isstack);
1090 }
1091#ifdef SDL_PLATFORM_TVOS
1092 else if (controller.microGamepad) {
1093 GCMicroGamepad *gamepad = controller.microGamepad;
1094
1095 Sint16 axes[] = {
1096 (Sint16)(gamepad.dpad.xAxis.value * 32767),
1097 (Sint16)(gamepad.dpad.yAxis.value * -32767),
1098 };
1099
1100 for (i = 0; i < SDL_arraysize(axes); i++) {
1101 SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]);
1102 }
1103
1104 bool buttons[joystick->nbuttons];
1105 int button_count = 0;
1106 buttons[button_count++] = gamepad.buttonA.isPressed;
1107 buttons[button_count++] = gamepad.buttonX.isPressed;
1108 buttons[button_count++] = (device->pause_button_pressed > 0);
1109
1110 for (i = 0; i < button_count; i++) {
1111 SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]);
1112 }
1113 }
1114#endif // SDL_PLATFORM_TVOS
1115
1116 if (joystick->nhats > 0) {
1117 SDL_SendJoystickHat(timestamp, joystick, 0, hatstate);
1118 }
1119
1120 if (device->pause_button_pressed) {
1121 // The pause callback is instantaneous, so we extend the duration to allow "holding down" by pressing it repeatedly
1122 const int PAUSE_BUTTON_PRESS_DURATION_MS = 250;
1123 if (SDL_GetTicks() >= device->pause_button_pressed + PAUSE_BUTTON_PRESS_DURATION_MS) {
1124 device->pause_button_pressed = 0;
1125 }
1126 }
1127
1128 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1129 if (device->has_dualshock_touchpad) {
1130 GCControllerDirectionPad *dpad;
1131
1132 dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne];
1133 if (dpad.xAxis.value != 0.f || dpad.yAxis.value != 0.f) {
1134 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, true, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f);
1135 } else {
1136 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, false, 0.0f, 0.0f, 1.0f);
1137 }
1138
1139 dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo];
1140 if (dpad.xAxis.value != 0.f || dpad.yAxis.value != 0.f) {
1141 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, true, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f);
1142 } else {
1143 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, false, 0.0f, 0.0f, 1.0f);
1144 }
1145 }
1146 }
1147
1148 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1149 GCMotion *motion = controller.motion;
1150 if (motion && motion.sensorsActive) {
1151 float data[3];
1152
1153 if (motion.hasRotationRate) {
1154 GCRotationRate rate = motion.rotationRate;
1155 data[0] = rate.x;
1156 data[1] = rate.z;
1157 data[2] = -rate.y;
1158 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, 3);
1159 }
1160 if (motion.hasGravityAndUserAcceleration) {
1161 GCAcceleration accel = motion.acceleration;
1162 data[0] = -accel.x * SDL_STANDARD_GRAVITY;
1163 data[1] = -accel.y * SDL_STANDARD_GRAVITY;
1164 data[2] = -accel.z * SDL_STANDARD_GRAVITY;
1165 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, 3);
1166 }
1167 }
1168 }
1169
1170 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1171 GCDeviceBattery *battery = controller.battery;
1172 if (battery) {
1173 SDL_PowerState state = SDL_POWERSTATE_UNKNOWN;
1174 int percent = (int)SDL_roundf(battery.batteryLevel * 100.0f);
1175
1176 switch (battery.batteryState) {
1177 case GCDeviceBatteryStateDischarging:
1178 state = SDL_POWERSTATE_ON_BATTERY;
1179 break;
1180 case GCDeviceBatteryStateCharging:
1181 state = SDL_POWERSTATE_CHARGING;
1182 break;
1183 case GCDeviceBatteryStateFull:
1184 state = SDL_POWERSTATE_CHARGED;
1185 break;
1186 default:
1187 break;
1188 }
1189
1190 SDL_SendJoystickPowerInfo(joystick, state, percent);
1191 }
1192 }
1193 }
1194#endif // SDL_JOYSTICK_MFI
1195}
1196
1197#ifdef SDL_JOYSTICK_MFI
1198@interface SDL3_RumbleMotor : NSObject
1199@property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0));
1200@property(nonatomic, strong) id<CHHapticPatternPlayer> player API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0));
1201@property bool active;
1202@end
1203
1204@implementation SDL3_RumbleMotor
1205{
1206}
1207
1208- (void)cleanup
1209{
1210 @autoreleasepool {
1211 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1212 if (self.player != nil) {
1213 [self.player cancelAndReturnError:nil];
1214 self.player = nil;
1215 }
1216 if (self.engine != nil) {
1217 [self.engine stopWithCompletionHandler:nil];
1218 self.engine = nil;
1219 }
1220 }
1221 }
1222}
1223
1224- (bool)setIntensity:(float)intensity
1225{
1226 @autoreleasepool {
1227 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1228 NSError *error = nil;
1229 CHHapticDynamicParameter *param;
1230
1231 if (self.engine == nil) {
1232 return SDL_SetError("Haptics engine was stopped");
1233 }
1234
1235 if (intensity == 0.0f) {
1236 if (self.player && self.active) {
1237 [self.player stopAtTime:0 error:&error];
1238 }
1239 self.active = false;
1240 return true;
1241 }
1242
1243 if (self.player == nil) {
1244 CHHapticEventParameter *event_param = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
1245 CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObjects:event_param, nil] relativeTime:0 duration:GCHapticDurationInfinite];
1246 CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:[NSArray arrayWithObject:event] parameters:[[NSArray alloc] init] error:&error];
1247 if (error != nil) {
1248 return SDL_SetError("Couldn't create haptic pattern: %s", [error.localizedDescription UTF8String]);
1249 }
1250
1251 self.player = [self.engine createPlayerWithPattern:pattern error:&error];
1252 if (error != nil) {
1253 return SDL_SetError("Couldn't create haptic player: %s", [error.localizedDescription UTF8String]);
1254 }
1255 self.active = false;
1256 }
1257
1258 param = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:intensity relativeTime:0];
1259 [self.player sendParameters:[NSArray arrayWithObject:param] atTime:0 error:&error];
1260 if (error != nil) {
1261 return SDL_SetError("Couldn't update haptic player: %s", [error.localizedDescription UTF8String]);
1262 }
1263
1264 if (!self.active) {
1265 [self.player startAtTime:0 error:&error];
1266 self.active = true;
1267 }
1268 }
1269
1270 return true;
1271 }
1272}
1273
1274- (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(10.16), ios(14.0), tvos(14.0))
1275{
1276 @autoreleasepool {
1277 NSError *error;
1278 __weak __typeof(self) weakSelf;
1279 self = [super init];
1280 weakSelf = self;
1281
1282 self.engine = [controller.haptics createEngineWithLocality:locality];
1283 if (self.engine == nil) {
1284 SDL_SetError("Couldn't create haptics engine");
1285 return nil;
1286 }
1287
1288 [self.engine startAndReturnError:&error];
1289 if (error != nil) {
1290 SDL_SetError("Couldn't start haptics engine");
1291 return nil;
1292 }
1293
1294 self.engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) {
1295 SDL3_RumbleMotor *_this = weakSelf;
1296 if (_this == nil) {
1297 return;
1298 }
1299
1300 _this.player = nil;
1301 _this.engine = nil;
1302 };
1303 self.engine.resetHandler = ^{
1304 SDL3_RumbleMotor *_this = weakSelf;
1305 if (_this == nil) {
1306 return;
1307 }
1308
1309 _this.player = nil;
1310 [_this.engine startAndReturnError:nil];
1311 };
1312
1313 return self;
1314 }
1315}
1316
1317@end
1318
1319@interface SDL3_RumbleContext : NSObject
1320@property(nonatomic, strong) SDL3_RumbleMotor *lowFrequencyMotor;
1321@property(nonatomic, strong) SDL3_RumbleMotor *highFrequencyMotor;
1322@property(nonatomic, strong) SDL3_RumbleMotor *leftTriggerMotor;
1323@property(nonatomic, strong) SDL3_RumbleMotor *rightTriggerMotor;
1324@end
1325
1326@implementation SDL3_RumbleContext
1327{
1328}
1329
1330- (id)initWithLowFrequencyMotor:(SDL3_RumbleMotor *)low_frequency_motor
1331 HighFrequencyMotor:(SDL3_RumbleMotor *)high_frequency_motor
1332 LeftTriggerMotor:(SDL3_RumbleMotor *)left_trigger_motor
1333 RightTriggerMotor:(SDL3_RumbleMotor *)right_trigger_motor
1334{
1335 self = [super init];
1336 self.lowFrequencyMotor = low_frequency_motor;
1337 self.highFrequencyMotor = high_frequency_motor;
1338 self.leftTriggerMotor = left_trigger_motor;
1339 self.rightTriggerMotor = right_trigger_motor;
1340 return self;
1341}
1342
1343- (bool)rumbleWithLowFrequency:(Uint16)low_frequency_rumble andHighFrequency:(Uint16)high_frequency_rumble
1344{
1345 bool result = true;
1346
1347 result &= [self.lowFrequencyMotor setIntensity:((float)low_frequency_rumble / 65535.0f)];
1348 result &= [self.highFrequencyMotor setIntensity:((float)high_frequency_rumble / 65535.0f)];
1349 return result;
1350}
1351
1352- (bool)rumbleLeftTrigger:(Uint16)left_rumble andRightTrigger:(Uint16)right_rumble
1353{
1354 bool result = false;
1355
1356 if (self.leftTriggerMotor && self.rightTriggerMotor) {
1357 result &= [self.leftTriggerMotor setIntensity:((float)left_rumble / 65535.0f)];
1358 result &= [self.rightTriggerMotor setIntensity:((float)right_rumble / 65535.0f)];
1359 } else {
1360 result = SDL_Unsupported();
1361 }
1362 return result;
1363}
1364
1365- (void)cleanup
1366{
1367 [self.lowFrequencyMotor cleanup];
1368 [self.highFrequencyMotor cleanup];
1369}
1370
1371@end
1372
1373static SDL3_RumbleContext *IOS_JoystickInitRumble(GCController *controller)
1374{
1375 @autoreleasepool {
1376 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1377 SDL3_RumbleMotor *low_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle];
1378 SDL3_RumbleMotor *high_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle];
1379 SDL3_RumbleMotor *left_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger];
1380 SDL3_RumbleMotor *right_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightTrigger];
1381 if (low_frequency_motor && high_frequency_motor) {
1382 return [[SDL3_RumbleContext alloc] initWithLowFrequencyMotor:low_frequency_motor
1383 HighFrequencyMotor:high_frequency_motor
1384 LeftTriggerMotor:left_trigger_motor
1385 RightTriggerMotor:right_trigger_motor];
1386 }
1387 }
1388 }
1389 return nil;
1390}
1391
1392#endif // SDL_JOYSTICK_MFI
1393
1394static bool IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1395{
1396#ifdef SDL_JOYSTICK_MFI
1397 SDL_JoystickDeviceItem *device = joystick->hwdata;
1398
1399 if (device == NULL) {
1400 return SDL_SetError("Controller is no longer connected");
1401 }
1402
1403 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1404 if (!device->rumble && device->controller && device->controller.haptics) {
1405 SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
1406 if (rumble) {
1407 device->rumble = (void *)CFBridgingRetain(rumble);
1408 }
1409 }
1410 }
1411
1412 if (device->rumble) {
1413 SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble;
1414 return [rumble rumbleWithLowFrequency:low_frequency_rumble andHighFrequency:high_frequency_rumble];
1415 }
1416#endif
1417 return SDL_Unsupported();
1418}
1419
1420static bool IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
1421{
1422#ifdef SDL_JOYSTICK_MFI
1423 SDL_JoystickDeviceItem *device = joystick->hwdata;
1424
1425 if (device == NULL) {
1426 return SDL_SetError("Controller is no longer connected");
1427 }
1428
1429 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1430 if (!device->rumble && device->controller && device->controller.haptics) {
1431 SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
1432 if (rumble) {
1433 device->rumble = (void *)CFBridgingRetain(rumble);
1434 }
1435 }
1436 }
1437
1438 if (device->rumble) {
1439 SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble;
1440 return [rumble rumbleLeftTrigger:left_rumble andRightTrigger:right_rumble];
1441 }
1442#endif
1443 return SDL_Unsupported();
1444}
1445
1446static bool IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1447{
1448 @autoreleasepool {
1449 SDL_JoystickDeviceItem *device = joystick->hwdata;
1450
1451 if (device == NULL) {
1452 return SDL_SetError("Controller is no longer connected");
1453 }
1454
1455 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1456 GCController *controller = device->controller;
1457 GCDeviceLight *light = controller.light;
1458 if (light) {
1459 light.color = [[GCColor alloc] initWithRed:(float)red / 255.0f
1460 green:(float)green / 255.0f
1461 blue:(float)blue / 255.0f];
1462 return true;
1463 }
1464 }
1465 }
1466 return SDL_Unsupported();
1467}
1468
1469static bool IOS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
1470{
1471 return SDL_Unsupported();
1472}
1473
1474static bool IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
1475{
1476 @autoreleasepool {
1477 SDL_JoystickDeviceItem *device = joystick->hwdata;
1478
1479 if (device == NULL) {
1480 return SDL_SetError("Controller is no longer connected");
1481 }
1482
1483 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1484 GCController *controller = device->controller;
1485 GCMotion *motion = controller.motion;
1486 if (motion) {
1487 motion.sensorsActive = enabled ? YES : NO;
1488 return true;
1489 }
1490 }
1491 }
1492
1493 return SDL_Unsupported();
1494}
1495
1496static void IOS_JoystickUpdate(SDL_Joystick *joystick)
1497{
1498 SDL_JoystickDeviceItem *device = joystick->hwdata;
1499
1500 if (device == NULL) {
1501 return;
1502 }
1503
1504 if (device->controller) {
1505 IOS_MFIJoystickUpdate(joystick);
1506 }
1507}
1508
1509static void IOS_JoystickClose(SDL_Joystick *joystick)
1510{
1511 SDL_JoystickDeviceItem *device = joystick->hwdata;
1512
1513 if (device == NULL) {
1514 return;
1515 }
1516
1517 device->joystick = NULL;
1518
1519#ifdef SDL_JOYSTICK_MFI
1520 @autoreleasepool {
1521 if (device->rumble) {
1522 SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble;
1523
1524 [rumble cleanup];
1525 CFRelease(device->rumble);
1526 device->rumble = NULL;
1527 }
1528
1529 if (device->controller) {
1530 GCController *controller = device->controller;
1531 controller.controllerPausedHandler = nil;
1532 controller.playerIndex = -1;
1533
1534 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1535 for (id key in controller.physicalInputProfile.buttons) {
1536 GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
1537 if ([button isBoundToSystemGesture]) {
1538 button.preferredSystemGestureState = GCSystemGestureStateEnabled;
1539 }
1540 }
1541 }
1542 }
1543 }
1544#endif // SDL_JOYSTICK_MFI
1545
1546 if (device->is_siri_remote) {
1547 --SDL_AppleTVRemoteOpenedAsJoystick;
1548 }
1549}
1550
1551static void IOS_JoystickQuit(void)
1552{
1553 @autoreleasepool {
1554#ifdef SDL_JOYSTICK_MFI
1555 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
1556
1557 if (connectObserver) {
1558 [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
1559 connectObserver = nil;
1560 }
1561
1562 if (disconnectObserver) {
1563 [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
1564 disconnectObserver = nil;
1565 }
1566
1567#ifdef SDL_PLATFORM_TVOS
1568 SDL_RemoveHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
1569 SDL_AppleTVRemoteRotationHintChanged, NULL);
1570#endif // SDL_PLATFORM_TVOS
1571#endif // SDL_JOYSTICK_MFI
1572
1573 while (deviceList != NULL) {
1574 IOS_RemoveJoystickDevice(deviceList);
1575 }
1576 }
1577
1578 numjoysticks = 0;
1579}
1580
1581static bool IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
1582{
1583 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
1584 if (device == NULL) {
1585 return false;
1586 }
1587
1588 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1589 int axis = 0;
1590 for (id key in device->axes) {
1591 if ([(NSString *)key isEqualToString:@"Left Thumbstick X Axis"] ||
1592 [(NSString *)key isEqualToString:@"Direction Pad X Axis"]) {
1593 out->leftx.kind = EMappingKind_Axis;
1594 out->leftx.target = axis;
1595 } else if ([(NSString *)key isEqualToString:@"Left Thumbstick Y Axis"] ||
1596 [(NSString *)key isEqualToString:@"Direction Pad Y Axis"]) {
1597 out->lefty.kind = EMappingKind_Axis;
1598 out->lefty.target = axis;
1599 out->lefty.axis_reversed = true;
1600 } else if ([(NSString *)key isEqualToString:@"Right Thumbstick X Axis"]) {
1601 out->rightx.kind = EMappingKind_Axis;
1602 out->rightx.target = axis;
1603 } else if ([(NSString *)key isEqualToString:@"Right Thumbstick Y Axis"]) {
1604 out->righty.kind = EMappingKind_Axis;
1605 out->righty.target = axis;
1606 out->righty.axis_reversed = true;
1607 } else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) {
1608 out->lefttrigger.kind = EMappingKind_Axis;
1609 out->lefttrigger.target = axis;
1610 out->lefttrigger.half_axis_positive = true;
1611 } else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) {
1612 out->righttrigger.kind = EMappingKind_Axis;
1613 out->righttrigger.target = axis;
1614 out->righttrigger.half_axis_positive = true;
1615 }
1616 ++axis;
1617 }
1618
1619 int button = 0;
1620 for (id key in device->buttons) {
1621 SDL_InputMapping *mapping = NULL;
1622
1623 if ([(NSString *)key isEqualToString:GCInputButtonA]) {
1624 if (device->is_siri_remote > 1) {
1625 // GCInputButtonA is triggered for any D-Pad press, ignore it in favor of "Button Center"
1626 } else if (device->has_nintendo_buttons) {
1627 mapping = &out->b;
1628 } else {
1629 mapping = &out->a;
1630 }
1631 } else if ([(NSString *)key isEqualToString:GCInputButtonB]) {
1632 if (device->has_nintendo_buttons) {
1633 mapping = &out->a;
1634 } else if (device->is_switch_joyconL || device->is_switch_joyconR) {
1635 mapping = &out->x;
1636 } else {
1637 mapping = &out->b;
1638 }
1639 } else if ([(NSString *)key isEqualToString:GCInputButtonX]) {
1640 if (device->has_nintendo_buttons) {
1641 mapping = &out->y;
1642 } else if (device->is_switch_joyconL || device->is_switch_joyconR) {
1643 mapping = &out->b;
1644 } else {
1645 mapping = &out->x;
1646 }
1647 } else if ([(NSString *)key isEqualToString:GCInputButtonY]) {
1648 if (device->has_nintendo_buttons) {
1649 mapping = &out->x;
1650 } else {
1651 mapping = &out->y;
1652 }
1653 } else if ([(NSString *)key isEqualToString:@"Direction Pad Left"]) {
1654 mapping = &out->dpleft;
1655 } else if ([(NSString *)key isEqualToString:@"Direction Pad Right"]) {
1656 mapping = &out->dpright;
1657 } else if ([(NSString *)key isEqualToString:@"Direction Pad Up"]) {
1658 mapping = &out->dpup;
1659 } else if ([(NSString *)key isEqualToString:@"Direction Pad Down"]) {
1660 mapping = &out->dpdown;
1661 } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Left"]) {
1662 mapping = &out->dpleft;
1663 } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Right"]) {
1664 mapping = &out->dpright;
1665 } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Up"]) {
1666 mapping = &out->dpup;
1667 } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Down"]) {
1668 mapping = &out->dpdown;
1669 } else if ([(NSString *)key isEqualToString:GCInputLeftShoulder]) {
1670 mapping = &out->leftshoulder;
1671 } else if ([(NSString *)key isEqualToString:GCInputRightShoulder]) {
1672 mapping = &out->rightshoulder;
1673 } else if ([(NSString *)key isEqualToString:GCInputLeftThumbstickButton]) {
1674 mapping = &out->leftstick;
1675 } else if ([(NSString *)key isEqualToString:GCInputRightThumbstickButton]) {
1676 mapping = &out->rightstick;
1677 } else if ([(NSString *)key isEqualToString:@"Button Home"]) {
1678 mapping = &out->guide;
1679 } else if ([(NSString *)key isEqualToString:GCInputButtonMenu]) {
1680 if (device->is_siri_remote) {
1681 mapping = &out->b;
1682 } else {
1683 mapping = &out->start;
1684 }
1685 } else if ([(NSString *)key isEqualToString:GCInputButtonOptions]) {
1686 mapping = &out->back;
1687 } else if ([(NSString *)key isEqualToString:@"Button Share"]) {
1688 mapping = &out->misc1;
1689 } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleOne]) {
1690 mapping = &out->right_paddle1;
1691 } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleTwo]) {
1692 mapping = &out->right_paddle2;
1693 } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleThree]) {
1694 mapping = &out->left_paddle1;
1695 } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleFour]) {
1696 mapping = &out->left_paddle2;
1697 } else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) {
1698 mapping = &out->lefttrigger;
1699 } else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) {
1700 mapping = &out->righttrigger;
1701 } else if ([(NSString *)key isEqualToString:GCInputDualShockTouchpadButton]) {
1702 mapping = &out->touchpad;
1703 } else if ([(NSString *)key isEqualToString:@"Button Center"]) {
1704 mapping = &out->a;
1705 }
1706 if (mapping && mapping->kind == EMappingKind_None) {
1707 mapping->kind = EMappingKind_Button;
1708 mapping->target = button;
1709 }
1710 ++button;
1711 }
1712
1713 return true;
1714 }
1715 return false;
1716}
1717
1718#if defined(SDL_JOYSTICK_MFI) && defined(SDL_PLATFORM_MACOS)
1719bool IOS_SupportedHIDDevice(IOHIDDeviceRef device)
1720{
1721 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, true)) {
1722 return false;
1723 }
1724
1725 if (@available(macOS 10.16, *)) {
1726 const int MAX_ATTEMPTS = 3;
1727 for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
1728 if ([GCController supportsHIDDevice:device]) {
1729 return true;
1730 }
1731
1732 // The framework may not have seen the device yet
1733 SDL_Delay(10);
1734 }
1735 }
1736 return false;
1737}
1738#endif
1739
1740#ifdef SDL_JOYSTICK_MFI
1741/* NOLINTNEXTLINE(readability-non-const-parameter): getCString takes a non-const char* */
1742static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char *name)
1743{
1744 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1745 if (element) {
1746 [element.sfSymbolsName getCString:name maxLength:255 encoding:NSASCIIStringEncoding];
1747 }
1748 }
1749}
1750
1751static GCControllerDirectionPad *GetDirectionalPadForController(GCController *controller)
1752{
1753 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1754 return controller.physicalInputProfile.dpads[GCInputDirectionPad];
1755 }
1756
1757 if (controller.extendedGamepad) {
1758 return controller.extendedGamepad.dpad;
1759 }
1760
1761 if (controller.microGamepad) {
1762 return controller.microGamepad.dpad;
1763 }
1764
1765 return nil;
1766}
1767#endif // SDL_JOYSTICK_MFI
1768
1769const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)
1770{
1771 char elementName[256];
1772 elementName[0] = '\0';
1773
1774#ifdef SDL_JOYSTICK_MFI
1775 if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) {
1776 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1777 GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller;
1778 NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
1779 switch (button) {
1780 case SDL_GAMEPAD_BUTTON_SOUTH:
1781 GetAppleSFSymbolsNameForElement(elements[GCInputButtonA], elementName);
1782 break;
1783 case SDL_GAMEPAD_BUTTON_EAST:
1784 GetAppleSFSymbolsNameForElement(elements[GCInputButtonB], elementName);
1785 break;
1786 case SDL_GAMEPAD_BUTTON_WEST:
1787 GetAppleSFSymbolsNameForElement(elements[GCInputButtonX], elementName);
1788 break;
1789 case SDL_GAMEPAD_BUTTON_NORTH:
1790 GetAppleSFSymbolsNameForElement(elements[GCInputButtonY], elementName);
1791 break;
1792 case SDL_GAMEPAD_BUTTON_BACK:
1793 GetAppleSFSymbolsNameForElement(elements[GCInputButtonOptions], elementName);
1794 break;
1795 case SDL_GAMEPAD_BUTTON_GUIDE:
1796 GetAppleSFSymbolsNameForElement(elements[@"Button Home"], elementName);
1797 break;
1798 case SDL_GAMEPAD_BUTTON_START:
1799 GetAppleSFSymbolsNameForElement(elements[GCInputButtonMenu], elementName);
1800 break;
1801 case SDL_GAMEPAD_BUTTON_LEFT_STICK:
1802 GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstickButton], elementName);
1803 break;
1804 case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
1805 GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstickButton], elementName);
1806 break;
1807 case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
1808 GetAppleSFSymbolsNameForElement(elements[GCInputLeftShoulder], elementName);
1809 break;
1810 case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
1811 GetAppleSFSymbolsNameForElement(elements[GCInputRightShoulder], elementName);
1812 break;
1813 case SDL_GAMEPAD_BUTTON_DPAD_UP:
1814 {
1815 GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
1816 if (dpad) {
1817 GetAppleSFSymbolsNameForElement(dpad.up, elementName);
1818 if (SDL_strlen(elementName) == 0) {
1819 SDL_strlcpy(elementName, "dpad.up.fill", sizeof(elementName));
1820 }
1821 }
1822 break;
1823 }
1824 case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
1825 {
1826 GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
1827 if (dpad) {
1828 GetAppleSFSymbolsNameForElement(dpad.down, elementName);
1829 if (SDL_strlen(elementName) == 0) {
1830 SDL_strlcpy(elementName, "dpad.down.fill", sizeof(elementName));
1831 }
1832 }
1833 break;
1834 }
1835 case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
1836 {
1837 GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
1838 if (dpad) {
1839 GetAppleSFSymbolsNameForElement(dpad.left, elementName);
1840 if (SDL_strlen(elementName) == 0) {
1841 SDL_strlcpy(elementName, "dpad.left.fill", sizeof(elementName));
1842 }
1843 }
1844 break;
1845 }
1846 case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
1847 {
1848 GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
1849 if (dpad) {
1850 GetAppleSFSymbolsNameForElement(dpad.right, elementName);
1851 if (SDL_strlen(elementName) == 0) {
1852 SDL_strlcpy(elementName, "dpad.right.fill", sizeof(elementName));
1853 }
1854 }
1855 break;
1856 }
1857 case SDL_GAMEPAD_BUTTON_MISC1:
1858 GetAppleSFSymbolsNameForElement(elements[GCInputDualShockTouchpadButton], elementName);
1859 break;
1860 case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1:
1861 GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleOne], elementName);
1862 break;
1863 case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1:
1864 GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleThree], elementName);
1865 break;
1866 case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2:
1867 GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleTwo], elementName);
1868 break;
1869 case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2:
1870 GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleFour], elementName);
1871 break;
1872 case SDL_GAMEPAD_BUTTON_TOUCHPAD:
1873 GetAppleSFSymbolsNameForElement(elements[GCInputDualShockTouchpadButton], elementName);
1874 break;
1875 default:
1876 break;
1877 }
1878 }
1879 }
1880#endif // SDL_JOYSTICK_MFI
1881
1882 return *elementName ? SDL_GetPersistentString(elementName) : NULL;
1883}
1884
1885const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)
1886{
1887 char elementName[256];
1888 elementName[0] = '\0';
1889
1890#ifdef SDL_JOYSTICK_MFI
1891 if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) {
1892 if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1893 GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller;
1894 NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
1895 switch (axis) {
1896 case SDL_GAMEPAD_AXIS_LEFTX:
1897 GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstick], elementName);
1898 break;
1899 case SDL_GAMEPAD_AXIS_LEFTY:
1900 GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstick], elementName);
1901 break;
1902 case SDL_GAMEPAD_AXIS_RIGHTX:
1903 GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstick], elementName);
1904 break;
1905 case SDL_GAMEPAD_AXIS_RIGHTY:
1906 GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstick], elementName);
1907 break;
1908 case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
1909 GetAppleSFSymbolsNameForElement(elements[GCInputLeftTrigger], elementName);
1910 break;
1911 case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
1912 GetAppleSFSymbolsNameForElement(elements[GCInputRightTrigger], elementName);
1913 break;
1914 default:
1915 break;
1916 }
1917 }
1918 }
1919#endif // SDL_JOYSTICK_MFI
1920
1921 return *elementName ? SDL_GetPersistentString(elementName) : NULL;
1922}
1923
1924SDL_JoystickDriver SDL_IOS_JoystickDriver = {
1925 IOS_JoystickInit,
1926 IOS_JoystickGetCount,
1927 IOS_JoystickDetect,
1928 IOS_JoystickIsDevicePresent,
1929 IOS_JoystickGetDeviceName,
1930 IOS_JoystickGetDevicePath,
1931 IOS_JoystickGetDeviceSteamVirtualGamepadSlot,
1932 IOS_JoystickGetDevicePlayerIndex,
1933 IOS_JoystickSetDevicePlayerIndex,
1934 IOS_JoystickGetDeviceGUID,
1935 IOS_JoystickGetDeviceInstanceID,
1936 IOS_JoystickOpen,
1937 IOS_JoystickRumble,
1938 IOS_JoystickRumbleTriggers,
1939 IOS_JoystickSetLED,
1940 IOS_JoystickSendEffect,
1941 IOS_JoystickSetSensorsEnabled,
1942 IOS_JoystickUpdate,
1943 IOS_JoystickClose,
1944 IOS_JoystickQuit,
1945 IOS_JoystickGetGamepadMapping
1946};
diff --git a/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick_c.h b/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick_c.h
new file mode 100644
index 0000000..783b3f4
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/apple/SDL_mfijoystick_c.h
@@ -0,0 +1,73 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_JOYSTICK_IOS_H
24#define SDL_JOYSTICK_IOS_H
25
26#include "../SDL_sysjoystick.h"
27
28#import <CoreFoundation/CoreFoundation.h>
29#import <Foundation/Foundation.h>
30
31@class GCController;
32
33typedef struct joystick_hwdata
34{
35 GCController __unsafe_unretained *controller;
36 void *rumble;
37 int pause_button_index;
38 Uint64 pause_button_pressed;
39
40 char *name;
41 SDL_Joystick *joystick;
42 SDL_JoystickID instance_id;
43 SDL_GUID guid;
44
45 int naxes;
46 int nbuttons;
47 int nhats;
48 Uint32 button_mask;
49 bool is_xbox;
50 bool is_ps4;
51 bool is_ps5;
52 bool is_switch_pro;
53 bool is_switch_joycon_pair;
54 bool is_switch_joyconL;
55 bool is_switch_joyconR;
56 bool is_stadia;
57 bool is_backbone_one;
58 int is_siri_remote;
59
60 NSArray __unsafe_unretained *axes;
61 NSArray __unsafe_unretained *buttons;
62
63 bool has_dualshock_touchpad;
64 bool has_xbox_paddles;
65 bool has_xbox_share_button;
66 bool has_nintendo_buttons;
67
68 struct joystick_hwdata *next;
69} joystick_hwdata;
70
71typedef joystick_hwdata SDL_JoystickDeviceItem;
72
73#endif // SDL_JOYSTICK_IOS_H
diff --git a/contrib/SDL-3.2.8/src/joystick/bsd/SDL_bsdjoystick.c b/contrib/SDL-3.2.8/src/joystick/bsd/SDL_bsdjoystick.c
new file mode 100644
index 0000000..b3fd3e9
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/bsd/SDL_bsdjoystick.c
@@ -0,0 +1,868 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_USBHID
24
25/*
26 * Joystick driver for the uhid(4) / ujoy(4) interface found in OpenBSD,
27 * NetBSD and FreeBSD.
28 *
29 * Maintainer: <vedge at csoft.org>
30 */
31
32#include <sys/param.h>
33#include <sys/stat.h>
34
35#include <unistd.h>
36#include <fcntl.h>
37#include <errno.h>
38
39#ifndef __FreeBSD_kernel_version
40#define __FreeBSD_kernel_version __FreeBSD_version
41#endif
42
43#ifdef HAVE_USB_H
44#include <usb.h>
45#endif
46#ifdef __DragonFly__
47#include <bus/u4b/usb.h>
48#include <bus/u4b/usbhid.h>
49#else
50#include <dev/usb/usb.h>
51#include <dev/usb/usbhid.h>
52#endif
53
54#ifdef HAVE_USBHID_H
55#include <usbhid.h>
56#elif defined(HAVE_LIBUSB_H)
57#include <libusb.h>
58#elif defined(HAVE_LIBUSBHID_H)
59#include <libusbhid.h>
60#endif
61
62#if defined(SDL_PLATFORM_FREEBSD)
63#include <osreldate.h>
64#if __FreeBSD_kernel_version > 800063
65#include <dev/usb/usb_ioctl.h>
66#elif defined(__DragonFly__)
67#include <bus/u4b/usb_ioctl.h>
68#endif
69#include <sys/joystick.h>
70#endif
71
72#ifdef SDL_HAVE_MACHINE_JOYSTICK_H
73#include <machine/joystick.h>
74#endif
75
76#include "../SDL_sysjoystick.h"
77#include "../SDL_joystick_c.h"
78#include "../hidapi/SDL_hidapijoystick_c.h"
79
80#if defined(SDL_PLATFORM_FREEBSD) || defined(SDL_HAVE_MACHINE_JOYSTICK_H) || defined(__FreeBSD_kernel__) || defined(__DragonFly_)
81#define SUPPORT_JOY_GAMEPORT
82#endif
83
84#define MAX_UHID_JOYS 64
85#define MAX_JOY_JOYS 2
86#define MAX_JOYS (MAX_UHID_JOYS + MAX_JOY_JOYS)
87
88#ifdef SDL_PLATFORM_OPENBSD
89
90#define HUG_DPAD_UP 0x90
91#define HUG_DPAD_DOWN 0x91
92#define HUG_DPAD_RIGHT 0x92
93#define HUG_DPAD_LEFT 0x93
94
95#define HAT_UP 0x01
96#define HAT_RIGHT 0x02
97#define HAT_DOWN 0x04
98#define HAT_LEFT 0x08
99
100#endif
101
102struct report
103{
104#if defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 900000) || \
105 defined(__DragonFly__)
106 void *buf; // Buffer
107#elif defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 800063)
108 struct usb_gen_descriptor *buf; // Buffer
109#else
110 struct usb_ctl_report *buf; // Buffer
111#endif
112 size_t size; // Buffer size
113 int rid; // Report ID
114 enum
115 {
116 SREPORT_UNINIT,
117 SREPORT_CLEAN,
118 SREPORT_DIRTY
119 } status;
120};
121
122static struct
123{
124 int uhid_report;
125 hid_kind_t kind;
126 const char *name;
127} const repinfo[] = {
128 { UHID_INPUT_REPORT, hid_input, "input" },
129 { UHID_OUTPUT_REPORT, hid_output, "output" },
130 { UHID_FEATURE_REPORT, hid_feature, "feature" }
131};
132
133enum
134{
135 REPORT_INPUT = 0,
136 REPORT_OUTPUT = 1,
137 REPORT_FEATURE = 2
138};
139
140enum
141{
142 JOYAXE_X,
143 JOYAXE_Y,
144 JOYAXE_Z,
145 JOYAXE_SLIDER,
146 JOYAXE_WHEEL,
147 JOYAXE_RX,
148 JOYAXE_RY,
149 JOYAXE_RZ,
150 JOYAXE_count
151};
152
153struct joystick_hwdata
154{
155 int fd;
156 enum
157 {
158 BSDJOY_UHID, // uhid(4)
159 BSDJOY_JOY // joy(4)
160 } type;
161
162 int naxes;
163 int nbuttons;
164 int nhats;
165 struct report_desc *repdesc;
166 struct report inreport;
167 int axis_map[JOYAXE_count]; /* map present JOYAXE_* to 0,1,.. */
168};
169
170// A linked list of available joysticks
171typedef struct SDL_joylist_item
172{
173 SDL_JoystickID device_instance;
174 char *path; // "/dev/uhid0" or whatever
175 char *name; // "SideWinder 3D Pro" or whatever
176 SDL_GUID guid;
177 dev_t devnum;
178 struct SDL_joylist_item *next;
179} SDL_joylist_item;
180
181static SDL_joylist_item *SDL_joylist = NULL;
182static SDL_joylist_item *SDL_joylist_tail = NULL;
183static int numjoysticks = 0;
184
185static bool report_alloc(struct report *, struct report_desc *, int);
186static void report_free(struct report *);
187
188#if defined(USBHID_UCR_DATA) || (defined(__FreeBSD_kernel__) && __FreeBSD_kernel_version <= 800063)
189#define REP_BUF_DATA(rep) ((rep)->buf->ucr_data)
190#elif (defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 900000)) || \
191 defined(__DragonFly__)
192#define REP_BUF_DATA(rep) ((rep)->buf)
193#elif (defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 800063))
194#define REP_BUF_DATA(rep) ((rep)->buf->ugd_data)
195#else
196#define REP_BUF_DATA(rep) ((rep)->buf->data)
197#endif
198
199static int usage_to_joyaxe(int usage)
200{
201 int joyaxe;
202 switch (usage) {
203 case HUG_X:
204 joyaxe = JOYAXE_X;
205 break;
206 case HUG_Y:
207 joyaxe = JOYAXE_Y;
208 break;
209 case HUG_Z:
210 joyaxe = JOYAXE_Z;
211 break;
212 case HUG_SLIDER:
213 joyaxe = JOYAXE_SLIDER;
214 break;
215 case HUG_WHEEL:
216 joyaxe = JOYAXE_WHEEL;
217 break;
218 case HUG_RX:
219 joyaxe = JOYAXE_RX;
220 break;
221 case HUG_RY:
222 joyaxe = JOYAXE_RY;
223 break;
224 case HUG_RZ:
225 joyaxe = JOYAXE_RZ;
226 break;
227 default:
228 joyaxe = -1;
229 }
230 return joyaxe;
231}
232
233static void FreeJoylistItem(SDL_joylist_item *item)
234{
235 SDL_free(item->path);
236 SDL_free(item->name);
237 SDL_free(item);
238}
239
240static void FreeHwData(struct joystick_hwdata *hw)
241{
242 if (hw->type == BSDJOY_UHID) {
243 report_free(&hw->inreport);
244
245 if (hw->repdesc) {
246 hid_dispose_report_desc(hw->repdesc);
247 }
248 }
249 close(hw->fd);
250 SDL_free(hw);
251}
252
253static struct joystick_hwdata *CreateHwData(const char *path)
254{
255 struct joystick_hwdata *hw;
256 struct hid_item hitem;
257 struct hid_data *hdata;
258 struct report *rep = NULL;
259 int fd;
260 int i;
261
262 fd = open(path, O_RDONLY | O_CLOEXEC);
263 if (fd == -1) {
264 SDL_SetError("%s: %s", path, strerror(errno));
265 return NULL;
266 }
267
268 hw = (struct joystick_hwdata *)
269 SDL_calloc(1, sizeof(struct joystick_hwdata));
270 if (!hw) {
271 close(fd);
272 return NULL;
273 }
274 hw->fd = fd;
275
276#ifdef SUPPORT_JOY_GAMEPORT
277 if (SDL_strncmp(path, "/dev/joy", 8) == 0) {
278 hw->type = BSDJOY_JOY;
279 hw->naxes = 2;
280 hw->nbuttons = 2;
281 } else
282#endif
283 {
284 hw->type = BSDJOY_UHID;
285 {
286 int ax;
287 for (ax = 0; ax < JOYAXE_count; ax++) {
288 hw->axis_map[ax] = -1;
289 }
290 }
291 hw->repdesc = hid_get_report_desc(fd);
292 if (!hw->repdesc) {
293 SDL_SetError("%s: USB_GET_REPORT_DESC: %s", path,
294 strerror(errno));
295 goto usberr;
296 }
297 rep = &hw->inreport;
298#if defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 800063) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
299 rep->rid = hid_get_report_id(fd);
300 if (rep->rid < 0) {
301#else
302 if (ioctl(fd, USB_GET_REPORT_ID, &rep->rid) < 0) {
303#endif
304 rep->rid = -1; // XXX
305 }
306 if (!report_alloc(rep, hw->repdesc, REPORT_INPUT)) {
307 goto usberr;
308 }
309 if (rep->size <= 0) {
310 SDL_SetError("%s: Input report descriptor has invalid length",
311 path);
312 goto usberr;
313 }
314#if defined(USBHID_NEW) || (defined(SDL_PLATFORM_FREEBSD) && __FreeBSD_kernel_version >= 500111) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
315 hdata = hid_start_parse(hw->repdesc, 1 << hid_input, rep->rid);
316#else
317 hdata = hid_start_parse(hw->repdesc, 1 << hid_input);
318#endif
319 if (!hdata) {
320 SDL_SetError("%s: Cannot start HID parser", path);
321 goto usberr;
322 }
323 for (i = 0; i < JOYAXE_count; i++) {
324 hw->axis_map[i] = -1;
325 }
326
327 while (hid_get_item(hdata, &hitem) > 0) {
328 switch (hitem.kind) {
329 case hid_input:
330 switch (HID_PAGE(hitem.usage)) {
331 case HUP_GENERIC_DESKTOP:
332 {
333 int usage = HID_USAGE(hitem.usage);
334 int joyaxe = usage_to_joyaxe(usage);
335 if (joyaxe >= 0) {
336 hw->axis_map[joyaxe] = 1;
337 } else if (usage == HUG_HAT_SWITCH
338#ifdef SDL_PLATFORM_OPENBSD
339 || usage == HUG_DPAD_UP
340#endif
341 ) {
342 hw->nhats++;
343 }
344 break;
345 }
346 case HUP_BUTTON:
347 {
348 int usage = HID_USAGE(hitem.usage);
349 if (usage > hw->nbuttons) {
350 hw->nbuttons = usage;
351 }
352 } break;
353 default:
354 break;
355 }
356 break;
357 default:
358 break;
359 }
360 }
361 hid_end_parse(hdata);
362 for (i = 0; i < JOYAXE_count; i++) {
363 if (hw->axis_map[i] > 0) {
364 hw->axis_map[i] = hw->naxes++;
365 }
366 }
367
368 if (hw->naxes == 0 && hw->nbuttons == 0 && hw->nhats == 0) {
369 SDL_SetError("%s: Not a joystick, ignoring", path);
370 goto usberr;
371 }
372 }
373
374 // The poll blocks the event thread.
375 fcntl(fd, F_SETFL, O_NONBLOCK);
376#ifdef SDL_PLATFORM_NETBSD
377 // Flush pending events
378 if (rep) {
379 while (read(fd, REP_BUF_DATA(rep), rep->size) == rep->size)
380 ;
381 }
382#endif
383
384 return hw;
385
386usberr:
387 FreeHwData(hw);
388 return NULL;
389}
390
391static bool MaybeAddDevice(const char *path)
392{
393 struct stat sb;
394 char *name = NULL;
395 SDL_GUID guid;
396 SDL_joylist_item *item;
397 struct joystick_hwdata *hw;
398
399 if (!path) {
400 return false;
401 }
402
403 if (stat(path, &sb) == -1) {
404 return false;
405 }
406
407 // Check to make sure it's not already in list.
408 for (item = SDL_joylist; item; item = item->next) {
409 if (sb.st_rdev == item->devnum) {
410 return false; // already have this one
411 }
412 }
413
414 hw = CreateHwData(path);
415 if (!hw) {
416 return false;
417 }
418
419 if (hw->type == BSDJOY_JOY) {
420 name = SDL_strdup("Gameport joystick");
421 guid = SDL_CreateJoystickGUIDForName(name);
422 } else {
423#ifdef USB_GET_DEVICEINFO
424 struct usb_device_info di;
425 if (ioctl(hw->fd, USB_GET_DEVICEINFO, &di) != -1) {
426 name = SDL_CreateJoystickName(di.udi_vendorNo, di.udi_productNo, di.udi_vendor, di.udi_product);
427 guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, di.udi_vendor, di.udi_product, 0, 0);
428
429 if (SDL_ShouldIgnoreJoystick(di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, name) ||
430 SDL_JoystickHandledByAnotherDriver(&SDL_BSD_JoystickDriver, di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, name)) {
431 SDL_free(name);
432 FreeHwData(hw);
433 return false;
434 }
435 }
436#endif // USB_GET_DEVICEINFO
437 }
438 if (!name) {
439 name = SDL_strdup(path);
440 guid = SDL_CreateJoystickGUIDForName(name);
441 }
442 FreeHwData(hw);
443
444 item = (SDL_joylist_item *)SDL_calloc(1, sizeof(SDL_joylist_item));
445 if (!item) {
446 SDL_free(name);
447 return false;
448 }
449
450 item->devnum = sb.st_rdev;
451 item->path = SDL_strdup(path);
452 item->name = name;
453 item->guid = guid;
454
455 if ((!item->path) || (!item->name)) {
456 FreeJoylistItem(item);
457 return false;
458 }
459
460 item->device_instance = SDL_GetNextObjectID();
461 if (!SDL_joylist_tail) {
462 SDL_joylist = SDL_joylist_tail = item;
463 } else {
464 SDL_joylist_tail->next = item;
465 SDL_joylist_tail = item;
466 }
467
468 // Need to increment the joystick count before we post the event
469 ++numjoysticks;
470
471 SDL_PrivateJoystickAdded(item->device_instance);
472
473 return true;
474}
475
476static bool BSD_JoystickInit(void)
477{
478 char s[16];
479 int i;
480
481 for (i = 0; i < MAX_UHID_JOYS; i++) {
482#if defined(SDL_PLATFORM_OPENBSD) && (OpenBSD >= 202105)
483 SDL_snprintf(s, SDL_arraysize(s), "/dev/ujoy/%d", i);
484#else
485 SDL_snprintf(s, SDL_arraysize(s), "/dev/uhid%d", i);
486#endif
487 MaybeAddDevice(s);
488 }
489#ifdef SUPPORT_JOY_GAMEPORT
490 for (i = 0; i < MAX_JOY_JOYS; i++) {
491 SDL_snprintf(s, SDL_arraysize(s), "/dev/joy%d", i);
492 MaybeAddDevice(s);
493 }
494#endif // SUPPORT_JOY_GAMEPORT
495
496 // Read the default USB HID usage table.
497 hid_init(NULL);
498
499 return true;
500}
501
502static int BSD_JoystickGetCount(void)
503{
504 return numjoysticks;
505}
506
507static void BSD_JoystickDetect(void)
508{
509}
510
511static bool BSD_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
512{
513 // We don't override any other drivers
514 return false;
515}
516
517static SDL_joylist_item *GetJoystickByDevIndex(int device_index)
518{
519 SDL_joylist_item *item = SDL_joylist;
520
521 if ((device_index < 0) || (device_index >= numjoysticks)) {
522 return NULL;
523 }
524
525 while (device_index > 0) {
526 SDL_assert(item != NULL);
527 device_index--;
528 item = item->next;
529 }
530
531 return item;
532}
533
534static const char *BSD_JoystickGetDeviceName(int device_index)
535{
536 return GetJoystickByDevIndex(device_index)->name;
537}
538
539static const char *BSD_JoystickGetDevicePath(int device_index)
540{
541 return GetJoystickByDevIndex(device_index)->path;
542}
543
544static int BSD_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
545{
546 return -1;
547}
548
549static int BSD_JoystickGetDevicePlayerIndex(int device_index)
550{
551 return -1;
552}
553
554static void BSD_JoystickSetDevicePlayerIndex(int device_index, int player_index)
555{
556}
557
558static SDL_GUID BSD_JoystickGetDeviceGUID(int device_index)
559{
560 return GetJoystickByDevIndex(device_index)->guid;
561}
562
563// Function to perform the mapping from device index to the instance id for this index
564static SDL_JoystickID BSD_JoystickGetDeviceInstanceID(int device_index)
565{
566 return GetJoystickByDevIndex(device_index)->device_instance;
567}
568
569static unsigned hatval_to_sdl(Sint32 hatval)
570{
571 static const unsigned hat_dir_map[8] = {
572 SDL_HAT_UP, SDL_HAT_RIGHTUP, SDL_HAT_RIGHT, SDL_HAT_RIGHTDOWN,
573 SDL_HAT_DOWN, SDL_HAT_LEFTDOWN, SDL_HAT_LEFT, SDL_HAT_LEFTUP
574 };
575 unsigned result;
576 if ((hatval & 7) == hatval)
577 result = hat_dir_map[hatval];
578 else
579 result = SDL_HAT_CENTERED;
580 return result;
581}
582
583static bool BSD_JoystickOpen(SDL_Joystick *joy, int device_index)
584{
585 SDL_joylist_item *item = GetJoystickByDevIndex(device_index);
586 struct joystick_hwdata *hw;
587
588 if (!item) {
589 return SDL_SetError("No such device");
590 }
591
592 hw = CreateHwData(item->path);
593 if (!hw) {
594 return false;
595 }
596
597 joy->hwdata = hw;
598 joy->naxes = hw->naxes;
599 joy->nbuttons = hw->nbuttons;
600 joy->nhats = hw->nhats;
601
602 return true;
603}
604
605static void BSD_JoystickUpdate(SDL_Joystick *joy)
606{
607 struct hid_item hitem;
608 struct hid_data *hdata;
609 struct report *rep;
610 int nbutton, naxe = -1;
611 Sint32 v;
612#ifdef SDL_PLATFORM_OPENBSD
613 Sint32 dpad[4] = { 0, 0, 0, 0 };
614#endif
615 Uint64 timestamp = SDL_GetTicksNS();
616
617#ifdef SUPPORT_JOY_GAMEPORT
618 struct joystick gameport;
619 static int x, y, xmin = 0xffff, ymin = 0xffff, xmax = 0, ymax = 0;
620
621 if (joy->hwdata->type == BSDJOY_JOY) {
622 while (read(joy->hwdata->fd, &gameport, sizeof(gameport)) == sizeof(gameport)) {
623 if (SDL_abs(x - gameport.x) > 8) {
624 x = gameport.x;
625 if (x < xmin) {
626 xmin = x;
627 }
628 if (x > xmax) {
629 xmax = x;
630 }
631 if (xmin == xmax) {
632 xmin--;
633 xmax++;
634 }
635 v = (((SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) * ((Sint32)x - xmin)) / (xmax - xmin)) + SDL_JOYSTICK_AXIS_MIN;
636 SDL_SendJoystickAxis(timestamp, joy, 0, v);
637 }
638 if (SDL_abs(y - gameport.y) > 8) {
639 y = gameport.y;
640 if (y < ymin) {
641 ymin = y;
642 }
643 if (y > ymax) {
644 ymax = y;
645 }
646 if (ymin == ymax) {
647 ymin--;
648 ymax++;
649 }
650 v = (((SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) * ((Sint32)y - ymin)) / (ymax - ymin)) + SDL_JOYSTICK_AXIS_MIN;
651 SDL_SendJoystickAxis(timestamp, joy, 1, v);
652 }
653 SDL_SendJoystickButton(timestamp, joy, 0, (gameport.b1 != 0));
654 SDL_SendJoystickButton(timestamp, joy, 1, (gameport.b2 != 0));
655 }
656 return;
657 }
658#endif // SUPPORT_JOY_GAMEPORT
659
660 rep = &joy->hwdata->inreport;
661
662 while (read(joy->hwdata->fd, REP_BUF_DATA(rep), rep->size) == rep->size) {
663#if defined(USBHID_NEW) || (defined(SDL_PLATFORM_FREEBSD) && __FreeBSD_kernel_version >= 500111) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
664 hdata = hid_start_parse(joy->hwdata->repdesc, 1 << hid_input, rep->rid);
665#else
666 hdata = hid_start_parse(joy->hwdata->repdesc, 1 << hid_input);
667#endif
668 if (!hdata) {
669 // fprintf(stderr, "%s: Cannot start HID parser\n", joy->hwdata->path);
670 continue;
671 }
672
673 while (hid_get_item(hdata, &hitem) > 0) {
674 switch (hitem.kind) {
675 case hid_input:
676 switch (HID_PAGE(hitem.usage)) {
677 case HUP_GENERIC_DESKTOP:
678 {
679 int usage = HID_USAGE(hitem.usage);
680 int joyaxe = usage_to_joyaxe(usage);
681 if (joyaxe >= 0) {
682 naxe = joy->hwdata->axis_map[joyaxe];
683 // scaleaxe
684 v = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem);
685 v = (((SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) * (v - hitem.logical_minimum)) / (hitem.logical_maximum - hitem.logical_minimum)) + SDL_JOYSTICK_AXIS_MIN;
686 SDL_SendJoystickAxis(timestamp, joy, naxe, v);
687 } else if (usage == HUG_HAT_SWITCH) {
688 v = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem);
689 SDL_SendJoystickHat(timestamp, joy, 0,
690 hatval_to_sdl(v) -
691 hitem.logical_minimum);
692 }
693#ifdef SDL_PLATFORM_OPENBSD
694 /* here D-pad directions are reported like separate buttons.
695 * calculate the SDL hat value from the 4 separate values.
696 */
697 switch (usage) {
698 case HUG_DPAD_UP:
699 dpad[0] = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem);
700 break;
701 case HUG_DPAD_DOWN:
702 dpad[1] = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem);
703 break;
704 case HUG_DPAD_RIGHT:
705 dpad[2] = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem);
706 break;
707 case HUG_DPAD_LEFT:
708 dpad[3] = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem);
709 break;
710 //default:
711 // no-op
712 }
713 SDL_PrivateJoystickHat(joy, 0, (dpad[0] * HAT_UP) |
714 (dpad[1] * HAT_DOWN) |
715 (dpad[2] * HAT_RIGHT) |
716 (dpad[3] * HAT_LEFT) );
717#endif
718 break;
719 }
720 case HUP_BUTTON:
721 v = (Sint32)hid_get_data(REP_BUF_DATA(rep), &hitem);
722 nbutton = HID_USAGE(hitem.usage) - 1; // SDL buttons are zero-based
723 SDL_SendJoystickButton(timestamp, joy, nbutton, (v != 0));
724 break;
725 default:
726 continue;
727 }
728 break;
729 default:
730 break;
731 }
732 }
733 hid_end_parse(hdata);
734 }
735}
736
737// Function to close a joystick after use
738static void BSD_JoystickClose(SDL_Joystick *joy)
739{
740 if (joy->hwdata) {
741 FreeHwData(joy->hwdata);
742 joy->hwdata = NULL;
743 }
744}
745
746static void BSD_JoystickQuit(void)
747{
748 SDL_joylist_item *item = NULL;
749 SDL_joylist_item *next = NULL;
750
751 for (item = SDL_joylist; item; item = next) {
752 next = item->next;
753 FreeJoylistItem(item);
754 }
755
756 SDL_joylist = SDL_joylist_tail = NULL;
757
758 numjoysticks = 0;
759}
760
761static bool report_alloc(struct report *r, struct report_desc *rd, int repind)
762{
763 int len;
764
765#ifdef __DragonFly__
766 len = hid_report_size(rd, repinfo[repind].kind, r->rid);
767#elif defined(SDL_PLATFORM_FREEBSD)
768#if (__FreeBSD_kernel_version >= 460000) || defined(__FreeBSD_kernel__)
769#if (__FreeBSD_kernel_version <= 500111)
770 len = hid_report_size(rd, r->rid, repinfo[repind].kind);
771#else
772 len = hid_report_size(rd, repinfo[repind].kind, r->rid);
773#endif
774#else
775 len = hid_report_size(rd, repinfo[repind].kind, &r->rid);
776#endif
777#else
778#ifdef USBHID_NEW
779 len = hid_report_size(rd, repinfo[repind].kind, r->rid);
780#else
781 len = hid_report_size(rd, repinfo[repind].kind, &r->rid);
782#endif
783#endif
784
785 if (len < 0) {
786 return SDL_SetError("Negative HID report size");
787 }
788 r->size = len;
789
790 if (r->size > 0) {
791#if defined(SDL_PLATFORM_FREEBSD) && (__FreeBSD_kernel_version > 900000) || defined(__DragonFly__)
792 r->buf = SDL_malloc(r->size);
793#else
794 r->buf = SDL_malloc(sizeof(*r->buf) - sizeof(REP_BUF_DATA(r)) +
795 r->size);
796#endif
797 if (!r->buf) {
798 return false;
799 }
800 } else {
801 r->buf = NULL;
802 }
803
804 r->status = SREPORT_CLEAN;
805 return true;
806}
807
808static void report_free(struct report *r)
809{
810 SDL_free(r->buf);
811 r->status = SREPORT_UNINIT;
812}
813
814static bool BSD_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
815{
816 return SDL_Unsupported();
817}
818
819static bool BSD_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
820{
821 return SDL_Unsupported();
822}
823
824static bool BSD_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
825{
826 return false;
827}
828
829static bool BSD_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
830{
831 return SDL_Unsupported();
832}
833
834static bool BSD_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
835{
836 return SDL_Unsupported();
837}
838
839static bool BSD_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
840{
841 return SDL_Unsupported();
842}
843
844SDL_JoystickDriver SDL_BSD_JoystickDriver = {
845 BSD_JoystickInit,
846 BSD_JoystickGetCount,
847 BSD_JoystickDetect,
848 BSD_JoystickIsDevicePresent,
849 BSD_JoystickGetDeviceName,
850 BSD_JoystickGetDevicePath,
851 BSD_JoystickGetDeviceSteamVirtualGamepadSlot,
852 BSD_JoystickGetDevicePlayerIndex,
853 BSD_JoystickSetDevicePlayerIndex,
854 BSD_JoystickGetDeviceGUID,
855 BSD_JoystickGetDeviceInstanceID,
856 BSD_JoystickOpen,
857 BSD_JoystickRumble,
858 BSD_JoystickRumbleTriggers,
859 BSD_JoystickSetLED,
860 BSD_JoystickSendEffect,
861 BSD_JoystickSetSensorsEnabled,
862 BSD_JoystickUpdate,
863 BSD_JoystickClose,
864 BSD_JoystickQuit,
865 BSD_JoystickGetGamepadMapping
866};
867
868#endif // SDL_JOYSTICK_USBHID
diff --git a/contrib/SDL-3.2.8/src/joystick/check_8bitdo.sh b/contrib/SDL-3.2.8/src/joystick/check_8bitdo.sh
new file mode 100755
index 0000000..7f6f742
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/check_8bitdo.sh
@@ -0,0 +1,15 @@
1#!/bin/sh
2#
3# Check to make sure 8BitDo controller configurations are correct
4
5echo "Expected output:"
6cat <<__EOF__
7 "050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b20,b:b21,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b23,y:b24,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
8 "050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b21,b:b20,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b24,y:b23,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
9
10__EOF__
11
12echo "Actual output:"
13${FGREP:-grep -F} 8BitDo SDL_gamepad_db.h | ${FGREP:-grep -F} -v hint
14${EGREP:-grep -E} "hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1" SDL_gamepad_db.h | ${FGREP:-grep -F} -i 8bit | ${FGREP:-grep -F} -v x:b2,y:b3 | ${FGREP:-grep -F} -v x:b3,y:b4
15${EGREP:-grep -E} "hint:.SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1" SDL_gamepad_db.h | ${FGREP:-grep -F} -i 8bit | ${FGREP:-grep -F} -v x:b3,y:b2 | ${FGREP:-grep -F} -v x:b4,y:b3
diff --git a/contrib/SDL-3.2.8/src/joystick/controller_list.h b/contrib/SDL-3.2.8/src/joystick/controller_list.h
new file mode 100644
index 0000000..5a62ece
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/controller_list.h
@@ -0,0 +1,609 @@
1/*
2 Copyright (C) Valve Corporation
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, subject to the following restrictions:
11
12 1. The origin of this software must not be misrepresented; you must not
13 claim that you wrote the original software. If you use this software
14 in a product, an acknowledgment in the product documentation would be
15 appreciated but is not required.
16 2. Altered source versions must be plainly marked as such, and must not be
17 misrepresented as being the original software.
18 3. This notice may not be removed or altered from any source distribution.
19*/
20#define MAKE_CONTROLLER_ID( nVID, nPID ) (unsigned int)( (unsigned int)nVID << 16 | (unsigned int)nPID )
21
22static const ControllerDescription_t arrControllers[] = {
23 { MAKE_CONTROLLER_ID( 0x0079, 0x181a ), k_eControllerType_PS3Controller, NULL }, // Venom Arcade Stick
24 { MAKE_CONTROLLER_ID( 0x0079, 0x1844 ), k_eControllerType_PS3Controller, NULL }, // From SDL
25 { MAKE_CONTROLLER_ID( 0x044f, 0xb315 ), k_eControllerType_PS3Controller, NULL }, // Firestorm Dual Analog 3
26 { MAKE_CONTROLLER_ID( 0x044f, 0xd007 ), k_eControllerType_PS3Controller, NULL }, // Thrustmaster wireless 3-1
27 { MAKE_CONTROLLER_ID( 0x046d, 0xcad1 ), k_eControllerType_PS3Controller, NULL }, // Logitech Chillstream
28 //{ MAKE_CONTROLLER_ID( 0x046d, 0xc24f ), k_eControllerType_PS3Controller, NULL }, // Logitech G29 (PS3)
29 { MAKE_CONTROLLER_ID( 0x054c, 0x0268 ), k_eControllerType_PS3Controller, NULL }, // Sony PS3 Controller
30 { MAKE_CONTROLLER_ID( 0x056e, 0x200f ), k_eControllerType_PS3Controller, NULL }, // From SDL
31 { MAKE_CONTROLLER_ID( 0x056e, 0x2013 ), k_eControllerType_PS3Controller, NULL }, // JC-U4113SBK
32 { MAKE_CONTROLLER_ID( 0x05b8, 0x1004 ), k_eControllerType_PS3Controller, NULL }, // From SDL
33 { MAKE_CONTROLLER_ID( 0x05b8, 0x1006 ), k_eControllerType_PS3Controller, NULL }, // JC-U3412SBK
34 { MAKE_CONTROLLER_ID( 0x06a3, 0xf622 ), k_eControllerType_PS3Controller, NULL }, // Cyborg V3
35 { MAKE_CONTROLLER_ID( 0x0738, 0x3180 ), k_eControllerType_PS3Controller, NULL }, // Mad Catz Alpha PS3 mode
36 { MAKE_CONTROLLER_ID( 0x0738, 0x3250 ), k_eControllerType_PS3Controller, NULL }, // madcats fightpad pro ps3
37 { MAKE_CONTROLLER_ID( 0x0738, 0x3481 ), k_eControllerType_PS3Controller, NULL }, // Mad Catz FightStick TE 2+ PS3
38 { MAKE_CONTROLLER_ID( 0x0738, 0x8180 ), k_eControllerType_PS3Controller, NULL }, // Mad Catz Alpha PS4 mode (no touchpad on device)
39 { MAKE_CONTROLLER_ID( 0x0738, 0x8838 ), k_eControllerType_PS3Controller, NULL }, // Madcatz Fightstick Pro
40 { MAKE_CONTROLLER_ID( 0x0810, 0x0001 ), k_eControllerType_PS3Controller, NULL }, // actually ps2 - maybe break out later
41 { MAKE_CONTROLLER_ID( 0x0810, 0x0003 ), k_eControllerType_PS3Controller, NULL }, // actually ps2 - maybe break out later
42 { MAKE_CONTROLLER_ID( 0x0925, 0x0005 ), k_eControllerType_PS3Controller, NULL }, // Sony PS3 Controller
43 { MAKE_CONTROLLER_ID( 0x0925, 0x8866 ), k_eControllerType_PS3Controller, NULL }, // PS2 maybe break out later
44 { MAKE_CONTROLLER_ID( 0x0925, 0x8888 ), k_eControllerType_PS3Controller, NULL }, // Actually ps2 -maybe break out later Lakeview Research WiseGroup Ltd, MP-8866 Dual Joypad
45 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0109 ), k_eControllerType_PS3Controller, NULL }, // PDP Versus Fighting Pad
46 { MAKE_CONTROLLER_ID( 0x0e6f, 0x011e ), k_eControllerType_PS3Controller, NULL }, // Rock Candy PS4
47 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0128 ), k_eControllerType_PS3Controller, NULL }, // Rock Candy PS3
48 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0214 ), k_eControllerType_PS3Controller, NULL }, // afterglow ps3
49 { MAKE_CONTROLLER_ID( 0x0e6f, 0x1314 ), k_eControllerType_PS3Controller, NULL }, // PDP Afterglow Wireless PS3 controller
50 { MAKE_CONTROLLER_ID( 0x0e6f, 0x6302 ), k_eControllerType_PS3Controller, NULL }, // From SDL
51 { MAKE_CONTROLLER_ID( 0x0e8f, 0x0008 ), k_eControllerType_PS3Controller, NULL }, // Green Asia
52 { MAKE_CONTROLLER_ID( 0x0e8f, 0x3075 ), k_eControllerType_PS3Controller, NULL }, // SpeedLink Strike FX
53 { MAKE_CONTROLLER_ID( 0x0e8f, 0x310d ), k_eControllerType_PS3Controller, NULL }, // From SDL
54 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0009 ), k_eControllerType_PS3Controller, NULL }, // HORI BDA GP1
55 { MAKE_CONTROLLER_ID( 0x0f0d, 0x004d ), k_eControllerType_PS3Controller, NULL }, // Horipad 3
56 { MAKE_CONTROLLER_ID( 0x0f0d, 0x005f ), k_eControllerType_PS3Controller, NULL }, // HORI Fighting Commander 4 PS3
57 { MAKE_CONTROLLER_ID( 0x0f0d, 0x006a ), k_eControllerType_PS3Controller, NULL }, // Real Arcade Pro 4
58 { MAKE_CONTROLLER_ID( 0x0f0d, 0x006e ), k_eControllerType_PS3Controller, NULL }, // HORI horipad4 ps3
59 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0085 ), k_eControllerType_PS3Controller, NULL }, // HORI Fighting Commander PS3
60 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0086 ), k_eControllerType_PS3Controller, NULL }, // HORI Fighting Commander PC (Uses the Xbox 360 protocol, but has PS3 buttons)
61 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0088 ), k_eControllerType_PS3Controller, NULL }, // HORI Fighting Stick mini 4
62 { MAKE_CONTROLLER_ID( 0x0f30, 0x1100 ), k_eControllerType_PS3Controller, NULL }, // Qanba Q1 fight stick
63 { MAKE_CONTROLLER_ID( 0x11ff, 0x3331 ), k_eControllerType_PS3Controller, NULL }, // SRXJ-PH2400
64 { MAKE_CONTROLLER_ID( 0x1345, 0x1000 ), k_eControllerType_PS3Controller, NULL }, // PS2 ACME GA-D5
65 { MAKE_CONTROLLER_ID( 0x1345, 0x6005 ), k_eControllerType_PS3Controller, NULL }, // ps2 maybe break out later
66 { MAKE_CONTROLLER_ID( 0x146b, 0x5500 ), k_eControllerType_PS3Controller, NULL }, // From SDL
67 { MAKE_CONTROLLER_ID( 0x1a34, 0x0836 ), k_eControllerType_PS3Controller, NULL }, // Afterglow PS3
68 { MAKE_CONTROLLER_ID( 0x20bc, 0x5500 ), k_eControllerType_PS3Controller, NULL }, // ShanWan PS3
69 { MAKE_CONTROLLER_ID( 0x20d6, 0x576d ), k_eControllerType_PS3Controller, NULL }, // Power A PS3
70 { MAKE_CONTROLLER_ID( 0x20d6, 0xca6d ), k_eControllerType_PS3Controller, NULL }, // BDA Pro Ex
71 { MAKE_CONTROLLER_ID( 0x2563, 0x0523 ), k_eControllerType_PS3Controller, NULL }, // Digiflip GP006
72 { MAKE_CONTROLLER_ID( 0x2563, 0x0575 ), k_eControllerType_PS3Controller, "Retro-bit Controller" }, // SWITCH CO., LTD. Retro-bit Controller
73 { MAKE_CONTROLLER_ID( 0x25f0, 0x83c3 ), k_eControllerType_PS3Controller, NULL }, // gioteck vx2
74 { MAKE_CONTROLLER_ID( 0x25f0, 0xc121 ), k_eControllerType_PS3Controller, NULL }, //
75 { MAKE_CONTROLLER_ID( 0x2c22, 0x2003 ), k_eControllerType_PS3Controller, NULL }, // Qanba Drone
76 { MAKE_CONTROLLER_ID( 0x2c22, 0x2302 ), k_eControllerType_PS3Controller, NULL }, // Qanba Obsidian
77 { MAKE_CONTROLLER_ID( 0x2c22, 0x2502 ), k_eControllerType_PS3Controller, NULL }, // Qanba Dragon
78 { MAKE_CONTROLLER_ID( 0x8380, 0x0003 ), k_eControllerType_PS3Controller, NULL }, // BTP 2163
79 { MAKE_CONTROLLER_ID( 0x8888, 0x0308 ), k_eControllerType_PS3Controller, NULL }, // Sony PS3 Controller
80
81 { MAKE_CONTROLLER_ID( 0x0079, 0x181b ), k_eControllerType_PS4Controller, NULL }, // Venom Arcade Stick - XXX:this may not work and may need to be called a ps3 controller
82 //{ MAKE_CONTROLLER_ID( 0x046d, 0xc260 ), k_eControllerType_PS4Controller, NULL }, // Logitech G29 (PS4)
83 { MAKE_CONTROLLER_ID( 0x044f, 0xd00e ), k_eControllerType_PS4Controller, NULL }, // Thrustmaster Eswap Pro - No gyro and lightbar doesn't change color. Works otherwise
84 { MAKE_CONTROLLER_ID( 0x054c, 0x05c4 ), k_eControllerType_PS4Controller, NULL }, // Sony PS4 Controller
85 { MAKE_CONTROLLER_ID( 0x054c, 0x05c5 ), k_eControllerType_PS4Controller, NULL }, // STRIKEPAD PS4 Grip Add-on
86 { MAKE_CONTROLLER_ID( 0x054c, 0x09cc ), k_eControllerType_PS4Controller, NULL }, // Sony PS4 Slim Controller
87 { MAKE_CONTROLLER_ID( 0x054c, 0x0ba0 ), k_eControllerType_PS4Controller, NULL }, // Sony PS4 Controller (Wireless dongle)
88 { MAKE_CONTROLLER_ID( 0x0738, 0x8250 ), k_eControllerType_PS4Controller, NULL }, // Mad Catz FightPad Pro PS4
89 { MAKE_CONTROLLER_ID( 0x0738, 0x8384 ), k_eControllerType_PS4Controller, NULL }, // Mad Catz FightStick TE S+ PS4
90 { MAKE_CONTROLLER_ID( 0x0738, 0x8480 ), k_eControllerType_PS4Controller, NULL }, // Mad Catz FightStick TE 2 PS4
91 { MAKE_CONTROLLER_ID( 0x0738, 0x8481 ), k_eControllerType_PS4Controller, NULL }, // Mad Catz FightStick TE 2+ PS4
92 { MAKE_CONTROLLER_ID( 0x0c12, 0x0e10 ), k_eControllerType_PS4Controller, NULL }, // Armor Armor 3 Pad PS4
93 { MAKE_CONTROLLER_ID( 0x0c12, 0x0e13 ), k_eControllerType_PS4Controller, NULL }, // ZEROPLUS P4 Wired Gamepad
94 { MAKE_CONTROLLER_ID( 0x0c12, 0x0e15 ), k_eControllerType_PS4Controller, NULL }, // Game:Pad 4
95 { MAKE_CONTROLLER_ID( 0x0c12, 0x0e20 ), k_eControllerType_PS4Controller, NULL }, // Brook Mars Controller - needs FW update to show up as Ps4 controller on PC. Has Gyro but touchpad is a single button.
96 { MAKE_CONTROLLER_ID( 0x0c12, 0x0ef6 ), k_eControllerType_PS4Controller, NULL }, // Hitbox Arcade Stick
97 { MAKE_CONTROLLER_ID( 0x0c12, 0x1cf6 ), k_eControllerType_PS4Controller, NULL }, // EMIO PS4 Elite Controller
98 { MAKE_CONTROLLER_ID( 0x0c12, 0x1e10 ), k_eControllerType_PS4Controller, NULL }, // P4 Wired Gamepad generic knock off - lightbar but not trackpad or gyro
99 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0203 ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS (PS4 peripheral but no trackpad/lightbar)
100 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0207 ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS V2 w/ Touchpad for PS4
101 { MAKE_CONTROLLER_ID( 0x0e6f, 0x020a ), k_eControllerType_PS4Controller, NULL }, // Victrix Pro FS PS4/PS5 (PS4 mode)
102 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0055 ), k_eControllerType_PS4Controller, NULL }, // HORIPAD 4 FPS
103 { MAKE_CONTROLLER_ID( 0x0f0d, 0x005e ), k_eControllerType_PS4Controller, NULL }, // HORI Fighting Commander 4 PS4
104 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0066 ), k_eControllerType_PS4Controller, NULL }, // HORIPAD 4 FPS Plus
105 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0084 ), k_eControllerType_PS4Controller, NULL }, // HORI Fighting Commander PS4
106 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0087 ), k_eControllerType_PS4Controller, NULL }, // HORI Fighting Stick mini 4
107 { MAKE_CONTROLLER_ID( 0x0f0d, 0x008a ), k_eControllerType_PS4Controller, NULL }, // HORI Real Arcade Pro 4
108 { MAKE_CONTROLLER_ID( 0x0f0d, 0x009c ), k_eControllerType_PS4Controller, NULL }, // HORI TAC PRO mousething
109 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00a0 ), k_eControllerType_PS4Controller, NULL }, // HORI TAC4 mousething
110 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00ed ), k_eControllerType_XInputPS4Controller, NULL }, // Hori Fighting Stick mini 4 kai - becomes an Xbox 360 controller on PC
111 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00ee ), k_eControllerType_PS4Controller, NULL }, // Hori mini wired https://www.playstation.com/en-us/explore/accessories/gaming-controllers/mini-wired-gamepad/
112 { MAKE_CONTROLLER_ID( 0x0f0d, 0x011c ), k_eControllerType_PS4Controller, NULL }, // Hori Fighting Stick α
113 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0123 ), k_eControllerType_PS4Controller, NULL }, // HORI Wireless Controller Light (Japan only) - only over bt- over usb is xbox and pid 0x0124
114 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0162 ), k_eControllerType_PS4Controller, NULL }, // HORI Fighting Commander OCTA
115 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0164 ), k_eControllerType_XInputPS4Controller, NULL }, // HORI Fighting Commander OCTA
116 { MAKE_CONTROLLER_ID( 0x11c0, 0x4001 ), k_eControllerType_PS4Controller, NULL }, // "PS4 Fun Controller" added from user log
117 { MAKE_CONTROLLER_ID( 0x146b, 0x0603 ), k_eControllerType_XInputPS4Controller, NULL }, // Nacon PS4 Compact Controller
118 { MAKE_CONTROLLER_ID( 0x146b, 0x0604 ), k_eControllerType_XInputPS4Controller, NULL }, // NACON Daija Arcade Stick
119 { MAKE_CONTROLLER_ID( 0x146b, 0x0605 ), k_eControllerType_XInputPS4Controller, NULL }, // NACON PS4 controller in Xbox mode - might also be other bigben brand xbox controllers
120 { MAKE_CONTROLLER_ID( 0x146b, 0x0606 ), k_eControllerType_XInputPS4Controller, NULL }, // NACON Unknown Controller
121 { MAKE_CONTROLLER_ID( 0x146b, 0x0609 ), k_eControllerType_XInputPS4Controller, NULL }, // NACON Wireless Controller for PS4
122 { MAKE_CONTROLLER_ID( 0x146b, 0x0d01 ), k_eControllerType_PS4Controller, NULL }, // Nacon Revolution Pro Controller - has gyro
123 { MAKE_CONTROLLER_ID( 0x146b, 0x0d02 ), k_eControllerType_PS4Controller, NULL }, // Nacon Revolution Pro Controller v2 - has gyro
124 { MAKE_CONTROLLER_ID( 0x146b, 0x0d06 ), k_eControllerType_PS4Controller, NULL }, // NACON Asymmetric Controller Wireless Dongle -- show up as ps4 until you connect controller to it then it reboots into Xbox controller with different vvid/pid
125 { MAKE_CONTROLLER_ID( 0x146b, 0x0d08 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution Unlimited Wireless Dongle
126 { MAKE_CONTROLLER_ID( 0x146b, 0x0d09 ), k_eControllerType_PS4Controller, NULL }, // NACON Daija Fight Stick - touchpad but no gyro/rumble
127 { MAKE_CONTROLLER_ID( 0x146b, 0x0d10 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution Infinite - has gyro
128 { MAKE_CONTROLLER_ID( 0x146b, 0x0d10 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution Unlimited
129 { MAKE_CONTROLLER_ID( 0x146b, 0x0d13 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution Pro Controller 3
130 { MAKE_CONTROLLER_ID( 0x146b, 0x1103 ), k_eControllerType_PS4Controller, NULL }, // NACON Asymmetric Controller -- on windows this doesn't enumerate
131 { MAKE_CONTROLLER_ID( 0x1532, 0X0401 ), k_eControllerType_PS4Controller, NULL }, // Razer Panthera PS4 Controller
132 { MAKE_CONTROLLER_ID( 0x1532, 0x1000 ), k_eControllerType_PS4Controller, NULL }, // Razer Raiju PS4 Controller
133 { MAKE_CONTROLLER_ID( 0x1532, 0x1004 ), k_eControllerType_PS4Controller, NULL }, // Razer Raiju 2 Ultimate USB
134 { MAKE_CONTROLLER_ID( 0x1532, 0x1007 ), k_eControllerType_PS4Controller, NULL }, // Razer Raiju 2 Tournament edition USB
135 { MAKE_CONTROLLER_ID( 0x1532, 0x1008 ), k_eControllerType_PS4Controller, NULL }, // Razer Panthera Evo Fightstick
136 { MAKE_CONTROLLER_ID( 0x1532, 0x1009 ), k_eControllerType_PS4Controller, NULL }, // Razer Raiju 2 Ultimate BT
137 { MAKE_CONTROLLER_ID( 0x1532, 0x100A ), k_eControllerType_PS4Controller, NULL }, // Razer Raiju 2 Tournament edition BT
138 { MAKE_CONTROLLER_ID( 0x1532, 0x1100 ), k_eControllerType_PS4Controller, NULL }, // Razer RAION Fightpad - Trackpad, no gyro, lightbar hardcoded to green
139 { MAKE_CONTROLLER_ID( 0x20d6, 0x792a ), k_eControllerType_PS4Controller, NULL }, // PowerA Fusion Fight Pad
140 { MAKE_CONTROLLER_ID( 0x2c22, 0x2000 ), k_eControllerType_PS4Controller, NULL }, // Qanba Drone
141 { MAKE_CONTROLLER_ID( 0x2c22, 0x2300 ), k_eControllerType_PS4Controller, NULL }, // Qanba Obsidian
142 { MAKE_CONTROLLER_ID( 0x2c22, 0x2303 ), k_eControllerType_XInputPS4Controller, NULL }, // Qanba Obsidian Arcade Joystick
143 { MAKE_CONTROLLER_ID( 0x2c22, 0x2500 ), k_eControllerType_PS4Controller, NULL }, // Qanba Dragon
144 { MAKE_CONTROLLER_ID( 0x2c22, 0x2503 ), k_eControllerType_XInputPS4Controller, NULL }, // Qanba Dragon Arcade Joystick
145 { MAKE_CONTROLLER_ID( 0x3285, 0x0d16 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution 5 Pro (PS4 mode with dongle)
146 { MAKE_CONTROLLER_ID( 0x3285, 0x0d17 ), k_eControllerType_PS4Controller, NULL }, // NACON Revolution 5 Pro (PS4 mode wired)
147 { MAKE_CONTROLLER_ID( 0x7545, 0x0104 ), k_eControllerType_PS4Controller, NULL }, // Armor 3 or Level Up Cobra - At least one variant has gyro
148 { MAKE_CONTROLLER_ID (0x9886, 0x0024 ), k_eControllerType_XInputPS4Controller, NULL }, // Astro C40 in Xbox 360 mode
149 { MAKE_CONTROLLER_ID( 0x9886, 0x0025 ), k_eControllerType_PS4Controller, NULL }, // Astro C40
150 // Removing the Giotek because there were a bunch of help tickets from users w/ issues including from non-PS4 controller users. This VID/PID is probably used in different FW's
151// { MAKE_CONTROLLER_ID( 0x7545, 0x1122 ), k_eControllerType_PS4Controller, NULL }, // Giotek VX4 - trackpad/gyro don't work. Had to not filter on interface info. Light bar is flaky, but works.
152
153 { MAKE_CONTROLLER_ID( 0x054c, 0x0ce6 ), k_eControllerType_PS5Controller, NULL }, // Sony DualSense Controller
154 { MAKE_CONTROLLER_ID( 0x054c, 0x0df2 ), k_eControllerType_PS5Controller, NULL }, // Sony DualSense Edge Controller
155 { MAKE_CONTROLLER_ID( 0x054c, 0x0e5f ), k_eControllerType_PS5Controller, NULL }, // Access Controller for PS5
156 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0209 ), k_eControllerType_PS5Controller, NULL }, // Victrix Pro FS PS4/PS5 (PS5 mode)
157 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0163 ), k_eControllerType_PS5Controller, NULL }, // HORI Fighting Commander OCTA
158 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0184 ), k_eControllerType_PS5Controller, NULL }, // Hori Fighting Stick α
159 { MAKE_CONTROLLER_ID( 0x1532, 0x100b ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wired)
160 { MAKE_CONTROLLER_ID( 0x1532, 0x100c ), k_eControllerType_PS5Controller, NULL }, // Razer Wolverine V2 Pro (Wireless)
161 { MAKE_CONTROLLER_ID( 0x1532, 0x1012 ), k_eControllerType_PS5Controller, NULL }, // Razer Kitsune
162 { MAKE_CONTROLLER_ID( 0x3285, 0x0d18 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode with dongle)
163 { MAKE_CONTROLLER_ID( 0x3285, 0x0d19 ), k_eControllerType_PS5Controller, NULL }, // NACON Revolution 5 Pro (PS5 mode wired)
164 { MAKE_CONTROLLER_ID( 0x358a, 0x0104 ), k_eControllerType_PS5Controller, NULL }, // Backbone One PlayStation Edition for iOS
165
166 { MAKE_CONTROLLER_ID( 0x0079, 0x0006 ), k_eControllerType_UnknownNonSteamController, NULL }, // DragonRise Generic USB PCB, sometimes configured as a PC Twin Shock Controller - looks like a DS3 but the face buttons are 1-4 instead of symbols
167
168 { MAKE_CONTROLLER_ID( 0x0079, 0x18d4 ), k_eControllerType_XBox360Controller, NULL }, // GPD Win 2 X-Box Controller
169 { MAKE_CONTROLLER_ID( 0x03eb, 0xff02 ), k_eControllerType_XBox360Controller, NULL }, // Wooting Two
170 { MAKE_CONTROLLER_ID( 0x044f, 0xb326 ), k_eControllerType_XBox360Controller, NULL }, // Thrustmaster Gamepad GP XID
171 { MAKE_CONTROLLER_ID( 0x045e, 0x028e ), k_eControllerType_XBox360Controller, "Xbox 360 Controller" }, // Microsoft Xbox 360 Wired Controller
172 { MAKE_CONTROLLER_ID( 0x045e, 0x028f ), k_eControllerType_XBox360Controller, "Xbox 360 Controller" }, // Microsoft Xbox 360 Play and Charge Cable
173 { MAKE_CONTROLLER_ID( 0x045e, 0x0291 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // X-box 360 Wireless Receiver (third party knockoff)
174 { MAKE_CONTROLLER_ID( 0x045e, 0x02a0 ), k_eControllerType_XBox360Controller, NULL }, // Microsoft Xbox 360 Big Button IR
175 { MAKE_CONTROLLER_ID( 0x045e, 0x02a1 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // Microsoft Xbox 360 Wireless Controller with XUSB driver on Windows
176 { MAKE_CONTROLLER_ID( 0x045e, 0x02a9 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // X-box 360 Wireless Receiver (third party knockoff)
177 { MAKE_CONTROLLER_ID( 0x045e, 0x0719 ), k_eControllerType_XBox360Controller, "Xbox 360 Wireless Controller" }, // Microsoft Xbox 360 Wireless Receiver
178 { MAKE_CONTROLLER_ID( 0x046d, 0xc21d ), k_eControllerType_XBox360Controller, NULL }, // Logitech Gamepad F310
179 { MAKE_CONTROLLER_ID( 0x046d, 0xc21e ), k_eControllerType_XBox360Controller, NULL }, // Logitech Gamepad F510
180 { MAKE_CONTROLLER_ID( 0x046d, 0xc21f ), k_eControllerType_XBox360Controller, NULL }, // Logitech Gamepad F710
181 { MAKE_CONTROLLER_ID( 0x046d, 0xc242 ), k_eControllerType_XBox360Controller, NULL }, // Logitech Chillstream Controller
182 { MAKE_CONTROLLER_ID( 0x056e, 0x2004 ), k_eControllerType_XBox360Controller, NULL }, // Elecom JC-U3613M
183// This isn't actually an Xbox 360 controller, it just looks like one
184// { MAKE_CONTROLLER_ID( 0x06a3, 0xf51a ), k_eControllerType_XBox360Controller, NULL }, // Saitek P3600
185 { MAKE_CONTROLLER_ID( 0x0738, 0x4716 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Wired Xbox 360 Controller
186 { MAKE_CONTROLLER_ID( 0x0738, 0x4718 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Street Fighter IV FightStick SE
187 { MAKE_CONTROLLER_ID( 0x0738, 0x4726 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Xbox 360 Controller
188 { MAKE_CONTROLLER_ID( 0x0738, 0x4728 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Street Fighter IV FightPad
189 { MAKE_CONTROLLER_ID( 0x0738, 0x4736 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz MicroCon Gamepad
190 { MAKE_CONTROLLER_ID( 0x0738, 0x4738 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Wired Xbox 360 Controller (SFIV)
191 { MAKE_CONTROLLER_ID( 0x0738, 0x4740 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Beat Pad
192 { MAKE_CONTROLLER_ID( 0x0738, 0xb726 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Xbox controller - MW2
193 { MAKE_CONTROLLER_ID( 0x0738, 0xbeef ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz JOYTECH NEO SE Advanced GamePad
194 { MAKE_CONTROLLER_ID( 0x0738, 0xcb02 ), k_eControllerType_XBox360Controller, NULL }, // Saitek Cyborg Rumble Pad - PC/Xbox 360
195 { MAKE_CONTROLLER_ID( 0x0738, 0xcb03 ), k_eControllerType_XBox360Controller, NULL }, // Saitek P3200 Rumble Pad - PC/Xbox 360
196 { MAKE_CONTROLLER_ID( 0x0738, 0xf738 ), k_eControllerType_XBox360Controller, NULL }, // Super SFIV FightStick TE S
197 { MAKE_CONTROLLER_ID( 0x0955, 0x7210 ), k_eControllerType_XBox360Controller, NULL }, // Nvidia Shield local controller
198 { MAKE_CONTROLLER_ID( 0x0955, 0xb400 ), k_eControllerType_XBox360Controller, NULL }, // NVIDIA Shield streaming controller
199 { MAKE_CONTROLLER_ID( 0x0b05, 0x1b4c ), k_eControllerType_XBox360Controller, NULL }, // ASUS ROG Ally X built-in controller
200 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0105 ), k_eControllerType_XBox360Controller, NULL }, // HSM3 Xbox360 dancepad
201 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0113 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Afterglow" }, // PDP Afterglow Gamepad for Xbox 360
202 { MAKE_CONTROLLER_ID( 0x0e6f, 0x011f ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Rock Candy" }, // PDP Rock Candy Gamepad for Xbox 360
203 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0125 ), k_eControllerType_XBox360Controller, "PDP INJUSTICE FightStick" }, // PDP INJUSTICE FightStick for Xbox 360
204 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0127 ), k_eControllerType_XBox360Controller, "PDP INJUSTICE FightPad" }, // PDP INJUSTICE FightPad for Xbox 360
205 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0131 ), k_eControllerType_XBox360Controller, "PDP EA Soccer Controller" }, // PDP EA Soccer Gamepad
206 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0133 ), k_eControllerType_XBox360Controller, "PDP Battlefield 4 Controller" }, // PDP Battlefield 4 Gamepad
207 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0143 ), k_eControllerType_XBox360Controller, "PDP MK X Fight Stick" }, // PDP MK X Fight Stick for Xbox 360
208 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0147 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Marvel Controller" }, // PDP Marvel Controller for Xbox 360
209 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0201 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Controller" }, // PDP Gamepad for Xbox 360
210 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0213 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Afterglow" }, // PDP Afterglow Gamepad for Xbox 360
211 { MAKE_CONTROLLER_ID( 0x0e6f, 0x021f ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Rock Candy" }, // PDP Rock Candy Gamepad for Xbox 360
212 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0301 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Controller" }, // PDP Gamepad for Xbox 360
213 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0313 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Afterglow" }, // PDP Afterglow Gamepad for Xbox 360
214 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0314 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Afterglow" }, // PDP Afterglow Gamepad for Xbox 360
215 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0401 ), k_eControllerType_XBox360Controller, "PDP Xbox 360 Controller" }, // PDP Gamepad for Xbox 360
216 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0413 ), k_eControllerType_XBox360Controller, NULL }, // PDP Afterglow AX.1 (unlisted)
217 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0501 ), k_eControllerType_XBox360Controller, NULL }, // PDP Xbox 360 Controller (unlisted)
218 { MAKE_CONTROLLER_ID( 0x0e6f, 0xf900 ), k_eControllerType_XBox360Controller, NULL }, // PDP Afterglow AX.1 (unlisted)
219 { MAKE_CONTROLLER_ID( 0x0f0d, 0x000a ), k_eControllerType_XBox360Controller, NULL }, // Hori Co. DOA4 FightStick
220 { MAKE_CONTROLLER_ID( 0x0f0d, 0x000c ), k_eControllerType_XBox360Controller, NULL }, // Hori PadEX Turbo
221 { MAKE_CONTROLLER_ID( 0x0f0d, 0x000d ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Stick EX2
222 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0016 ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro.EX
223 { MAKE_CONTROLLER_ID( 0x0f0d, 0x001b ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro VX
224 { MAKE_CONTROLLER_ID( 0x0f0d, 0x008c ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro 4
225 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00db ), k_eControllerType_XBox360Controller, "HORI Slime Controller" }, // Hori Dragon Quest Slime Controller
226 { MAKE_CONTROLLER_ID( 0x0f0d, 0x011e ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Stick α
227 { MAKE_CONTROLLER_ID( 0x1038, 0x1430 ), k_eControllerType_XBox360Controller, "SteelSeries Stratus Duo" }, // SteelSeries Stratus Duo
228 { MAKE_CONTROLLER_ID( 0x1038, 0x1431 ), k_eControllerType_XBox360Controller, "SteelSeries Stratus Duo" }, // SteelSeries Stratus Duo
229 { MAKE_CONTROLLER_ID( 0x1038, 0xb360 ), k_eControllerType_XBox360Controller, NULL }, // SteelSeries Nimbus/Stratus XL
230 { MAKE_CONTROLLER_ID( 0x11c9, 0x55f0 ), k_eControllerType_XBox360Controller, NULL }, // Nacon GC-100XF
231 { MAKE_CONTROLLER_ID( 0x12ab, 0x0004 ), k_eControllerType_XBox360Controller, NULL }, // Honey Bee Xbox360 dancepad
232 { MAKE_CONTROLLER_ID( 0x12ab, 0x0301 ), k_eControllerType_XBox360Controller, NULL }, // PDP AFTERGLOW AX.1
233 { MAKE_CONTROLLER_ID( 0x12ab, 0x0303 ), k_eControllerType_XBox360Controller, NULL }, // Mortal Kombat Klassic FightStick
234 { MAKE_CONTROLLER_ID( 0x1430, 0x02a0 ), k_eControllerType_XBox360Controller, NULL }, // RedOctane Controller Adapter
235 { MAKE_CONTROLLER_ID( 0x1430, 0x4748 ), k_eControllerType_XBox360Controller, NULL }, // RedOctane Guitar Hero X-plorer
236 { MAKE_CONTROLLER_ID( 0x1430, 0xf801 ), k_eControllerType_XBox360Controller, NULL }, // RedOctane Controller
237 { MAKE_CONTROLLER_ID( 0x146b, 0x0601 ), k_eControllerType_XBox360Controller, NULL }, // BigBen Interactive XBOX 360 Controller
238// { MAKE_CONTROLLER_ID( 0x1532, 0x0037 ), k_eControllerType_XBox360Controller, NULL }, // Razer Sabertooth
239 { MAKE_CONTROLLER_ID( 0x15e4, 0x3f00 ), k_eControllerType_XBox360Controller, NULL }, // Power A Mini Pro Elite
240 { MAKE_CONTROLLER_ID( 0x15e4, 0x3f0a ), k_eControllerType_XBox360Controller, NULL }, // Xbox Airflo wired controller
241 { MAKE_CONTROLLER_ID( 0x15e4, 0x3f10 ), k_eControllerType_XBox360Controller, NULL }, // Batarang Xbox 360 controller
242 { MAKE_CONTROLLER_ID( 0x162e, 0xbeef ), k_eControllerType_XBox360Controller, NULL }, // Joytech Neo-Se Take2
243 { MAKE_CONTROLLER_ID( 0x1689, 0xfd00 ), k_eControllerType_XBox360Controller, NULL }, // Razer Onza Tournament Edition
244 { MAKE_CONTROLLER_ID( 0x1689, 0xfd01 ), k_eControllerType_XBox360Controller, NULL }, // Razer Onza Classic Edition
245 { MAKE_CONTROLLER_ID( 0x1689, 0xfe00 ), k_eControllerType_XBox360Controller, NULL }, // Razer Sabertooth
246 { MAKE_CONTROLLER_ID( 0x1949, 0x041a ), k_eControllerType_XBox360Controller, "Amazon Luna Controller" }, // Amazon Luna Controller
247 { MAKE_CONTROLLER_ID( 0x1bad, 0x0002 ), k_eControllerType_XBox360Controller, NULL }, // Harmonix Rock Band Guitar
248 { MAKE_CONTROLLER_ID( 0x1bad, 0x0003 ), k_eControllerType_XBox360Controller, NULL }, // Harmonix Rock Band Drumkit
249 { MAKE_CONTROLLER_ID( 0x1bad, 0xf016 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Xbox 360 Controller
250 { MAKE_CONTROLLER_ID( 0x1bad, 0xf018 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Street Fighter IV SE Fighting Stick
251 { MAKE_CONTROLLER_ID( 0x1bad, 0xf019 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Brawlstick for Xbox 360
252 { MAKE_CONTROLLER_ID( 0x1bad, 0xf021 ), k_eControllerType_XBox360Controller, NULL }, // Mad Cats Ghost Recon FS GamePad
253 { MAKE_CONTROLLER_ID( 0x1bad, 0xf023 ), k_eControllerType_XBox360Controller, NULL }, // MLG Pro Circuit Controller (Xbox)
254 { MAKE_CONTROLLER_ID( 0x1bad, 0xf025 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Call Of Duty
255 { MAKE_CONTROLLER_ID( 0x1bad, 0xf027 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz FPS Pro
256 { MAKE_CONTROLLER_ID( 0x1bad, 0xf028 ), k_eControllerType_XBox360Controller, NULL }, // Street Fighter IV FightPad
257 { MAKE_CONTROLLER_ID( 0x1bad, 0xf02e ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Fightpad
258 { MAKE_CONTROLLER_ID( 0x1bad, 0xf036 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz MicroCon GamePad Pro
259 { MAKE_CONTROLLER_ID( 0x1bad, 0xf038 ), k_eControllerType_XBox360Controller, NULL }, // Street Fighter IV FightStick TE
260 { MAKE_CONTROLLER_ID( 0x1bad, 0xf039 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz MvC2 TE
261 { MAKE_CONTROLLER_ID( 0x1bad, 0xf03a ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz SFxT Fightstick Pro
262 { MAKE_CONTROLLER_ID( 0x1bad, 0xf03d ), k_eControllerType_XBox360Controller, NULL }, // Street Fighter IV Arcade Stick TE - Chun Li
263 { MAKE_CONTROLLER_ID( 0x1bad, 0xf03e ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz MLG FightStick TE
264 { MAKE_CONTROLLER_ID( 0x1bad, 0xf03f ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz FightStick SoulCaliber
265 { MAKE_CONTROLLER_ID( 0x1bad, 0xf042 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz FightStick TES+
266 { MAKE_CONTROLLER_ID( 0x1bad, 0xf080 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz FightStick TE2
267 { MAKE_CONTROLLER_ID( 0x1bad, 0xf501 ), k_eControllerType_XBox360Controller, NULL }, // HoriPad EX2 Turbo
268 { MAKE_CONTROLLER_ID( 0x1bad, 0xf502 ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro.VX SA
269 { MAKE_CONTROLLER_ID( 0x1bad, 0xf503 ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Stick VX
270 { MAKE_CONTROLLER_ID( 0x1bad, 0xf504 ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro. EX
271 { MAKE_CONTROLLER_ID( 0x1bad, 0xf505 ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Stick EX2B
272 { MAKE_CONTROLLER_ID( 0x1bad, 0xf506 ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro.EX Premium VLX
273 { MAKE_CONTROLLER_ID( 0x1bad, 0xf900 ), k_eControllerType_XBox360Controller, NULL }, // Harmonix Xbox 360 Controller
274 { MAKE_CONTROLLER_ID( 0x1bad, 0xf901 ), k_eControllerType_XBox360Controller, NULL }, // Gamestop Xbox 360 Controller
275 { MAKE_CONTROLLER_ID( 0x1bad, 0xf902 ), k_eControllerType_XBox360Controller, NULL }, // Mad Catz Gamepad2
276 { MAKE_CONTROLLER_ID( 0x1bad, 0xf903 ), k_eControllerType_XBox360Controller, NULL }, // Tron Xbox 360 controller
277 { MAKE_CONTROLLER_ID( 0x1bad, 0xf904 ), k_eControllerType_XBox360Controller, NULL }, // PDP Versus Fighting Pad
278 { MAKE_CONTROLLER_ID( 0x1bad, 0xf906 ), k_eControllerType_XBox360Controller, NULL }, // MortalKombat FightStick
279 { MAKE_CONTROLLER_ID( 0x1bad, 0xfa01 ), k_eControllerType_XBox360Controller, NULL }, // MadCatz GamePad
280 { MAKE_CONTROLLER_ID( 0x1bad, 0xfd00 ), k_eControllerType_XBox360Controller, NULL }, // Razer Onza TE
281 { MAKE_CONTROLLER_ID( 0x1bad, 0xfd01 ), k_eControllerType_XBox360Controller, NULL }, // Razer Onza
282 { MAKE_CONTROLLER_ID( 0x24c6, 0x5000 ), k_eControllerType_XBox360Controller, NULL }, // Razer Atrox Arcade Stick
283 { MAKE_CONTROLLER_ID( 0x24c6, 0x5300 ), k_eControllerType_XBox360Controller, NULL }, // PowerA MINI PROEX Controller
284 { MAKE_CONTROLLER_ID( 0x24c6, 0x5303 ), k_eControllerType_XBox360Controller, NULL }, // Xbox Airflo wired controller
285 { MAKE_CONTROLLER_ID( 0x24c6, 0x530a ), k_eControllerType_XBox360Controller, NULL }, // Xbox 360 Pro EX Controller
286 { MAKE_CONTROLLER_ID( 0x24c6, 0x531a ), k_eControllerType_XBox360Controller, NULL }, // PowerA Pro Ex
287 { MAKE_CONTROLLER_ID( 0x24c6, 0x5397 ), k_eControllerType_XBox360Controller, NULL }, // FUS1ON Tournament Controller
288 { MAKE_CONTROLLER_ID( 0x24c6, 0x5500 ), k_eControllerType_XBox360Controller, NULL }, // Hori XBOX 360 EX 2 with Turbo
289 { MAKE_CONTROLLER_ID( 0x24c6, 0x5501 ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro VX-SA
290 { MAKE_CONTROLLER_ID( 0x24c6, 0x5502 ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Stick VX Alt
291 { MAKE_CONTROLLER_ID( 0x24c6, 0x5503 ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Edge
292 { MAKE_CONTROLLER_ID( 0x24c6, 0x5506 ), k_eControllerType_XBox360Controller, NULL }, // Hori SOULCALIBUR V Stick
293 { MAKE_CONTROLLER_ID( 0x24c6, 0x550d ), k_eControllerType_XBox360Controller, NULL }, // Hori GEM Xbox controller
294 { MAKE_CONTROLLER_ID( 0x24c6, 0x550e ), k_eControllerType_XBox360Controller, NULL }, // Hori Real Arcade Pro V Kai 360
295 { MAKE_CONTROLLER_ID( 0x24c6, 0x5508 ), k_eControllerType_XBox360Controller, NULL }, // Hori PAD A
296 { MAKE_CONTROLLER_ID( 0x24c6, 0x5510 ), k_eControllerType_XBox360Controller, NULL }, // Hori Fighting Commander ONE
297 { MAKE_CONTROLLER_ID( 0x24c6, 0x5b00 ), k_eControllerType_XBox360Controller, NULL }, // ThrustMaster Ferrari Italia 458 Racing Wheel
298 { MAKE_CONTROLLER_ID( 0x24c6, 0x5b02 ), k_eControllerType_XBox360Controller, NULL }, // Thrustmaster, Inc. GPX Controller
299 { MAKE_CONTROLLER_ID( 0x24c6, 0x5b03 ), k_eControllerType_XBox360Controller, NULL }, // Thrustmaster Ferrari 458 Racing Wheel
300 { MAKE_CONTROLLER_ID( 0x24c6, 0x5d04 ), k_eControllerType_XBox360Controller, NULL }, // Razer Sabertooth
301 { MAKE_CONTROLLER_ID( 0x24c6, 0xfafa ), k_eControllerType_XBox360Controller, NULL }, // Aplay Controller
302 { MAKE_CONTROLLER_ID( 0x24c6, 0xfafb ), k_eControllerType_XBox360Controller, NULL }, // Aplay Controller
303 { MAKE_CONTROLLER_ID( 0x24c6, 0xfafc ), k_eControllerType_XBox360Controller, NULL }, // Afterglow Gamepad 1
304 { MAKE_CONTROLLER_ID( 0x24c6, 0xfafd ), k_eControllerType_XBox360Controller, NULL }, // Afterglow Gamepad 3
305 { MAKE_CONTROLLER_ID( 0x24c6, 0xfafe ), k_eControllerType_XBox360Controller, NULL }, // Rock Candy Gamepad for Xbox 360
306
307 { MAKE_CONTROLLER_ID( 0x03f0, 0x0495 ), k_eControllerType_XBoxOneController, NULL }, // HP HyperX Clutch Gladiate
308 { MAKE_CONTROLLER_ID( 0x044f, 0xd012 ), k_eControllerType_XBoxOneController, NULL }, // ThrustMaster eSwap PRO Controller Xbox
309 { MAKE_CONTROLLER_ID( 0x045e, 0x02d1 ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft Xbox One Controller
310 { MAKE_CONTROLLER_ID( 0x045e, 0x02dd ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft Xbox One Controller (Firmware 2015)
311 { MAKE_CONTROLLER_ID( 0x045e, 0x02e0 ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller (Bluetooth)
312 { MAKE_CONTROLLER_ID( 0x045e, 0x02e3 ), k_eControllerType_XBoxOneController, "Xbox One Elite Controller" }, // Microsoft Xbox One Elite Controller
313 { MAKE_CONTROLLER_ID( 0x045e, 0x02ea ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller
314 { MAKE_CONTROLLER_ID( 0x045e, 0x02fd ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller (Bluetooth)
315 { MAKE_CONTROLLER_ID( 0x045e, 0x02ff ), k_eControllerType_XBoxOneController, "Xbox One Controller" }, // Microsoft Xbox One Controller with XBOXGIP driver on Windows
316 { MAKE_CONTROLLER_ID( 0x045e, 0x0b00 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft Xbox One Elite Series 2 Controller
317 { MAKE_CONTROLLER_ID( 0x045e, 0x0b05 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft Xbox One Elite Series 2 Controller (Bluetooth)
318 { MAKE_CONTROLLER_ID( 0x045e, 0x0b0a ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft Xbox Adaptive Controller
319 { MAKE_CONTROLLER_ID( 0x045e, 0x0b0c ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft Xbox Adaptive Controller (Bluetooth)
320 { MAKE_CONTROLLER_ID( 0x045e, 0x0b12 ), k_eControllerType_XBoxOneController, "Xbox Series X Controller" }, // Microsoft Xbox Series X Controller
321 { MAKE_CONTROLLER_ID( 0x045e, 0x0b13 ), k_eControllerType_XBoxOneController, "Xbox Series X Controller" }, // Microsoft Xbox Series X Controller (BLE)
322 { MAKE_CONTROLLER_ID( 0x045e, 0x0b20 ), k_eControllerType_XBoxOneController, "Xbox One S Controller" }, // Microsoft Xbox One S Controller (BLE)
323 { MAKE_CONTROLLER_ID( 0x045e, 0x0b21 ), k_eControllerType_XBoxOneController, "Xbox Adaptive Controller" }, // Microsoft Xbox Adaptive Controller (BLE)
324 { MAKE_CONTROLLER_ID( 0x045e, 0x0b22 ), k_eControllerType_XBoxOneController, "Xbox One Elite 2 Controller" }, // Microsoft Xbox One Elite Series 2 Controller (BLE)
325 { MAKE_CONTROLLER_ID( 0x0738, 0x4a01 ), k_eControllerType_XBoxOneController, NULL }, // Mad Catz FightStick TE 2
326 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0139 ), k_eControllerType_XBoxOneController, "PDP Xbox One Afterglow" }, // PDP Afterglow Wired Controller for Xbox One
327 { MAKE_CONTROLLER_ID( 0x0e6f, 0x013B ), k_eControllerType_XBoxOneController, "PDP Xbox One Face-Off Controller" }, // PDP Face-Off Gamepad for Xbox One
328 { MAKE_CONTROLLER_ID( 0x0e6f, 0x013a ), k_eControllerType_XBoxOneController, NULL }, // PDP Xbox One Controller (unlisted)
329 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0145 ), k_eControllerType_XBoxOneController, "PDP MK X Fight Pad" }, // PDP MK X Fight Pad for Xbox One
330 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0146 ), k_eControllerType_XBoxOneController, "PDP Xbox One Rock Candy" }, // PDP Rock Candy Wired Controller for Xbox One
331 { MAKE_CONTROLLER_ID( 0x0e6f, 0x015b ), k_eControllerType_XBoxOneController, "PDP Fallout 4 Vault Boy Controller" }, // PDP Fallout 4 Vault Boy Wired Controller for Xbox One
332 { MAKE_CONTROLLER_ID( 0x0e6f, 0x015c ), k_eControllerType_XBoxOneController, "PDP Xbox One @Play Controller" }, // PDP @Play Wired Controller for Xbox One
333 { MAKE_CONTROLLER_ID( 0x0e6f, 0x015d ), k_eControllerType_XBoxOneController, "PDP Mirror's Edge Controller" }, // PDP Mirror's Edge Wired Controller for Xbox One
334 { MAKE_CONTROLLER_ID( 0x0e6f, 0x015f ), k_eControllerType_XBoxOneController, "PDP Metallic Controller" }, // PDP Metallic Wired Controller for Xbox One
335 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0160 ), k_eControllerType_XBoxOneController, "PDP NFL Face-Off Controller" }, // PDP NFL Official Face-Off Wired Controller for Xbox One
336 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0161 ), k_eControllerType_XBoxOneController, "PDP Xbox One Camo" }, // PDP Camo Wired Controller for Xbox One
337 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0162 ), k_eControllerType_XBoxOneController, "PDP Xbox One Controller" }, // PDP Wired Controller for Xbox One
338 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0163 ), k_eControllerType_XBoxOneController, "PDP Deliverer of Truth" }, // PDP Legendary Collection: Deliverer of Truth
339 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0164 ), k_eControllerType_XBoxOneController, "PDP Battlefield 1 Controller" }, // PDP Battlefield 1 Official Wired Controller for Xbox One
340 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0165 ), k_eControllerType_XBoxOneController, "PDP Titanfall 2 Controller" }, // PDP Titanfall 2 Official Wired Controller for Xbox One
341 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0166 ), k_eControllerType_XBoxOneController, "PDP Mass Effect: Andromeda Controller" }, // PDP Mass Effect: Andromeda Official Wired Controller for Xbox One
342 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0167 ), k_eControllerType_XBoxOneController, "PDP Halo Wars 2 Face-Off Controller" }, // PDP Halo Wars 2 Official Face-Off Wired Controller for Xbox One
343 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0205 ), k_eControllerType_XBoxOneController, "PDP Victrix Pro Fight Stick" }, // PDP Victrix Pro Fight Stick
344 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0206 ), k_eControllerType_XBoxOneController, "PDP Mortal Kombat Controller" }, // PDP Mortal Kombat 25 Anniversary Edition Stick (Xbox One)
345 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0246 ), k_eControllerType_XBoxOneController, "PDP Xbox One Rock Candy" }, // PDP Rock Candy Wired Controller for Xbox One
346 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0261 ), k_eControllerType_XBoxOneController, "PDP Xbox One Camo" }, // PDP Camo Wired Controller
347 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0262 ), k_eControllerType_XBoxOneController, "PDP Xbox One Controller" }, // PDP Wired Controller
348 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a0 ), k_eControllerType_XBoxOneController, "PDP Xbox One Midnight Blue" }, // PDP Wired Controller for Xbox One - Midnight Blue
349 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a1 ), k_eControllerType_XBoxOneController, "PDP Xbox One Verdant Green" }, // PDP Wired Controller for Xbox One - Verdant Green
350 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a2 ), k_eControllerType_XBoxOneController, "PDP Xbox One Crimson Red" }, // PDP Wired Controller for Xbox One - Crimson Red
351 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a3 ), k_eControllerType_XBoxOneController, "PDP Xbox One Arctic White" }, // PDP Wired Controller for Xbox One - Arctic White
352 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a4 ), k_eControllerType_XBoxOneController, "PDP Xbox One Phantom Black" }, // PDP Wired Controller for Xbox One - Stealth Series | Phantom Black
353 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a5 ), k_eControllerType_XBoxOneController, "PDP Xbox One Ghost White" }, // PDP Wired Controller for Xbox One - Stealth Series | Ghost White
354 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a6 ), k_eControllerType_XBoxOneController, "PDP Xbox One Revenant Blue" }, // PDP Wired Controller for Xbox One - Stealth Series | Revenant Blue
355 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a7 ), k_eControllerType_XBoxOneController, "PDP Xbox One Raven Black" }, // PDP Wired Controller for Xbox One - Raven Black
356 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a8 ), k_eControllerType_XBoxOneController, "PDP Xbox One Arctic White" }, // PDP Wired Controller for Xbox One - Arctic White
357 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02a9 ), k_eControllerType_XBoxOneController, "PDP Xbox One Midnight Blue" }, // PDP Wired Controller for Xbox One - Midnight Blue
358 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02aa ), k_eControllerType_XBoxOneController, "PDP Xbox One Verdant Green" }, // PDP Wired Controller for Xbox One - Verdant Green
359 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ab ), k_eControllerType_XBoxOneController, "PDP Xbox One Crimson Red" }, // PDP Wired Controller for Xbox One - Crimson Red
360 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ac ), k_eControllerType_XBoxOneController, "PDP Xbox One Ember Orange" }, // PDP Wired Controller for Xbox One - Ember Orange
361 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ad ), k_eControllerType_XBoxOneController, "PDP Xbox One Phantom Black" }, // PDP Wired Controller for Xbox One - Stealth Series | Phantom Black
362 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ae ), k_eControllerType_XBoxOneController, "PDP Xbox One Ghost White" }, // PDP Wired Controller for Xbox One - Stealth Series | Ghost White
363 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02af ), k_eControllerType_XBoxOneController, "PDP Xbox One Revenant Blue" }, // PDP Wired Controller for Xbox One - Stealth Series | Revenant Blue
364 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02b0 ), k_eControllerType_XBoxOneController, "PDP Xbox One Raven Black" }, // PDP Wired Controller for Xbox One - Raven Black
365 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02b1 ), k_eControllerType_XBoxOneController, "PDP Xbox One Arctic White" }, // PDP Wired Controller for Xbox One - Arctic White
366 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02b3 ), k_eControllerType_XBoxOneController, "PDP Xbox One Afterglow" }, // PDP Afterglow Prismatic Wired Controller
367 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02b5 ), k_eControllerType_XBoxOneController, "PDP Xbox One GAMEware Controller" }, // PDP GAMEware Wired Controller Xbox One
368 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02b6 ), k_eControllerType_XBoxOneController, NULL }, // PDP One-Handed Joystick Adaptive Controller
369 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02bd ), k_eControllerType_XBoxOneController, "PDP Xbox One Royal Purple" }, // PDP Wired Controller for Xbox One - Royal Purple
370 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02be ), k_eControllerType_XBoxOneController, "PDP Xbox One Raven Black" }, // PDP Deluxe Wired Controller for Xbox One - Raven Black
371 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02bf ), k_eControllerType_XBoxOneController, "PDP Xbox One Midnight Blue" }, // PDP Deluxe Wired Controller for Xbox One - Midnight Blue
372 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c0 ), k_eControllerType_XBoxOneController, "PDP Xbox One Phantom Black" }, // PDP Deluxe Wired Controller for Xbox One - Stealth Series | Phantom Black
373 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c1 ), k_eControllerType_XBoxOneController, "PDP Xbox One Ghost White" }, // PDP Deluxe Wired Controller for Xbox One - Stealth Series | Ghost White
374 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c2 ), k_eControllerType_XBoxOneController, "PDP Xbox One Revenant Blue" }, // PDP Deluxe Wired Controller for Xbox One - Stealth Series | Revenant Blue
375 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c3 ), k_eControllerType_XBoxOneController, "PDP Xbox One Verdant Green" }, // PDP Deluxe Wired Controller for Xbox One - Verdant Green
376 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c4 ), k_eControllerType_XBoxOneController, "PDP Xbox One Ember Orange" }, // PDP Deluxe Wired Controller for Xbox One - Ember Orange
377 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c5 ), k_eControllerType_XBoxOneController, "PDP Xbox One Royal Purple" }, // PDP Deluxe Wired Controller for Xbox One - Royal Purple
378 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c6 ), k_eControllerType_XBoxOneController, "PDP Xbox One Crimson Red" }, // PDP Deluxe Wired Controller for Xbox One - Crimson Red
379 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c7 ), k_eControllerType_XBoxOneController, "PDP Xbox One Arctic White" }, // PDP Deluxe Wired Controller for Xbox One - Arctic White
380 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c8 ), k_eControllerType_XBoxOneController, "PDP Kingdom Hearts Controller" }, // PDP Kingdom Hearts Wired Controller
381 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02c9 ), k_eControllerType_XBoxOneController, "PDP Xbox One Phantasm Red" }, // PDP Deluxe Wired Controller for Xbox One - Stealth Series | Phantasm Red
382 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ca ), k_eControllerType_XBoxOneController, "PDP Xbox One Specter Violet" }, // PDP Deluxe Wired Controller for Xbox One - Stealth Series | Specter Violet
383 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02cb ), k_eControllerType_XBoxOneController, "PDP Xbox One Specter Violet" }, // PDP Wired Controller for Xbox One - Stealth Series | Specter Violet
384 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02cd ), k_eControllerType_XBoxOneController, "PDP Xbox One Blu-merang" }, // PDP Rock Candy Wired Controller for Xbox One - Blu-merang
385 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02ce ), k_eControllerType_XBoxOneController, "PDP Xbox One Cranblast" }, // PDP Rock Candy Wired Controller for Xbox One - Cranblast
386 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02cf ), k_eControllerType_XBoxOneController, "PDP Xbox One Aqualime" }, // PDP Rock Candy Wired Controller for Xbox One - Aqualime
387 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02d5 ), k_eControllerType_XBoxOneController, "PDP Xbox One Red Camo" }, // PDP Wired Controller for Xbox One - Red Camo
388 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0346 ), k_eControllerType_XBoxOneController, "PDP Xbox One RC Gamepad" }, // PDP RC Gamepad for Xbox One
389 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0446 ), k_eControllerType_XBoxOneController, "PDP Xbox One RC Gamepad" }, // PDP RC Gamepad for Xbox One
390 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02da ), k_eControllerType_XBoxOneController, "PDP Xbox Series X Afterglow" }, // PDP Xbox Series X Afterglow
391 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02d6 ), k_eControllerType_XBoxOneController, "Victrix Gambit Tournament Controller" }, // Victrix Gambit Tournament Controller
392 { MAKE_CONTROLLER_ID( 0x0e6f, 0x02d9 ), k_eControllerType_XBoxOneController, "PDP Xbox Series X Midnight Blue" }, // PDP Xbox Series X Midnight Blue
393 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0063 ), k_eControllerType_XBoxOneController, NULL }, // Hori Real Arcade Pro Hayabusa (USA) Xbox One
394 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0067 ), k_eControllerType_XBoxOneController, NULL }, // HORIPAD ONE
395 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0078 ), k_eControllerType_XBoxOneController, NULL }, // Hori Real Arcade Pro V Kai Xbox One
396 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00c5 ), k_eControllerType_XBoxOneController, NULL }, // HORI Fighting Commander
397 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0150 ), k_eControllerType_XBoxOneController, NULL }, // HORI Fighting Commander OCTA for Xbox Series X
398 { MAKE_CONTROLLER_ID( 0x10f5, 0x7009 ), k_eControllerType_XBoxOneController, NULL }, // Turtle Beach Recon Controller
399 { MAKE_CONTROLLER_ID( 0x10f5, 0x7013 ), k_eControllerType_XBoxOneController, NULL }, // Turtle Beach REACT-R
400 { MAKE_CONTROLLER_ID( 0x1532, 0x0a00 ), k_eControllerType_XBoxOneController, NULL }, // Razer Atrox Arcade Stick
401 { MAKE_CONTROLLER_ID( 0x1532, 0x0a03 ), k_eControllerType_XBoxOneController, NULL }, // Razer Wildcat
402 { MAKE_CONTROLLER_ID( 0x1532, 0x0a14 ), k_eControllerType_XBoxOneController, NULL }, // Razer Wolverine Ultimate
403 { MAKE_CONTROLLER_ID( 0x1532, 0x0a15 ), k_eControllerType_XBoxOneController, NULL }, // Razer Wolverine Tournament Edition
404 { MAKE_CONTROLLER_ID( 0x20d6, 0x2001 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Black Inline
405 { MAKE_CONTROLLER_ID( 0x20d6, 0x2002 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Gray/White Inline
406 { MAKE_CONTROLLER_ID( 0x20d6, 0x2003 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Green Inline
407 { MAKE_CONTROLLER_ID( 0x20d6, 0x2004 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Pink inline
408 { MAKE_CONTROLLER_ID( 0x20d6, 0x2005 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X Wired Controller Core - Black
409 { MAKE_CONTROLLER_ID( 0x20d6, 0x2006 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X Wired Controller Core - White
410 { MAKE_CONTROLLER_ID( 0x20d6, 0x2009 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Red inline
411 { MAKE_CONTROLLER_ID( 0x20d6, 0x200a ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Blue inline
412 { MAKE_CONTROLLER_ID( 0x20d6, 0x200b ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Camo Metallic Red
413 { MAKE_CONTROLLER_ID( 0x20d6, 0x200c ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Camo Metallic Blue
414 { MAKE_CONTROLLER_ID( 0x20d6, 0x200d ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Seafoam Fade
415 { MAKE_CONTROLLER_ID( 0x20d6, 0x200e ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Midnight Blue
416 { MAKE_CONTROLLER_ID( 0x20d6, 0x200f ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Soldier Green
417 { MAKE_CONTROLLER_ID( 0x20d6, 0x2011 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired - Metallic Ice
418 { MAKE_CONTROLLER_ID( 0x20d6, 0x2012 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X Cuphead EnWired Controller - Mugman
419 { MAKE_CONTROLLER_ID( 0x20d6, 0x2015 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Blue Hint
420 { MAKE_CONTROLLER_ID( 0x20d6, 0x2016 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller - Green Hint
421 { MAKE_CONTROLLER_ID( 0x20d6, 0x2017 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Cntroller - Arctic Camo
422 { MAKE_CONTROLLER_ID( 0x20d6, 0x2018 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Arc Lightning
423 { MAKE_CONTROLLER_ID( 0x20d6, 0x2019 ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Royal Purple
424 { MAKE_CONTROLLER_ID( 0x20d6, 0x201a ), k_eControllerType_XBoxOneController, "PowerA Xbox Series X Controller" }, // PowerA Xbox Series X EnWired Controller Nebula
425 { MAKE_CONTROLLER_ID( 0x20d6, 0x4001 ), k_eControllerType_XBoxOneController, "PowerA Fusion Pro 2 Controller" }, // PowerA Fusion Pro 2 Wired Controller (Xbox Series X style)
426 { MAKE_CONTROLLER_ID( 0x20d6, 0x4002 ), k_eControllerType_XBoxOneController, "PowerA Spectra Infinity Controller" }, // PowerA Spectra Infinity Wired Controller (Xbox Series X style)
427 { MAKE_CONTROLLER_ID( 0x20d6, 0x890b ), k_eControllerType_XBoxOneController, NULL }, // PowerA MOGA XP-Ultra Controller (Xbox Series X style)
428 { MAKE_CONTROLLER_ID( 0x24c6, 0x541a ), k_eControllerType_XBoxOneController, NULL }, // PowerA Xbox One Mini Wired Controller
429 { MAKE_CONTROLLER_ID( 0x24c6, 0x542a ), k_eControllerType_XBoxOneController, NULL }, // Xbox ONE spectra
430 { MAKE_CONTROLLER_ID( 0x24c6, 0x543a ), k_eControllerType_XBoxOneController, "PowerA Xbox One Controller" }, // PowerA Xbox ONE liquid metal controller
431 { MAKE_CONTROLLER_ID( 0x24c6, 0x551a ), k_eControllerType_XBoxOneController, NULL }, // PowerA FUSION Pro Controller
432 { MAKE_CONTROLLER_ID( 0x24c6, 0x561a ), k_eControllerType_XBoxOneController, NULL }, // PowerA FUSION Controller
433 { MAKE_CONTROLLER_ID( 0x24c6, 0x581a ), k_eControllerType_XBoxOneController, NULL }, // BDA XB1 Classic Controller
434 { MAKE_CONTROLLER_ID( 0x24c6, 0x591a ), k_eControllerType_XBoxOneController, NULL }, // PowerA FUSION Pro Controller
435 { MAKE_CONTROLLER_ID( 0x24c6, 0x592a ), k_eControllerType_XBoxOneController, NULL }, // BDA XB1 Spectra Pro
436 { MAKE_CONTROLLER_ID( 0x24c6, 0x791a ), k_eControllerType_XBoxOneController, NULL }, // PowerA Fusion Fight Pad
437 { MAKE_CONTROLLER_ID( 0x2dc8, 0x2002 ), k_eControllerType_XBoxOneController, NULL }, // 8BitDo Ultimate Wired Controller for Xbox
438 { MAKE_CONTROLLER_ID( 0x2dc8, 0x3106 ), k_eControllerType_XBoxOneController, NULL }, // 8Bitdo Ultimate Wired Controller. Windows, Android, Switch.
439 { MAKE_CONTROLLER_ID( 0x2e24, 0x0652 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin Duke
440 { MAKE_CONTROLLER_ID( 0x2e24, 0x1618 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin Duke
441 { MAKE_CONTROLLER_ID( 0x2e24, 0x1688 ), k_eControllerType_XBoxOneController, NULL }, // Hyperkin X91
442 { MAKE_CONTROLLER_ID( 0x146b, 0x0611 ), k_eControllerType_XBoxOneController, NULL }, // Xbox Controller Mode for NACON Revolution 3
443
444 // These have been added via Minidump for unrecognized Xinput controller assert
445 { MAKE_CONTROLLER_ID( 0x0000, 0x0000 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
446 { MAKE_CONTROLLER_ID( 0x045e, 0x02a2 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller - Microsoft VID
447 { MAKE_CONTROLLER_ID( 0x0e6f, 0x1414 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
448 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0159 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
449 { MAKE_CONTROLLER_ID( 0x24c6, 0xfaff ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
450 { MAKE_CONTROLLER_ID( 0x0f0d, 0x006d ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
451 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00a4 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
452 { MAKE_CONTROLLER_ID( 0x0079, 0x1832 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
453 { MAKE_CONTROLLER_ID( 0x0079, 0x187f ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
454 { MAKE_CONTROLLER_ID( 0x0079, 0x1883 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
455 { MAKE_CONTROLLER_ID( 0x03eb, 0xff01 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
456 { MAKE_CONTROLLER_ID( 0x0c12, 0x0ef8 ), k_eControllerType_XBox360Controller, NULL }, // Homemade fightstick based on brook pcb (with XInput driver??)
457 { MAKE_CONTROLLER_ID( 0x046d, 0x1000 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
458 { MAKE_CONTROLLER_ID( 0x11ff, 0x0511 ), k_eControllerType_XBox360Controller, NULL }, // PXN V900
459 { MAKE_CONTROLLER_ID( 0x1345, 0x6006 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
460
461 { MAKE_CONTROLLER_ID( 0x056e, 0x2012 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
462 { MAKE_CONTROLLER_ID( 0x146b, 0x0602 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
463 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00ae ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
464 { MAKE_CONTROLLER_ID( 0x046d, 0x0401 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput
465 { MAKE_CONTROLLER_ID( 0x046d, 0x0301 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput
466 { MAKE_CONTROLLER_ID( 0x046d, 0xcaa3 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput
467 { MAKE_CONTROLLER_ID( 0x046d, 0xc261 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput
468 { MAKE_CONTROLLER_ID( 0x046d, 0x0291 ), k_eControllerType_XBox360Controller, NULL }, // logitech xinput
469 { MAKE_CONTROLLER_ID( 0x0079, 0x18d3 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
470 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00b1 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
471 { MAKE_CONTROLLER_ID( 0x0001, 0x0001 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
472 { MAKE_CONTROLLER_ID( 0x0079, 0x188e ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
473 { MAKE_CONTROLLER_ID( 0x0079, 0x187c ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
474 { MAKE_CONTROLLER_ID( 0x0079, 0x189c ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
475 { MAKE_CONTROLLER_ID( 0x0079, 0x1874 ), k_eControllerType_XBox360Controller, NULL }, // Unknown Controller
476
477 { MAKE_CONTROLLER_ID( 0x2f24, 0x0050 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
478 { MAKE_CONTROLLER_ID( 0x2f24, 0x2e ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
479 { MAKE_CONTROLLER_ID( 0x2f24, 0x91 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
480 { MAKE_CONTROLLER_ID( 0x1430, 0x719 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
481 { MAKE_CONTROLLER_ID( 0xf0d, 0xed ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
482 { MAKE_CONTROLLER_ID( 0xf0d, 0xc0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
483 { MAKE_CONTROLLER_ID( 0xe6f, 0x152 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
484 { MAKE_CONTROLLER_ID( 0xe6f, 0x2a7 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
485 { MAKE_CONTROLLER_ID( 0x46d, 0x1007 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
486 { MAKE_CONTROLLER_ID( 0xe6f, 0x2b8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
487 { MAKE_CONTROLLER_ID( 0xe6f, 0x2a8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
488 { MAKE_CONTROLLER_ID( 0x79, 0x18a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
489
490 // Added from Minidumps 10-9-19
491 { MAKE_CONTROLLER_ID( 0x0, 0x6686 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
492 { MAKE_CONTROLLER_ID( 0x12ab, 0x304 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
493 { MAKE_CONTROLLER_ID( 0x1430, 0x291 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
494 { MAKE_CONTROLLER_ID( 0x1430, 0x2a9 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
495 { MAKE_CONTROLLER_ID( 0x1430, 0x70b ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
496 { MAKE_CONTROLLER_ID( 0x1bad, 0x28e ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
497 { MAKE_CONTROLLER_ID( 0x1bad, 0x2a0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
498 { MAKE_CONTROLLER_ID( 0x1bad, 0x5500 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
499 { MAKE_CONTROLLER_ID( 0x20ab, 0x55ef ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
500 { MAKE_CONTROLLER_ID( 0x24c6, 0x5509 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
501 { MAKE_CONTROLLER_ID( 0x2516, 0x69 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
502 { MAKE_CONTROLLER_ID( 0x25b1, 0x360 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
503 { MAKE_CONTROLLER_ID( 0x2c22, 0x2203 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
504 { MAKE_CONTROLLER_ID( 0x2f24, 0x11 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
505 { MAKE_CONTROLLER_ID( 0x2f24, 0x53 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
506 { MAKE_CONTROLLER_ID( 0x2f24, 0xb7 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
507 { MAKE_CONTROLLER_ID( 0x46d, 0x0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
508 { MAKE_CONTROLLER_ID( 0x46d, 0x1004 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
509 { MAKE_CONTROLLER_ID( 0x46d, 0x1008 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
510 { MAKE_CONTROLLER_ID( 0x46d, 0xf301 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
511 { MAKE_CONTROLLER_ID( 0x738, 0x2a0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
512 { MAKE_CONTROLLER_ID( 0x738, 0x7263 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
513 { MAKE_CONTROLLER_ID( 0x738, 0xb738 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
514 { MAKE_CONTROLLER_ID( 0x738, 0xcb29 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
515 { MAKE_CONTROLLER_ID( 0x738, 0xf401 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
516 { MAKE_CONTROLLER_ID( 0x79, 0x18c2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
517 { MAKE_CONTROLLER_ID( 0x79, 0x18c8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
518 { MAKE_CONTROLLER_ID( 0x79, 0x18cf ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
519 { MAKE_CONTROLLER_ID( 0xc12, 0xe17 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
520 { MAKE_CONTROLLER_ID( 0xc12, 0xe1c ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
521 { MAKE_CONTROLLER_ID( 0xc12, 0xe22 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
522 { MAKE_CONTROLLER_ID( 0xc12, 0xe30 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
523 { MAKE_CONTROLLER_ID( 0xd2d2, 0xd2d2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
524 { MAKE_CONTROLLER_ID( 0xd62, 0x9a1a ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
525 { MAKE_CONTROLLER_ID( 0xd62, 0x9a1b ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
526 { MAKE_CONTROLLER_ID( 0xe00, 0xe00 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
527 { MAKE_CONTROLLER_ID( 0xe6f, 0x12a ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
528 { MAKE_CONTROLLER_ID( 0xe6f, 0x2a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
529 { MAKE_CONTROLLER_ID( 0xe6f, 0x2a2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
530 { MAKE_CONTROLLER_ID( 0xe6f, 0x2a5 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
531 { MAKE_CONTROLLER_ID( 0xe6f, 0x2b2 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
532 { MAKE_CONTROLLER_ID( 0xe6f, 0x2bd ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
533 { MAKE_CONTROLLER_ID( 0xe6f, 0x2bf ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
534 { MAKE_CONTROLLER_ID( 0xe6f, 0x2c0 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
535 { MAKE_CONTROLLER_ID( 0xe6f, 0x2c6 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
536 { MAKE_CONTROLLER_ID( 0xf0d, 0x97 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
537 { MAKE_CONTROLLER_ID( 0xf0d, 0xba ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
538 { MAKE_CONTROLLER_ID( 0xf0d, 0xd8 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
539 { MAKE_CONTROLLER_ID( 0xfff, 0x2a1 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
540 { MAKE_CONTROLLER_ID( 0x45e, 0x867 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
541 // Added 12-17-2020
542 { MAKE_CONTROLLER_ID( 0x16d0, 0xf3f ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
543 { MAKE_CONTROLLER_ID( 0x2f24, 0x8f ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
544 { MAKE_CONTROLLER_ID( 0xe6f, 0xf501 ), k_eControllerType_XBoxOneController, NULL }, // Unknown Controller
545
546 //{ MAKE_CONTROLLER_ID( 0x1949, 0x0402 ), /*android*/, NULL }, // Unknown Controller
547
548 { MAKE_CONTROLLER_ID( 0x05ac, 0x0001 ), k_eControllerType_AppleController, NULL }, // MFI Extended Gamepad (generic entry for iOS/tvOS)
549 { MAKE_CONTROLLER_ID( 0x05ac, 0x0002 ), k_eControllerType_AppleController, NULL }, // MFI Standard Gamepad (generic entry for iOS/tvOS)
550
551 { MAKE_CONTROLLER_ID( 0x057e, 0x2006 ), k_eControllerType_SwitchJoyConLeft, NULL }, // Nintendo Switch Joy-Con (Left)
552 { MAKE_CONTROLLER_ID( 0x057e, 0x2007 ), k_eControllerType_SwitchJoyConRight, NULL }, // Nintendo Switch Joy-Con (Right)
553 { MAKE_CONTROLLER_ID( 0x057e, 0x2008 ), k_eControllerType_SwitchJoyConPair, NULL }, // Nintendo Switch Joy-Con (Left+Right Combined)
554
555 // This same controller ID is spoofed by many 3rd-party Switch controllers.
556 // The ones we currently know of are:
557 // * Any 8bitdo controller with Switch support
558 // * ORTZ Gaming Wireless Pro Controller
559 // * ZhiXu Gamepad Wireless
560 // * Sunwaytek Wireless Motion Controller for Nintendo Switch
561 { MAKE_CONTROLLER_ID( 0x057e, 0x2009 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Switch Pro Controller
562 //{ MAKE_CONTROLLER_ID( 0x057e, 0x2017 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SNES Controller
563 //{ MAKE_CONTROLLER_ID( 0x057e, 0x2019 ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online N64 Controller
564 //{ MAKE_CONTROLLER_ID( 0x057e, 0x201e ), k_eControllerType_SwitchProController, NULL }, // Nintendo Online SEGA Genesis Controller
565
566 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00c1 ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORIPAD for Nintendo Switch
567 { MAKE_CONTROLLER_ID( 0x0f0d, 0x0092 ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Pokken Tournament DX Pro Pad
568 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00f6 ), k_eControllerType_SwitchProController, NULL }, // HORI Wireless Switch Pad
569 // The HORIPAD S, which comes in multiple styles:
570 // - NSW-108, classic GameCube controller
571 // - NSW-244, Fighting Commander arcade pad
572 // - NSW-278, Hori Pad Mini gamepad
573 // - NSW-326, HORIPAD FPS for Nintendo Switch
574 //
575 // The first two, at least, shouldn't have their buttons remapped, and since we
576 // can't tell which model we're actually using, we won't do any button remapping
577 // for any of them.
578 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00dc ), k_eControllerType_XInputSwitchController, NULL }, // HORIPAD S - Looks like a Switch controller but uses the Xbox 360 controller protocol, there is also a version of this that looks like a GameCube controller
579 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0180 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Faceoff Wired Pro Controller for Nintendo Switch
580 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0181 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Faceoff Deluxe Wired Pro Controller for Nintendo Switch
581 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0184 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Faceoff Wired Deluxe+ Audio Controller
582 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0185 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Wired Fight Pad Pro for Nintendo Switch
583 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0186 ), k_eControllerType_SwitchProController, NULL }, // PDP Afterglow Wireless Switch Controller - working gyro. USB is for charging only. Many later "Wireless" line devices w/ gyro also use this vid/pid
584 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0187 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Rockcandy Wired Controller
585 { MAKE_CONTROLLER_ID( 0x0e6f, 0x0188 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Afterglow Wired Deluxe+ Audio Controller
586 { MAKE_CONTROLLER_ID( 0x0f0d, 0x00aa ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Real Arcade Pro V Hayabusa in Switch Mode
587 { MAKE_CONTROLLER_ID( 0x20d6, 0xa711 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Wired Controller Plus/PowerA Wired Controller Nintendo GameCube Style
588 { MAKE_CONTROLLER_ID( 0x20d6, 0xa712 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Fight Pad
589 { MAKE_CONTROLLER_ID( 0x20d6, 0xa713 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Super Mario Controller
590 { MAKE_CONTROLLER_ID( 0x20d6, 0xa714 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Spectra Controller
591 { MAKE_CONTROLLER_ID( 0x20d6, 0xa715 ), k_eControllerType_SwitchInputOnlyController, NULL }, // Power A Fusion Wireless Arcade Stick (USB Mode) Over BT is shows up as 057e 2009
592 { MAKE_CONTROLLER_ID( 0x20d6, 0xa716 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Pro Controller - USB requires toggling switch on back of device
593 { MAKE_CONTROLLER_ID( 0x20d6, 0xa718 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Nano Wired Controller
594 { MAKE_CONTROLLER_ID( 0x33dd, 0x0001 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Black
595 { MAKE_CONTROLLER_ID( 0x33dd, 0x0002 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch ??
596 { MAKE_CONTROLLER_ID( 0x33dd, 0x0003 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Red
597
598 // Valve products
599 { MAKE_CONTROLLER_ID( 0x0000, 0x11fb ), k_eControllerType_MobileTouch, NULL }, // Streaming mobile touch virtual controls
600 { MAKE_CONTROLLER_ID( 0x28de, 0x1101 ), k_eControllerType_SteamController, NULL }, // Valve Legacy Steam Controller (CHELL)
601 { MAKE_CONTROLLER_ID( 0x28de, 0x1102 ), k_eControllerType_SteamController, NULL }, // Valve wired Steam Controller (D0G)
602 { MAKE_CONTROLLER_ID( 0x28de, 0x1105 ), k_eControllerType_SteamController, NULL }, // Valve Bluetooth Steam Controller (D0G)
603 { MAKE_CONTROLLER_ID( 0x28de, 0x1106 ), k_eControllerType_SteamController, NULL }, // Valve Bluetooth Steam Controller (D0G)
604 { MAKE_CONTROLLER_ID( 0x28de, 0x11ff ), k_eControllerType_UnknownNonSteamController, NULL }, // Steam Virtual Gamepad
605 { MAKE_CONTROLLER_ID( 0x28de, 0x1142 ), k_eControllerType_SteamController, NULL }, // Valve wireless Steam Controller
606 { MAKE_CONTROLLER_ID( 0x28de, 0x1201 ), k_eControllerType_SteamControllerV2, NULL }, // Valve wired Steam Controller (HEADCRAB)
607 { MAKE_CONTROLLER_ID( 0x28de, 0x1202 ), k_eControllerType_SteamControllerV2, NULL }, // Valve Bluetooth Steam Controller (HEADCRAB)
608 { MAKE_CONTROLLER_ID( 0x28de, 0x1205 ), k_eControllerType_SteamControllerNeptune, NULL }, // Valve Steam Deck Builtin Controller
609};
diff --git a/contrib/SDL-3.2.8/src/joystick/controller_type.c b/contrib/SDL-3.2.8/src/joystick/controller_type.c
new file mode 100644
index 0000000..f179d27
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/controller_type.c
@@ -0,0 +1,140 @@
1/*
2 Copyright (C) Valve Corporation
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, subject to the following restrictions:
11
12 1. The origin of this software must not be misrepresented; you must not
13 claim that you wrote the original software. If you use this software
14 in a product, an acknowledgment in the product documentation would be
15 appreciated but is not required.
16 2. Altered source versions must be plainly marked as such, and must not be
17 misrepresented as being the original software.
18 3. This notice may not be removed or altered from any source distribution.
19*/
20#include "SDL_internal.h"
21
22
23#include "controller_type.h"
24#include "controller_list.h"
25
26
27static const char *GetControllerTypeOverride( int nVID, int nPID )
28{
29 const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLERTYPE);
30 if (hint) {
31 char key[32];
32 const char *spot = NULL;
33
34 SDL_snprintf(key, sizeof(key), "0x%.4x/0x%.4x=", nVID, nPID);
35 spot = SDL_strstr(hint, key);
36 if (!spot) {
37 SDL_snprintf(key, sizeof(key), "0x%.4X/0x%.4X=", nVID, nPID);
38 spot = SDL_strstr(hint, key);
39 }
40 if (spot) {
41 spot += SDL_strlen(key);
42 if (SDL_strncmp(spot, "k_eControllerType_", 18) == 0) {
43 spot += 18;
44 }
45 return spot;
46 }
47 }
48 return NULL;
49}
50
51
52EControllerType GuessControllerType( int nVID, int nPID )
53{
54#if 0//def _DEBUG
55 // Verify that there are no duplicates in the controller list
56 // If the list were sorted, we could do this much more efficiently, as well as improve lookup speed.
57 static bool s_bCheckedForDuplicates;
58 if ( !s_bCheckedForDuplicates )
59 {
60 s_bCheckedForDuplicates = true;
61 int i, j;
62 for ( i = 0; i < sizeof( arrControllers ) / sizeof( arrControllers[ 0 ] ); ++i )
63 {
64 for ( j = i + 1; j < sizeof( arrControllers ) / sizeof( arrControllers[ 0 ] ); ++j )
65 {
66 if ( arrControllers[ i ].m_unDeviceID == arrControllers[ j ].m_unDeviceID )
67 {
68 Log( "Duplicate controller entry found for VID 0x%.4x PID 0x%.4x\n", ( arrControllers[ i ].m_unDeviceID >> 16 ), arrControllers[ i ].m_unDeviceID & 0xFFFF );
69 }
70 }
71 }
72 }
73#endif // _DEBUG
74
75 unsigned int unDeviceID = MAKE_CONTROLLER_ID( nVID, nPID );
76 int iIndex;
77
78 const char *pszOverride = GetControllerTypeOverride( nVID, nPID );
79 if ( pszOverride )
80 {
81 if ( SDL_strncasecmp( pszOverride, "Xbox360", 7 ) == 0 )
82 {
83 return k_eControllerType_XBox360Controller;
84 }
85 if ( SDL_strncasecmp( pszOverride, "XboxOne", 7 ) == 0 )
86 {
87 return k_eControllerType_XBoxOneController;
88 }
89 if ( SDL_strncasecmp( pszOverride, "PS3", 3 ) == 0 )
90 {
91 return k_eControllerType_PS3Controller;
92 }
93 if ( SDL_strncasecmp( pszOverride, "PS4", 3 ) == 0 )
94 {
95 return k_eControllerType_PS4Controller;
96 }
97 if ( SDL_strncasecmp( pszOverride, "PS5", 3 ) == 0 )
98 {
99 return k_eControllerType_PS5Controller;
100 }
101 if ( SDL_strncasecmp( pszOverride, "SwitchPro", 9 ) == 0 )
102 {
103 return k_eControllerType_SwitchProController;
104 }
105 if ( SDL_strncasecmp( pszOverride, "Steam", 5 ) == 0 )
106 {
107 return k_eControllerType_SteamController;
108 }
109 return k_eControllerType_UnknownNonSteamController;
110 }
111
112 for ( iIndex = 0; iIndex < sizeof( arrControllers ) / sizeof( arrControllers[0] ); ++iIndex )
113 {
114 if ( unDeviceID == arrControllers[ iIndex ].m_unDeviceID )
115 {
116 return arrControllers[ iIndex ].m_eControllerType;
117 }
118 }
119
120 return k_eControllerType_UnknownNonSteamController;
121
122}
123
124const char *GuessControllerName( int nVID, int nPID )
125{
126 unsigned int unDeviceID = MAKE_CONTROLLER_ID( nVID, nPID );
127 int iIndex;
128 for ( iIndex = 0; iIndex < sizeof( arrControllers ) / sizeof( arrControllers[0] ); ++iIndex )
129 {
130 if ( unDeviceID == arrControllers[ iIndex ].m_unDeviceID )
131 {
132 return arrControllers[ iIndex ].m_pszName;
133 }
134 }
135
136 return NULL;
137
138}
139
140#undef MAKE_CONTROLLER_ID
diff --git a/contrib/SDL-3.2.8/src/joystick/controller_type.h b/contrib/SDL-3.2.8/src/joystick/controller_type.h
new file mode 100644
index 0000000..155c8ad
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/controller_type.h
@@ -0,0 +1,78 @@
1/*
2 Copyright (C) Valve Corporation
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, subject to the following restrictions:
11
12 1. The origin of this software must not be misrepresented; you must not
13 claim that you wrote the original software. If you use this software
14 in a product, an acknowledgment in the product documentation would be
15 appreciated but is not required.
16 2. Altered source versions must be plainly marked as such, and must not be
17 misrepresented as being the original software.
18 3. This notice may not be removed or altered from any source distribution.
19*/
20
21#ifndef CONTROLLER_TYPE_H
22#define CONTROLLER_TYPE_H
23#ifdef _WIN32
24#pragma once
25#endif
26
27//-----------------------------------------------------------------------------
28// Purpose: Steam Controller models
29// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN A DATABASE
30//-----------------------------------------------------------------------------
31typedef enum
32{
33 k_eControllerType_None = -1,
34 k_eControllerType_Unknown = 0,
35
36 // Steam Controllers
37 k_eControllerType_UnknownSteamController = 1,
38 k_eControllerType_SteamController = 2,
39 k_eControllerType_SteamControllerV2 = 3,
40 k_eControllerType_SteamControllerNeptune = 4,
41
42 // Other Controllers
43 k_eControllerType_UnknownNonSteamController = 30,
44 k_eControllerType_XBox360Controller = 31,
45 k_eControllerType_XBoxOneController = 32,
46 k_eControllerType_PS3Controller = 33,
47 k_eControllerType_PS4Controller = 34,
48 k_eControllerType_WiiController = 35,
49 k_eControllerType_AppleController = 36,
50 k_eControllerType_AndroidController = 37,
51 k_eControllerType_SwitchProController = 38,
52 k_eControllerType_SwitchJoyConLeft = 39,
53 k_eControllerType_SwitchJoyConRight = 40,
54 k_eControllerType_SwitchJoyConPair = 41,
55 k_eControllerType_SwitchInputOnlyController = 42,
56 k_eControllerType_MobileTouch = 43,
57 k_eControllerType_XInputSwitchController = 44, // Client-side only, used to mark Nintendo Switch style controllers as using XInput instead of the Nintendo Switch protocol
58 k_eControllerType_PS5Controller = 45,
59 k_eControllerType_XInputPS4Controller = 46, // Client-side only, used to mark DualShock 4 style controllers using XInput instead of the DualShock 4 controller protocol
60 k_eControllerType_LastController, // Don't add game controllers below this enumeration - this enumeration can change value
61
62 // Keyboards and Mice
63 k_eControllertype_GenericKeyboard = 400,
64 k_eControllertype_GenericMouse = 800,
65} EControllerType;
66
67typedef struct
68{
69 unsigned int m_unDeviceID;
70 EControllerType m_eControllerType;
71 const char *m_pszName;
72} ControllerDescription_t;
73
74
75extern EControllerType GuessControllerType( int nVID, int nPID );
76extern const char *GuessControllerName( int nVID, int nPID );
77
78#endif // CONTROLLER_TYPE_H
diff --git a/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick.c b/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick.c
new file mode 100644
index 0000000..9327276
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick.c
@@ -0,0 +1,1089 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_IOKIT
24
25#include "../SDL_sysjoystick.h"
26#include "../SDL_joystick_c.h"
27#include "SDL_iokitjoystick_c.h"
28#include "../hidapi/SDL_hidapijoystick_c.h"
29#include "../../haptic/darwin/SDL_syshaptic_c.h" // For haptic hot plugging
30
31#define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
32
33#define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF)
34
35// The base object of the HID Manager API
36static IOHIDManagerRef hidman = NULL;
37
38// Linked list of all available devices
39static recDevice *gpDeviceList = NULL;
40
41void FreeRumbleEffectData(FFEFFECT *effect)
42{
43 if (!effect) {
44 return;
45 }
46 SDL_free(effect->rgdwAxes);
47 SDL_free(effect->rglDirection);
48 SDL_free(effect->lpvTypeSpecificParams);
49 SDL_free(effect);
50}
51
52FFEFFECT *CreateRumbleEffectData(Sint16 magnitude)
53{
54 FFEFFECT *effect;
55 FFPERIODIC *periodic;
56
57 // Create the effect
58 effect = (FFEFFECT *)SDL_calloc(1, sizeof(*effect));
59 if (!effect) {
60 return NULL;
61 }
62 effect->dwSize = sizeof(*effect);
63 effect->dwGain = 10000;
64 effect->dwFlags = FFEFF_OBJECTOFFSETS;
65 effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000; // In microseconds.
66 effect->dwTriggerButton = FFEB_NOTRIGGER;
67
68 effect->cAxes = 2;
69 effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD));
70 if (!effect->rgdwAxes) {
71 FreeRumbleEffectData(effect);
72 return NULL;
73 }
74
75 effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG));
76 if (!effect->rglDirection) {
77 FreeRumbleEffectData(effect);
78 return NULL;
79 }
80 effect->dwFlags |= FFEFF_CARTESIAN;
81
82 periodic = (FFPERIODIC *)SDL_calloc(1, sizeof(*periodic));
83 if (!periodic) {
84 FreeRumbleEffectData(effect);
85 return NULL;
86 }
87 periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
88 periodic->dwPeriod = 1000000;
89
90 effect->cbTypeSpecificParams = sizeof(*periodic);
91 effect->lpvTypeSpecificParams = periodic;
92
93 return effect;
94}
95
96static recDevice *GetDeviceForIndex(int device_index)
97{
98 recDevice *device = gpDeviceList;
99 while (device) {
100 if (!device->removed) {
101 if (device_index == 0) {
102 break;
103 }
104
105 --device_index;
106 }
107 device = device->pNext;
108 }
109 return device;
110}
111
112static void FreeElementList(recElement *pElement)
113{
114 while (pElement) {
115 recElement *pElementNext = pElement->pNext;
116 SDL_free(pElement);
117 pElement = pElementNext;
118 }
119}
120
121static recDevice *FreeDevice(recDevice *removeDevice)
122{
123 recDevice *pDeviceNext = NULL;
124 if (removeDevice) {
125 if (removeDevice->deviceRef) {
126 if (removeDevice->runLoopAttached) {
127 /* Calling IOHIDDeviceUnscheduleFromRunLoop without a prior,
128 * paired call to IOHIDDeviceScheduleWithRunLoop can lead
129 * to crashes in MacOS 10.14.x and earlier. This doesn't
130 * appear to be a problem in MacOS 10.15.x, but we'll
131 * do it anyways. (Part-of fix for Bug 5034)
132 */
133 IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
134 }
135 CFRelease(removeDevice->deviceRef);
136 removeDevice->deviceRef = NULL;
137 }
138
139 /* clear out any reference to removeDevice from an associated,
140 * live instance of SDL_Joystick (Part-of fix for Bug 5034)
141 */
142 SDL_LockJoysticks();
143 if (removeDevice->joystick) {
144 removeDevice->joystick->hwdata = NULL;
145 }
146 SDL_UnlockJoysticks();
147
148 // save next device prior to disposing of this device
149 pDeviceNext = removeDevice->pNext;
150
151 if (gpDeviceList == removeDevice) {
152 gpDeviceList = pDeviceNext;
153 } else if (gpDeviceList) {
154 recDevice *device;
155
156 for (device = gpDeviceList; device; device = device->pNext) {
157 if (device->pNext == removeDevice) {
158 device->pNext = pDeviceNext;
159 break;
160 }
161 }
162 }
163 removeDevice->pNext = NULL;
164
165 // free element lists
166 FreeElementList(removeDevice->firstAxis);
167 FreeElementList(removeDevice->firstButton);
168 FreeElementList(removeDevice->firstHat);
169
170 SDL_free(removeDevice);
171 }
172 return pDeviceNext;
173}
174
175static bool GetHIDElementState(recDevice *pDevice, recElement *pElement, SInt32 *pValue)
176{
177 SInt32 value = 0;
178 bool result = false;
179
180 if (pDevice && pDevice->deviceRef && pElement) {
181 IOHIDValueRef valueRef;
182 if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
183 value = (SInt32)IOHIDValueGetIntegerValue(valueRef);
184
185 // record min and max for auto calibration
186 if (value < pElement->minReport) {
187 pElement->minReport = value;
188 }
189 if (value > pElement->maxReport) {
190 pElement->maxReport = value;
191 }
192 *pValue = value;
193
194 result = true;
195 }
196 }
197 return result;
198}
199
200static bool GetHIDScaledCalibratedState(recDevice *pDevice, recElement *pElement, SInt32 min, SInt32 max, SInt32 *pValue)
201{
202 const float deviceScale = max - min;
203 const float readScale = pElement->maxReport - pElement->minReport;
204 bool result = false;
205 if (GetHIDElementState(pDevice, pElement, pValue)) {
206 if (readScale == 0) {
207 result = true; // no scaling at all
208 } else {
209 *pValue = (Sint32)(((*pValue - pElement->minReport) * deviceScale / readScale) + min);
210 result = true;
211 }
212 }
213 return result;
214}
215
216static void JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
217{
218 recDevice *device = (recDevice *)ctx;
219 device->removed = true;
220 if (device->deviceRef) {
221 // deviceRef was invalidated due to the remove
222 CFRelease(device->deviceRef);
223 device->deviceRef = NULL;
224 }
225 if (device->ffeffect_ref) {
226 FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref);
227 device->ffeffect_ref = NULL;
228 }
229 if (device->ffeffect) {
230 FreeRumbleEffectData(device->ffeffect);
231 device->ffeffect = NULL;
232 }
233 if (device->ffdevice) {
234 FFReleaseDevice(device->ffdevice);
235 device->ffdevice = NULL;
236 device->ff_initialized = false;
237 }
238#ifdef SDL_HAPTIC_IOKIT
239 MacHaptic_MaybeRemoveDevice(device->ffservice);
240#endif
241
242 SDL_PrivateJoystickRemoved(device->instance_id);
243}
244
245static void AddHIDElement(const void *value, void *parameter);
246
247// Call AddHIDElement() on all elements in an array of IOHIDElementRefs
248static void AddHIDElements(CFArrayRef array, recDevice *pDevice)
249{
250 const CFRange range = { 0, CFArrayGetCount(array) };
251 CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
252}
253
254static bool ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem)
255{
256 while (listitem) {
257 if (listitem->cookie == cookie) {
258 return true;
259 }
260 listitem = listitem->pNext;
261 }
262 return false;
263}
264
265// See if we care about this HID element, and if so, note it in our recDevice.
266static void AddHIDElement(const void *value, void *parameter)
267{
268 recDevice *pDevice = (recDevice *)parameter;
269 IOHIDElementRef refElement = (IOHIDElementRef)value;
270 const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
271
272 if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
273 const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
274 const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
275 const uint32_t usage = IOHIDElementGetUsage(refElement);
276 recElement *element = NULL;
277 recElement **headElement = NULL;
278
279 // look at types of interest
280 switch (IOHIDElementGetType(refElement)) {
281 case kIOHIDElementTypeInput_Misc:
282 case kIOHIDElementTypeInput_Button:
283 case kIOHIDElementTypeInput_Axis:
284 {
285 switch (usagePage) { // only interested in kHIDPage_GenericDesktop and kHIDPage_Button
286 case kHIDPage_GenericDesktop:
287 switch (usage) {
288 case kHIDUsage_GD_X:
289 case kHIDUsage_GD_Y:
290 case kHIDUsage_GD_Z:
291 case kHIDUsage_GD_Rx:
292 case kHIDUsage_GD_Ry:
293 case kHIDUsage_GD_Rz:
294 case kHIDUsage_GD_Slider:
295 case kHIDUsage_GD_Dial:
296 case kHIDUsage_GD_Wheel:
297 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
298 element = (recElement *)SDL_calloc(1, sizeof(recElement));
299 if (element) {
300 pDevice->axes++;
301 headElement = &(pDevice->firstAxis);
302 }
303 }
304 break;
305
306 case kHIDUsage_GD_Hatswitch:
307 if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
308 element = (recElement *)SDL_calloc(1, sizeof(recElement));
309 if (element) {
310 pDevice->hats++;
311 headElement = &(pDevice->firstHat);
312 }
313 }
314 break;
315 case kHIDUsage_GD_DPadUp:
316 case kHIDUsage_GD_DPadDown:
317 case kHIDUsage_GD_DPadRight:
318 case kHIDUsage_GD_DPadLeft:
319 case kHIDUsage_GD_Start:
320 case kHIDUsage_GD_Select:
321 case kHIDUsage_GD_SystemMainMenu:
322 if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
323 element = (recElement *)SDL_calloc(1, sizeof(recElement));
324 if (element) {
325 pDevice->buttons++;
326 headElement = &(pDevice->firstButton);
327 }
328 }
329 break;
330 }
331 break;
332
333 case kHIDPage_Simulation:
334 switch (usage) {
335 case kHIDUsage_Sim_Rudder:
336 case kHIDUsage_Sim_Throttle:
337 case kHIDUsage_Sim_Accelerator:
338 case kHIDUsage_Sim_Brake:
339 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
340 element = (recElement *)SDL_calloc(1, sizeof(recElement));
341 if (element) {
342 pDevice->axes++;
343 headElement = &(pDevice->firstAxis);
344 }
345 }
346 break;
347
348 default:
349 break;
350 }
351 break;
352
353 case kHIDPage_Button:
354 case kHIDPage_Consumer: // e.g. 'pause' button on Steelseries MFi gamepads.
355 if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
356 element = (recElement *)SDL_calloc(1, sizeof(recElement));
357 if (element) {
358 pDevice->buttons++;
359 headElement = &(pDevice->firstButton);
360 }
361 }
362 break;
363
364 default:
365 break;
366 }
367 } break;
368
369 case kIOHIDElementTypeCollection:
370 {
371 CFArrayRef array = IOHIDElementGetChildren(refElement);
372 if (array) {
373 AddHIDElements(array, pDevice);
374 }
375 } break;
376
377 default:
378 break;
379 }
380
381 if (element && headElement) { // add to list
382 recElement *elementPrevious = NULL;
383 recElement *elementCurrent = *headElement;
384 while (elementCurrent && usage >= elementCurrent->usage) {
385 elementPrevious = elementCurrent;
386 elementCurrent = elementCurrent->pNext;
387 }
388 if (elementPrevious) {
389 elementPrevious->pNext = element;
390 } else {
391 *headElement = element;
392 }
393
394 element->elementRef = refElement;
395 element->usagePage = usagePage;
396 element->usage = usage;
397 element->pNext = elementCurrent;
398
399 element->minReport = element->min = (SInt32)IOHIDElementGetLogicalMin(refElement);
400 element->maxReport = element->max = (SInt32)IOHIDElementGetLogicalMax(refElement);
401 element->cookie = IOHIDElementGetCookie(refElement);
402
403 pDevice->elements++;
404 }
405 }
406}
407
408static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *product_string)
409{
410 int slot = -1;
411
412 if (vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX360_WIRED_CONTROLLER) {
413 // Gamepad name is "GamePad-N", where N is slot + 1
414 if (SDL_sscanf(product_string, "GamePad-%d", &slot) == 1) {
415 slot -= 1;
416 }
417 }
418 return slot;
419}
420
421static bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
422{
423 Sint32 vendor = 0;
424 Sint32 product = 0;
425 Sint32 version = 0;
426 char *name;
427 char manufacturer_string[256];
428 char product_string[256];
429 CFTypeRef refCF = NULL;
430 CFArrayRef array = NULL;
431
432 // get usage page and usage
433 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
434 if (refCF) {
435 CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
436 }
437 if (pDevice->usagePage != kHIDPage_GenericDesktop) {
438 return false; // Filter device list to non-keyboard/mouse stuff
439 }
440
441 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
442 if (refCF) {
443 CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
444 }
445
446 if ((pDevice->usage != kHIDUsage_GD_Joystick &&
447 pDevice->usage != kHIDUsage_GD_GamePad &&
448 pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
449 return false; // Filter device list to non-keyboard/mouse stuff
450 }
451
452 /* Make sure we retain the use of the IOKit-provided device-object,
453 lest the device get disconnected and we try to use it. (Fixes
454 SDL-Bugzilla #4961, aka. https://bugzilla.libsdl.org/show_bug.cgi?id=4961 )
455 */
456 CFRetain(hidDevice);
457
458 /* Now that we've CFRetain'ed the device-object (for our use), we'll
459 save the reference to it.
460 */
461 pDevice->deviceRef = hidDevice;
462
463 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
464 if (refCF) {
465 CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
466 }
467
468 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
469 if (refCF) {
470 CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
471 }
472
473 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
474 if (refCF) {
475 CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
476 }
477
478 if (SDL_IsJoystickXboxOne(vendor, product)) {
479 // We can't actually use this API for Xbox controllers
480 return false;
481 }
482
483 // get device name
484 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
485 if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string, sizeof(manufacturer_string), kCFStringEncodingUTF8))) {
486 manufacturer_string[0] = '\0';
487 }
488 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
489 if ((!refCF) || (!CFStringGetCString(refCF, product_string, sizeof(product_string), kCFStringEncodingUTF8))) {
490 product_string[0] = '\0';
491 }
492 name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);
493 if (name) {
494 SDL_strlcpy(pDevice->product, name, sizeof(pDevice->product));
495 SDL_free(name);
496 }
497
498 if (SDL_ShouldIgnoreJoystick(vendor, product, version, pDevice->product)) {
499 return false;
500 }
501
502 if (SDL_JoystickHandledByAnotherDriver(&SDL_DARWIN_JoystickDriver, vendor, product, version, pDevice->product)) {
503 return false;
504 }
505
506 pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, manufacturer_string, product_string, 0, 0);
507 pDevice->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot((Uint16)vendor, (Uint16)product, product_string);
508
509 array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
510 if (array) {
511 AddHIDElements(array, pDevice);
512 CFRelease(array);
513 }
514
515 return true;
516}
517
518static bool JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
519{
520 recDevice *i;
521
522#ifdef SDL_JOYSTICK_MFI
523 extern bool IOS_SupportedHIDDevice(IOHIDDeviceRef device);
524 if (IOS_SupportedHIDDevice(ioHIDDeviceObject)) {
525 return true;
526 }
527#endif
528
529 for (i = gpDeviceList; i; i = i->pNext) {
530 if (i->deviceRef == ioHIDDeviceObject) {
531 return true;
532 }
533 }
534 return false;
535}
536
537static void JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
538{
539 recDevice *device;
540 io_service_t ioservice;
541
542 if (res != kIOReturnSuccess) {
543 return;
544 }
545
546 if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
547 return; // IOKit sent us a duplicate.
548 }
549
550 device = (recDevice *)SDL_calloc(1, sizeof(recDevice));
551 if (!device) {
552 return;
553 }
554
555 if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
556 FreeDevice(device);
557 return; // not a device we care about, probably.
558 }
559
560 // Get notified when this device is disconnected.
561 IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
562 IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
563 device->runLoopAttached = true;
564
565 // Allocate an instance ID for this device
566 device->instance_id = SDL_GetNextObjectID();
567
568 // We have to do some storage of the io_service_t for SDL_OpenHapticFromJoystick
569 ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
570 if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
571 device->ffservice = ioservice;
572#ifdef SDL_HAPTIC_IOKIT
573 MacHaptic_MaybeAddDevice(ioservice);
574#endif
575 }
576
577 // Add device to the end of the list
578 if (!gpDeviceList) {
579 gpDeviceList = device;
580 } else {
581 recDevice *curdevice;
582
583 curdevice = gpDeviceList;
584 while (curdevice->pNext) {
585 curdevice = curdevice->pNext;
586 }
587 curdevice->pNext = device;
588 }
589
590 SDL_PrivateJoystickAdded(device->instance_id);
591}
592
593static bool ConfigHIDManager(CFArrayRef matchingArray)
594{
595 CFRunLoopRef runloop = CFRunLoopGetCurrent();
596
597 if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
598 return false;
599 }
600
601 IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
602 IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
603 IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
604
605 while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
606 // no-op. Callback fires once per existing device.
607 }
608
609 // future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now.
610
611 return true; // good to go.
612}
613
614static CFDictionaryRef CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
615{
616 CFDictionaryRef result = NULL;
617 CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
618 CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
619 const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) };
620 const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef };
621
622 if (pageNumRef && usageNumRef) {
623 result = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
624 }
625
626 if (pageNumRef) {
627 CFRelease(pageNumRef);
628 }
629 if (usageNumRef) {
630 CFRelease(usageNumRef);
631 }
632
633 if (!result) {
634 *okay = 0;
635 }
636
637 return result;
638}
639
640static bool CreateHIDManager(void)
641{
642 bool result = false;
643 int okay = 1;
644 const void *vals[] = {
645 (void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
646 (void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
647 (void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
648 };
649 const size_t numElements = SDL_arraysize(vals);
650 CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
651 size_t i;
652
653 for (i = 0; i < numElements; i++) {
654 if (vals[i]) {
655 CFRelease((CFTypeRef)vals[i]);
656 }
657 }
658
659 if (array) {
660 hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
661 if (hidman != NULL) {
662 result = ConfigHIDManager(array);
663 }
664 CFRelease(array);
665 }
666
667 return result;
668}
669
670static bool DARWIN_JoystickInit(void)
671{
672 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_IOKIT, true)) {
673 return true;
674 }
675
676 if (!CreateHIDManager()) {
677 return SDL_SetError("Joystick: Couldn't initialize HID Manager");
678 }
679
680 return true;
681}
682
683static int DARWIN_JoystickGetCount(void)
684{
685 recDevice *device = gpDeviceList;
686 int nJoySticks = 0;
687
688 while (device) {
689 if (!device->removed) {
690 nJoySticks++;
691 }
692 device = device->pNext;
693 }
694
695 return nJoySticks;
696}
697
698static void DARWIN_JoystickDetect(void)
699{
700 recDevice *device = gpDeviceList;
701 while (device) {
702 if (device->removed) {
703 device = FreeDevice(device);
704 } else {
705 device = device->pNext;
706 }
707 }
708
709 if (hidman) {
710 /* run this after the checks above so we don't set device->removed and delete the device before
711 DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */
712 while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
713 // no-op. Pending callbacks will fire in CFRunLoopRunInMode().
714 }
715 }
716}
717
718static bool DARWIN_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
719{
720 // We don't override any other drivers
721 return false;
722}
723
724static const char *DARWIN_JoystickGetDeviceName(int device_index)
725{
726 recDevice *device = GetDeviceForIndex(device_index);
727 return device ? device->product : "UNKNOWN";
728}
729
730static const char *DARWIN_JoystickGetDevicePath(int device_index)
731{
732 return NULL;
733}
734
735static int DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
736{
737 recDevice *device = GetDeviceForIndex(device_index);
738 return device ? device->steam_virtual_gamepad_slot : -1;
739}
740
741static int DARWIN_JoystickGetDevicePlayerIndex(int device_index)
742{
743 return -1;
744}
745
746static void DARWIN_JoystickSetDevicePlayerIndex(int device_index, int player_index)
747{
748}
749
750static SDL_GUID DARWIN_JoystickGetDeviceGUID(int device_index)
751{
752 recDevice *device = GetDeviceForIndex(device_index);
753 SDL_GUID guid;
754 if (device) {
755 guid = device->guid;
756 } else {
757 SDL_zero(guid);
758 }
759 return guid;
760}
761
762static SDL_JoystickID DARWIN_JoystickGetDeviceInstanceID(int device_index)
763{
764 recDevice *device = GetDeviceForIndex(device_index);
765 return device ? device->instance_id : 0;
766}
767
768static bool DARWIN_JoystickOpen(SDL_Joystick *joystick, int device_index)
769{
770 recDevice *device = GetDeviceForIndex(device_index);
771
772 joystick->hwdata = device;
773 device->joystick = joystick;
774 joystick->name = device->product;
775
776 joystick->naxes = device->axes;
777 joystick->nhats = device->hats;
778 joystick->nbuttons = device->buttons;
779
780 if (device->ffservice) {
781 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
782 }
783
784 return true;
785}
786
787/*
788 * Like strerror but for force feedback errors.
789 */
790static const char *FFStrError(unsigned int err)
791{
792 switch (err) {
793 case FFERR_DEVICEFULL:
794 return "device full";
795 // This should be valid, but for some reason isn't defined...
796 /* case FFERR_DEVICENOTREG:
797 return "device not registered"; */
798 case FFERR_DEVICEPAUSED:
799 return "device paused";
800 case FFERR_DEVICERELEASED:
801 return "device released";
802 case FFERR_EFFECTPLAYING:
803 return "effect playing";
804 case FFERR_EFFECTTYPEMISMATCH:
805 return "effect type mismatch";
806 case FFERR_EFFECTTYPENOTSUPPORTED:
807 return "effect type not supported";
808 case FFERR_GENERIC:
809 return "undetermined error";
810 case FFERR_HASEFFECTS:
811 return "device has effects";
812 case FFERR_INCOMPLETEEFFECT:
813 return "incomplete effect";
814 case FFERR_INTERNAL:
815 return "internal fault";
816 case FFERR_INVALIDDOWNLOADID:
817 return "invalid download id";
818 case FFERR_INVALIDPARAM:
819 return "invalid parameter";
820 case FFERR_MOREDATA:
821 return "more data";
822 case FFERR_NOINTERFACE:
823 return "interface not supported";
824 case FFERR_NOTDOWNLOADED:
825 return "effect is not downloaded";
826 case FFERR_NOTINITIALIZED:
827 return "object has not been initialized";
828 case FFERR_OUTOFMEMORY:
829 return "out of memory";
830 case FFERR_UNPLUGGED:
831 return "device is unplugged";
832 case FFERR_UNSUPPORTED:
833 return "function call unsupported";
834 case FFERR_UNSUPPORTEDAXIS:
835 return "axis unsupported";
836
837 default:
838 return "unknown error";
839 }
840}
841
842static bool DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude)
843{
844 HRESULT result;
845
846 if (!device->ffdevice) {
847 result = FFCreateDevice(device->ffservice, &device->ffdevice);
848 if (result != FF_OK) {
849 return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result));
850 }
851 }
852
853 // Reset and then enable actuators
854 result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET);
855 if (result != FF_OK) {
856 return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result));
857 }
858
859 result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON);
860 if (result != FF_OK) {
861 return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result));
862 }
863
864 // Create the effect
865 device->ffeffect = CreateRumbleEffectData(magnitude);
866 if (!device->ffeffect) {
867 return false;
868 }
869
870 result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID,
871 device->ffeffect, &device->ffeffect_ref);
872 if (result != FF_OK) {
873 return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result));
874 }
875 return true;
876}
877
878static bool DARWIN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
879{
880 HRESULT result;
881 recDevice *device = joystick->hwdata;
882
883 // Scale and average the two rumble strengths
884 Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
885
886 if (!device) {
887 return SDL_SetError("Rumble failed, device disconnected");
888 }
889
890 if (!device->ffservice) {
891 return SDL_Unsupported();
892 }
893
894 if (device->ff_initialized) {
895 FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams);
896 periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
897
898 result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect,
899 (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));
900 if (result != FF_OK) {
901 return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result));
902 }
903 } else {
904 if (!DARWIN_JoystickInitRumble(device, magnitude)) {
905 return false;
906 }
907 device->ff_initialized = true;
908 }
909
910 result = FFEffectStart(device->ffeffect_ref, 1, 0);
911 if (result != FF_OK) {
912 return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result));
913 }
914 return true;
915}
916
917static bool DARWIN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
918{
919 return SDL_Unsupported();
920}
921
922static bool DARWIN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
923{
924 return SDL_Unsupported();
925}
926
927static bool DARWIN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
928{
929 return SDL_Unsupported();
930}
931
932static bool DARWIN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
933{
934 return SDL_Unsupported();
935}
936
937static void DARWIN_JoystickUpdate(SDL_Joystick *joystick)
938{
939 recDevice *device = joystick->hwdata;
940 recElement *element;
941 SInt32 value, range;
942 int i, goodRead = false;
943 Uint64 timestamp = SDL_GetTicksNS();
944
945 if (!device) {
946 return;
947 }
948
949 if (device->removed) { // device was unplugged; ignore it.
950 if (joystick->hwdata) {
951 joystick->hwdata = NULL;
952 }
953 return;
954 }
955
956 element = device->firstAxis;
957 i = 0;
958
959 while (element) {
960 goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value);
961 if (goodRead) {
962 SDL_SendJoystickAxis(timestamp, joystick, i, value);
963 }
964
965 element = element->pNext;
966 ++i;
967 }
968
969 element = device->firstButton;
970 i = 0;
971 while (element) {
972 goodRead = GetHIDElementState(device, element, &value);
973 if (goodRead) {
974 SDL_SendJoystickButton(timestamp, joystick, i, (value != 0));
975 }
976
977 element = element->pNext;
978 ++i;
979 }
980
981 element = device->firstHat;
982 i = 0;
983
984 while (element) {
985 Uint8 pos = 0;
986
987 range = (element->max - element->min + 1);
988 goodRead = GetHIDElementState(device, element, &value);
989 if (goodRead) {
990 value -= element->min;
991 if (range == 4) { // 4 position hatswitch - scale up value
992 value *= 2;
993 } else if (range != 8) { // Neither a 4 nor 8 positions - fall back to default position (centered)
994 value = -1;
995 }
996 switch (value) {
997 case 0:
998 pos = SDL_HAT_UP;
999 break;
1000 case 1:
1001 pos = SDL_HAT_RIGHTUP;
1002 break;
1003 case 2:
1004 pos = SDL_HAT_RIGHT;
1005 break;
1006 case 3:
1007 pos = SDL_HAT_RIGHTDOWN;
1008 break;
1009 case 4:
1010 pos = SDL_HAT_DOWN;
1011 break;
1012 case 5:
1013 pos = SDL_HAT_LEFTDOWN;
1014 break;
1015 case 6:
1016 pos = SDL_HAT_LEFT;
1017 break;
1018 case 7:
1019 pos = SDL_HAT_LEFTUP;
1020 break;
1021 default:
1022 /* Every other value is mapped to center. We do that because some
1023 * joysticks use 8 and some 15 for this value, and apparently
1024 * there are even more variants out there - so we try to be generous.
1025 */
1026 pos = SDL_HAT_CENTERED;
1027 break;
1028 }
1029
1030 SDL_SendJoystickHat(timestamp, joystick, i, pos);
1031 }
1032
1033 element = element->pNext;
1034 ++i;
1035 }
1036}
1037
1038static void DARWIN_JoystickClose(SDL_Joystick *joystick)
1039{
1040 recDevice *device = joystick->hwdata;
1041 if (device) {
1042 device->joystick = NULL;
1043 }
1044}
1045
1046static void DARWIN_JoystickQuit(void)
1047{
1048 while (FreeDevice(gpDeviceList)) {
1049 // spin
1050 }
1051
1052 if (hidman) {
1053 IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
1054 IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
1055 CFRelease(hidman);
1056 hidman = NULL;
1057 }
1058}
1059
1060static bool DARWIN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
1061{
1062 return false;
1063}
1064
1065SDL_JoystickDriver SDL_DARWIN_JoystickDriver = {
1066 DARWIN_JoystickInit,
1067 DARWIN_JoystickGetCount,
1068 DARWIN_JoystickDetect,
1069 DARWIN_JoystickIsDevicePresent,
1070 DARWIN_JoystickGetDeviceName,
1071 DARWIN_JoystickGetDevicePath,
1072 DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot,
1073 DARWIN_JoystickGetDevicePlayerIndex,
1074 DARWIN_JoystickSetDevicePlayerIndex,
1075 DARWIN_JoystickGetDeviceGUID,
1076 DARWIN_JoystickGetDeviceInstanceID,
1077 DARWIN_JoystickOpen,
1078 DARWIN_JoystickRumble,
1079 DARWIN_JoystickRumbleTriggers,
1080 DARWIN_JoystickSetLED,
1081 DARWIN_JoystickSendEffect,
1082 DARWIN_JoystickSetSensorsEnabled,
1083 DARWIN_JoystickUpdate,
1084 DARWIN_JoystickClose,
1085 DARWIN_JoystickQuit,
1086 DARWIN_JoystickGetGamepadMapping
1087};
1088
1089#endif // SDL_JOYSTICK_IOKIT
diff --git a/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick_c.h
new file mode 100644
index 0000000..91deb24
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick_c.h
@@ -0,0 +1,80 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_JOYSTICK_IOKIT_H
24#define SDL_JOYSTICK_IOKIT_H
25
26#include <IOKit/hid/IOHIDLib.h>
27#include <ForceFeedback/ForceFeedback.h>
28#include <ForceFeedback/ForceFeedbackConstants.h>
29
30struct recElement
31{
32 IOHIDElementRef elementRef;
33 IOHIDElementCookie cookie;
34 uint32_t usagePage, usage; // HID usage
35 SInt32 min; // reported min value possible
36 SInt32 max; // reported max value possible
37
38 // runtime variables used for auto-calibration
39 SInt32 minReport; // min returned value
40 SInt32 maxReport; // max returned value
41
42 struct recElement *pNext; // next element in list
43};
44typedef struct recElement recElement;
45
46struct joystick_hwdata
47{
48 IOHIDDeviceRef deviceRef; // HIDManager device handle
49 io_service_t ffservice; // Interface for force feedback, 0 = no ff
50 FFDeviceObjectReference ffdevice;
51 FFEFFECT *ffeffect;
52 FFEffectObjectReference ffeffect_ref;
53 bool ff_initialized;
54
55 char product[256]; // name of product
56 uint32_t usage; // usage page from IOUSBHID Parser.h which defines general usage
57 uint32_t usagePage; // usage within above page from IOUSBHID Parser.h which defines specific usage
58
59 int axes; // number of axis (calculated, not reported by device)
60 int buttons; // number of buttons (calculated, not reported by device)
61 int hats; // number of hat switches (calculated, not reported by device)
62 int elements; // number of total elements (should be total of above) (calculated, not reported by device)
63
64 recElement *firstAxis;
65 recElement *firstButton;
66 recElement *firstHat;
67
68 bool removed;
69 SDL_Joystick *joystick;
70 bool runLoopAttached; // is 'deviceRef' attached to a CFRunLoop?
71
72 int instance_id;
73 SDL_GUID guid;
74 int steam_virtual_gamepad_slot;
75
76 struct joystick_hwdata *pNext; // next device
77};
78typedef struct joystick_hwdata recDevice;
79
80#endif // SDL_JOYSTICK_IOKIT_H
diff --git a/contrib/SDL-3.2.8/src/joystick/dummy/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/dummy/SDL_sysjoystick.c
new file mode 100644
index 0000000..8240d45
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/dummy/SDL_sysjoystick.c
@@ -0,0 +1,156 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED)
24
25// This is the dummy implementation of the SDL joystick API
26
27#include "../SDL_sysjoystick.h"
28#include "../SDL_joystick_c.h"
29
30static bool DUMMY_JoystickInit(void)
31{
32 return true;
33}
34
35static int DUMMY_JoystickGetCount(void)
36{
37 return 0;
38}
39
40static void DUMMY_JoystickDetect(void)
41{
42}
43
44static bool DUMMY_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
45{
46 return false;
47}
48
49static const char *DUMMY_JoystickGetDeviceName(int device_index)
50{
51 return NULL;
52}
53
54static const char *DUMMY_JoystickGetDevicePath(int device_index)
55{
56 return NULL;
57}
58
59static int DUMMY_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
60{
61 return -1;
62}
63
64static int DUMMY_JoystickGetDevicePlayerIndex(int device_index)
65{
66 return -1;
67}
68
69static void DUMMY_JoystickSetDevicePlayerIndex(int device_index, int player_index)
70{
71}
72
73static SDL_GUID DUMMY_JoystickGetDeviceGUID(int device_index)
74{
75 SDL_GUID guid;
76 SDL_zero(guid);
77 return guid;
78}
79
80static SDL_JoystickID DUMMY_JoystickGetDeviceInstanceID(int device_index)
81{
82 return 0;
83}
84
85static bool DUMMY_JoystickOpen(SDL_Joystick *joystick, int device_index)
86{
87 return SDL_SetError("Logic error: No joysticks available");
88}
89
90static bool DUMMY_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
91{
92 return SDL_Unsupported();
93}
94
95static bool DUMMY_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
96{
97 return SDL_Unsupported();
98}
99
100static bool DUMMY_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
101{
102 return SDL_Unsupported();
103}
104
105static bool DUMMY_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
106{
107 return SDL_Unsupported();
108}
109
110static bool DUMMY_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
111{
112 return SDL_Unsupported();
113}
114
115static void DUMMY_JoystickUpdate(SDL_Joystick *joystick)
116{
117}
118
119static void DUMMY_JoystickClose(SDL_Joystick *joystick)
120{
121}
122
123static void DUMMY_JoystickQuit(void)
124{
125}
126
127static bool DUMMY_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
128{
129 return false;
130}
131
132SDL_JoystickDriver SDL_DUMMY_JoystickDriver = {
133 DUMMY_JoystickInit,
134 DUMMY_JoystickGetCount,
135 DUMMY_JoystickDetect,
136 DUMMY_JoystickIsDevicePresent,
137 DUMMY_JoystickGetDeviceName,
138 DUMMY_JoystickGetDevicePath,
139 DUMMY_JoystickGetDeviceSteamVirtualGamepadSlot,
140 DUMMY_JoystickGetDevicePlayerIndex,
141 DUMMY_JoystickSetDevicePlayerIndex,
142 DUMMY_JoystickGetDeviceGUID,
143 DUMMY_JoystickGetDeviceInstanceID,
144 DUMMY_JoystickOpen,
145 DUMMY_JoystickRumble,
146 DUMMY_JoystickRumbleTriggers,
147 DUMMY_JoystickSetLED,
148 DUMMY_JoystickSendEffect,
149 DUMMY_JoystickSetSensorsEnabled,
150 DUMMY_JoystickUpdate,
151 DUMMY_JoystickClose,
152 DUMMY_JoystickQuit,
153 DUMMY_JoystickGetGamepadMapping
154};
155
156#endif // SDL_JOYSTICK_DUMMY || SDL_JOYSTICK_DISABLED
diff --git a/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick.c
new file mode 100644
index 0000000..b481d5d
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick.c
@@ -0,0 +1,445 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_JOYSTICK_EMSCRIPTEN
25
26#include <stdio.h> // For the definition of NULL
27
28#include "SDL_sysjoystick_c.h"
29#include "../SDL_joystick_c.h"
30
31static SDL_joylist_item *JoystickByIndex(int index);
32
33static SDL_joylist_item *SDL_joylist = NULL;
34static SDL_joylist_item *SDL_joylist_tail = NULL;
35static int numjoysticks = 0;
36
37static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData)
38{
39 SDL_joylist_item *item;
40 int i;
41
42 SDL_LockJoysticks();
43
44 if (JoystickByIndex(gamepadEvent->index) != NULL) {
45 goto done;
46 }
47
48 item = (SDL_joylist_item *)SDL_malloc(sizeof(SDL_joylist_item));
49 if (!item) {
50 goto done;
51 }
52
53 SDL_zerop(item);
54 item->index = gamepadEvent->index;
55
56 item->name = SDL_CreateJoystickName(0, 0, NULL, gamepadEvent->id);
57 if (!item->name) {
58 SDL_free(item);
59 goto done;
60 }
61
62 item->mapping = SDL_strdup(gamepadEvent->mapping);
63 if (!item->mapping) {
64 SDL_free(item->name);
65 SDL_free(item);
66 goto done;
67 }
68
69 item->naxes = gamepadEvent->numAxes;
70 item->nbuttons = gamepadEvent->numButtons;
71 item->device_instance = SDL_GetNextObjectID();
72
73 item->timestamp = gamepadEvent->timestamp;
74
75 for (i = 0; i < item->naxes; i++) {
76 item->axis[i] = gamepadEvent->axis[i];
77 }
78
79 for (i = 0; i < item->nbuttons; i++) {
80 item->analogButton[i] = gamepadEvent->analogButton[i];
81 item->digitalButton[i] = gamepadEvent->digitalButton[i];
82 }
83
84 if (!SDL_joylist_tail) {
85 SDL_joylist = SDL_joylist_tail = item;
86 } else {
87 SDL_joylist_tail->next = item;
88 SDL_joylist_tail = item;
89 }
90
91 ++numjoysticks;
92
93 SDL_PrivateJoystickAdded(item->device_instance);
94
95#ifdef DEBUG_JOYSTICK
96 SDL_Log("Number of joysticks is %d", numjoysticks);
97#endif
98#ifdef DEBUG_JOYSTICK
99 SDL_Log("Added joystick with index %d", item->index);
100#endif
101
102done:
103 SDL_UnlockJoysticks();
104
105 return 1;
106}
107
108static EM_BOOL Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData)
109{
110 SDL_joylist_item *item = SDL_joylist;
111 SDL_joylist_item *prev = NULL;
112
113 SDL_LockJoysticks();
114
115 while (item) {
116 if (item->index == gamepadEvent->index) {
117 break;
118 }
119 prev = item;
120 item = item->next;
121 }
122
123 if (!item) {
124 goto done;
125 }
126
127 if (item->joystick) {
128 item->joystick->hwdata = NULL;
129 }
130
131 if (prev) {
132 prev->next = item->next;
133 } else {
134 SDL_assert(SDL_joylist == item);
135 SDL_joylist = item->next;
136 }
137 if (item == SDL_joylist_tail) {
138 SDL_joylist_tail = prev;
139 }
140
141 // Need to decrement the joystick count before we post the event
142 --numjoysticks;
143
144 SDL_PrivateJoystickRemoved(item->device_instance);
145
146#ifdef DEBUG_JOYSTICK
147 SDL_Log("Removed joystick with id %d", item->device_instance);
148#endif
149 SDL_free(item->name);
150 SDL_free(item->mapping);
151 SDL_free(item);
152
153done:
154 SDL_UnlockJoysticks();
155
156 return 1;
157}
158
159// Function to perform any system-specific joystick related cleanup
160static void EMSCRIPTEN_JoystickQuit(void)
161{
162 SDL_joylist_item *item = NULL;
163 SDL_joylist_item *next = NULL;
164
165 for (item = SDL_joylist; item; item = next) {
166 next = item->next;
167 SDL_free(item->mapping);
168 SDL_free(item->name);
169 SDL_free(item);
170 }
171
172 SDL_joylist = SDL_joylist_tail = NULL;
173
174 numjoysticks = 0;
175
176 emscripten_set_gamepadconnected_callback(NULL, 0, NULL);
177 emscripten_set_gamepaddisconnected_callback(NULL, 0, NULL);
178}
179
180// Function to scan the system for joysticks.
181static bool EMSCRIPTEN_JoystickInit(void)
182{
183 int rc, i, numjs;
184 EmscriptenGamepadEvent gamepadState;
185
186 numjoysticks = 0;
187
188 rc = emscripten_sample_gamepad_data();
189
190 // Check if gamepad is supported by browser
191 if (rc == EMSCRIPTEN_RESULT_NOT_SUPPORTED) {
192 return SDL_SetError("Gamepads not supported");
193 }
194
195 numjs = emscripten_get_num_gamepads();
196
197 // handle already connected gamepads
198 if (numjs > 0) {
199 for (i = 0; i < numjs; i++) {
200 rc = emscripten_get_gamepad_status(i, &gamepadState);
201 if (rc == EMSCRIPTEN_RESULT_SUCCESS) {
202 Emscripten_JoyStickConnected(EMSCRIPTEN_EVENT_GAMEPADCONNECTED,
203 &gamepadState,
204 NULL);
205 }
206 }
207 }
208
209 rc = emscripten_set_gamepadconnected_callback(NULL,
210 0,
211 Emscripten_JoyStickConnected);
212
213 if (rc != EMSCRIPTEN_RESULT_SUCCESS) {
214 EMSCRIPTEN_JoystickQuit();
215 return SDL_SetError("Could not set gamepad connect callback");
216 }
217
218 rc = emscripten_set_gamepaddisconnected_callback(NULL,
219 0,
220 Emscripten_JoyStickDisconnected);
221 if (rc != EMSCRIPTEN_RESULT_SUCCESS) {
222 EMSCRIPTEN_JoystickQuit();
223 return SDL_SetError("Could not set gamepad disconnect callback");
224 }
225
226 return true;
227}
228
229// Returns item matching given SDL device index.
230static SDL_joylist_item *JoystickByDeviceIndex(int device_index)
231{
232 SDL_joylist_item *item = SDL_joylist;
233
234 while (0 < device_index) {
235 --device_index;
236 item = item->next;
237 }
238
239 return item;
240}
241
242// Returns item matching given HTML gamepad index.
243static SDL_joylist_item *JoystickByIndex(int index)
244{
245 SDL_joylist_item *item = SDL_joylist;
246
247 if (index < 0) {
248 return NULL;
249 }
250
251 while (item) {
252 if (item->index == index) {
253 break;
254 }
255 item = item->next;
256 }
257
258 return item;
259}
260
261static int EMSCRIPTEN_JoystickGetCount(void)
262{
263 return numjoysticks;
264}
265
266static void EMSCRIPTEN_JoystickDetect(void)
267{
268}
269
270static bool EMSCRIPTEN_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
271{
272 // We don't override any other drivers
273 return false;
274}
275
276static const char *EMSCRIPTEN_JoystickGetDeviceName(int device_index)
277{
278 return JoystickByDeviceIndex(device_index)->name;
279}
280
281static const char *EMSCRIPTEN_JoystickGetDevicePath(int device_index)
282{
283 return NULL;
284}
285
286static int EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
287{
288 return -1;
289}
290
291static int EMSCRIPTEN_JoystickGetDevicePlayerIndex(int device_index)
292{
293 return -1;
294}
295
296static void EMSCRIPTEN_JoystickSetDevicePlayerIndex(int device_index, int player_index)
297{
298}
299
300static SDL_JoystickID EMSCRIPTEN_JoystickGetDeviceInstanceID(int device_index)
301{
302 return JoystickByDeviceIndex(device_index)->device_instance;
303}
304
305static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index)
306{
307 SDL_joylist_item *item = JoystickByDeviceIndex(device_index);
308
309 if (!item) {
310 return SDL_SetError("No such device");
311 }
312
313 if (item->joystick) {
314 return SDL_SetError("Joystick already opened");
315 }
316
317 joystick->hwdata = (struct joystick_hwdata *)item;
318 item->joystick = joystick;
319
320 // HTML5 Gamepad API doesn't say anything about these
321 joystick->nhats = 0;
322
323 joystick->nbuttons = item->nbuttons;
324 joystick->naxes = item->naxes;
325
326 return true;
327}
328
329/* Function to update the state of a joystick - called as a device poll.
330 * This function shouldn't update the joystick structure directly,
331 * but instead should call SDL_PrivateJoystick*() to deliver events
332 * and update joystick device state.
333 */
334static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick)
335{
336 EmscriptenGamepadEvent gamepadState;
337 SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
338 int i, result;
339 Uint64 timestamp = SDL_GetTicksNS();
340
341 emscripten_sample_gamepad_data();
342
343 if (item) {
344 result = emscripten_get_gamepad_status(item->index, &gamepadState);
345 if (result == EMSCRIPTEN_RESULT_SUCCESS) {
346 if (gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) {
347 for (i = 0; i < item->nbuttons; i++) {
348 if (item->digitalButton[i] != gamepadState.digitalButton[i]) {
349 bool down = (gamepadState.digitalButton[i] != 0);
350 SDL_SendJoystickButton(timestamp, item->joystick, i, down);
351 }
352
353 // store values to compare them in the next update
354 item->analogButton[i] = gamepadState.analogButton[i];
355 item->digitalButton[i] = gamepadState.digitalButton[i];
356 }
357
358 for (i = 0; i < item->naxes; i++) {
359 if (item->axis[i] != gamepadState.axis[i]) {
360 // do we need to do conversion?
361 SDL_SendJoystickAxis(timestamp, item->joystick, i,
362 (Sint16)(32767. * gamepadState.axis[i]));
363 }
364
365 // store to compare in next update
366 item->axis[i] = gamepadState.axis[i];
367 }
368
369 item->timestamp = gamepadState.timestamp;
370 }
371 }
372 }
373}
374
375// Function to close a joystick after use
376static void EMSCRIPTEN_JoystickClose(SDL_Joystick *joystick)
377{
378 SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
379 if (item) {
380 item->joystick = NULL;
381 }
382}
383
384static SDL_GUID EMSCRIPTEN_JoystickGetDeviceGUID(int device_index)
385{
386 // the GUID is just the name for now
387 const char *name = EMSCRIPTEN_JoystickGetDeviceName(device_index);
388 return SDL_CreateJoystickGUIDForName(name);
389}
390
391static bool EMSCRIPTEN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
392{
393 return SDL_Unsupported();
394}
395
396static bool EMSCRIPTEN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
397{
398 return SDL_Unsupported();
399}
400
401static bool EMSCRIPTEN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
402{
403 return false;
404}
405
406static bool EMSCRIPTEN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
407{
408 return SDL_Unsupported();
409}
410
411static bool EMSCRIPTEN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
412{
413 return SDL_Unsupported();
414}
415
416static bool EMSCRIPTEN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
417{
418 return SDL_Unsupported();
419}
420
421SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver = {
422 EMSCRIPTEN_JoystickInit,
423 EMSCRIPTEN_JoystickGetCount,
424 EMSCRIPTEN_JoystickDetect,
425 EMSCRIPTEN_JoystickIsDevicePresent,
426 EMSCRIPTEN_JoystickGetDeviceName,
427 EMSCRIPTEN_JoystickGetDevicePath,
428 EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot,
429 EMSCRIPTEN_JoystickGetDevicePlayerIndex,
430 EMSCRIPTEN_JoystickSetDevicePlayerIndex,
431 EMSCRIPTEN_JoystickGetDeviceGUID,
432 EMSCRIPTEN_JoystickGetDeviceInstanceID,
433 EMSCRIPTEN_JoystickOpen,
434 EMSCRIPTEN_JoystickRumble,
435 EMSCRIPTEN_JoystickRumbleTriggers,
436 EMSCRIPTEN_JoystickSetLED,
437 EMSCRIPTEN_JoystickSendEffect,
438 EMSCRIPTEN_JoystickSetSensorsEnabled,
439 EMSCRIPTEN_JoystickUpdate,
440 EMSCRIPTEN_JoystickClose,
441 EMSCRIPTEN_JoystickQuit,
442 EMSCRIPTEN_JoystickGetGamepadMapping
443};
444
445#endif // SDL_JOYSTICK_EMSCRIPTEN
diff --git a/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick_c.h
new file mode 100644
index 0000000..e03a27c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/emscripten/SDL_sysjoystick_c.h
@@ -0,0 +1,49 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifdef SDL_JOYSTICK_EMSCRIPTEN
25#include "../SDL_sysjoystick.h"
26
27#include <emscripten/html5.h>
28
29// A linked list of available joysticks
30typedef struct SDL_joylist_item
31{
32 int index;
33 char *name;
34 char *mapping;
35 SDL_JoystickID device_instance;
36 SDL_Joystick *joystick;
37 int nbuttons;
38 int naxes;
39 double timestamp;
40 double axis[64];
41 double analogButton[64];
42 EM_BOOL digitalButton[64];
43
44 struct SDL_joylist_item *next;
45} SDL_joylist_item;
46
47typedef SDL_joylist_item joystick_hwdata;
48
49#endif // SDL_JOYSTICK_EMSCRIPTEN
diff --git a/contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c
new file mode 100644
index 0000000..6cf0a90
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c
@@ -0,0 +1,828 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_GAMEINPUT
24
25#include "../SDL_sysjoystick.h"
26#include "../usb_ids.h"
27#include "../../core/windows/SDL_gameinput.h"
28
29// Default value for SDL_HINT_JOYSTICK_GAMEINPUT
30#if defined(SDL_PLATFORM_GDK)
31#define SDL_GAMEINPUT_DEFAULT true
32#else
33#define SDL_GAMEINPUT_DEFAULT false
34#endif
35
36enum
37{
38 SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE = 11
39};
40
41typedef struct GAMEINPUT_InternalDevice
42{
43 IGameInputDevice *device;
44 char path[(APP_LOCAL_DEVICE_ID_SIZE * 2) + 1];
45 char *name;
46 SDL_GUID guid; // generated by SDL
47 SDL_JoystickID device_instance; // generated by SDL
48 const GameInputDeviceInfo *info;
49 bool isAdded;
50 bool isDeleteRequested;
51} GAMEINPUT_InternalDevice;
52
53typedef struct GAMEINPUT_InternalList
54{
55 GAMEINPUT_InternalDevice **devices;
56 int count;
57} GAMEINPUT_InternalList;
58
59typedef struct joystick_hwdata
60{
61 GAMEINPUT_InternalDevice *devref;
62 bool report_sensors;
63 GameInputRumbleParams rumbleParams;
64 GameInputCallbackToken system_button_callback_token;
65} GAMEINPUT_InternalJoystickHwdata;
66
67static GAMEINPUT_InternalList g_GameInputList = { NULL };
68static IGameInput *g_pGameInput = NULL;
69static GameInputCallbackToken g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
70static Uint64 g_GameInputTimestampOffset;
71
72static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info)
73{
74 if (info->supportedInput & GameInputKindGamepad) {
75 return true;
76 }
77 return false;
78}
79
80static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice)
81{
82 GAMEINPUT_InternalDevice **devicelist = NULL;
83 GAMEINPUT_InternalDevice *elem = NULL;
84 const GameInputDeviceInfo *info = NULL;
85 Uint16 bus = SDL_HARDWARE_BUS_USB;
86 Uint16 vendor = 0;
87 Uint16 product = 0;
88 Uint16 version = 0;
89 const char *manufacturer_string = NULL;
90 const char *product_string = NULL;
91 char tmp[4];
92 int idx = 0;
93
94 SDL_AssertJoysticksLocked();
95
96 info = IGameInputDevice_GetDeviceInfo(pDevice);
97 if (info->capabilities & GameInputDeviceCapabilityWireless) {
98 bus = SDL_HARDWARE_BUS_BLUETOOTH;
99 } else {
100 bus = SDL_HARDWARE_BUS_USB;
101 }
102 vendor = info->vendorId;
103 product = info->productId;
104 version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor;
105
106 if (SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, "")) {
107 return true;
108 }
109
110 for (idx = 0; idx < g_GameInputList.count; ++idx) {
111 elem = g_GameInputList.devices[idx];
112 if (elem && elem->device == pDevice) {
113 // we're already added
114 elem->isDeleteRequested = false;
115 return true;
116 }
117 }
118
119 elem = (GAMEINPUT_InternalDevice *)SDL_calloc(1, sizeof(*elem));
120 if (!elem) {
121 return false;
122 }
123
124 devicelist = (GAMEINPUT_InternalDevice **)SDL_realloc(g_GameInputList.devices, sizeof(elem) * (g_GameInputList.count + 1LL));
125 if (!devicelist) {
126 SDL_free(elem);
127 return false;
128 }
129
130 // Generate a device path
131 for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) {
132 SDL_snprintf(tmp, SDL_arraysize(tmp), "%02hhX", info->deviceId.value[idx]);
133 SDL_strlcat(elem->path, tmp, SDL_arraysize(tmp));
134 }
135
136 if (info->deviceStrings) {
137 // In theory we could get the manufacturer and product strings here, but they're NULL for all the controllers I've tested
138 }
139
140 if (info->displayName) {
141 // This could give us a product string, but it's NULL for all the controllers I've tested
142 }
143
144 IGameInputDevice_AddRef(pDevice);
145 elem->device = pDevice;
146 elem->name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);
147 elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, manufacturer_string, product_string, 'g', 0);
148 elem->device_instance = SDL_GetNextObjectID();
149 elem->info = info;
150
151 g_GameInputList.devices = devicelist;
152 g_GameInputList.devices[g_GameInputList.count++] = elem;
153
154 return true;
155}
156
157static bool GAMEINPUT_InternalRemoveByIndex(int idx)
158{
159 GAMEINPUT_InternalDevice **devicelist = NULL;
160 GAMEINPUT_InternalDevice *elem;
161 int bytes = 0;
162
163 SDL_AssertJoysticksLocked();
164
165 if (idx < 0 || idx >= g_GameInputList.count) {
166 return SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx);
167 }
168
169 elem = g_GameInputList.devices[idx];
170 if (elem) {
171 IGameInputDevice_Release(elem->device);
172 SDL_free(elem->name);
173 SDL_free(elem);
174 }
175 g_GameInputList.devices[idx] = NULL;
176
177 if (g_GameInputList.count == 1) {
178 // last element in the list, free the entire list then
179 SDL_free(g_GameInputList.devices);
180 g_GameInputList.devices = NULL;
181 } else {
182 if (idx != g_GameInputList.count - 1) {
183 bytes = sizeof(*devicelist) * (g_GameInputList.count - idx);
184 SDL_memmove(&g_GameInputList.devices[idx], &g_GameInputList.devices[idx + 1], bytes);
185 }
186 }
187
188 // decrement the count and return
189 --g_GameInputList.count;
190 return true;
191}
192
193static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx)
194{
195 // We're guaranteed that the index is in range when this is called
196 SDL_AssertJoysticksLocked();
197 return g_GameInputList.devices[idx];
198}
199
200static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback(
201 _In_ GameInputCallbackToken callbackToken,
202 _In_ void* context,
203 _In_ IGameInputDevice* device,
204 _In_ uint64_t timestamp,
205 _In_ GameInputDeviceStatus currentStatus,
206 _In_ GameInputDeviceStatus previousStatus)
207{
208 int idx = 0;
209 GAMEINPUT_InternalDevice *elem = NULL;
210
211 if (!device) {
212 // This should never happen, but ignore it if it does
213 return;
214 }
215
216 SDL_LockJoysticks();
217
218 if (currentStatus & GameInputDeviceConnected) {
219 GAMEINPUT_InternalAddOrFind(device);
220 } else {
221 for (idx = 0; idx < g_GameInputList.count; ++idx) {
222 elem = g_GameInputList.devices[idx];
223 if (elem && elem->device == device) {
224 // will be deleted on the next Detect call
225 elem->isDeleteRequested = true;
226 break;
227 }
228 }
229 }
230
231 SDL_UnlockJoysticks();
232}
233
234static void GAMEINPUT_JoystickDetect(void);
235
236static bool GAMEINPUT_JoystickInit(void)
237{
238 HRESULT hR;
239
240 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) {
241 return true;
242 }
243
244 if (!SDL_InitGameInput(&g_pGameInput)) {
245 return false;
246 }
247
248 hR = IGameInput_RegisterDeviceCallback(g_pGameInput,
249 NULL,
250 GameInputKindController,
251 GameInputDeviceConnected,
252 GameInputBlockingEnumeration,
253 NULL,
254 GAMEINPUT_InternalJoystickDeviceCallback,
255 &g_GameInputCallbackToken);
256 if (FAILED(hR)) {
257 return SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08lX", hR);
258 }
259
260 // Calculate the relative offset between SDL timestamps and GameInput timestamps
261 Uint64 now = SDL_GetTicksNS();
262 uint64_t timestampUS = IGameInput_GetCurrentTimestamp(g_pGameInput);
263 g_GameInputTimestampOffset = (SDL_NS_TO_US(now) - timestampUS);
264
265 GAMEINPUT_JoystickDetect();
266
267 return true;
268}
269
270static int GAMEINPUT_JoystickGetCount(void)
271{
272 SDL_AssertJoysticksLocked();
273
274 return g_GameInputList.count;
275}
276
277static void GAMEINPUT_JoystickDetect(void)
278{
279 int idx;
280 GAMEINPUT_InternalDevice *elem = NULL;
281
282 SDL_AssertJoysticksLocked();
283
284 for (idx = 0; idx < g_GameInputList.count; ++idx) {
285 elem = g_GameInputList.devices[idx];
286 if (!elem) {
287 continue;
288 }
289
290 if (!elem->isAdded) {
291 SDL_PrivateJoystickAdded(elem->device_instance);
292 elem->isAdded = true;
293 }
294
295 if (elem->isDeleteRequested || !(IGameInputDevice_GetDeviceStatus(elem->device) & GameInputDeviceConnected)) {
296 SDL_PrivateJoystickRemoved(elem->device_instance);
297 GAMEINPUT_InternalRemoveByIndex(idx--);
298 }
299 }
300}
301
302static bool GAMEINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
303{
304 SDL_AssertJoysticksLocked();
305
306 if (g_pGameInput) {
307 if (vendor_id == USB_VENDOR_MICROSOFT &&
308 product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) {
309 // The Xbox One controller shows up as a hardcoded raw input VID/PID, which we definitely handle
310 return true;
311 }
312
313 for (int i = 0; i < g_GameInputList.count; ++i) {
314 GAMEINPUT_InternalDevice *elem = g_GameInputList.devices[i];
315 if (elem && vendor_id == elem->info->vendorId && product_id == elem->info->productId) {
316 return true;
317 }
318 }
319 }
320 return false;
321}
322
323static const char *GAMEINPUT_JoystickGetDeviceName(int device_index)
324{
325 return GAMEINPUT_InternalFindByIndex(device_index)->name;
326}
327
328static const char *GAMEINPUT_JoystickGetDevicePath(int device_index)
329{
330 // APP_LOCAL_DEVICE_ID as a hex string, since it's required for some association callbacks
331 return GAMEINPUT_InternalFindByIndex(device_index)->path;
332}
333
334static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
335{
336 return -1;
337}
338
339static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index)
340{
341 return -1;
342}
343
344static void GAMEINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index)
345{
346}
347
348static SDL_GUID GAMEINPUT_JoystickGetDeviceGUID(int device_index)
349{
350 return GAMEINPUT_InternalFindByIndex(device_index)->guid;
351}
352
353static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index)
354{
355 return GAMEINPUT_InternalFindByIndex(device_index)->device_instance;
356}
357
358static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *device)
359{
360 GameInputBatteryState battery_state;
361 SDL_PowerState state;
362 int percent = 0;
363
364 SDL_zero(battery_state);
365 IGameInputDevice_GetBatteryState(device, &battery_state);
366
367 switch (battery_state.status) {
368 case GameInputBatteryNotPresent:
369 state = SDL_POWERSTATE_NO_BATTERY;
370 break;
371 case GameInputBatteryDischarging:
372 state = SDL_POWERSTATE_ON_BATTERY;
373 break;
374 case GameInputBatteryIdle:
375 state = SDL_POWERSTATE_CHARGED;
376 break;
377 case GameInputBatteryCharging:
378 state = SDL_POWERSTATE_CHARGING;
379 break;
380 default:
381 state = SDL_POWERSTATE_UNKNOWN;
382 break;
383 }
384 if (battery_state.fullChargeCapacity > 0.0f) {
385 percent = (int)SDL_roundf((battery_state.remainingCapacity / battery_state.fullChargeCapacity) * 100.0f);
386 }
387 SDL_SendJoystickPowerInfo(joystick, state, percent);
388}
389
390#ifdef IGameInput_RegisterSystemButtonCallback
391
392static void CALLBACK GAMEINPUT_InternalSystemButtonCallback(
393 _In_ GameInputCallbackToken callbackToken,
394 _In_ void * context,
395 _In_ IGameInputDevice * device,
396 _In_ uint64_t timestampUS,
397 _In_ GameInputSystemButtons currentButtons,
398 _In_ GameInputSystemButtons previousButtons)
399{
400 SDL_Joystick *joystick = (SDL_Joystick *)context;
401
402 GameInputSystemButtons changedButtons = (previousButtons ^ currentButtons);
403 if (changedButtons) {
404 Uint64 timestamp = SDL_US_TO_NS(timestampUS + g_GameInputTimestampOffset);
405
406 SDL_LockJoysticks();
407 if (changedButtons & GameInputSystemButtonGuide) {
408 bool down = ((currentButtons & GameInputSystemButtonGuide) != 0);
409 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, down);
410 }
411 if (changedButtons & GameInputSystemButtonShare) {
412 bool down = ((currentButtons & GameInputSystemButtonShare) != 0);
413 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE, down);
414 }
415 SDL_UnlockJoysticks();
416 }
417}
418
419#endif // IGameInput_RegisterSystemButtonCallback
420
421static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)
422{
423 GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
424 const GameInputDeviceInfo *info = elem->info;
425 GAMEINPUT_InternalJoystickHwdata *hwdata = NULL;
426
427 if (!elem) {
428 return false;
429 }
430
431 hwdata = (GAMEINPUT_InternalJoystickHwdata *)SDL_calloc(1, sizeof(*hwdata));
432 if (!hwdata) {
433 return false;
434 }
435
436 hwdata->devref = elem;
437
438 joystick->hwdata = hwdata;
439 if (GAMEINPUT_InternalIsGamepad(info)) {
440 joystick->naxes = 6;
441 joystick->nbuttons = 11;
442 joystick->nhats = 1;
443
444#ifdef IGameInput_RegisterSystemButtonCallback
445 if (info->supportedSystemButtons != GameInputSystemButtonNone) {
446 if (info->supportedSystemButtons & GameInputSystemButtonShare) {
447 ++joystick->nbuttons;
448 }
449
450#if 1 // The C macro in GameInput.h version 10.0.26100 refers to a focus policy which I guess has been removed from the final API?
451#undef IGameInput_RegisterSystemButtonCallback
452#define IGameInput_RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken) ((This)->lpVtbl->RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken))
453#endif
454 IGameInput_RegisterSystemButtonCallback(g_pGameInput, elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token);
455 }
456#endif // IGameInput_RegisterSystemButtonCallback
457 } else {
458 joystick->naxes = info->controllerAxisCount;
459 joystick->nbuttons = info->controllerButtonCount;
460 joystick->nhats = info->controllerSwitchCount;
461 }
462
463 if (info->supportedRumbleMotors & (GameInputRumbleLowFrequency | GameInputRumbleHighFrequency)) {
464 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
465 }
466 if (info->supportedRumbleMotors & (GameInputRumbleLeftTrigger | GameInputRumbleRightTrigger)) {
467 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
468 }
469
470 if (info->supportedInput & GameInputKindTouch) {
471 SDL_PrivateJoystickAddTouchpad(joystick, info->touchPointCount);
472 }
473
474 if (info->supportedInput & GameInputKindMotion) {
475 // FIXME: What's the sensor update rate?
476 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
477 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
478 }
479
480 if (info->capabilities & GameInputDeviceCapabilityWireless) {
481 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
482 } else {
483 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
484 }
485 return true;
486}
487
488static bool GAMEINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
489{
490 // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it
491 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
492 GameInputRumbleParams *params = &hwdata->rumbleParams;
493 params->lowFrequency = (float)low_frequency_rumble / (float)SDL_MAX_UINT16;
494 params->highFrequency = (float)high_frequency_rumble / (float)SDL_MAX_UINT16;
495 IGameInputDevice_SetRumbleState(hwdata->devref->device, params);
496 return true;
497}
498
499static bool GAMEINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
500{
501 // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it
502 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
503 GameInputRumbleParams *params = &hwdata->rumbleParams;
504 params->leftTrigger = (float)left_rumble / (float)SDL_MAX_UINT16;
505 params->rightTrigger = (float)right_rumble / (float)SDL_MAX_UINT16;
506 IGameInputDevice_SetRumbleState(hwdata->devref->device, params);
507 return true;
508}
509
510static bool GAMEINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
511{
512 return SDL_Unsupported();
513}
514
515static bool GAMEINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
516{
517 return SDL_Unsupported();
518}
519
520static bool GAMEINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
521{
522 joystick->hwdata->report_sensors = enabled;
523 return true;
524}
525
526static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
527{
528 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
529 IGameInputDevice *device = hwdata->devref->device;
530 const GameInputDeviceInfo *info = hwdata->devref->info;
531 IGameInputReading *reading = NULL;
532 Uint64 timestamp;
533 GameInputGamepadState state;
534 HRESULT hR;
535
536 hR = IGameInput_GetCurrentReading(g_pGameInput, info->supportedInput, device, &reading);
537 if (FAILED(hR)) {
538 // don't SetError here since there can be a legitimate case when there's no reading avail
539 return;
540 }
541
542 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + g_GameInputTimestampOffset);
543
544 if (GAMEINPUT_InternalIsGamepad(info)) {
545 static WORD s_XInputButtons[] = {
546 GameInputGamepadA, // SDL_GAMEPAD_BUTTON_SOUTH
547 GameInputGamepadB, // SDL_GAMEPAD_BUTTON_EAST
548 GameInputGamepadX, // SDL_GAMEPAD_BUTTON_WEST
549 GameInputGamepadY, // SDL_GAMEPAD_BUTTON_NORTH
550 GameInputGamepadView, // SDL_GAMEPAD_BUTTON_BACK
551 0, // The guide button is not available
552 GameInputGamepadMenu, // SDL_GAMEPAD_BUTTON_START
553 GameInputGamepadLeftThumbstick, // SDL_GAMEPAD_BUTTON_LEFT_STICK
554 GameInputGamepadRightThumbstick, // SDL_GAMEPAD_BUTTON_RIGHT_STICK
555 GameInputGamepadLeftShoulder, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER
556 GameInputGamepadRightShoulder, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER
557 };
558 Uint8 btnidx = 0, hat = 0;
559
560 if (IGameInputReading_GetGamepadState(reading, &state)) {
561 for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) {
562 WORD button_mask = s_XInputButtons[btnidx];
563 if (!button_mask) {
564 continue;
565 }
566 bool down = ((state.buttons & button_mask) != 0);
567 SDL_SendJoystickButton(timestamp, joystick, btnidx, down);
568 }
569
570 if (state.buttons & GameInputGamepadDPadUp) {
571 hat |= SDL_HAT_UP;
572 }
573 if (state.buttons & GameInputGamepadDPadDown) {
574 hat |= SDL_HAT_DOWN;
575 }
576 if (state.buttons & GameInputGamepadDPadLeft) {
577 hat |= SDL_HAT_LEFT;
578 }
579 if (state.buttons & GameInputGamepadDPadRight) {
580 hat |= SDL_HAT_RIGHT;
581 }
582 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
583
584#define CONVERT_AXIS(v) (Sint16)(((v) < 0.0f) ? ((v)*32768.0f) : ((v)*32767.0f))
585 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, CONVERT_AXIS(state.leftThumbstickX));
586 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, CONVERT_AXIS(-state.leftThumbstickY));
587 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, CONVERT_AXIS(state.rightThumbstickX));
588 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, CONVERT_AXIS(-state.rightThumbstickY));
589#undef CONVERT_AXIS
590#define CONVERT_TRIGGER(v) (Sint16)((v)*65535.0f - 32768.0f)
591 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, CONVERT_TRIGGER(state.leftTrigger));
592 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, CONVERT_TRIGGER(state.rightTrigger));
593#undef CONVERT_TRIGGER
594 }
595 } else {
596 bool *button_state = SDL_stack_alloc(bool, info->controllerButtonCount);
597 float *axis_state = SDL_stack_alloc(float, info->controllerAxisCount);
598 GameInputSwitchPosition *switch_state = SDL_stack_alloc(GameInputSwitchPosition, info->controllerSwitchCount);
599
600 if (button_state) {
601 uint32_t i;
602 uint32_t button_count = IGameInputReading_GetControllerButtonState(reading, info->controllerButtonCount, button_state);
603 for (i = 0; i < button_count; ++i) {
604 SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]);
605 }
606 SDL_stack_free(button_state);
607 }
608
609#define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f)
610 if (axis_state) {
611 uint32_t i;
612 uint32_t axis_count = IGameInputReading_GetControllerAxisState(reading, info->controllerAxisCount, axis_state);
613 for (i = 0; i < axis_count; ++i) {
614 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i]));
615 }
616 SDL_stack_free(axis_state);
617 }
618#undef CONVERT_AXIS
619
620 if (switch_state) {
621 uint32_t i;
622 uint32_t switch_count = IGameInputReading_GetControllerSwitchState(reading, info->controllerSwitchCount, switch_state);
623 for (i = 0; i < switch_count; ++i) {
624 Uint8 hat;
625 switch (switch_state[i]) {
626 case GameInputSwitchUp:
627 hat = SDL_HAT_UP;
628 break;
629 case GameInputSwitchUpRight:
630 hat = SDL_HAT_UP | SDL_HAT_RIGHT;
631 break;
632 case GameInputSwitchRight:
633 hat = SDL_HAT_RIGHT;
634 break;
635 case GameInputSwitchDownRight:
636 hat = SDL_HAT_DOWN | SDL_HAT_RIGHT;
637 break;
638 case GameInputSwitchDown:
639 hat = SDL_HAT_DOWN;
640 break;
641 case GameInputSwitchDownLeft:
642 hat = SDL_HAT_DOWN | SDL_HAT_LEFT;
643 break;
644 case GameInputSwitchLeft:
645 hat = SDL_HAT_LEFT;
646 break;
647 case GameInputSwitchUpLeft:
648 hat = SDL_HAT_UP | SDL_HAT_LEFT;
649 break;
650 case GameInputSwitchCenter:
651 default:
652 hat = SDL_HAT_CENTERED;
653 break;
654 }
655 SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat);
656 }
657 SDL_stack_free(switch_state);
658 }
659 }
660
661 if (info->supportedInput & GameInputKindTouch) {
662 GameInputTouchState *touch_state = SDL_stack_alloc(GameInputTouchState, info->touchPointCount);
663 if (touch_state) {
664 uint32_t i;
665 uint32_t touch_count = IGameInputReading_GetTouchState(reading, info->touchPointCount, touch_state);
666 for (i = 0; i < touch_count; ++i) {
667 GameInputTouchState *touch = &touch_state[i];
668 // FIXME: We should use touch->touchId to track fingers instead of using i below
669 SDL_SendJoystickTouchpad(timestamp, joystick, 0, i, true, touch->positionX * info->touchSensorInfo[i].resolutionX, touch->positionY * info->touchSensorInfo[0].resolutionY, touch->pressure);
670 }
671 SDL_stack_free(touch_state);
672 }
673 }
674
675 if (hwdata->report_sensors) {
676 GameInputMotionState motion_state;
677
678 if (IGameInputReading_GetMotionState(reading, &motion_state)) {
679 // FIXME: How do we interpret the motion data?
680 }
681 }
682
683 IGameInputReading_Release(reading);
684
685 // FIXME: We can poll this at a much lower rate
686 GAMEINPUT_UpdatePowerInfo(joystick, device);
687}
688
689static void GAMEINPUT_JoystickClose(SDL_Joystick* joystick)
690{
691 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
692
693 if (hwdata->system_button_callback_token) {
694 IGameInput_UnregisterCallback(g_pGameInput, hwdata->system_button_callback_token, 5000);
695 }
696 SDL_free(hwdata);
697
698 joystick->hwdata = NULL;
699}
700
701static void GAMEINPUT_JoystickQuit(void)
702{
703 if (g_pGameInput) {
704 // free the callback
705 IGameInput_UnregisterCallback(g_pGameInput, g_GameInputCallbackToken, /*timeoutInUs:*/ 10000);
706 g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
707
708 // free the list
709 while (g_GameInputList.count > 0) {
710 GAMEINPUT_InternalRemoveByIndex(0);
711 }
712
713 SDL_QuitGameInput();
714 g_pGameInput = NULL;
715 }
716}
717
718static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
719{
720 GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
721
722 if (!GAMEINPUT_InternalIsGamepad(elem->info)) {
723 return false;
724 }
725
726 out->a.kind = EMappingKind_Button;
727 out->a.target = SDL_GAMEPAD_BUTTON_SOUTH;
728
729 out->b.kind = EMappingKind_Button;
730 out->b.target = SDL_GAMEPAD_BUTTON_EAST;
731
732 out->x.kind = EMappingKind_Button;
733 out->x.target = SDL_GAMEPAD_BUTTON_WEST;
734
735 out->y.kind = EMappingKind_Button;
736 out->y.target = SDL_GAMEPAD_BUTTON_NORTH;
737
738 out->back.kind = EMappingKind_Button;
739 out->back.target = SDL_GAMEPAD_BUTTON_BACK;
740
741#ifdef IGameInput_RegisterSystemButtonCallback
742 if (elem->info->supportedSystemButtons & GameInputSystemButtonGuide) {
743 out->guide.kind = EMappingKind_Button;
744 out->guide.target = SDL_GAMEPAD_BUTTON_GUIDE;
745 }
746
747 if (elem->info->supportedSystemButtons & GameInputSystemButtonShare) {
748 out->misc1.kind = EMappingKind_Button;
749 out->misc1.target = SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE;
750 }
751#endif
752
753 out->start.kind = EMappingKind_Button;
754 out->start.target = SDL_GAMEPAD_BUTTON_START;
755
756 out->leftstick.kind = EMappingKind_Button;
757 out->leftstick.target = SDL_GAMEPAD_BUTTON_LEFT_STICK;
758
759 out->rightstick.kind = EMappingKind_Button;
760 out->rightstick.target = SDL_GAMEPAD_BUTTON_RIGHT_STICK;
761
762 out->leftshoulder.kind = EMappingKind_Button;
763 out->leftshoulder.target = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER;
764
765 out->rightshoulder.kind = EMappingKind_Button;
766 out->rightshoulder.target = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER;
767
768 out->dpup.kind = EMappingKind_Hat;
769 out->dpup.target = SDL_HAT_UP;
770
771 out->dpdown.kind = EMappingKind_Hat;
772 out->dpdown.target = SDL_HAT_DOWN;
773
774 out->dpleft.kind = EMappingKind_Hat;
775 out->dpleft.target = SDL_HAT_LEFT;
776
777 out->dpright.kind = EMappingKind_Hat;
778 out->dpright.target = SDL_HAT_RIGHT;
779
780 out->leftx.kind = EMappingKind_Axis;
781 out->leftx.target = SDL_GAMEPAD_AXIS_LEFTX;
782
783 out->lefty.kind = EMappingKind_Axis;
784 out->lefty.target = SDL_GAMEPAD_AXIS_LEFTY;
785
786 out->rightx.kind = EMappingKind_Axis;
787 out->rightx.target = SDL_GAMEPAD_AXIS_RIGHTX;
788
789 out->righty.kind = EMappingKind_Axis;
790 out->righty.target = SDL_GAMEPAD_AXIS_RIGHTY;
791
792 out->lefttrigger.kind = EMappingKind_Axis;
793 out->lefttrigger.target = SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
794
795 out->righttrigger.kind = EMappingKind_Axis;
796 out->righttrigger.target = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
797
798 return true;
799}
800
801
802SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver =
803{
804 GAMEINPUT_JoystickInit,
805 GAMEINPUT_JoystickGetCount,
806 GAMEINPUT_JoystickDetect,
807 GAMEINPUT_JoystickIsDevicePresent,
808 GAMEINPUT_JoystickGetDeviceName,
809 GAMEINPUT_JoystickGetDevicePath,
810 GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot,
811 GAMEINPUT_JoystickGetDevicePlayerIndex,
812 GAMEINPUT_JoystickSetDevicePlayerIndex,
813 GAMEINPUT_JoystickGetDeviceGUID,
814 GAMEINPUT_JoystickGetDeviceInstanceID,
815 GAMEINPUT_JoystickOpen,
816 GAMEINPUT_JoystickRumble,
817 GAMEINPUT_JoystickRumbleTriggers,
818 GAMEINPUT_JoystickSetLED,
819 GAMEINPUT_JoystickSendEffect,
820 GAMEINPUT_JoystickSetSensorsEnabled,
821 GAMEINPUT_JoystickUpdate,
822 GAMEINPUT_JoystickClose,
823 GAMEINPUT_JoystickQuit,
824 GAMEINPUT_JoystickGetGamepadMapping
825};
826
827
828#endif // SDL_JOYSTICK_GAMEINPUT
diff --git a/contrib/SDL-3.2.8/src/joystick/haiku/SDL_haikujoystick.cc b/contrib/SDL-3.2.8/src/joystick/haiku/SDL_haikujoystick.cc
new file mode 100644
index 0000000..977e4fc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/haiku/SDL_haikujoystick.cc
@@ -0,0 +1,315 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HAIKU
24
25// This is the Haiku implementation of the SDL joystick API
26
27#include <support/String.h>
28#include <device/Joystick.h>
29
30extern "C"
31{
32
33#include "../SDL_sysjoystick.h"
34#include "../SDL_joystick_c.h"
35
36
37// The maximum number of joysticks we'll detect
38#define MAX_JOYSTICKS 16
39
40// A list of available joysticks
41 static char *SDL_joyport[MAX_JOYSTICKS];
42 static char *SDL_joyname[MAX_JOYSTICKS];
43
44// The private structure used to keep track of a joystick
45 struct joystick_hwdata
46 {
47 BJoystick *stick;
48 uint8 *new_hats;
49 int16 *new_axes;
50 };
51
52 static int numjoysticks = 0;
53
54 static bool HAIKU_JoystickInit(void)
55 {
56 BJoystick joystick;
57 int i;
58 int32 nports;
59 char name[B_OS_NAME_LENGTH];
60
61 // Search for attached joysticks
62 nports = joystick.CountDevices();
63 numjoysticks = 0;
64 SDL_memset(SDL_joyport, 0, sizeof(SDL_joyport));
65 SDL_memset(SDL_joyname, 0, sizeof(SDL_joyname));
66 for (i = 0; (numjoysticks < MAX_JOYSTICKS) && (i < nports); ++i) {
67 if (joystick.GetDeviceName(i, name) == B_OK) {
68 if (joystick.Open(name) != B_ERROR) {
69 BString stick_name;
70 joystick.GetControllerName(&stick_name);
71 SDL_joyport[numjoysticks] = SDL_strdup(name);
72 SDL_joyname[numjoysticks] = SDL_CreateJoystickName(0, 0, NULL, stick_name.String());
73 numjoysticks++;
74 joystick.Close();
75
76 SDL_PrivateJoystickAdded(numjoysticks);
77 }
78 }
79 }
80 return true;
81 }
82
83 static int HAIKU_JoystickGetCount(void)
84 {
85 return numjoysticks;
86 }
87
88 static void HAIKU_JoystickDetect(void)
89 {
90 }
91
92 static bool HAIKU_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
93 {
94 // We don't override any other drivers
95 return false;
96 }
97
98 static const char *HAIKU_JoystickGetDeviceName(int device_index)
99 {
100 return SDL_joyname[device_index];
101 }
102
103 static const char *HAIKU_JoystickGetDevicePath(int device_index)
104 {
105 return SDL_joyport[device_index];
106 }
107
108 static int HAIKU_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
109 {
110 return -1;
111 }
112
113 static int HAIKU_JoystickGetDevicePlayerIndex(int device_index)
114 {
115 return -1;
116 }
117
118 static void HAIKU_JoystickSetDevicePlayerIndex(int device_index, int player_index)
119 {
120 }
121
122 static SDL_JoystickID HAIKU_JoystickGetDeviceInstanceID(int device_index)
123 {
124 return device_index + 1;
125 }
126
127 static void HAIKU_JoystickClose(SDL_Joystick *joystick);
128
129 static bool HAIKU_JoystickOpen(SDL_Joystick *joystick, int device_index)
130 {
131 BJoystick *stick;
132
133 // Create the joystick data structure
134 joystick->hwdata = (struct joystick_hwdata *) SDL_calloc(1, sizeof(*joystick->hwdata));
135 if (joystick->hwdata == NULL) {
136 return false;
137 }
138 stick = new BJoystick;
139 joystick->hwdata->stick = stick;
140
141 // Open the requested joystick for use
142 if (stick->Open(SDL_joyport[device_index]) == B_ERROR) {
143 HAIKU_JoystickClose(joystick);
144 return SDL_SetError("Unable to open joystick");
145 }
146
147 // Set the joystick to calibrated mode
148 stick->EnableCalibration();
149
150 // Get the number of buttons, hats, and axes on the joystick
151 joystick->nbuttons = stick->CountButtons();
152 joystick->naxes = stick->CountAxes();
153 joystick->nhats = stick->CountHats();
154
155 joystick->hwdata->new_axes = (int16 *) SDL_calloc(joystick->naxes, sizeof(int16));
156 joystick->hwdata->new_hats = (uint8 *) SDL_calloc(joystick->nhats, sizeof(uint8));
157 if (!joystick->hwdata->new_hats || !joystick->hwdata->new_axes) {
158 HAIKU_JoystickClose(joystick);
159 return false;
160 }
161
162 // We're done!
163 return true;
164 }
165
166/* Function to update the state of a joystick - called as a device poll.
167 * This function shouldn't update the joystick structure directly,
168 * but instead should call SDL_PrivateJoystick*() to deliver events
169 * and update joystick device state.
170 */
171 static void HAIKU_JoystickUpdate(SDL_Joystick *joystick)
172 {
173 static const Uint8 hat_map[9] = {
174 SDL_HAT_CENTERED,
175 SDL_HAT_UP,
176 SDL_HAT_RIGHTUP,
177 SDL_HAT_RIGHT,
178 SDL_HAT_RIGHTDOWN,
179 SDL_HAT_DOWN,
180 SDL_HAT_LEFTDOWN,
181 SDL_HAT_LEFT,
182 SDL_HAT_LEFTUP
183 };
184
185 BJoystick *stick;
186 int i;
187 int16 *axes;
188 uint8 *hats;
189 uint32 buttons;
190 Uint64 timestamp = SDL_GetTicksNS();
191
192 // Set up data pointers
193 stick = joystick->hwdata->stick;
194 axes = joystick->hwdata->new_axes;
195 hats = joystick->hwdata->new_hats;
196
197 // Get the new joystick state
198 stick->Update();
199 stick->GetAxisValues(axes);
200 stick->GetHatValues(hats);
201 buttons = stick->ButtonValues();
202
203 // Generate axis motion events
204 for (i = 0; i < joystick->naxes; ++i) {
205 SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]);
206 }
207
208 // Generate hat change events
209 for (i = 0; i < joystick->nhats; ++i) {
210 SDL_SendJoystickHat(timestamp, joystick, i, hat_map[hats[i]]);
211 }
212
213 // Generate button events
214 for (i = 0; i < joystick->nbuttons; ++i) {
215 bool down = ((buttons & 0x01) != 0);
216 SDL_SendJoystickButton(timestamp, joystick, i, down);
217 buttons >>= 1;
218 }
219 }
220
221// Function to close a joystick after use
222 static void HAIKU_JoystickClose(SDL_Joystick *joystick)
223 {
224 if (joystick->hwdata) {
225 joystick->hwdata->stick->Close();
226 delete joystick->hwdata->stick;
227 SDL_free(joystick->hwdata->new_hats);
228 SDL_free(joystick->hwdata->new_axes);
229 SDL_free(joystick->hwdata);
230 }
231 }
232
233// Function to perform any system-specific joystick related cleanup
234 static void HAIKU_JoystickQuit(void)
235 {
236 int i;
237
238 for (i = 0; i < numjoysticks; ++i) {
239 SDL_free(SDL_joyport[i]);
240 }
241 SDL_joyport[0] = NULL;
242
243 for (i = 0; i < numjoysticks; ++i) {
244 SDL_free(SDL_joyname[i]);
245 }
246 SDL_joyname[0] = NULL;
247 }
248
249 static SDL_GUID HAIKU_JoystickGetDeviceGUID(int device_index)
250 {
251 // the GUID is just the name for now
252 const char *name = HAIKU_JoystickGetDeviceName(device_index);
253 return SDL_CreateJoystickGUIDForName(name);
254 }
255
256 static bool HAIKU_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
257 {
258 return SDL_Unsupported();
259 }
260
261
262 static bool HAIKU_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
263 {
264 return SDL_Unsupported();
265 }
266
267 static bool HAIKU_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
268 {
269 return false;
270 }
271
272 static bool HAIKU_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
273 {
274 return SDL_Unsupported();
275 }
276
277
278 static bool HAIKU_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
279 {
280 return SDL_Unsupported();
281 }
282
283 static bool HAIKU_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
284 {
285 return SDL_Unsupported();
286 }
287
288 SDL_JoystickDriver SDL_HAIKU_JoystickDriver =
289 {
290 HAIKU_JoystickInit,
291 HAIKU_JoystickGetCount,
292 HAIKU_JoystickDetect,
293 HAIKU_JoystickIsDevicePresent,
294 HAIKU_JoystickGetDeviceName,
295 HAIKU_JoystickGetDevicePath,
296 HAIKU_JoystickGetDeviceSteamVirtualGamepadSlot,
297 HAIKU_JoystickGetDevicePlayerIndex,
298 HAIKU_JoystickSetDevicePlayerIndex,
299 HAIKU_JoystickGetDeviceGUID,
300 HAIKU_JoystickGetDeviceInstanceID,
301 HAIKU_JoystickOpen,
302 HAIKU_JoystickRumble,
303 HAIKU_JoystickRumbleTriggers,
304 HAIKU_JoystickSetLED,
305 HAIKU_JoystickSendEffect,
306 HAIKU_JoystickSetSensorsEnabled,
307 HAIKU_JoystickUpdate,
308 HAIKU_JoystickClose,
309 HAIKU_JoystickQuit,
310 HAIKU_JoystickGetGamepadMapping
311 };
312
313} // extern "C"
314
315#endif // SDL_JOYSTICK_HAIKU
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_combined.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_combined.c
new file mode 100644
index 0000000..5426edb
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_combined.c
@@ -0,0 +1,236 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21// This driver supports the Nintendo Switch Joy-Cons pair controllers
22#include "SDL_internal.h"
23
24#ifdef SDL_JOYSTICK_HIDAPI
25
26#include "SDL_hidapijoystick_c.h"
27#include "../SDL_sysjoystick.h"
28
29static void HIDAPI_DriverCombined_RegisterHints(SDL_HintCallback callback, void *userdata)
30{
31}
32
33static void HIDAPI_DriverCombined_UnregisterHints(SDL_HintCallback callback, void *userdata)
34{
35}
36
37static bool HIDAPI_DriverCombined_IsEnabled(void)
38{
39 return true;
40}
41
42static bool HIDAPI_DriverCombined_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
43{
44 // This is always explicitly created for combined devices
45 return false;
46}
47
48static bool HIDAPI_DriverCombined_InitDevice(SDL_HIDAPI_Device *device)
49{
50 return HIDAPI_JoystickConnected(device, NULL);
51}
52
53static int HIDAPI_DriverCombined_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
54{
55 return -1;
56}
57
58static void HIDAPI_DriverCombined_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
59{
60}
61
62static bool HIDAPI_DriverCombined_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
63{
64 int i;
65 char *serial = NULL, *new_serial;
66 size_t serial_length = 0, new_length;
67
68 SDL_AssertJoysticksLocked();
69
70 for (i = 0; i < device->num_children; ++i) {
71 SDL_HIDAPI_Device *child = device->children[i];
72 if (!child->driver->OpenJoystick(child, joystick)) {
73 child->broken = true;
74
75 while (i-- > 0) {
76 child = device->children[i];
77 child->driver->CloseJoystick(child, joystick);
78 }
79 if (serial) {
80 SDL_free(serial);
81 }
82 return false;
83 }
84
85 // Extend the serial number with the child serial number
86 if (joystick->serial) {
87 new_length = serial_length + 1 + SDL_strlen(joystick->serial);
88 new_serial = (char *)SDL_realloc(serial, new_length);
89 if (new_serial) {
90 if (serial) {
91 SDL_strlcat(new_serial, ",", new_length);
92 SDL_strlcat(new_serial, joystick->serial, new_length);
93 } else {
94 SDL_strlcpy(new_serial, joystick->serial, new_length);
95 }
96 serial = new_serial;
97 serial_length = new_length;
98 }
99 SDL_free(joystick->serial);
100 joystick->serial = NULL;
101 }
102 }
103
104 // Update the joystick with the combined serial numbers
105 if (joystick->serial) {
106 SDL_free(joystick->serial);
107 }
108 joystick->serial = serial;
109
110 return true;
111}
112
113static bool HIDAPI_DriverCombined_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
114{
115 int i;
116 bool result = false;
117
118 for (i = 0; i < device->num_children; ++i) {
119 SDL_HIDAPI_Device *child = device->children[i];
120 if (child->driver->RumbleJoystick(child, joystick, low_frequency_rumble, high_frequency_rumble)) {
121 result = true;
122 }
123 }
124 return result;
125}
126
127static bool HIDAPI_DriverCombined_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
128{
129 int i;
130 bool result = false;
131
132 for (i = 0; i < device->num_children; ++i) {
133 SDL_HIDAPI_Device *child = device->children[i];
134 if (child->driver->RumbleJoystickTriggers(child, joystick, left_rumble, right_rumble)) {
135 result = true;
136 }
137 }
138 return result;
139}
140
141static Uint32 HIDAPI_DriverCombined_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
142{
143 int i;
144 Uint32 caps = 0;
145
146 for (i = 0; i < device->num_children; ++i) {
147 SDL_HIDAPI_Device *child = device->children[i];
148 caps |= child->driver->GetJoystickCapabilities(child, joystick);
149 }
150 return caps;
151}
152
153static bool HIDAPI_DriverCombined_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
154{
155 int i;
156 bool result = false;
157
158 for (i = 0; i < device->num_children; ++i) {
159 SDL_HIDAPI_Device *child = device->children[i];
160 if (child->driver->SetJoystickLED(child, joystick, red, green, blue)) {
161 result = true;
162 }
163 }
164 return result;
165}
166
167static bool HIDAPI_DriverCombined_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
168{
169 return SDL_Unsupported();
170}
171
172static bool HIDAPI_DriverCombined_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
173{
174 int i;
175 bool result = false;
176
177 for (i = 0; i < device->num_children; ++i) {
178 SDL_HIDAPI_Device *child = device->children[i];
179 if (child->driver->SetJoystickSensorsEnabled(child, joystick, enabled)) {
180 result = true;
181 }
182 }
183 return result;
184}
185
186static bool HIDAPI_DriverCombined_UpdateDevice(SDL_HIDAPI_Device *device)
187{
188 int i;
189 int result = true;
190
191 for (i = 0; i < device->num_children; ++i) {
192 SDL_HIDAPI_Device *child = device->children[i];
193 if (!child->driver->UpdateDevice(child)) {
194 result = false;
195 }
196 }
197 return result;
198}
199
200static void HIDAPI_DriverCombined_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
201{
202 int i;
203
204 for (i = 0; i < device->num_children; ++i) {
205 SDL_HIDAPI_Device *child = device->children[i];
206 child->driver->CloseJoystick(child, joystick);
207 }
208}
209
210static void HIDAPI_DriverCombined_FreeDevice(SDL_HIDAPI_Device *device)
211{
212}
213
214SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined = {
215 "SDL_JOYSTICK_HIDAPI_COMBINED",
216 true,
217 HIDAPI_DriverCombined_RegisterHints,
218 HIDAPI_DriverCombined_UnregisterHints,
219 HIDAPI_DriverCombined_IsEnabled,
220 HIDAPI_DriverCombined_IsSupportedDevice,
221 HIDAPI_DriverCombined_InitDevice,
222 HIDAPI_DriverCombined_GetDevicePlayerIndex,
223 HIDAPI_DriverCombined_SetDevicePlayerIndex,
224 HIDAPI_DriverCombined_UpdateDevice,
225 HIDAPI_DriverCombined_OpenJoystick,
226 HIDAPI_DriverCombined_RumbleJoystick,
227 HIDAPI_DriverCombined_RumbleJoystickTriggers,
228 HIDAPI_DriverCombined_GetJoystickCapabilities,
229 HIDAPI_DriverCombined_SetJoystickLED,
230 HIDAPI_DriverCombined_SendJoystickEffect,
231 HIDAPI_DriverCombined_SetJoystickSensorsEnabled,
232 HIDAPI_DriverCombined_CloseJoystick,
233 HIDAPI_DriverCombined_FreeDevice,
234};
235
236#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_gamecube.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_gamecube.c
new file mode 100644
index 0000000..4d45c7a
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_gamecube.c
@@ -0,0 +1,534 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28#include "SDL_hidapi_rumble.h"
29#include "../../hidapi/SDL_hidapi_c.h"
30
31#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
32
33// Define this if you want to log all packets from the controller
34// #define DEBUG_GAMECUBE_PROTOCOL
35
36#define MAX_CONTROLLERS 4
37
38typedef struct
39{
40 bool pc_mode;
41 SDL_JoystickID joysticks[MAX_CONTROLLERS];
42 Uint8 wireless[MAX_CONTROLLERS];
43 Uint8 min_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT];
44 Uint8 max_axis[MAX_CONTROLLERS * SDL_GAMEPAD_AXIS_COUNT];
45 Uint8 rumbleAllowed[MAX_CONTROLLERS];
46 Uint8 rumble[1 + MAX_CONTROLLERS];
47 // Without this variable, hid_write starts to lag a TON
48 bool rumbleUpdate;
49 bool useRumbleBrake;
50} SDL_DriverGameCube_Context;
51
52static void HIDAPI_DriverGameCube_RegisterHints(SDL_HintCallback callback, void *userdata)
53{
54 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata);
55}
56
57static void HIDAPI_DriverGameCube_UnregisterHints(SDL_HintCallback callback, void *userdata)
58{
59 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, callback, userdata);
60}
61
62static bool HIDAPI_DriverGameCube_IsEnabled(void)
63{
64 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
65 SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,
66 SDL_HIDAPI_DEFAULT));
67}
68
69static bool HIDAPI_DriverGameCube_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
70{
71 if (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) {
72 // Nintendo Co., Ltd. Wii U GameCube Controller Adapter
73 return true;
74 }
75 if (vendor_id == USB_VENDOR_DRAGONRISE &&
76 (product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 ||
77 product_id == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2)) {
78 // EVORETRO GameCube Controller Adapter
79 return true;
80 }
81 return false;
82}
83
84static void ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index)
85{
86 SDL_memset(&ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 - 88, SDL_GAMEPAD_AXIS_COUNT);
87 SDL_memset(&ctx->max_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT], 128 + 88, SDL_GAMEPAD_AXIS_COUNT);
88
89 // Trigger axes may have a higher resting value
90 ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_LEFT_TRIGGER] = 40;
91 ctx->min_axis[joystick_index * SDL_GAMEPAD_AXIS_COUNT + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER] = 40;
92}
93
94static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
95{
96 if (hint) {
97 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata;
98 ctx->useRumbleBrake = SDL_GetStringBoolean(hint, false);
99 }
100}
101
102static bool HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
103{
104 SDL_DriverGameCube_Context *ctx;
105 Uint8 packet[37];
106 Uint8 *curSlot;
107 Uint8 i;
108 int size;
109 Uint8 initMagic = 0x13;
110 Uint8 rumbleMagic = 0x11;
111
112#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS
113 SDL_EnableGameCubeAdaptors();
114#endif
115
116 ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
117 if (!ctx) {
118 return false;
119 }
120 device->context = ctx;
121
122 ctx->joysticks[0] = 0;
123 ctx->joysticks[1] = 0;
124 ctx->joysticks[2] = 0;
125 ctx->joysticks[3] = 0;
126 ctx->rumble[0] = rumbleMagic;
127 ctx->useRumbleBrake = false;
128
129 if (device->vendor_id != USB_VENDOR_NINTENDO) {
130 ctx->pc_mode = true;
131 }
132
133 if (ctx->pc_mode) {
134 for (i = 0; i < MAX_CONTROLLERS; ++i) {
135 ResetAxisRange(ctx, i);
136 HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
137 }
138 } else {
139 // This is all that's needed to initialize the device. Really!
140 if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) {
141 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
142 "HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028");
143 return false;
144 }
145
146 // Wait for the adapter to initialize
147 SDL_Delay(10);
148
149 // Add all the applicable joysticks
150 while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
151#ifdef DEBUG_GAMECUBE_PROTOCOL
152 HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size);
153#endif
154 if (size < 37 || packet[0] != 0x21) {
155 continue; // Nothing to do yet...?
156 }
157
158 // Go through all 4 slots
159 curSlot = packet + 1;
160 for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
161 ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
162
163 // Only allow rumble if the adapter's second USB cable is connected
164 ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i];
165
166 if (curSlot[0] & 0x30) { // 0x10 - Wired, 0x20 - Wireless
167 if (ctx->joysticks[i] == 0) {
168 ResetAxisRange(ctx, i);
169 HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
170 }
171 } else {
172 if (ctx->joysticks[i] != 0) {
173 HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
174 ctx->joysticks[i] = 0;
175 }
176 continue;
177 }
178 }
179 }
180 }
181
182 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE,
183 SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
184
185 HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller");
186
187 return true;
188}
189
190static int HIDAPI_DriverGameCube_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
191{
192 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
193 Uint8 i;
194
195 for (i = 0; i < 4; ++i) {
196 if (instance_id == ctx->joysticks[i]) {
197 return i;
198 }
199 }
200 return -1;
201}
202
203static void HIDAPI_DriverGameCube_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
204{
205}
206
207static void HIDAPI_DriverGameCube_HandleJoystickPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, const Uint8 *packet, int size)
208{
209 SDL_Joystick *joystick;
210 Uint8 i, v;
211 Sint16 axis_value;
212 Uint64 timestamp = SDL_GetTicksNS();
213
214 if (size != 10) {
215 return; // How do we handle this packet?
216 }
217
218 i = packet[0] - 1;
219 if (i >= MAX_CONTROLLERS) {
220 return; // How do we handle this packet?
221 }
222
223 joystick = SDL_GetJoystickFromID(ctx->joysticks[i]);
224 if (!joystick) {
225 // Hasn't been opened yet, skip
226 return;
227 }
228
229#define READ_BUTTON(off, flag, button) \
230 SDL_SendJoystickButton( \
231 timestamp, \
232 joystick, \
233 button, \
234 ((packet[off] & flag) != 0));
235 READ_BUTTON(1, 0x02, 0) // A
236 READ_BUTTON(1, 0x04, 1) // B
237 READ_BUTTON(1, 0x08, 3) // Y
238 READ_BUTTON(1, 0x01, 2) // X
239 READ_BUTTON(2, 0x80, 4) // DPAD_LEFT
240 READ_BUTTON(2, 0x20, 5) // DPAD_RIGHT
241 READ_BUTTON(2, 0x40, 6) // DPAD_DOWN
242 READ_BUTTON(2, 0x10, 7) // DPAD_UP
243 READ_BUTTON(2, 0x02, 8) // START
244 READ_BUTTON(1, 0x80, 9) // RIGHTSHOULDER
245 /* These two buttons are for the bottoms of the analog triggers.
246 * More than likely, you're going to want to read the axes instead!
247 * -flibit
248 */
249 READ_BUTTON(1, 0x20, 10) // TRIGGERRIGHT
250 READ_BUTTON(1, 0x10, 11) // TRIGGERLEFT
251#undef READ_BUTTON
252
253#define READ_AXIS(off, axis, invert) \
254 v = invert ? (0xff - packet[off]) : packet[off]; \
255 if (v < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
256 ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \
257 if (v > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
258 ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = v; \
259 axis_value = (Sint16)HIDAPI_RemapVal(v, ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \
260 SDL_SendJoystickAxis( \
261 timestamp, \
262 joystick, \
263 axis, axis_value);
264 READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX, 0)
265 READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY, 1)
266 READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTX, 0)
267 READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTY, 1)
268 READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0)
269 READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0)
270#undef READ_AXIS
271}
272
273static void HIDAPI_DriverGameCube_HandleNintendoPacket(SDL_HIDAPI_Device *device, SDL_DriverGameCube_Context *ctx, Uint8 *packet, int size)
274{
275 SDL_Joystick *joystick;
276 Uint8 *curSlot;
277 Uint8 i;
278 Sint16 axis_value;
279 Uint64 timestamp = SDL_GetTicksNS();
280
281 if (size < 37 || packet[0] != 0x21) {
282 return; // Nothing to do right now...?
283 }
284
285 // Go through all 4 slots
286 curSlot = packet + 1;
287 for (i = 0; i < MAX_CONTROLLERS; i += 1, curSlot += 9) {
288 ctx->wireless[i] = (curSlot[0] & 0x20) != 0;
289
290 // Only allow rumble if the adapter's second USB cable is connected
291 ctx->rumbleAllowed[i] = (curSlot[0] & 0x04) && !ctx->wireless[i];
292
293 if (curSlot[0] & 0x30) { // 0x10 - Wired, 0x20 - Wireless
294 if (ctx->joysticks[i] == 0) {
295 ResetAxisRange(ctx, i);
296 HIDAPI_JoystickConnected(device, &ctx->joysticks[i]);
297 }
298 joystick = SDL_GetJoystickFromID(ctx->joysticks[i]);
299
300 // Hasn't been opened yet, skip
301 if (!joystick) {
302 continue;
303 }
304 } else {
305 if (ctx->joysticks[i] != 0) {
306 HIDAPI_JoystickDisconnected(device, ctx->joysticks[i]);
307 ctx->joysticks[i] = 0;
308 }
309 continue;
310 }
311
312#define READ_BUTTON(off, flag, button) \
313 SDL_SendJoystickButton( \
314 timestamp, \
315 joystick, \
316 button, \
317 ((curSlot[off] & flag) != 0));
318 READ_BUTTON(1, 0x01, 0) // A
319 READ_BUTTON(1, 0x02, 1) // B
320 READ_BUTTON(1, 0x04, 2) // X
321 READ_BUTTON(1, 0x08, 3) // Y
322 READ_BUTTON(1, 0x10, 4) // DPAD_LEFT
323 READ_BUTTON(1, 0x20, 5) // DPAD_RIGHT
324 READ_BUTTON(1, 0x40, 6) // DPAD_DOWN
325 READ_BUTTON(1, 0x80, 7) // DPAD_UP
326 READ_BUTTON(2, 0x01, 8) // START
327 READ_BUTTON(2, 0x02, 9) // RIGHTSHOULDER
328 /* These two buttons are for the bottoms of the analog triggers.
329 * More than likely, you're going to want to read the axes instead!
330 * -flibit
331 */
332 READ_BUTTON(2, 0x04, 10) // TRIGGERRIGHT
333 READ_BUTTON(2, 0x08, 11) // TRIGGERLEFT
334#undef READ_BUTTON
335
336#define READ_AXIS(off, axis) \
337 if (curSlot[off] < ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
338 ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \
339 if (curSlot[off] > ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis]) \
340 ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis] = curSlot[off]; \
341 axis_value = (Sint16)HIDAPI_RemapVal(curSlot[off], ctx->min_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], ctx->max_axis[i * SDL_GAMEPAD_AXIS_COUNT + axis], SDL_MIN_SINT16, SDL_MAX_SINT16); \
342 SDL_SendJoystickAxis( \
343 timestamp, \
344 joystick, \
345 axis, axis_value);
346 READ_AXIS(3, SDL_GAMEPAD_AXIS_LEFTX)
347 READ_AXIS(4, SDL_GAMEPAD_AXIS_LEFTY)
348 READ_AXIS(5, SDL_GAMEPAD_AXIS_RIGHTX)
349 READ_AXIS(6, SDL_GAMEPAD_AXIS_RIGHTY)
350 READ_AXIS(7, SDL_GAMEPAD_AXIS_LEFT_TRIGGER)
351 READ_AXIS(8, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)
352#undef READ_AXIS
353 }
354}
355
356static bool HIDAPI_DriverGameCube_UpdateDevice(SDL_HIDAPI_Device *device)
357{
358 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
359 Uint8 packet[USB_PACKET_LENGTH];
360 int size;
361
362 // Read input packet
363 while ((size = SDL_hid_read_timeout(device->dev, packet, sizeof(packet), 0)) > 0) {
364#ifdef DEBUG_GAMECUBE_PROTOCOL
365 HIDAPI_DumpPacket("Nintendo GameCube packet: size = %d", packet, size);
366#endif
367 if (ctx->pc_mode) {
368 HIDAPI_DriverGameCube_HandleJoystickPacket(device, ctx, packet, size);
369 } else {
370 HIDAPI_DriverGameCube_HandleNintendoPacket(device, ctx, packet, size);
371 }
372 }
373
374 // Write rumble packet
375 if (ctx->rumbleUpdate) {
376 SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
377 ctx->rumbleUpdate = false;
378 }
379
380 // If we got here, nothing bad happened!
381 return true;
382}
383
384static bool HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
385{
386 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
387 Uint8 i;
388
389 SDL_AssertJoysticksLocked();
390
391 for (i = 0; i < MAX_CONTROLLERS; i += 1) {
392 if (joystick->instance_id == ctx->joysticks[i]) {
393 joystick->nbuttons = 12;
394 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
395 if (ctx->wireless[i]) {
396 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
397 } else {
398 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
399 }
400 return true;
401 }
402 }
403 return false; // Should never get here!
404}
405
406static bool HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
407{
408 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
409 Uint8 i, val;
410
411 SDL_AssertJoysticksLocked();
412
413 if (ctx->pc_mode) {
414 return SDL_Unsupported();
415 }
416
417 for (i = 0; i < MAX_CONTROLLERS; i += 1) {
418 if (joystick->instance_id == ctx->joysticks[i]) {
419 if (ctx->wireless[i]) {
420 return SDL_SetError("Nintendo GameCube WaveBird controllers do not support rumble");
421 }
422 if (!ctx->rumbleAllowed[i]) {
423 return SDL_SetError("Second USB cable for WUP-028 not connected");
424 }
425 if (ctx->useRumbleBrake) {
426 if (low_frequency_rumble == 0 && high_frequency_rumble > 0) {
427 val = 0; // if only low is 0 we want to do a regular stop
428 } else if (low_frequency_rumble == 0 && high_frequency_rumble == 0) {
429 val = 2; // if both frequencies are 0 we want to do a hard stop
430 } else {
431 val = 1; // normal rumble
432 }
433 } else {
434 val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
435 }
436 if (val != ctx->rumble[i + 1]) {
437 ctx->rumble[i + 1] = val;
438 ctx->rumbleUpdate = true;
439 }
440 return true;
441 }
442 }
443
444 // Should never get here!
445 return SDL_SetError("Couldn't find joystick");
446}
447
448static bool HIDAPI_DriverGameCube_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
449{
450 return SDL_Unsupported();
451}
452
453static Uint32 HIDAPI_DriverGameCube_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
454{
455 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
456 Uint32 result = 0;
457
458 SDL_AssertJoysticksLocked();
459
460 if (!ctx->pc_mode) {
461 Uint8 i;
462
463 for (i = 0; i < MAX_CONTROLLERS; i += 1) {
464 if (joystick->instance_id == ctx->joysticks[i]) {
465 if (!ctx->wireless[i] && ctx->rumbleAllowed[i]) {
466 result |= SDL_JOYSTICK_CAP_RUMBLE;
467 break;
468 }
469 }
470 }
471 }
472
473 return result;
474}
475
476static bool HIDAPI_DriverGameCube_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
477{
478 return SDL_Unsupported();
479}
480
481static bool HIDAPI_DriverGameCube_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
482{
483 return SDL_Unsupported();
484}
485
486static bool HIDAPI_DriverGameCube_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
487{
488 return SDL_Unsupported();
489}
490
491static void HIDAPI_DriverGameCube_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
492{
493 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
494
495 // Stop rumble activity
496 if (ctx->rumbleUpdate) {
497 SDL_HIDAPI_SendRumble(device, ctx->rumble, sizeof(ctx->rumble));
498 ctx->rumbleUpdate = false;
499 }
500}
501
502static void HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device)
503{
504 SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
505
506 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE,
507 SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
508}
509
510SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube = {
511 SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
512 true,
513 HIDAPI_DriverGameCube_RegisterHints,
514 HIDAPI_DriverGameCube_UnregisterHints,
515 HIDAPI_DriverGameCube_IsEnabled,
516 HIDAPI_DriverGameCube_IsSupportedDevice,
517 HIDAPI_DriverGameCube_InitDevice,
518 HIDAPI_DriverGameCube_GetDevicePlayerIndex,
519 HIDAPI_DriverGameCube_SetDevicePlayerIndex,
520 HIDAPI_DriverGameCube_UpdateDevice,
521 HIDAPI_DriverGameCube_OpenJoystick,
522 HIDAPI_DriverGameCube_RumbleJoystick,
523 HIDAPI_DriverGameCube_RumbleJoystickTriggers,
524 HIDAPI_DriverGameCube_GetJoystickCapabilities,
525 HIDAPI_DriverGameCube_SetJoystickLED,
526 HIDAPI_DriverGameCube_SendJoystickEffect,
527 HIDAPI_DriverGameCube_SetJoystickSensorsEnabled,
528 HIDAPI_DriverGameCube_CloseJoystick,
529 HIDAPI_DriverGameCube_FreeDevice,
530};
531
532#endif // SDL_JOYSTICK_HIDAPI_GAMECUBE
533
534#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_luna.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_luna.c
new file mode 100644
index 0000000..7c889a6
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_luna.c
@@ -0,0 +1,421 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../SDL_sysjoystick.h"
26#include "SDL_hidapijoystick_c.h"
27#include "SDL_hidapi_rumble.h"
28
29#ifdef SDL_JOYSTICK_HIDAPI_LUNA
30
31// Define this if you want to log all packets from the controller
32// #define DEBUG_LUNA_PROTOCOL
33
34// Sending rumble on macOS blocks for a long time and eventually fails
35#ifndef SDL_PLATFORM_MACOS
36#define ENABLE_LUNA_BLUETOOTH_RUMBLE
37#endif
38
39enum
40{
41 SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE = 11,
42 SDL_GAMEPAD_NUM_LUNA_BUTTONS,
43};
44
45typedef struct
46{
47 Uint8 last_state[USB_PACKET_LENGTH];
48} SDL_DriverLuna_Context;
49
50static void HIDAPI_DriverLuna_RegisterHints(SDL_HintCallback callback, void *userdata)
51{
52 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata);
53}
54
55static void HIDAPI_DriverLuna_UnregisterHints(SDL_HintCallback callback, void *userdata)
56{
57 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata);
58}
59
60static bool HIDAPI_DriverLuna_IsEnabled(void)
61{
62 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LUNA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
63}
64
65static bool HIDAPI_DriverLuna_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
66{
67 return SDL_IsJoystickAmazonLunaController(vendor_id, product_id);
68}
69
70static bool HIDAPI_DriverLuna_InitDevice(SDL_HIDAPI_Device *device)
71{
72 SDL_DriverLuna_Context *ctx;
73
74 ctx = (SDL_DriverLuna_Context *)SDL_calloc(1, sizeof(*ctx));
75 if (!ctx) {
76 return false;
77 }
78 device->context = ctx;
79
80 HIDAPI_SetDeviceName(device, "Amazon Luna Controller");
81
82 return HIDAPI_JoystickConnected(device, NULL);
83}
84
85static int HIDAPI_DriverLuna_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
86{
87 return -1;
88}
89
90static void HIDAPI_DriverLuna_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
91{
92}
93
94static bool HIDAPI_DriverLuna_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
95{
96 SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;
97
98 SDL_AssertJoysticksLocked();
99
100 SDL_zeroa(ctx->last_state);
101
102 // Initialize the joystick capabilities
103 joystick->nbuttons = SDL_GAMEPAD_NUM_LUNA_BUTTONS;
104 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
105 joystick->nhats = 1;
106
107 return true;
108}
109
110static bool HIDAPI_DriverLuna_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
111{
112#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE
113 if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {
114 // Same packet as on Xbox One controllers connected via Bluetooth
115 Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };
116
117 // Magnitude is 1..100 so scale the 16-bit input here
118 rumble_packet[4] = (Uint8)(low_frequency_rumble / 655);
119 rumble_packet[5] = (Uint8)(high_frequency_rumble / 655);
120
121 if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
122 return SDL_SetError("Couldn't send rumble packet");
123 }
124
125 return true;
126 }
127#endif // ENABLE_LUNA_BLUETOOTH_RUMBLE
128
129 // There is currently no rumble packet over USB
130 return SDL_Unsupported();
131}
132
133static bool HIDAPI_DriverLuna_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
134{
135 return SDL_Unsupported();
136}
137
138static Uint32 HIDAPI_DriverLuna_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
139{
140 Uint32 result = 0;
141
142#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE
143 if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {
144 result |= SDL_JOYSTICK_CAP_RUMBLE;
145 }
146#endif
147
148 return result;
149}
150
151static bool HIDAPI_DriverLuna_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
152{
153 return SDL_Unsupported();
154}
155
156static bool HIDAPI_DriverLuna_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
157{
158 return SDL_Unsupported();
159}
160
161static bool HIDAPI_DriverLuna_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
162{
163 return SDL_Unsupported();
164}
165
166static void HIDAPI_DriverLuna_HandleUSBStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size)
167{
168 Uint64 timestamp = SDL_GetTicksNS();
169
170 if (ctx->last_state[1] != data[1]) {
171 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[1] & 0x01) != 0));
172 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[1] & 0x02) != 0));
173 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[1] & 0x04) != 0));
174 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[1] & 0x08) != 0));
175 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0));
176 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0));
177 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x40) != 0));
178 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x80) != 0));
179 }
180 if (ctx->last_state[2] != data[2]) {
181 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x01) != 0));
182 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[2] & 0x02) != 0));
183 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x04) != 0));
184 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x08) != 0));
185 }
186
187 if (ctx->last_state[3] != data[3]) {
188 Uint8 hat;
189
190 switch (data[3] & 0x0f) {
191 case 0:
192 hat = SDL_HAT_UP;
193 break;
194 case 1:
195 hat = SDL_HAT_RIGHTUP;
196 break;
197 case 2:
198 hat = SDL_HAT_RIGHT;
199 break;
200 case 3:
201 hat = SDL_HAT_RIGHTDOWN;
202 break;
203 case 4:
204 hat = SDL_HAT_DOWN;
205 break;
206 case 5:
207 hat = SDL_HAT_LEFTDOWN;
208 break;
209 case 6:
210 hat = SDL_HAT_LEFT;
211 break;
212 case 7:
213 hat = SDL_HAT_LEFTUP;
214 break;
215 default:
216 hat = SDL_HAT_CENTERED;
217 break;
218 }
219 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
220 }
221
222#define READ_STICK_AXIS(offset) \
223 (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16))
224 {
225 Sint16 axis = READ_STICK_AXIS(4);
226 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
227 axis = READ_STICK_AXIS(5);
228 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
229 axis = READ_STICK_AXIS(6);
230 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
231 axis = READ_STICK_AXIS(7);
232 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
233 }
234#undef READ_STICK_AXIS
235
236#define READ_TRIGGER_AXIS(offset) \
237 (Sint16) HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16)
238 {
239 Sint16 axis = READ_TRIGGER_AXIS(8);
240 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
241 axis = READ_TRIGGER_AXIS(9);
242 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
243 }
244#undef READ_TRIGGER_AXIS
245
246 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
247}
248
249static void HIDAPI_DriverLuna_HandleBluetoothStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size)
250{
251 Uint64 timestamp = SDL_GetTicksNS();
252
253 if (size >= 2 && data[0] == 0x02) {
254 // Home button has dedicated report
255 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x1) != 0));
256 return;
257 }
258
259 if (size >= 2 && data[0] == 0x04) {
260 // Battery level report
261 int percent = (int)SDL_roundf((data[1] / 255.0f) * 100.0f);
262 SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent);
263 return;
264 }
265
266 if (size < 17 || data[0] != 0x01) {
267 // We don't know how to handle this report
268 return;
269 }
270
271 if (ctx->last_state[13] != data[13]) {
272 Uint8 hat;
273
274 switch (data[13] & 0x0f) {
275 case 1:
276 hat = SDL_HAT_UP;
277 break;
278 case 2:
279 hat = SDL_HAT_RIGHTUP;
280 break;
281 case 3:
282 hat = SDL_HAT_RIGHT;
283 break;
284 case 4:
285 hat = SDL_HAT_RIGHTDOWN;
286 break;
287 case 5:
288 hat = SDL_HAT_DOWN;
289 break;
290 case 6:
291 hat = SDL_HAT_LEFTDOWN;
292 break;
293 case 7:
294 hat = SDL_HAT_LEFT;
295 break;
296 case 8:
297 hat = SDL_HAT_LEFTUP;
298 break;
299 default:
300 hat = SDL_HAT_CENTERED;
301 break;
302 }
303 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
304 }
305
306 if (ctx->last_state[14] != data[14]) {
307 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0));
308 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0));
309 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0));
310 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0));
311 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0));
312 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0));
313 }
314 if (ctx->last_state[15] != data[15]) {
315 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0));
316 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0));
317 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0));
318 }
319 if (ctx->last_state[16] != data[16]) {
320 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[16] & 0x01) != 0));
321 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[16] & 0x02) != 0));
322 }
323
324#define READ_STICK_AXIS(offset) \
325 (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16))
326 {
327 Sint16 axis = READ_STICK_AXIS(2);
328 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
329 axis = READ_STICK_AXIS(4);
330 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
331 axis = READ_STICK_AXIS(6);
332 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
333 axis = READ_STICK_AXIS(8);
334 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
335 }
336#undef READ_STICK_AXIS
337
338#define READ_TRIGGER_AXIS(offset) \
339 (Sint16) HIDAPI_RemapVal((float)((int)(((data[offset] | (data[offset + 1] << 8)) & 0x3ff) - 0x200)), 0x00 - 0x200, 0x3ff - 0x200, SDL_MIN_SINT16, SDL_MAX_SINT16)
340 {
341 Sint16 axis = READ_TRIGGER_AXIS(9);
342 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
343 axis = READ_TRIGGER_AXIS(11);
344 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
345 }
346#undef READ_TRIGGER_AXIS
347
348 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
349}
350
351static bool HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device)
352{
353 SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;
354 SDL_Joystick *joystick = NULL;
355 Uint8 data[USB_PACKET_LENGTH];
356 int size = 0;
357
358 if (device->num_joysticks > 0) {
359 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
360 } else {
361 return false;
362 }
363
364 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
365#ifdef DEBUG_LUNA_PROTOCOL
366 HIDAPI_DumpPacket("Amazon Luna packet: size = %d", data, size);
367#endif
368 if (!joystick) {
369 continue;
370 }
371
372 switch (size) {
373 case 10:
374 HIDAPI_DriverLuna_HandleUSBStatePacket(joystick, ctx, data, size);
375 break;
376 default:
377 HIDAPI_DriverLuna_HandleBluetoothStatePacket(joystick, ctx, data, size);
378 break;
379 }
380 }
381
382 if (size < 0) {
383 // Read error, device is disconnected
384 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
385 }
386 return (size >= 0);
387}
388
389static void HIDAPI_DriverLuna_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
390{
391}
392
393static void HIDAPI_DriverLuna_FreeDevice(SDL_HIDAPI_Device *device)
394{
395}
396
397SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna = {
398 SDL_HINT_JOYSTICK_HIDAPI_LUNA,
399 true,
400 HIDAPI_DriverLuna_RegisterHints,
401 HIDAPI_DriverLuna_UnregisterHints,
402 HIDAPI_DriverLuna_IsEnabled,
403 HIDAPI_DriverLuna_IsSupportedDevice,
404 HIDAPI_DriverLuna_InitDevice,
405 HIDAPI_DriverLuna_GetDevicePlayerIndex,
406 HIDAPI_DriverLuna_SetDevicePlayerIndex,
407 HIDAPI_DriverLuna_UpdateDevice,
408 HIDAPI_DriverLuna_OpenJoystick,
409 HIDAPI_DriverLuna_RumbleJoystick,
410 HIDAPI_DriverLuna_RumbleJoystickTriggers,
411 HIDAPI_DriverLuna_GetJoystickCapabilities,
412 HIDAPI_DriverLuna_SetJoystickLED,
413 HIDAPI_DriverLuna_SendJoystickEffect,
414 HIDAPI_DriverLuna_SetJoystickSensorsEnabled,
415 HIDAPI_DriverLuna_CloseJoystick,
416 HIDAPI_DriverLuna_FreeDevice,
417};
418
419#endif // SDL_JOYSTICK_HIDAPI_LUNA
420
421#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_nintendo.h b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_nintendo.h
new file mode 100644
index 0000000..0a5836f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_nintendo.h
@@ -0,0 +1,49 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22// These are values used in the controller type byte of the controller GUID
23
24// These values come directly out of the hardware, so don't change them
25typedef enum
26{
27 k_eSwitchDeviceInfoControllerType_Unknown = 0,
28 k_eSwitchDeviceInfoControllerType_JoyConLeft = 1,
29 k_eSwitchDeviceInfoControllerType_JoyConRight = 2,
30 k_eSwitchDeviceInfoControllerType_ProController = 3,
31 k_eSwitchDeviceInfoControllerType_LicProController = 6,
32 k_eSwitchDeviceInfoControllerType_HVCLeft = 7,
33 k_eSwitchDeviceInfoControllerType_HVCRight = 8,
34 k_eSwitchDeviceInfoControllerType_NESLeft = 9,
35 k_eSwitchDeviceInfoControllerType_NESRight = 10,
36 k_eSwitchDeviceInfoControllerType_SNES = 11,
37 k_eSwitchDeviceInfoControllerType_N64 = 12,
38 k_eSwitchDeviceInfoControllerType_SEGA_Genesis = 13,
39} ESwitchDeviceInfoControllerType;
40
41// These values are used internally but can be updated as needed
42typedef enum
43{
44 k_eWiiExtensionControllerType_Unknown = 0,
45 k_eWiiExtensionControllerType_None = 128,
46 k_eWiiExtensionControllerType_Nunchuk = 129,
47 k_eWiiExtensionControllerType_Gamepad = 130,
48 k_eWiiExtensionControllerType_WiiUPro = 131,
49} EWiiExtensionControllerType;
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps3.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps3.c
new file mode 100644
index 0000000..6c82647
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps3.c
@@ -0,0 +1,1446 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28#include "SDL_hidapi_rumble.h"
29
30#ifdef SDL_JOYSTICK_HIDAPI_PS3
31
32// Define this if you want to log all packets from the controller
33// #define DEBUG_PS3_PROTOCOL
34
35#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
36
37typedef enum
38{
39 k_EPS3ReportIdState = 1,
40 k_EPS3ReportIdEffects = 1,
41} EPS3ReportId;
42
43typedef enum
44{
45 k_EPS3SonySixaxisReportIdState = 0,
46 k_EPS3SonySixaxisReportIdEffects = 0,
47} EPS3SonySixaxisReportId;
48
49// Commands for Sony's sixaxis.sys Windows driver
50// All commands must be sent using 49-byte buffer containing output report
51// Byte 0 indicates reportId and must always be 0
52// Byte 1 indicates a command, supported values are specified below:
53typedef enum
54{
55 // This command allows to set user LEDs.
56 // Bytes 5,6.7.8 contain mode for corresponding LED: 0 - LED is off, 1 - LED in on, 2 - LED is flashing.
57 // Bytes 9-16 specify 64-bit LED flash period in 100 ns units if some LED is flashing, otherwise not used.
58 k_EPS3SixaxisCommandSetLEDs = 1,
59
60 // This command allows to set left and right motors.
61 // Byte 5 is right motor duration (0-255) and byte 6, if not zero, activates right motor. Zero value disables right motor.
62 // Byte 7 is left motor duration (0-255) and byte 8 is left motor amplitude (0-255).
63 k_EPS3SixaxisCommandSetMotors = 2,
64
65 // This command allows to block/unblock setting device LEDs by applications.
66 // Byte 5 is used as parameter - any non-zero value blocks LEDs, zero value will unblock LEDs.
67 k_EPS3SixaxisCommandBlockLEDs = 3,
68
69 // This command refreshes driver settings. No parameters used.
70 // When sixaxis driver loads it reads 'CurrentDriverSetting' binary value from 'HKLM\System\CurrentControlSet\Services\sixaxis\Parameters' registry key.
71 // If the key is not present then default values are used. Sending this command forces sixaxis driver to re-read the registry and update driver settings.
72 k_EPS3SixaxisCommandRefreshDriverSetting = 9,
73
74 // This command clears current bluetooth pairing. No parameters used.
75 k_EPS3SixaxisCommandClearPairing = 10
76} EPS3SixaxisDriverCommands;
77
78typedef struct
79{
80 SDL_HIDAPI_Device *device;
81 SDL_Joystick *joystick;
82 bool is_shanwan;
83 bool has_analog_buttons;
84 bool report_sensors;
85 bool effects_updated;
86 int player_index;
87 Uint8 rumble_left;
88 Uint8 rumble_right;
89 Uint8 last_state[USB_PACKET_LENGTH];
90} SDL_DriverPS3_Context;
91
92static bool HIDAPI_DriverPS3_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size);
93
94static void HIDAPI_DriverPS3_RegisterHints(SDL_HintCallback callback, void *userdata)
95{
96 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3, callback, userdata);
97}
98
99static void HIDAPI_DriverPS3_UnregisterHints(SDL_HintCallback callback, void *userdata)
100{
101 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3, callback, userdata);
102}
103
104static bool HIDAPI_DriverPS3_IsEnabled(void)
105{
106 bool default_value;
107
108#ifdef SDL_PLATFORM_MACOS
109 // This works well on macOS
110 default_value = true;
111#elif defined(SDL_PLATFORM_WIN32)
112 /* For official Sony driver (sixaxis.sys) use SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER.
113 *
114 * See https://github.com/ViGEm/DsHidMini as an alternative driver
115 */
116 default_value = false;
117#elif defined(SDL_PLATFORM_LINUX)
118 /* Linux drivers do a better job of managing the transition between
119 * USB and Bluetooth. There are also some quirks in communicating
120 * with PS3 controllers that have been implemented in SDL's hidapi
121 * for libusb, but are not possible to support using hidraw if the
122 * kernel doesn't already know about them.
123 */
124 default_value = false;
125#else
126 // Untested, default off
127 default_value = false;
128#endif
129
130 if (default_value) {
131 default_value = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT);
132 }
133 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3, default_value);
134}
135
136static bool HIDAPI_DriverPS3_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
137{
138 if (vendor_id == USB_VENDOR_SONY && product_id == USB_PRODUCT_SONY_DS3) {
139 return true;
140 }
141 if (vendor_id == USB_VENDOR_SHANWAN && product_id == USB_PRODUCT_SHANWAN_DS3) {
142 return true;
143 }
144 return false;
145}
146
147static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)
148{
149 SDL_memset(report, 0, length);
150 report[0] = report_id;
151 return SDL_hid_get_feature_report(dev, report, length);
152}
153
154static int SendFeatureReport(SDL_hid_device *dev, Uint8 *report, size_t length)
155{
156 return SDL_hid_send_feature_report(dev, report, length);
157}
158
159static bool HIDAPI_DriverPS3_InitDevice(SDL_HIDAPI_Device *device)
160{
161 SDL_DriverPS3_Context *ctx;
162 bool is_shanwan = false;
163
164 if (device->vendor_id == USB_VENDOR_SONY &&
165 SDL_strncasecmp(device->name, "ShanWan", 7) == 0) {
166 is_shanwan = true;
167 }
168 if (device->vendor_id == USB_VENDOR_SHANWAN ||
169 device->vendor_id == USB_VENDOR_SHANWAN_ALT) {
170 is_shanwan = true;
171 }
172
173 ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx));
174 if (!ctx) {
175 return false;
176 }
177 ctx->device = device;
178 ctx->is_shanwan = is_shanwan;
179 ctx->has_analog_buttons = true;
180
181 device->context = ctx;
182
183 // Set the controller into report mode over Bluetooth
184 if (device->is_bluetooth) {
185 Uint8 data[] = { 0xf4, 0x42, 0x03, 0x00, 0x00 };
186
187 SendFeatureReport(device->dev, data, sizeof(data));
188 }
189
190 // Set the controller into report mode over USB
191 if (!device->is_bluetooth) {
192 Uint8 data[USB_PACKET_LENGTH];
193
194 int size = ReadFeatureReport(device->dev, 0xf2, data, 17);
195 if (size < 0) {
196 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
197 "HIDAPI_DriverPS3_InitDevice(): Couldn't read feature report 0xf2");
198 return false;
199 }
200#ifdef DEBUG_PS3_PROTOCOL
201 HIDAPI_DumpPacket("PS3 0xF2 packet: size = %d", data, size);
202#endif
203 size = ReadFeatureReport(device->dev, 0xf5, data, 8);
204 if (size < 0) {
205 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
206 "HIDAPI_DriverPS3_InitDevice(): Couldn't read feature report 0xf5");
207 return false;
208 }
209#ifdef DEBUG_PS3_PROTOCOL
210 HIDAPI_DumpPacket("PS3 0xF5 packet: size = %d", data, size);
211#endif
212 if (!ctx->is_shanwan) {
213 // An output report could cause ShanWan controllers to rumble non-stop
214 SDL_hid_write(device->dev, data, 1);
215 }
216 }
217
218 device->type = SDL_GAMEPAD_TYPE_PS3;
219 HIDAPI_SetDeviceName(device, "PS3 Controller");
220
221 return HIDAPI_JoystickConnected(device, NULL);
222}
223
224static int HIDAPI_DriverPS3_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
225{
226 return -1;
227}
228
229static bool HIDAPI_DriverPS3_UpdateEffects(SDL_HIDAPI_Device *device)
230{
231 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
232
233 Uint8 effects[] = {
234 0x01, 0xff, 0x00, 0xff, 0x00,
235 0x00, 0x00, 0x00, 0x00, 0x00,
236 0xff, 0x27, 0x10, 0x00, 0x32,
237 0xff, 0x27, 0x10, 0x00, 0x32,
238 0xff, 0x27, 0x10, 0x00, 0x32,
239 0xff, 0x27, 0x10, 0x00, 0x32,
240 0x00, 0x00, 0x00, 0x00, 0x00
241 };
242
243 effects[2] = ctx->rumble_right ? 1 : 0;
244 effects[4] = ctx->rumble_left;
245
246 effects[9] = (0x01 << (1 + (ctx->player_index % 4)));
247
248 return HIDAPI_DriverPS3_SendJoystickEffect(device, ctx->joystick, effects, sizeof(effects));
249}
250
251static void HIDAPI_DriverPS3_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
252{
253 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
254
255 if (!ctx) {
256 return;
257 }
258
259 ctx->player_index = player_index;
260
261 // This will set the new LED state based on the new player index
262 HIDAPI_DriverPS3_UpdateEffects(device);
263}
264
265static bool HIDAPI_DriverPS3_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
266{
267 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
268
269 SDL_AssertJoysticksLocked();
270
271 ctx->joystick = joystick;
272 ctx->effects_updated = false;
273 ctx->rumble_left = 0;
274 ctx->rumble_right = 0;
275 SDL_zeroa(ctx->last_state);
276
277 // Initialize player index (needed for setting LEDs)
278 ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
279
280 // Initialize the joystick capabilities
281 joystick->nbuttons = 11;
282 joystick->naxes = 6;
283 if (ctx->has_analog_buttons) {
284 joystick->naxes += 10;
285 }
286 joystick->nhats = 1;
287
288 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f);
289
290 return true;
291}
292
293static bool HIDAPI_DriverPS3_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
294{
295 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
296
297 ctx->rumble_left = (low_frequency_rumble >> 8);
298 ctx->rumble_right = (high_frequency_rumble >> 8);
299
300 return HIDAPI_DriverPS3_UpdateEffects(device);
301}
302
303static bool HIDAPI_DriverPS3_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
304{
305 return SDL_Unsupported();
306}
307
308static Uint32 HIDAPI_DriverPS3_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
309{
310 return SDL_JOYSTICK_CAP_RUMBLE;
311}
312
313static bool HIDAPI_DriverPS3_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
314{
315 return SDL_Unsupported();
316}
317
318static bool HIDAPI_DriverPS3_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
319{
320 Uint8 data[49];
321 int report_size, offset;
322
323 SDL_zeroa(data);
324
325 data[0] = k_EPS3ReportIdEffects;
326 report_size = sizeof(data);
327 offset = 1;
328 SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));
329
330 if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) {
331 return SDL_SetError("Couldn't send rumble packet");
332 }
333 return true;
334}
335
336static bool HIDAPI_DriverPS3_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
337{
338 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
339
340 ctx->report_sensors = enabled;
341
342 return true;
343}
344
345static float HIDAPI_DriverPS3_ScaleAccel(Sint16 value)
346{
347 // Accelerometer values are in big endian order
348 value = SDL_Swap16BE(value);
349 return ((float)(value - 511) / 113.0f) * SDL_STANDARD_GRAVITY;
350}
351
352static void HIDAPI_DriverPS3_HandleMiniStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size)
353{
354 Sint16 axis;
355 Uint64 timestamp = SDL_GetTicksNS();
356
357 if (ctx->last_state[4] != data[4]) {
358 Uint8 hat;
359
360 switch (data[4] & 0x0f) {
361 case 0:
362 hat = SDL_HAT_UP;
363 break;
364 case 1:
365 hat = SDL_HAT_RIGHTUP;
366 break;
367 case 2:
368 hat = SDL_HAT_RIGHT;
369 break;
370 case 3:
371 hat = SDL_HAT_RIGHTDOWN;
372 break;
373 case 4:
374 hat = SDL_HAT_DOWN;
375 break;
376 case 5:
377 hat = SDL_HAT_LEFTDOWN;
378 break;
379 case 6:
380 hat = SDL_HAT_LEFT;
381 break;
382 case 7:
383 hat = SDL_HAT_LEFTUP;
384 break;
385 default:
386 hat = SDL_HAT_CENTERED;
387 break;
388 }
389 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
390
391 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[4] & 0x10) != 0));
392 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[4] & 0x20) != 0));
393 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[4] & 0x40) != 0));
394 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[4] & 0x80) != 0));
395 }
396
397 if (ctx->last_state[5] != data[5]) {
398 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[5] & 0x01) != 0));
399 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[5] & 0x02) != 0));
400 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[5] & 0x04) ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN);
401 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[5] & 0x08) ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN);
402 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[5] & 0x10) != 0));
403 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[5] & 0x20) != 0));
404 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[5] & 0x40) != 0));
405 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[5] & 0x80) != 0));
406 }
407
408 axis = ((int)data[2] * 257) - 32768;
409 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
410 axis = ((int)data[3] * 257) - 32768;
411 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
412 axis = ((int)data[0] * 257) - 32768;
413 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
414 axis = ((int)data[1] * 257) - 32768;
415 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
416
417 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
418}
419
420static void HIDAPI_DriverPS3_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size)
421{
422 Sint16 axis;
423 Uint64 timestamp = SDL_GetTicksNS();
424
425 if (ctx->last_state[2] != data[2]) {
426 Uint8 hat = 0;
427
428 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x01) != 0));
429 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x02) != 0));
430 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x04) != 0));
431 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x08) != 0));
432
433 if (data[2] & 0x10) {
434 hat |= SDL_HAT_UP;
435 }
436 if (data[2] & 0x20) {
437 hat |= SDL_HAT_RIGHT;
438 }
439 if (data[2] & 0x40) {
440 hat |= SDL_HAT_DOWN;
441 }
442 if (data[2] & 0x80) {
443 hat |= SDL_HAT_LEFT;
444 }
445 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
446 }
447
448 if (ctx->last_state[3] != data[3]) {
449 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0));
450 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x08) != 0));
451 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x10) != 0));
452 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));
453 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0));
454 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x80) != 0));
455 }
456
457 if (ctx->last_state[4] != data[4]) {
458 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[4] & 0x01) != 0));
459 }
460
461 axis = ((int)data[18] * 257) - 32768;
462 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
463 axis = ((int)data[19] * 257) - 32768;
464 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
465 axis = ((int)data[6] * 257) - 32768;
466 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
467 axis = ((int)data[7] * 257) - 32768;
468 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
469 axis = ((int)data[8] * 257) - 32768;
470 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
471 axis = ((int)data[9] * 257) - 32768;
472 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
473
474 // Buttons are mapped as axes in the order they appear in the button enumeration
475 if (ctx->has_analog_buttons) {
476 static int button_axis_offsets[] = {
477 24, // SDL_GAMEPAD_BUTTON_SOUTH
478 23, // SDL_GAMEPAD_BUTTON_EAST
479 25, // SDL_GAMEPAD_BUTTON_WEST
480 22, // SDL_GAMEPAD_BUTTON_NORTH
481 0, // SDL_GAMEPAD_BUTTON_BACK
482 0, // SDL_GAMEPAD_BUTTON_GUIDE
483 0, // SDL_GAMEPAD_BUTTON_START
484 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK
485 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK
486 20, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER
487 21, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER
488 14, // SDL_GAMEPAD_BUTTON_DPAD_UP
489 16, // SDL_GAMEPAD_BUTTON_DPAD_DOWN
490 17, // SDL_GAMEPAD_BUTTON_DPAD_LEFT
491 15, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT
492 };
493 Uint8 i, axis_index = 6;
494
495 for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) {
496 int offset = button_axis_offsets[i];
497 if (!offset) {
498 // This button doesn't report as an axis
499 continue;
500 }
501
502 axis = ((int)data[offset] * 257) - 32768;
503 SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis);
504 ++axis_index;
505 }
506 }
507
508 if (ctx->report_sensors) {
509 float sensor_data[3];
510
511 sensor_data[0] = HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[41], data[42]));
512 sensor_data[1] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[45], data[46]));
513 sensor_data[2] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[43], data[44]));
514 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data));
515 }
516
517 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
518}
519
520static bool HIDAPI_DriverPS3_UpdateDevice(SDL_HIDAPI_Device *device)
521{
522 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
523 SDL_Joystick *joystick = NULL;
524 Uint8 data[USB_PACKET_LENGTH];
525 int size;
526
527 if (device->num_joysticks > 0) {
528 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
529 } else {
530 return false;
531 }
532
533 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
534#ifdef DEBUG_PS3_PROTOCOL
535 HIDAPI_DumpPacket("PS3 packet: size = %d", data, size);
536#endif
537 if (!joystick) {
538 continue;
539 }
540
541 if (size == 7) {
542 // Seen on a ShanWan PS2 -> PS3 USB converter
543 HIDAPI_DriverPS3_HandleMiniStatePacket(joystick, ctx, data, size);
544
545 // Wait for the first report to set the LED state after the controller stops blinking
546 if (!ctx->effects_updated) {
547 HIDAPI_DriverPS3_UpdateEffects(device);
548 ctx->effects_updated = true;
549 }
550 continue;
551 }
552
553 switch (data[0]) {
554 case k_EPS3ReportIdState:
555 if (data[1] == 0xFF) {
556 // Invalid data packet, ignore
557 break;
558 }
559 HIDAPI_DriverPS3_HandleStatePacket(joystick, ctx, data, size);
560
561 // Wait for the first report to set the LED state after the controller stops blinking
562 if (!ctx->effects_updated) {
563 HIDAPI_DriverPS3_UpdateEffects(device);
564 ctx->effects_updated = true;
565 }
566 break;
567 default:
568#ifdef DEBUG_JOYSTICK
569 SDL_Log("Unknown PS3 packet: 0x%.2x", data[0]);
570#endif
571 break;
572 }
573 }
574
575 if (size < 0) {
576 // Read error, device is disconnected
577 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
578 }
579 return (size >= 0);
580}
581
582static void HIDAPI_DriverPS3_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
583{
584 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
585
586 ctx->joystick = NULL;
587}
588
589static void HIDAPI_DriverPS3_FreeDevice(SDL_HIDAPI_Device *device)
590{
591}
592
593SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3 = {
594 SDL_HINT_JOYSTICK_HIDAPI_PS3,
595 true,
596 HIDAPI_DriverPS3_RegisterHints,
597 HIDAPI_DriverPS3_UnregisterHints,
598 HIDAPI_DriverPS3_IsEnabled,
599 HIDAPI_DriverPS3_IsSupportedDevice,
600 HIDAPI_DriverPS3_InitDevice,
601 HIDAPI_DriverPS3_GetDevicePlayerIndex,
602 HIDAPI_DriverPS3_SetDevicePlayerIndex,
603 HIDAPI_DriverPS3_UpdateDevice,
604 HIDAPI_DriverPS3_OpenJoystick,
605 HIDAPI_DriverPS3_RumbleJoystick,
606 HIDAPI_DriverPS3_RumbleJoystickTriggers,
607 HIDAPI_DriverPS3_GetJoystickCapabilities,
608 HIDAPI_DriverPS3_SetJoystickLED,
609 HIDAPI_DriverPS3_SendJoystickEffect,
610 HIDAPI_DriverPS3_SetJoystickSensorsEnabled,
611 HIDAPI_DriverPS3_CloseJoystick,
612 HIDAPI_DriverPS3_FreeDevice,
613};
614
615static bool HIDAPI_DriverPS3ThirdParty_IsEnabled(void)
616{
617 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3,
618 SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,
619 SDL_HIDAPI_DEFAULT));
620}
621
622static bool HIDAPI_DriverPS3ThirdParty_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
623{
624 Uint8 data[USB_PACKET_LENGTH];
625 int size;
626
627 if (vendor_id == USB_VENDOR_LOGITECH &&
628 product_id == USB_PRODUCT_LOGITECH_CHILLSTREAM) {
629 return true;
630 }
631
632 if ((type == SDL_GAMEPAD_TYPE_PS3 && vendor_id != USB_VENDOR_SONY) ||
633 HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) {
634 if (device && device->dev) {
635 size = ReadFeatureReport(device->dev, 0x03, data, sizeof(data));
636 if (size == 8 && data[2] == 0x26) {
637 // Supported third party controller
638 return true;
639 } else {
640 return false;
641 }
642 } else {
643 // Might be supported by this driver, enumerate and find out
644 return true;
645 }
646 }
647 return false;
648}
649
650static bool HIDAPI_DriverPS3ThirdParty_InitDevice(SDL_HIDAPI_Device *device)
651{
652 SDL_DriverPS3_Context *ctx;
653
654 ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx));
655 if (!ctx) {
656 return false;
657 }
658 ctx->device = device;
659 if (device->vendor_id == USB_VENDOR_SWITCH && device->product_id == USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER) {
660 ctx->has_analog_buttons = false;
661 } else {
662 ctx->has_analog_buttons = true;
663 }
664
665 device->context = ctx;
666
667 device->type = SDL_GAMEPAD_TYPE_PS3;
668
669 if (device->vendor_id == USB_VENDOR_LOGITECH &&
670 device->product_id == USB_PRODUCT_LOGITECH_CHILLSTREAM) {
671 HIDAPI_SetDeviceName(device, "Logitech ChillStream");
672 }
673
674 return HIDAPI_JoystickConnected(device, NULL);
675}
676
677static int HIDAPI_DriverPS3ThirdParty_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
678{
679 return -1;
680}
681
682static void HIDAPI_DriverPS3ThirdParty_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
683{
684}
685
686static bool HIDAPI_DriverPS3ThirdParty_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
687{
688 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
689
690 SDL_AssertJoysticksLocked();
691
692 ctx->joystick = joystick;
693 SDL_zeroa(ctx->last_state);
694
695 // Initialize the joystick capabilities
696 joystick->nbuttons = 11;
697 joystick->naxes = 6;
698 if (ctx->has_analog_buttons) {
699 joystick->naxes += 10;
700 }
701 joystick->nhats = 1;
702
703 if (device->vendor_id == USB_VENDOR_SWITCH && device->product_id == USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER) {
704 // This is a wireless controller using a USB dongle
705 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
706 }
707
708 return true;
709}
710
711static bool HIDAPI_DriverPS3ThirdParty_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
712{
713 return SDL_Unsupported();
714}
715
716static bool HIDAPI_DriverPS3ThirdParty_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
717{
718 return SDL_Unsupported();
719}
720
721static Uint32 HIDAPI_DriverPS3ThirdParty_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
722{
723 return 0;
724}
725
726static bool HIDAPI_DriverPS3ThirdParty_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
727{
728 return SDL_Unsupported();
729}
730
731static bool HIDAPI_DriverPS3ThirdParty_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
732{
733 return SDL_Unsupported();
734}
735
736static bool HIDAPI_DriverPS3ThirdParty_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
737{
738 return SDL_Unsupported();
739}
740
741static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size)
742{
743 Sint16 axis;
744 Uint64 timestamp = SDL_GetTicksNS();
745
746 if (ctx->last_state[0] != data[0]) {
747 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x01) != 0));
748 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x02) != 0));
749 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x04) != 0));
750 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x08) != 0));
751 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x10) != 0));
752 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x20) != 0));
753 }
754
755 if (ctx->last_state[1] != data[1]) {
756 Uint8 hat;
757
758 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x01) != 0));
759 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x02) != 0));
760 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x04) != 0));
761 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x08) != 0));
762
763 switch (data[1] >> 4) {
764 case 0:
765 hat = SDL_HAT_UP;
766 break;
767 case 1:
768 hat = SDL_HAT_RIGHTUP;
769 break;
770 case 2:
771 hat = SDL_HAT_RIGHT;
772 break;
773 case 3:
774 hat = SDL_HAT_RIGHTDOWN;
775 break;
776 case 4:
777 hat = SDL_HAT_DOWN;
778 break;
779 case 5:
780 hat = SDL_HAT_LEFTDOWN;
781 break;
782 case 6:
783 hat = SDL_HAT_LEFT;
784 break;
785 case 7:
786 hat = SDL_HAT_LEFTUP;
787 break;
788 default:
789 hat = SDL_HAT_CENTERED;
790 break;
791 }
792 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
793 }
794
795 axis = ((int)data[16] * 257) - 32768;
796 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
797 axis = ((int)data[17] * 257) - 32768;
798 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
799 axis = ((int)data[2] * 257) - 32768;
800 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
801 axis = ((int)data[3] * 257) - 32768;
802 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
803 axis = ((int)data[4] * 257) - 32768;
804 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
805 axis = ((int)data[5] * 257) - 32768;
806 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
807
808 // Buttons are mapped as axes in the order they appear in the button enumeration
809 if (ctx->has_analog_buttons) {
810 static int button_axis_offsets[] = {
811 12, // SDL_GAMEPAD_BUTTON_SOUTH
812 11, // SDL_GAMEPAD_BUTTON_EAST
813 13, // SDL_GAMEPAD_BUTTON_WEST
814 10, // SDL_GAMEPAD_BUTTON_NORTH
815 0, // SDL_GAMEPAD_BUTTON_BACK
816 0, // SDL_GAMEPAD_BUTTON_GUIDE
817 0, // SDL_GAMEPAD_BUTTON_START
818 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK
819 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK
820 14, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER
821 15, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER
822 8, // SDL_GAMEPAD_BUTTON_DPAD_UP
823 9, // SDL_GAMEPAD_BUTTON_DPAD_DOWN
824 7, // SDL_GAMEPAD_BUTTON_DPAD_LEFT
825 6, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT
826 };
827 Uint8 i, axis_index = 6;
828
829 for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) {
830 int offset = button_axis_offsets[i];
831 if (!offset) {
832 // This button doesn't report as an axis
833 continue;
834 }
835
836 axis = ((int)data[offset] * 257) - 32768;
837 SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis);
838 ++axis_index;
839 }
840 }
841
842 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
843}
844
845static void HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size)
846{
847 Sint16 axis;
848 Uint64 timestamp = SDL_GetTicksNS();
849
850 if (ctx->last_state[0] != data[0]) {
851 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x01) != 0));
852 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x02) != 0));
853 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x04) != 0));
854 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x08) != 0));
855 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x10) != 0));
856 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x20) != 0));
857 }
858
859 if (ctx->last_state[1] != data[1]) {
860 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x01) != 0));
861 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x02) != 0));
862 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x04) != 0));
863 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x08) != 0));
864 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0));
865 }
866
867 if (ctx->device->vendor_id == USB_VENDOR_SAITEK && ctx->device->product_id == USB_PRODUCT_SAITEK_CYBORG_V3) {
868 // Cyborg V.3 Rumble Pad doesn't set the dpad bits as expected, so use the axes instead
869 Uint8 hat = 0;
870
871 if (data[7]) {
872 hat |= SDL_HAT_RIGHT;
873 }
874 if (data[8]) {
875 hat |= SDL_HAT_LEFT;
876 }
877 if (data[9]) {
878 hat |= SDL_HAT_UP;
879 }
880 if (data[10]) {
881 hat |= SDL_HAT_DOWN;
882 }
883 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
884 } else {
885 if (ctx->last_state[2] != data[2]) {
886 Uint8 hat;
887
888 switch (data[2] & 0x0f) {
889 case 0:
890 hat = SDL_HAT_UP;
891 break;
892 case 1:
893 hat = SDL_HAT_RIGHTUP;
894 break;
895 case 2:
896 hat = SDL_HAT_RIGHT;
897 break;
898 case 3:
899 hat = SDL_HAT_RIGHTDOWN;
900 break;
901 case 4:
902 hat = SDL_HAT_DOWN;
903 break;
904 case 5:
905 hat = SDL_HAT_LEFTDOWN;
906 break;
907 case 6:
908 hat = SDL_HAT_LEFT;
909 break;
910 case 7:
911 hat = SDL_HAT_LEFTUP;
912 break;
913 default:
914 hat = SDL_HAT_CENTERED;
915 break;
916 }
917 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
918 }
919 }
920
921 if (data[0] & 0x40) {
922 axis = 32767;
923 } else {
924 axis = ((int)data[17] * 257) - 32768;
925 }
926 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
927 if (data[0] & 0x80) {
928 axis = 32767;
929 } else {
930 axis = ((int)data[18] * 257) - 32768;
931 }
932 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
933 axis = ((int)data[3] * 257) - 32768;
934 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
935 axis = ((int)data[4] * 257) - 32768;
936 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
937 axis = ((int)data[5] * 257) - 32768;
938 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
939 axis = ((int)data[6] * 257) - 32768;
940 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
941
942 // Buttons are mapped as axes in the order they appear in the button enumeration
943 if (ctx->has_analog_buttons) {
944 static int button_axis_offsets[] = {
945 13, // SDL_GAMEPAD_BUTTON_SOUTH
946 12, // SDL_GAMEPAD_BUTTON_EAST
947 14, // SDL_GAMEPAD_BUTTON_WEST
948 11, // SDL_GAMEPAD_BUTTON_NORTH
949 0, // SDL_GAMEPAD_BUTTON_BACK
950 0, // SDL_GAMEPAD_BUTTON_GUIDE
951 0, // SDL_GAMEPAD_BUTTON_START
952 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK
953 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK
954 15, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER
955 16, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER
956 9, // SDL_GAMEPAD_BUTTON_DPAD_UP
957 10, // SDL_GAMEPAD_BUTTON_DPAD_DOWN
958 8, // SDL_GAMEPAD_BUTTON_DPAD_LEFT
959 7, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT
960 };
961 Uint8 i, axis_index = 6;
962
963 for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) {
964 int offset = button_axis_offsets[i];
965 if (!offset) {
966 // This button doesn't report as an axis
967 continue;
968 }
969
970 axis = ((int)data[offset] * 257) - 32768;
971 SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis);
972 ++axis_index;
973 }
974 }
975
976 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
977}
978
979static bool HIDAPI_DriverPS3ThirdParty_UpdateDevice(SDL_HIDAPI_Device *device)
980{
981 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
982 SDL_Joystick *joystick = NULL;
983 Uint8 data[USB_PACKET_LENGTH];
984 int size;
985
986 if (device->num_joysticks > 0) {
987 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
988 } else {
989 return false;
990 }
991
992 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
993#ifdef DEBUG_PS3_PROTOCOL
994 HIDAPI_DumpPacket("PS3 packet: size = %d", data, size);
995#endif
996 if (!joystick) {
997 continue;
998 }
999
1000 if (size >= 19) {
1001 HIDAPI_DriverPS3ThirdParty_HandleStatePacket19(joystick, ctx, data, size);
1002 } else if (size == 18) {
1003 // This packet format was seen with the Logitech ChillStream
1004 HIDAPI_DriverPS3ThirdParty_HandleStatePacket18(joystick, ctx, data, size);
1005 } else {
1006#ifdef DEBUG_JOYSTICK
1007 SDL_Log("Unknown PS3 packet, size %d", size);
1008#endif
1009 }
1010 }
1011
1012 if (size < 0) {
1013 // Read error, device is disconnected
1014 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1015 }
1016 return (size >= 0);
1017}
1018
1019static void HIDAPI_DriverPS3ThirdParty_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1020{
1021 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
1022
1023 ctx->joystick = NULL;
1024}
1025
1026static void HIDAPI_DriverPS3ThirdParty_FreeDevice(SDL_HIDAPI_Device *device)
1027{
1028}
1029
1030SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3ThirdParty = {
1031 SDL_HINT_JOYSTICK_HIDAPI_PS3,
1032 true,
1033 HIDAPI_DriverPS3_RegisterHints,
1034 HIDAPI_DriverPS3_UnregisterHints,
1035 HIDAPI_DriverPS3ThirdParty_IsEnabled,
1036 HIDAPI_DriverPS3ThirdParty_IsSupportedDevice,
1037 HIDAPI_DriverPS3ThirdParty_InitDevice,
1038 HIDAPI_DriverPS3ThirdParty_GetDevicePlayerIndex,
1039 HIDAPI_DriverPS3ThirdParty_SetDevicePlayerIndex,
1040 HIDAPI_DriverPS3ThirdParty_UpdateDevice,
1041 HIDAPI_DriverPS3ThirdParty_OpenJoystick,
1042 HIDAPI_DriverPS3ThirdParty_RumbleJoystick,
1043 HIDAPI_DriverPS3ThirdParty_RumbleJoystickTriggers,
1044 HIDAPI_DriverPS3ThirdParty_GetJoystickCapabilities,
1045 HIDAPI_DriverPS3ThirdParty_SetJoystickLED,
1046 HIDAPI_DriverPS3ThirdParty_SendJoystickEffect,
1047 HIDAPI_DriverPS3ThirdParty_SetJoystickSensorsEnabled,
1048 HIDAPI_DriverPS3ThirdParty_CloseJoystick,
1049 HIDAPI_DriverPS3ThirdParty_FreeDevice,
1050};
1051
1052static bool HIDAPI_DriverPS3_UpdateRumbleSonySixaxis(SDL_HIDAPI_Device *device);
1053static bool HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(SDL_HIDAPI_Device *device);
1054
1055static void HIDAPI_DriverPS3SonySixaxis_RegisterHints(SDL_HintCallback callback, void *userdata)
1056{
1057 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, callback, userdata);
1058}
1059
1060static void HIDAPI_DriverPS3SonySixaxis_UnregisterHints(SDL_HintCallback callback, void *userdata)
1061{
1062 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, callback, userdata);
1063}
1064
1065static bool HIDAPI_DriverPS3SonySixaxis_IsEnabled(void)
1066{
1067#ifdef SDL_PLATFORM_WIN32
1068 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, false);
1069#else
1070 return false;
1071#endif
1072}
1073
1074static bool HIDAPI_DriverPS3SonySixaxis_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
1075{
1076 if (vendor_id == USB_VENDOR_SONY && product_id == USB_PRODUCT_SONY_DS3) {
1077 return true;
1078 }
1079 return false;
1080}
1081
1082static bool HIDAPI_DriverPS3SonySixaxis_InitDevice(SDL_HIDAPI_Device *device)
1083{
1084 SDL_DriverPS3_Context *ctx;
1085
1086 ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx));
1087 if (!ctx) {
1088 return false;
1089 }
1090 ctx->device = device;
1091 ctx->has_analog_buttons = true;
1092
1093 device->context = ctx;
1094
1095 Uint8 data[USB_PACKET_LENGTH];
1096
1097 int size = ReadFeatureReport(device->dev, 0xf2, data, sizeof(data));
1098 if (size < 0) {
1099 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
1100 "HIDAPI_DriverPS3SonySixaxis_InitDevice(): Couldn't read feature report 0xf2. Trying again with 0x0.");
1101 SDL_zeroa(data);
1102 size = ReadFeatureReport(device->dev, 0x00, data, sizeof(data));
1103 if (size < 0) {
1104 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
1105 "HIDAPI_DriverPS3SonySixaxis_InitDevice(): Couldn't read feature report 0x00.");
1106 return false;
1107 }
1108#ifdef DEBUG_PS3_PROTOCOL
1109 HIDAPI_DumpPacket("PS3 0x0 packet: size = %d", data, size);
1110#endif
1111 }
1112#ifdef DEBUG_PS3_PROTOCOL
1113 HIDAPI_DumpPacket("PS3 0xF2 packet: size = %d", data, size);
1114#endif
1115
1116 device->type = SDL_GAMEPAD_TYPE_PS3;
1117 HIDAPI_SetDeviceName(device, "PS3 Controller");
1118
1119 return HIDAPI_JoystickConnected(device, NULL);
1120}
1121
1122static int HIDAPI_DriverPS3SonySixaxis_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
1123{
1124 return -1;
1125}
1126
1127static void HIDAPI_DriverPS3SonySixaxis_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
1128{
1129 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
1130
1131 if (!ctx) {
1132 return;
1133 }
1134
1135 ctx->player_index = player_index;
1136
1137 // This will set the new LED state based on the new player index
1138 HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(device);
1139}
1140
1141static bool HIDAPI_DriverPS3SonySixaxis_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1142{
1143 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
1144
1145 SDL_AssertJoysticksLocked();
1146
1147 ctx->joystick = joystick;
1148 ctx->effects_updated = false;
1149 ctx->rumble_left = 0;
1150 ctx->rumble_right = 0;
1151 SDL_zeroa(ctx->last_state);
1152
1153 // Initialize player index (needed for setting LEDs)
1154 ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
1155
1156 // Initialize the joystick capabilities
1157 joystick->nbuttons = 11;
1158 joystick->naxes = 6;
1159 if (ctx->has_analog_buttons) {
1160 joystick->naxes += 10;
1161 }
1162 joystick->nhats = 1;
1163
1164 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f);
1165
1166 return true;
1167}
1168
1169static bool HIDAPI_DriverPS3SonySixaxis_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1170{
1171 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
1172
1173 ctx->rumble_left = (low_frequency_rumble >> 8);
1174 ctx->rumble_right = (high_frequency_rumble >> 8);
1175
1176 return HIDAPI_DriverPS3_UpdateRumbleSonySixaxis(device);
1177}
1178
1179static bool HIDAPI_DriverPS3SonySixaxis_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
1180{
1181 return SDL_Unsupported();
1182}
1183
1184static Uint32 HIDAPI_DriverPS3SonySixaxis_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1185{
1186 return 0;
1187}
1188
1189static bool HIDAPI_DriverPS3SonySixaxis_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1190{
1191 return SDL_Unsupported();
1192}
1193
1194static bool HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
1195{
1196 Uint8 data[49];
1197 int report_size;
1198
1199 SDL_zeroa(data);
1200
1201 data[0] = k_EPS3SonySixaxisReportIdEffects;
1202 report_size = sizeof(data);
1203
1204 // No offset with Sony sixaxis.sys driver
1205 SDL_memcpy(&data, effect, SDL_min(sizeof(data), (size_t)size));
1206
1207 if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) {
1208 return SDL_SetError("Couldn't send rumble packet");
1209 }
1210 return true;
1211}
1212
1213static bool HIDAPI_DriverPS3SonySixaxis_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
1214{
1215 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
1216
1217 ctx->report_sensors = enabled;
1218
1219 return true;
1220}
1221
1222static void HIDAPI_DriverPS3SonySixaxis_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context *ctx, Uint8 *data, int size)
1223{
1224 Sint16 axis;
1225 Uint64 timestamp = SDL_GetTicksNS();
1226
1227 if (ctx->last_state[2] != data[2]) {
1228 Uint8 hat = 0;
1229
1230 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x01) != 0));
1231 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x02) != 0));
1232 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x04) != 0));
1233 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x08) != 0));
1234
1235 if (data[2] & 0x10) {
1236 hat |= SDL_HAT_UP;
1237 }
1238 if (data[2] & 0x20) {
1239 hat |= SDL_HAT_RIGHT;
1240 }
1241 if (data[2] & 0x40) {
1242 hat |= SDL_HAT_DOWN;
1243 }
1244 if (data[2] & 0x80) {
1245 hat |= SDL_HAT_LEFT;
1246 }
1247 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1248 }
1249
1250 if (ctx->last_state[3] != data[3]) {
1251 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0));
1252 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x08) != 0));
1253 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x10) != 0));
1254 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));
1255 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0));
1256 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x80) != 0));
1257 }
1258
1259 if (ctx->last_state[4] != data[4]) {
1260 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[4] & 0x01) != 0));
1261 }
1262
1263 axis = ((int)data[18] * 257) - 32768;
1264 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
1265 axis = ((int)data[19] * 257) - 32768;
1266 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
1267 axis = ((int)data[6] * 257) - 32768;
1268 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
1269 axis = ((int)data[7] * 257) - 32768;
1270 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
1271 axis = ((int)data[8] * 257) - 32768;
1272 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
1273 axis = ((int)data[9] * 257) - 32768;
1274 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
1275
1276 // Buttons are mapped as axes in the order they appear in the button enumeration
1277 if (ctx->has_analog_buttons) {
1278 static int button_axis_offsets[] = {
1279 24, // SDL_GAMEPAD_BUTTON_SOUTH
1280 23, // SDL_GAMEPAD_BUTTON_EAST
1281 25, // SDL_GAMEPAD_BUTTON_WEST
1282 22, // SDL_GAMEPAD_BUTTON_NORTH
1283 0, // SDL_GAMEPAD_BUTTON_BACK
1284 0, // SDL_GAMEPAD_BUTTON_GUIDE
1285 0, // SDL_GAMEPAD_BUTTON_START
1286 0, // SDL_GAMEPAD_BUTTON_LEFT_STICK
1287 0, // SDL_GAMEPAD_BUTTON_RIGHT_STICK
1288 20, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER
1289 21, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER
1290 14, // SDL_GAMEPAD_BUTTON_DPAD_UP
1291 16, // SDL_GAMEPAD_BUTTON_DPAD_DOWN
1292 17, // SDL_GAMEPAD_BUTTON_DPAD_LEFT
1293 15, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT
1294 };
1295 Uint8 i, axis_index = 6;
1296
1297 for (i = 0; i < SDL_arraysize(button_axis_offsets); ++i) {
1298 int offset = button_axis_offsets[i];
1299 if (!offset) {
1300 // This button doesn't report as an axis
1301 continue;
1302 }
1303
1304 axis = ((int)data[offset] * 257) - 32768;
1305 SDL_SendJoystickAxis(timestamp, joystick, axis_index, axis);
1306 ++axis_index;
1307 }
1308 }
1309
1310 if (ctx->report_sensors) {
1311 float sensor_data[3];
1312
1313 sensor_data[0] = HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[41], data[42]));
1314 sensor_data[1] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[45], data[46]));
1315 sensor_data[2] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[43], data[44]));
1316 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data));
1317 }
1318
1319 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
1320}
1321
1322static bool HIDAPI_DriverPS3SonySixaxis_UpdateDevice(SDL_HIDAPI_Device *device)
1323{
1324 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
1325 SDL_Joystick *joystick = NULL;
1326 Uint8 data[USB_PACKET_LENGTH];
1327 int size;
1328
1329 if (device->num_joysticks > 0) {
1330 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1331 } else {
1332 return false;
1333 }
1334
1335 if (!joystick) {
1336 return false;
1337 }
1338
1339 // With sixaxis.sys driver we need to use hid_get_feature_report instead of hid_read
1340 size = ReadFeatureReport(device->dev, 0x0, data, sizeof(data));
1341 if (size < 0) {
1342 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
1343 "HIDAPI_DriverPS3SonySixaxis_UpdateDevice(): Couldn't read feature report 0x00");
1344 return false;
1345 }
1346
1347 switch (data[0]) {
1348 case k_EPS3SonySixaxisReportIdState:
1349 HIDAPI_DriverPS3SonySixaxis_HandleStatePacket(joystick, ctx, &data[1], size - 1); // report data starts in data[1]
1350
1351 // Wait for the first report to set the LED state after the controller stops blinking
1352 if (!ctx->effects_updated) {
1353 HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(device);
1354 ctx->effects_updated = true;
1355 }
1356
1357 break;
1358 default:
1359#ifdef DEBUG_JOYSTICK
1360 SDL_Log("Unknown PS3 packet: 0x%.2x", data[0]);
1361#endif
1362 break;
1363 }
1364
1365 if (size < 0) {
1366 // Read error, device is disconnected
1367 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1368 }
1369 return (size >= 0);
1370}
1371
1372static void HIDAPI_DriverPS3SonySixaxis_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1373{
1374 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
1375
1376 ctx->joystick = NULL;
1377}
1378
1379static void HIDAPI_DriverPS3SonySixaxis_FreeDevice(SDL_HIDAPI_Device *device)
1380{
1381}
1382
1383static bool HIDAPI_DriverPS3_UpdateRumbleSonySixaxis(SDL_HIDAPI_Device *device)
1384{
1385 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
1386
1387 Uint8 effects[] = {
1388 0x0, // Report Id
1389 k_EPS3SixaxisCommandSetMotors, // 2 = Set Motors
1390 0x00, 0x00, 0x00, // padding
1391 0xff, // Small Motor duration - 0xff is forever
1392 0x00, // Small Motor off/on (0 or 1)
1393 0xff, // Large Motor duration - 0xff is forever
1394 0x00 // Large Motor force (0 to 255)
1395 };
1396
1397 effects[6] = ctx->rumble_right ? 1 : 0; // Small motor
1398 effects[8] = ctx->rumble_left; // Large motor
1399
1400 return HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect(device, ctx->joystick, effects, sizeof(effects));
1401}
1402
1403static bool HIDAPI_DriverPS3_UpdateLEDsSonySixaxis(SDL_HIDAPI_Device *device)
1404{
1405 SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
1406
1407 Uint8 effects[] = {
1408 0x0, // Report Id
1409 k_EPS3SixaxisCommandSetLEDs, // 1 = Set LEDs
1410 0x00, 0x00, 0x00, // padding
1411 0x00, 0x00, 0x00, 0x00 // LED #4, LED #3, LED #2, LED #1 (0 = Off, 1 = On, 2 = Flashing)
1412 };
1413
1414 // Turn on LED light on DS3 Controller for relevant player (player_index 0 lights up LED #1, player_index 1 lights up LED #2, etc)
1415 if (ctx->player_index < 4) {
1416 effects[8 - ctx->player_index] = 1;
1417 }
1418
1419 return HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect(device, ctx->joystick, effects, sizeof(effects));
1420}
1421
1422SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3SonySixaxis = {
1423 SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER,
1424 true,
1425 HIDAPI_DriverPS3SonySixaxis_RegisterHints,
1426 HIDAPI_DriverPS3SonySixaxis_UnregisterHints,
1427 HIDAPI_DriverPS3SonySixaxis_IsEnabled,
1428 HIDAPI_DriverPS3SonySixaxis_IsSupportedDevice,
1429 HIDAPI_DriverPS3SonySixaxis_InitDevice,
1430 HIDAPI_DriverPS3SonySixaxis_GetDevicePlayerIndex,
1431 HIDAPI_DriverPS3SonySixaxis_SetDevicePlayerIndex,
1432 HIDAPI_DriverPS3SonySixaxis_UpdateDevice,
1433 HIDAPI_DriverPS3SonySixaxis_OpenJoystick,
1434 HIDAPI_DriverPS3SonySixaxis_RumbleJoystick,
1435 HIDAPI_DriverPS3SonySixaxis_RumbleJoystickTriggers,
1436 HIDAPI_DriverPS3SonySixaxis_GetJoystickCapabilities,
1437 HIDAPI_DriverPS3SonySixaxis_SetJoystickLED,
1438 HIDAPI_DriverPS3SonySixaxis_SendJoystickEffect,
1439 HIDAPI_DriverPS3SonySixaxis_SetJoystickSensorsEnabled,
1440 HIDAPI_DriverPS3SonySixaxis_CloseJoystick,
1441 HIDAPI_DriverPS3SonySixaxis_FreeDevice,
1442};
1443
1444#endif // SDL_JOYSTICK_HIDAPI_PS3
1445
1446#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps4.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps4.c
new file mode 100644
index 0000000..7404bf2
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -0,0 +1,1390 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21/* This driver supports both simplified reports and the extended input reports enabled by Steam.
22 Code and logic contributed by Valve Corporation under the SDL zlib license.
23*/
24#include "SDL_internal.h"
25
26#ifdef SDL_JOYSTICK_HIDAPI
27
28#include "../../SDL_hints_c.h"
29#include "../SDL_sysjoystick.h"
30#include "SDL_hidapijoystick_c.h"
31#include "SDL_hidapi_rumble.h"
32
33#ifdef SDL_JOYSTICK_HIDAPI_PS4
34
35// Define this if you want to log all packets from the controller
36#if 0
37#define DEBUG_PS4_PROTOCOL
38#endif
39
40// Define this if you want to log calibration data
41#if 0
42#define DEBUG_PS4_CALIBRATION
43#endif
44
45#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500
46
47#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
48#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \
49 (((Uint32)(B)) << 8) | \
50 (((Uint32)(C)) << 16) | \
51 (((Uint32)(D)) << 24))
52
53enum
54{
55 SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD = 11
56};
57
58typedef enum
59{
60 k_EPS4ReportIdUsbState = 1,
61 k_EPS4ReportIdUsbEffects = 5,
62 k_EPS4ReportIdBluetoothState1 = 17,
63 k_EPS4ReportIdBluetoothState2 = 18,
64 k_EPS4ReportIdBluetoothState3 = 19,
65 k_EPS4ReportIdBluetoothState4 = 20,
66 k_EPS4ReportIdBluetoothState5 = 21,
67 k_EPS4ReportIdBluetoothState6 = 22,
68 k_EPS4ReportIdBluetoothState7 = 23,
69 k_EPS4ReportIdBluetoothState8 = 24,
70 k_EPS4ReportIdBluetoothState9 = 25,
71 k_EPS4ReportIdBluetoothEffects = 17,
72 k_EPS4ReportIdDisconnectMessage = 226,
73} EPS4ReportId;
74
75typedef enum
76{
77 k_ePS4FeatureReportIdGyroCalibration_USB = 0x02,
78 k_ePS4FeatureReportIdCapabilities = 0x03,
79 k_ePS4FeatureReportIdGyroCalibration_BT = 0x05,
80 k_ePS4FeatureReportIdSerialNumber = 0x12,
81} EPS4FeatureReportID;
82
83typedef struct
84{
85 Uint8 ucLeftJoystickX;
86 Uint8 ucLeftJoystickY;
87 Uint8 ucRightJoystickX;
88 Uint8 ucRightJoystickY;
89 Uint8 rgucButtonsHatAndCounter[3];
90 Uint8 ucTriggerLeft;
91 Uint8 ucTriggerRight;
92 Uint8 rgucTimestamp[2];
93 Uint8 _rgucPad0[1];
94 Uint8 rgucGyroX[2];
95 Uint8 rgucGyroY[2];
96 Uint8 rgucGyroZ[2];
97 Uint8 rgucAccelX[2];
98 Uint8 rgucAccelY[2];
99 Uint8 rgucAccelZ[2];
100 Uint8 _rgucPad1[5];
101 Uint8 ucBatteryLevel;
102 Uint8 _rgucPad2[4];
103 Uint8 ucTouchpadCounter1;
104 Uint8 rgucTouchpadData1[3];
105 Uint8 ucTouchpadCounter2;
106 Uint8 rgucTouchpadData2[3];
107} PS4StatePacket_t;
108
109typedef struct
110{
111 Uint8 ucRumbleRight;
112 Uint8 ucRumbleLeft;
113 Uint8 ucLedRed;
114 Uint8 ucLedGreen;
115 Uint8 ucLedBlue;
116 Uint8 ucLedDelayOn;
117 Uint8 ucLedDelayOff;
118 Uint8 _rgucPad0[8];
119 Uint8 ucVolumeLeft;
120 Uint8 ucVolumeRight;
121 Uint8 ucVolumeMic;
122 Uint8 ucVolumeSpeaker;
123} DS4EffectsState_t;
124
125typedef struct
126{
127 Sint16 bias;
128 float scale;
129} IMUCalibrationData;
130
131/* Rumble hint mode:
132 * "0": enhanced features are never used
133 * "1": enhanced features are always used
134 * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.
135 */
136typedef enum
137{
138 PS4_ENHANCED_REPORT_HINT_OFF,
139 PS4_ENHANCED_REPORT_HINT_ON,
140 PS4_ENHANCED_REPORT_HINT_AUTO
141} HIDAPI_PS4_EnhancedReportHint;
142
143typedef struct
144{
145 SDL_HIDAPI_Device *device;
146 SDL_Joystick *joystick;
147 bool is_dongle;
148 bool is_nacon_dongle;
149 bool official_controller;
150 bool sensors_supported;
151 bool lightbar_supported;
152 bool vibration_supported;
153 bool touchpad_supported;
154 bool effects_supported;
155 HIDAPI_PS4_EnhancedReportHint enhanced_report_hint;
156 bool enhanced_reports;
157 bool enhanced_mode;
158 bool enhanced_mode_available;
159 Uint8 report_interval;
160 bool report_sensors;
161 bool report_touchpad;
162 bool report_battery;
163 bool hardware_calibration;
164 IMUCalibrationData calibration[6];
165 Uint64 last_packet;
166 int player_index;
167 Uint8 rumble_left;
168 Uint8 rumble_right;
169 bool color_set;
170 Uint8 led_red;
171 Uint8 led_green;
172 Uint8 led_blue;
173 Uint16 gyro_numerator;
174 Uint16 gyro_denominator;
175 Uint16 accel_numerator;
176 Uint16 accel_denominator;
177 Uint64 sensor_ticks;
178 Uint16 last_tick;
179 Uint16 valid_crc_packets; // wrapping counter
180 PS4StatePacket_t last_state;
181} SDL_DriverPS4_Context;
182
183static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage);
184
185static void HIDAPI_DriverPS4_RegisterHints(SDL_HintCallback callback, void *userdata)
186{
187 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4, callback, userdata);
188}
189
190static void HIDAPI_DriverPS4_UnregisterHints(SDL_HintCallback callback, void *userdata)
191{
192 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4, callback, userdata);
193}
194
195static bool HIDAPI_DriverPS4_IsEnabled(void)
196{
197 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
198}
199
200static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)
201{
202 SDL_memset(report, 0, length);
203 report[0] = report_id;
204 return SDL_hid_get_feature_report(dev, report, length);
205}
206
207static bool HIDAPI_DriverPS4_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
208{
209 Uint8 data[USB_PACKET_LENGTH];
210 int size;
211
212 if (type == SDL_GAMEPAD_TYPE_PS4) {
213 return true;
214 }
215
216 if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) {
217 if (device && device->dev) {
218 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data));
219 if (size == 48 && data[2] == 0x27) {
220 // Supported third party controller
221 return true;
222 } else {
223 return false;
224 }
225 } else {
226 // Might be supported by this driver, enumerate and find out
227 return true;
228 }
229 }
230
231 return false;
232}
233
234static void SetLedsForPlayerIndex(DS4EffectsState_t *effects, int player_index)
235{
236 /* This list is the same as what hid-sony.c uses in the Linux kernel.
237 The first 4 values correspond to what the PS4 assigns.
238 */
239 static const Uint8 colors[7][3] = {
240 { 0x00, 0x00, 0x40 }, // Blue
241 { 0x40, 0x00, 0x00 }, // Red
242 { 0x00, 0x40, 0x00 }, // Green
243 { 0x20, 0x00, 0x20 }, // Pink
244 { 0x02, 0x01, 0x00 }, // Orange
245 { 0x00, 0x01, 0x01 }, // Teal
246 { 0x01, 0x01, 0x01 } // White
247 };
248
249 if (player_index >= 0) {
250 player_index %= SDL_arraysize(colors);
251 } else {
252 player_index = 0;
253 }
254
255 effects->ucLedRed = colors[player_index][0];
256 effects->ucLedGreen = colors[player_index][1];
257 effects->ucLedBlue = colors[player_index][2];
258}
259
260static bool ReadWiredSerial(SDL_HIDAPI_Device *device, char *serial, size_t serial_size)
261{
262 Uint8 data[USB_PACKET_LENGTH];
263 int size;
264
265 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data));
266 if (size >= 7 && (data[1] || data[2] || data[3] || data[4] || data[5] || data[6])) {
267 (void)SDL_snprintf(serial, serial_size, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
268 data[6], data[5], data[4], data[3], data[2], data[1]);
269 return true;
270 }
271 return false;
272}
273
274static bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device)
275{
276 SDL_DriverPS4_Context *ctx;
277 Uint8 data[USB_PACKET_LENGTH];
278 int size;
279 char serial[18];
280 SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
281
282 ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx));
283 if (!ctx) {
284 return false;
285 }
286 ctx->device = device;
287
288 ctx->gyro_numerator = 1;
289 ctx->gyro_denominator = 16;
290 ctx->accel_numerator = 1;
291 ctx->accel_denominator = 8192;
292
293 device->context = ctx;
294
295 if (device->serial && SDL_strlen(device->serial) == 12) {
296 int i, j;
297
298 j = -1;
299 for (i = 0; i < 12; i += 2) {
300 j += 1;
301 SDL_memmove(&serial[j], &device->serial[i], 2);
302 j += 2;
303 serial[j] = '-';
304 }
305 serial[j] = '\0';
306 } else {
307 serial[0] = '\0';
308 }
309
310 // Check for type of connection
311 ctx->is_dongle = (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_DONGLE);
312 if (ctx->is_dongle) {
313 ReadWiredSerial(device, serial, sizeof(serial));
314 ctx->enhanced_reports = true;
315 } else if (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) {
316 ctx->enhanced_reports = true;
317
318 } else if (device->vendor_id == USB_VENDOR_SONY) {
319 if (device->is_bluetooth) {
320 // Read a report to see if we're in enhanced mode
321 size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);
322#ifdef DEBUG_PS4_PROTOCOL
323 if (size > 0) {
324 HIDAPI_DumpPacket("PS4 first packet: size = %d", data, size);
325 } else {
326 SDL_Log("PS4 first packet: size = %d", size);
327 }
328#endif
329 if (size > 0 &&
330 data[0] >= k_EPS4ReportIdBluetoothState1 &&
331 data[0] <= k_EPS4ReportIdBluetoothState9) {
332 ctx->enhanced_reports = true;
333 }
334 } else {
335 ReadWiredSerial(device, serial, sizeof(serial));
336 ctx->enhanced_reports = true;
337 }
338 } else {
339 // Third party controllers appear to all be wired
340 ctx->enhanced_reports = true;
341 }
342
343 if (device->vendor_id == USB_VENDOR_SONY) {
344 ctx->official_controller = true;
345 ctx->sensors_supported = true;
346 ctx->lightbar_supported = true;
347 ctx->vibration_supported = true;
348 ctx->touchpad_supported = true;
349 } else {
350 // Third party controller capability request
351 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data));
352 // Get the device capabilities
353 if (size == 48 && data[2] == 0x27) {
354 Uint8 capabilities = data[4];
355 Uint8 device_type = data[5];
356 Uint16 gyro_numerator = LOAD16(data[10], data[11]);
357 Uint16 gyro_denominator = LOAD16(data[12], data[13]);
358 Uint16 accel_numerator = LOAD16(data[14], data[15]);
359 Uint16 accel_denominator = LOAD16(data[16], data[17]);
360
361#ifdef DEBUG_PS4_PROTOCOL
362 HIDAPI_DumpPacket("PS4 capabilities: size = %d", data, size);
363#endif
364 if (capabilities & 0x02) {
365 ctx->sensors_supported = true;
366 }
367 if (capabilities & 0x04) {
368 ctx->lightbar_supported = true;
369 }
370 if (capabilities & 0x08) {
371 ctx->vibration_supported = true;
372 }
373 if (capabilities & 0x40) {
374 ctx->touchpad_supported = true;
375 }
376
377 switch (device_type) {
378 case 0x00:
379 joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
380 break;
381 case 0x01:
382 joystick_type = SDL_JOYSTICK_TYPE_GUITAR;
383 break;
384 case 0x02:
385 joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;
386 break;
387 case 0x04:
388 joystick_type = SDL_JOYSTICK_TYPE_DANCE_PAD;
389 break;
390 case 0x06:
391 joystick_type = SDL_JOYSTICK_TYPE_WHEEL;
392 break;
393 case 0x07:
394 joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
395 break;
396 case 0x08:
397 joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;
398 break;
399 default:
400 joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN;
401 break;
402 }
403
404 if (gyro_numerator && gyro_denominator) {
405 ctx->gyro_numerator = gyro_numerator;
406 ctx->gyro_denominator = gyro_denominator;
407 }
408 if (accel_numerator && accel_denominator) {
409 ctx->accel_numerator = accel_numerator;
410 ctx->accel_denominator = accel_denominator;
411 }
412 } else if (device->vendor_id == USB_VENDOR_RAZER) {
413 // The Razer Raiju doesn't respond to the detection protocol, but has a touchpad and vibration
414 ctx->vibration_supported = true;
415 ctx->touchpad_supported = true;
416 }
417 }
418 ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported);
419
420 if (device->vendor_id == USB_VENDOR_NACON_ALT &&
421 device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRELESS) {
422 ctx->is_nacon_dongle = true;
423 }
424
425 if (device->vendor_id == USB_VENDOR_PDP &&
426 (device->product_id == USB_PRODUCT_VICTRIX_FS_PRO ||
427 device->product_id == USB_PRODUCT_VICTRIX_FS_PRO_V2)) {
428 /* The Victrix FS Pro V2 reports that it has lightbar support,
429 * but it doesn't respond to the effects packet, and will hang
430 * on reboot if we send it.
431 */
432 ctx->effects_supported = false;
433 }
434
435 device->joystick_type = joystick_type;
436 device->type = SDL_GAMEPAD_TYPE_PS4;
437 if (ctx->official_controller) {
438 HIDAPI_SetDeviceName(device, "PS4 Controller");
439 }
440 HIDAPI_SetDeviceSerial(device, serial);
441
442 // Prefer the USB device over the Bluetooth device
443 if (device->is_bluetooth) {
444 if (HIDAPI_HasConnectedUSBDevice(device->serial)) {
445 return true;
446 }
447 } else {
448 HIDAPI_DisconnectBluetoothDevice(device->serial);
449 }
450 if ((ctx->is_dongle || ctx->is_nacon_dongle) && serial[0] == '\0') {
451 // Not yet connected
452 return true;
453 }
454 return HIDAPI_JoystickConnected(device, NULL);
455}
456
457static int HIDAPI_DriverPS4_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
458{
459 return -1;
460}
461
462static bool HIDAPI_DriverPS4_LoadOfficialCalibrationData(SDL_HIDAPI_Device *device)
463{
464 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
465 int i, tries, size;
466 bool have_data = false;
467 Uint8 data[USB_PACKET_LENGTH];
468
469 if (!ctx->official_controller) {
470#ifdef DEBUG_PS4_CALIBRATION
471 SDL_Log("Not an official controller, ignoring calibration");
472#endif
473 return false;
474 }
475
476 for (tries = 0; tries < 5; ++tries) {
477 // For Bluetooth controllers, this report switches them into advanced report mode
478 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_USB, data, sizeof(data));
479 if (size < 35) {
480#ifdef DEBUG_PS4_CALIBRATION
481 SDL_Log("Short read of calibration data: %d, ignoring calibration", size);
482#endif
483 return false;
484 }
485
486 if (device->is_bluetooth) {
487 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_BT, data, sizeof(data));
488 if (size < 35) {
489#ifdef DEBUG_PS4_CALIBRATION
490 SDL_Log("Short read of calibration data: %d, ignoring calibration", size);
491#endif
492 return false;
493 }
494 }
495
496 // In some cases this report returns all zeros. Usually immediately after connection with the PS4 Dongle
497 for (i = 0; i < size; ++i) {
498 if (data[i]) {
499 have_data = true;
500 break;
501 }
502 }
503 if (have_data) {
504 break;
505 }
506
507 SDL_Delay(2);
508 }
509
510 if (have_data) {
511 Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias;
512 Sint16 sGyroPitchPlus, sGyroPitchMinus;
513 Sint16 sGyroYawPlus, sGyroYawMinus;
514 Sint16 sGyroRollPlus, sGyroRollMinus;
515 Sint16 sGyroSpeedPlus, sGyroSpeedMinus;
516
517 Sint16 sAccXPlus, sAccXMinus;
518 Sint16 sAccYPlus, sAccYMinus;
519 Sint16 sAccZPlus, sAccZMinus;
520
521 float flNumerator;
522 float flDenominator;
523 Sint16 sRange2g;
524
525#ifdef DEBUG_PS4_CALIBRATION
526 HIDAPI_DumpPacket("PS4 calibration packet: size = %d", data, size);
527#endif
528
529 sGyroPitchBias = LOAD16(data[1], data[2]);
530 sGyroYawBias = LOAD16(data[3], data[4]);
531 sGyroRollBias = LOAD16(data[5], data[6]);
532
533 if (device->is_bluetooth || ctx->is_dongle) {
534 sGyroPitchPlus = LOAD16(data[7], data[8]);
535 sGyroYawPlus = LOAD16(data[9], data[10]);
536 sGyroRollPlus = LOAD16(data[11], data[12]);
537 sGyroPitchMinus = LOAD16(data[13], data[14]);
538 sGyroYawMinus = LOAD16(data[15], data[16]);
539 sGyroRollMinus = LOAD16(data[17], data[18]);
540 } else {
541 sGyroPitchPlus = LOAD16(data[7], data[8]);
542 sGyroPitchMinus = LOAD16(data[9], data[10]);
543 sGyroYawPlus = LOAD16(data[11], data[12]);
544 sGyroYawMinus = LOAD16(data[13], data[14]);
545 sGyroRollPlus = LOAD16(data[15], data[16]);
546 sGyroRollMinus = LOAD16(data[17], data[18]);
547 }
548
549 sGyroSpeedPlus = LOAD16(data[19], data[20]);
550 sGyroSpeedMinus = LOAD16(data[21], data[22]);
551
552 sAccXPlus = LOAD16(data[23], data[24]);
553 sAccXMinus = LOAD16(data[25], data[26]);
554 sAccYPlus = LOAD16(data[27], data[28]);
555 sAccYMinus = LOAD16(data[29], data[30]);
556 sAccZPlus = LOAD16(data[31], data[32]);
557 sAccZMinus = LOAD16(data[33], data[34]);
558
559 flNumerator = (float)(sGyroSpeedPlus + sGyroSpeedMinus) * ctx->gyro_denominator / ctx->gyro_numerator;
560 flDenominator = (float)(SDL_abs(sGyroPitchPlus - sGyroPitchBias) + SDL_abs(sGyroPitchMinus - sGyroPitchBias));
561 if (flDenominator != 0.0f) {
562 ctx->calibration[0].bias = sGyroPitchBias;
563 ctx->calibration[0].scale = flNumerator / flDenominator;
564 }
565
566 flDenominator = (float)(SDL_abs(sGyroYawPlus - sGyroYawBias) + SDL_abs(sGyroYawMinus - sGyroYawBias));
567 if (flDenominator != 0.0f) {
568 ctx->calibration[1].bias = sGyroYawBias;
569 ctx->calibration[1].scale = flNumerator / flDenominator;
570 }
571
572 flDenominator = (float)(SDL_abs(sGyroRollPlus - sGyroRollBias) + SDL_abs(sGyroRollMinus - sGyroRollBias));
573 if (flDenominator != 0.0f) {
574 ctx->calibration[2].bias = sGyroRollBias;
575 ctx->calibration[2].scale = flNumerator / flDenominator;
576 }
577
578 sRange2g = sAccXPlus - sAccXMinus;
579 ctx->calibration[3].bias = sAccXPlus - sRange2g / 2;
580 ctx->calibration[3].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g;
581
582 sRange2g = sAccYPlus - sAccYMinus;
583 ctx->calibration[4].bias = sAccYPlus - sRange2g / 2;
584 ctx->calibration[4].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g;
585
586 sRange2g = sAccZPlus - sAccZMinus;
587 ctx->calibration[5].bias = sAccZPlus - sRange2g / 2;
588 ctx->calibration[5].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g;
589
590 ctx->hardware_calibration = true;
591 for (i = 0; i < 6; ++i) {
592#ifdef DEBUG_PS4_CALIBRATION
593 SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].scale);
594#endif
595 // Some controllers have a bad calibration
596 if (SDL_abs(ctx->calibration[i].bias) > 1024 || SDL_fabsf(1.0f - ctx->calibration[i].scale) > 0.5f) {
597#ifdef DEBUG_PS4_CALIBRATION
598 SDL_Log("invalid calibration, ignoring");
599#endif
600 ctx->hardware_calibration = false;
601 }
602 }
603 } else {
604#ifdef DEBUG_PS4_CALIBRATION
605 SDL_Log("Calibration data not available");
606#endif
607 }
608 return ctx->hardware_calibration;
609}
610
611static void HIDAPI_DriverPS4_LoadCalibrationData(SDL_HIDAPI_Device *device)
612{
613 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
614 int i;
615
616 if (!HIDAPI_DriverPS4_LoadOfficialCalibrationData(device)) {
617 for (i = 0; i < SDL_arraysize(ctx->calibration); ++i) {
618 ctx->calibration[i].bias = 0;
619 ctx->calibration[i].scale = 1.0f;
620 }
621 }
622
623 // Scale the raw data to the units expected by SDL
624 for (i = 0; i < SDL_arraysize(ctx->calibration); ++i) {
625 double scale = ctx->calibration[i].scale;
626
627 if (i < 3) {
628 scale *= ((double)ctx->gyro_numerator / ctx->gyro_denominator) * SDL_PI_D / 180.0;
629
630 if (device->vendor_id == USB_VENDOR_SONY &&
631 device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) {
632 // The Armor-X Pro seems to only deliver half the rotation it should
633 scale *= 2.0;
634 }
635 } else {
636 scale *= ((double)ctx->accel_numerator / ctx->accel_denominator) * SDL_STANDARD_GRAVITY;
637
638 if (device->vendor_id == USB_VENDOR_SONY &&
639 device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) {
640 /* The Armor-X Pro seems to only deliver half the acceleration it should,
641 * and in the opposite direction on all axes */
642 scale *= -2.0;
643 }
644 }
645 ctx->calibration[i].scale = (float)scale;
646 }
647}
648
649static float HIDAPI_DriverPS4_ApplyCalibrationData(SDL_DriverPS4_Context *ctx, int index, Sint16 value)
650{
651 IMUCalibrationData *calibration = &ctx->calibration[index];
652
653 return ((float)value - calibration->bias) * calibration->scale;
654}
655
656static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, bool application_usage)
657{
658 DS4EffectsState_t effects;
659
660 SDL_zero(effects);
661
662 if (ctx->vibration_supported) {
663 effects.ucRumbleLeft = ctx->rumble_left;
664 effects.ucRumbleRight = ctx->rumble_right;
665 }
666
667 if (ctx->lightbar_supported) {
668 // Populate the LED state with the appropriate color from our lookup table
669 if (ctx->color_set) {
670 effects.ucLedRed = ctx->led_red;
671 effects.ucLedGreen = ctx->led_green;
672 effects.ucLedBlue = ctx->led_blue;
673 } else {
674 SetLedsForPlayerIndex(&effects, ctx->player_index);
675 }
676 }
677 return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage);
678}
679
680static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device)
681{
682 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
683
684 if (ctx->enhanced_reports) {
685 // This is just a dummy packet that should have no effect, since we don't set the CRC
686 Uint8 data[78];
687
688 SDL_zeroa(data);
689
690 data[0] = k_EPS4ReportIdBluetoothEffects;
691 data[1] = 0xC0; // Magic value HID + CRC
692
693 if (SDL_HIDAPI_LockRumble()) {
694 SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data));
695 }
696 } else {
697#if 0 /* The 8BitDo Zero 2 has perfect emulation of a PS4 controller, except it
698 * only sends reports when the state changes, so we can't disconnect here.
699 */
700 // We can't even send an invalid effects packet, or it will put the controller in enhanced mode
701 if (device->num_joysticks > 0) {
702 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
703 }
704#endif
705 }
706}
707
708static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx)
709{
710 if (ctx->enhanced_mode_available) {
711 return;
712 }
713 ctx->enhanced_mode_available = true;
714
715 if (ctx->touchpad_supported) {
716 SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2);
717 ctx->report_touchpad = true;
718 }
719
720 if (ctx->sensors_supported) {
721 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval));
722 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval));
723 }
724
725 if (ctx->official_controller) {
726 ctx->report_battery = true;
727 }
728
729 HIDAPI_UpdateDeviceProperties(ctx->device);
730}
731
732static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx)
733{
734 HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);
735
736 if (!ctx->enhanced_mode) {
737 ctx->enhanced_mode = true;
738
739 // Switch into enhanced report mode
740 HIDAPI_DriverPS4_UpdateEffects(ctx, false);
741 }
742}
743
744static void HIDAPI_DriverPS4_SetEnhancedReportHint(SDL_DriverPS4_Context *ctx, HIDAPI_PS4_EnhancedReportHint enhanced_report_hint)
745{
746 switch (enhanced_report_hint) {
747 case PS4_ENHANCED_REPORT_HINT_OFF:
748 // Nothing to do, enhanced mode is a one-way ticket
749 break;
750 case PS4_ENHANCED_REPORT_HINT_ON:
751 HIDAPI_DriverPS4_SetEnhancedMode(ctx);
752 break;
753 case PS4_ENHANCED_REPORT_HINT_AUTO:
754 HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);
755 break;
756 }
757 ctx->enhanced_report_hint = enhanced_report_hint;
758}
759
760static void HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS4_Context *ctx)
761{
762 ctx->enhanced_reports = true;
763
764 if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) {
765 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
766 }
767}
768
769static void HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS4_Context *ctx)
770{
771 if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) {
772 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
773 }
774}
775
776static void SDLCALL SDL_PS4EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
777{
778 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;
779
780 if (ctx->device->is_bluetooth) {
781 if (hint && SDL_strcasecmp(hint, "auto") == 0) {
782 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_AUTO);
783 } else if (SDL_GetStringBoolean(hint, true)) {
784 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
785 } else {
786 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_OFF);
787 }
788 } else {
789 HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
790 }
791}
792
793static void SDLCALL SDL_PS4ReportIntervalHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
794{
795 const int DEFAULT_REPORT_INTERVAL = 4;
796 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;
797 int new_report_interval = DEFAULT_REPORT_INTERVAL;
798
799 if (hint) {
800 int report_interval = SDL_atoi(hint);
801 switch (report_interval) {
802 case 1:
803 case 2:
804 case 4:
805 // Valid values
806 new_report_interval = report_interval;
807 break;
808 default:
809 break;
810 }
811 }
812
813 if (new_report_interval != ctx->report_interval) {
814 ctx->report_interval = (Uint8)new_report_interval;
815
816 HIDAPI_DriverPS4_UpdateEffects(ctx, false);
817 SDL_LockJoysticks();
818 SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval));
819 SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval));
820 SDL_UnlockJoysticks();
821 }
822}
823
824static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
825{
826 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
827
828 if (!ctx->joystick) {
829 return;
830 }
831
832 ctx->player_index = player_index;
833
834 // This will set the new LED state based on the new player index
835 // SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode
836 HIDAPI_DriverPS4_UpdateEffects(ctx, false);
837}
838
839static bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
840{
841 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
842
843 SDL_AssertJoysticksLocked();
844
845 ctx->joystick = joystick;
846 ctx->last_packet = SDL_GetTicks();
847 ctx->report_sensors = false;
848 ctx->report_touchpad = false;
849 ctx->rumble_left = 0;
850 ctx->rumble_right = 0;
851 ctx->color_set = false;
852 SDL_zero(ctx->last_state);
853
854 // Initialize player index (needed for setting LEDs)
855 ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
856
857 // Initialize the joystick capabilities
858 joystick->nbuttons = 11;
859 if (ctx->touchpad_supported) {
860 joystick->nbuttons += 1;
861 }
862 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
863 joystick->nhats = 1;
864
865 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL,
866 SDL_PS4ReportIntervalHintChanged, ctx);
867 SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
868 SDL_PS4EnhancedReportsChanged, ctx);
869 return true;
870}
871
872static bool HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
873{
874 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
875
876 if (!ctx->vibration_supported) {
877 return SDL_Unsupported();
878 }
879
880 ctx->rumble_left = (low_frequency_rumble >> 8);
881 ctx->rumble_right = (high_frequency_rumble >> 8);
882
883 return HIDAPI_DriverPS4_UpdateEffects(ctx, true);
884}
885
886static bool HIDAPI_DriverPS4_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
887{
888 return SDL_Unsupported();
889}
890
891static Uint32 HIDAPI_DriverPS4_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
892{
893 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
894 Uint32 result = 0;
895
896 if (ctx->enhanced_mode_available) {
897 if (ctx->lightbar_supported) {
898 result |= SDL_JOYSTICK_CAP_RGB_LED;
899 }
900 if (ctx->vibration_supported) {
901 result |= SDL_JOYSTICK_CAP_RUMBLE;
902 }
903 }
904
905 return result;
906}
907
908static bool HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
909{
910 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
911
912 if (!ctx->lightbar_supported) {
913 return SDL_Unsupported();
914 }
915
916 ctx->color_set = true;
917 ctx->led_red = red;
918 ctx->led_green = green;
919 ctx->led_blue = blue;
920
921 return HIDAPI_DriverPS4_UpdateEffects(ctx, true);
922}
923
924static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage)
925{
926 Uint8 data[78];
927 int report_size, offset;
928
929 if (!ctx->effects_supported) {
930 // We shouldn't be sending packets to this controller
931 return SDL_Unsupported();
932 }
933
934 if (!ctx->enhanced_mode) {
935 if (application_usage) {
936 HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx);
937 }
938
939 if (!ctx->enhanced_mode) {
940 // We're not in enhanced mode, effects aren't allowed
941 return SDL_Unsupported();
942 }
943 }
944
945 SDL_zeroa(data);
946
947 if (ctx->device->is_bluetooth && ctx->official_controller) {
948 data[0] = k_EPS4ReportIdBluetoothEffects;
949 data[1] = 0xC0 | ctx->report_interval; // Magic value HID + CRC, also sets update interval
950 data[3] = 0x03; // 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval
951
952 report_size = 78;
953 offset = 6;
954 } else {
955 data[0] = k_EPS4ReportIdUsbEffects;
956 data[1] = 0x07; // Magic value
957
958 report_size = 32;
959 offset = 4;
960 }
961
962 SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));
963
964 if (ctx->device->is_bluetooth) {
965 // Bluetooth reports need a CRC at the end of the packet (at least on Linux)
966 Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation
967 Uint32 unCRC;
968 unCRC = SDL_crc32(0, &ubHdr, 1);
969 unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC)));
970 SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));
971 }
972
973 if (SDL_HIDAPI_SendRumble(ctx->device, data, report_size) != report_size) {
974 return SDL_SetError("Couldn't send rumble packet");
975 }
976 return true;
977}
978
979static bool HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
980{
981 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
982
983 return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, effect, size, true);
984}
985
986static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
987{
988 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
989
990 HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx);
991
992 if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) {
993 return SDL_Unsupported();
994 }
995
996 if (enabled) {
997 HIDAPI_DriverPS4_LoadCalibrationData(device);
998 }
999 ctx->report_sensors = enabled;
1000
1001 return true;
1002}
1003
1004static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS4_Context *ctx, PS4StatePacket_t *packet, int size)
1005{
1006 static const float TOUCHPAD_SCALEX = 1.0f / 1920;
1007 static const float TOUCHPAD_SCALEY = 1.0f / 920; // This is noted as being 944 resolution, but 920 feels better
1008 Sint16 axis;
1009 bool touchpad_down;
1010 int touchpad_x, touchpad_y;
1011 Uint64 timestamp = SDL_GetTicksNS();
1012
1013 if (size > 9 && ctx->report_touchpad && ctx->enhanced_reports) {
1014 touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);
1015 touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);
1016 touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);
1017 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1018
1019 touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);
1020 touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);
1021 touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);
1022 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1023 }
1024
1025 if (ctx->last_state.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) {
1026 {
1027 Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4);
1028
1029 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));
1030 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));
1031 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));
1032 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));
1033 }
1034 {
1035 Uint8 hat;
1036 Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F);
1037
1038 switch (data) {
1039 case 0:
1040 hat = SDL_HAT_UP;
1041 break;
1042 case 1:
1043 hat = SDL_HAT_RIGHTUP;
1044 break;
1045 case 2:
1046 hat = SDL_HAT_RIGHT;
1047 break;
1048 case 3:
1049 hat = SDL_HAT_RIGHTDOWN;
1050 break;
1051 case 4:
1052 hat = SDL_HAT_DOWN;
1053 break;
1054 case 5:
1055 hat = SDL_HAT_LEFTDOWN;
1056 break;
1057 case 6:
1058 hat = SDL_HAT_LEFT;
1059 break;
1060 case 7:
1061 hat = SDL_HAT_LEFTUP;
1062 break;
1063 default:
1064 hat = SDL_HAT_CENTERED;
1065 break;
1066 }
1067 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1068 }
1069 }
1070
1071 if (ctx->last_state.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) {
1072 Uint8 data = packet->rgucButtonsHatAndCounter[1];
1073
1074 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));
1075 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));
1076 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));
1077 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));
1078 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));
1079 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));
1080 }
1081
1082 /* Some fightsticks, ex: Victrix FS Pro will only this these digital trigger bits and not the analog values so this needs to run whenever the
1083 trigger is evaluated
1084 */
1085 if (packet->rgucButtonsHatAndCounter[1] & 0x0C) {
1086 Uint8 data = packet->rgucButtonsHatAndCounter[1];
1087 packet->ucTriggerLeft = (data & 0x04) && packet->ucTriggerLeft == 0 ? 255 : packet->ucTriggerLeft;
1088 packet->ucTriggerRight = (data & 0x08) && packet->ucTriggerRight == 0 ? 255 : packet->ucTriggerRight;
1089 }
1090
1091 if (ctx->last_state.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) {
1092 Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03);
1093
1094 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));
1095 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD, ((data & 0x02) != 0));
1096 }
1097
1098 axis = ((int)packet->ucTriggerLeft * 257) - 32768;
1099 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
1100 axis = ((int)packet->ucTriggerRight * 257) - 32768;
1101 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
1102 axis = ((int)packet->ucLeftJoystickX * 257) - 32768;
1103 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
1104 axis = ((int)packet->ucLeftJoystickY * 257) - 32768;
1105 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
1106 axis = ((int)packet->ucRightJoystickX * 257) - 32768;
1107 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
1108 axis = ((int)packet->ucRightJoystickY * 257) - 32768;
1109 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
1110
1111 if (size > 9 && ctx->report_battery && ctx->enhanced_reports) {
1112 SDL_PowerState state;
1113 int percent;
1114 Uint8 level = (packet->ucBatteryLevel & 0x0F);
1115
1116 if (packet->ucBatteryLevel & 0x10) {
1117 if (level <= 10) {
1118 state = SDL_POWERSTATE_CHARGING;
1119 percent = SDL_min(level * 10 + 5, 100);
1120 } else if (level == 11) {
1121 state = SDL_POWERSTATE_CHARGED;
1122 percent = 100;
1123 } else {
1124 state = SDL_POWERSTATE_UNKNOWN;
1125 percent = 0;
1126 }
1127 } else {
1128 state = SDL_POWERSTATE_ON_BATTERY;
1129 percent = SDL_min(level * 10 + 5, 100);
1130 }
1131 SDL_SendJoystickPowerInfo(joystick, state, percent);
1132 }
1133
1134 if (size > 9 && ctx->report_sensors) {
1135 Uint16 tick;
1136 Uint16 delta;
1137 Uint64 sensor_timestamp;
1138 float data[3];
1139
1140 tick = LOAD16(packet->rgucTimestamp[0], packet->rgucTimestamp[1]);
1141 if (ctx->last_tick < tick) {
1142 delta = (tick - ctx->last_tick);
1143 } else {
1144 delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);
1145 }
1146 ctx->sensor_ticks += delta;
1147 ctx->last_tick = tick;
1148
1149 // Sensor timestamp is in 5.33us units
1150 sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US * 16) / 3;
1151
1152 data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));
1153 data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));
1154 data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));
1155 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3);
1156
1157 data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));
1158 data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));
1159 data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));
1160 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3);
1161 }
1162
1163 SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));
1164}
1165
1166static bool VerifyCRC(Uint8 *data, int size)
1167{
1168 Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation
1169 Uint32 unCRC, unPacketCRC;
1170 Uint8 *packetCRC = data + size - sizeof(unPacketCRC);
1171 unCRC = SDL_crc32(0, &ubHdr, 1);
1172 unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC)));
1173
1174 unPacketCRC = LOAD32(packetCRC[0],
1175 packetCRC[1],
1176 packetCRC[2],
1177 packetCRC[3]);
1178 return (unCRC == unPacketCRC);
1179}
1180
1181static bool HIDAPI_DriverPS4_IsPacketValid(SDL_DriverPS4_Context *ctx, Uint8 *data, int size)
1182{
1183 switch (data[0]) {
1184 case k_EPS4ReportIdUsbState:
1185 if (size == 10) {
1186 // This is non-enhanced mode, this packet is fine
1187 return true;
1188 }
1189
1190 if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS4StatePacket_t))) {
1191 // The report timestamp doesn't change when the controller isn't connected
1192 PS4StatePacket_t *packet = (PS4StatePacket_t *)&data[1];
1193 if (SDL_memcmp(packet->rgucTimestamp, ctx->last_state.rgucTimestamp, sizeof(packet->rgucTimestamp)) == 0) {
1194 return false;
1195 }
1196 if (ctx->last_state.rgucAccelX[0] == 0 && ctx->last_state.rgucAccelX[1] == 0 &&
1197 ctx->last_state.rgucAccelY[0] == 0 && ctx->last_state.rgucAccelY[1] == 0 &&
1198 ctx->last_state.rgucAccelZ[0] == 0 && ctx->last_state.rgucAccelZ[1] == 0) {
1199 // We don't have any state to compare yet, go ahead and copy it
1200 SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS4StatePacket_t));
1201 return false;
1202 }
1203 }
1204
1205 /* In the case of a DS4 USB dongle, bit[2] of byte 31 indicates if a DS4 is actually connected (indicated by '0').
1206 * For non-dongle, this bit is always 0 (connected).
1207 * This is usually the ID over USB, but the DS4v2 that started shipping with the PS4 Slim will also send this
1208 * packet over BT with a size of 128
1209 */
1210 if (size >= 64 && !(data[31] & 0x04)) {
1211 return true;
1212 }
1213 break;
1214 case k_EPS4ReportIdBluetoothState1:
1215 case k_EPS4ReportIdBluetoothState2:
1216 case k_EPS4ReportIdBluetoothState3:
1217 case k_EPS4ReportIdBluetoothState4:
1218 case k_EPS4ReportIdBluetoothState5:
1219 case k_EPS4ReportIdBluetoothState6:
1220 case k_EPS4ReportIdBluetoothState7:
1221 case k_EPS4ReportIdBluetoothState8:
1222 case k_EPS4ReportIdBluetoothState9:
1223 // Bluetooth state packets have two additional bytes at the beginning, the first notes if HID data is present
1224 if (size >= 78 && (data[1] & 0x80)) {
1225 if (VerifyCRC(data, 78)) {
1226 ++ctx->valid_crc_packets;
1227 } else {
1228 if (ctx->valid_crc_packets > 0) {
1229 --ctx->valid_crc_packets;
1230 }
1231 if (ctx->valid_crc_packets >= 3) {
1232 // We're generally getting valid CRC, but failed one
1233 return false;
1234 }
1235 }
1236 return true;
1237 }
1238 break;
1239 default:
1240 break;
1241 }
1242 return false;
1243}
1244
1245static bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
1246{
1247 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
1248 SDL_Joystick *joystick = NULL;
1249 Uint8 data[USB_PACKET_LENGTH * 2];
1250 int size;
1251 int packet_count = 0;
1252 Uint64 now = SDL_GetTicks();
1253
1254 if (device->num_joysticks > 0) {
1255 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1256 }
1257
1258 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
1259#ifdef DEBUG_PS4_PROTOCOL
1260 HIDAPI_DumpPacket("PS4 packet: size = %d", data, size);
1261#endif
1262 if (!HIDAPI_DriverPS4_IsPacketValid(ctx, data, size)) {
1263 continue;
1264 }
1265
1266 ++packet_count;
1267 ctx->last_packet = now;
1268
1269 if (!joystick) {
1270 continue;
1271 }
1272
1273 switch (data[0]) {
1274 case k_EPS4ReportIdUsbState:
1275 HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[1], size - 1);
1276 break;
1277 case k_EPS4ReportIdBluetoothState1:
1278 case k_EPS4ReportIdBluetoothState2:
1279 case k_EPS4ReportIdBluetoothState3:
1280 case k_EPS4ReportIdBluetoothState4:
1281 case k_EPS4ReportIdBluetoothState5:
1282 case k_EPS4ReportIdBluetoothState6:
1283 case k_EPS4ReportIdBluetoothState7:
1284 case k_EPS4ReportIdBluetoothState8:
1285 case k_EPS4ReportIdBluetoothState9:
1286 // This is the extended report, we can enable effects now in auto mode
1287 HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(ctx);
1288
1289 // Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present
1290 HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[3], size - 3);
1291 break;
1292 default:
1293#ifdef DEBUG_JOYSTICK
1294 SDL_Log("Unknown PS4 packet: 0x%.2x", data[0]);
1295#endif
1296 break;
1297 }
1298 }
1299
1300 if (device->is_bluetooth) {
1301 if (packet_count == 0) {
1302 // Check to see if it looks like the device disconnected
1303 if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {
1304 // Send an empty output report to tickle the Bluetooth stack
1305 HIDAPI_DriverPS4_TickleBluetooth(device);
1306 ctx->last_packet = now;
1307 }
1308 } else {
1309 // Reconnect the Bluetooth device once the USB device is gone
1310 if (device->num_joysticks == 0 &&
1311 !HIDAPI_HasConnectedUSBDevice(device->serial)) {
1312 HIDAPI_JoystickConnected(device, NULL);
1313 }
1314 }
1315 }
1316
1317 if (ctx->is_dongle || ctx->is_nacon_dongle) {
1318 if (packet_count == 0) {
1319 if (device->num_joysticks > 0) {
1320 // Check to see if it looks like the device disconnected
1321 if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {
1322 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1323 }
1324 }
1325 } else {
1326 if (device->num_joysticks == 0) {
1327 char serial[18];
1328 size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data));
1329 if (size >= 7 && (data[1] || data[2] || data[3] || data[4] || data[5] || data[6])) {
1330 (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
1331 data[6], data[5], data[4], data[3], data[2], data[1]);
1332 HIDAPI_SetDeviceSerial(device, serial);
1333 }
1334 HIDAPI_JoystickConnected(device, NULL);
1335 }
1336 }
1337 }
1338
1339 if (packet_count == 0 && size < 0 && device->num_joysticks > 0) {
1340 // Read error, device is disconnected
1341 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1342 }
1343 return (size >= 0);
1344}
1345
1346static void HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1347{
1348 SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
1349
1350 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL,
1351 SDL_PS4ReportIntervalHintChanged, ctx);
1352 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
1353 SDL_PS4EnhancedReportsChanged, ctx);
1354
1355 ctx->joystick = NULL;
1356
1357 ctx->report_sensors = false;
1358 ctx->enhanced_mode = false;
1359 ctx->enhanced_mode_available = false;
1360}
1361
1362static void HIDAPI_DriverPS4_FreeDevice(SDL_HIDAPI_Device *device)
1363{
1364}
1365
1366SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 = {
1367 SDL_HINT_JOYSTICK_HIDAPI_PS4,
1368 true,
1369 HIDAPI_DriverPS4_RegisterHints,
1370 HIDAPI_DriverPS4_UnregisterHints,
1371 HIDAPI_DriverPS4_IsEnabled,
1372 HIDAPI_DriverPS4_IsSupportedDevice,
1373 HIDAPI_DriverPS4_InitDevice,
1374 HIDAPI_DriverPS4_GetDevicePlayerIndex,
1375 HIDAPI_DriverPS4_SetDevicePlayerIndex,
1376 HIDAPI_DriverPS4_UpdateDevice,
1377 HIDAPI_DriverPS4_OpenJoystick,
1378 HIDAPI_DriverPS4_RumbleJoystick,
1379 HIDAPI_DriverPS4_RumbleJoystickTriggers,
1380 HIDAPI_DriverPS4_GetJoystickCapabilities,
1381 HIDAPI_DriverPS4_SetJoystickLED,
1382 HIDAPI_DriverPS4_SendJoystickEffect,
1383 HIDAPI_DriverPS4_SetJoystickSensorsEnabled,
1384 HIDAPI_DriverPS4_CloseJoystick,
1385 HIDAPI_DriverPS4_FreeDevice,
1386};
1387
1388#endif // SDL_JOYSTICK_HIDAPI_PS4
1389
1390#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps5.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps5.c
new file mode 100644
index 0000000..abf59a8
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_ps5.c
@@ -0,0 +1,1624 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28#include "SDL_hidapi_rumble.h"
29
30#ifdef SDL_JOYSTICK_HIDAPI_PS5
31
32// Define this if you want to log all packets from the controller
33#if 0
34#define DEBUG_PS5_PROTOCOL
35#endif
36
37// Define this if you want to log calibration data
38#if 0
39#define DEBUG_PS5_CALIBRATION
40#endif
41
42#define GYRO_RES_PER_DEGREE 1024.0f
43#define ACCEL_RES_PER_G 8192.0f
44#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500
45
46#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
47#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \
48 (((Uint32)(B)) << 8) | \
49 (((Uint32)(C)) << 16) | \
50 (((Uint32)(D)) << 24))
51
52enum
53{
54 SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD = 11,
55 SDL_GAMEPAD_BUTTON_PS5_MICROPHONE,
56 SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION,
57 SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION,
58 SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE,
59 SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE
60};
61
62typedef enum
63{
64 k_EPS5ReportIdState = 0x01,
65 k_EPS5ReportIdUsbEffects = 0x02,
66 k_EPS5ReportIdBluetoothEffects = 0x31,
67 k_EPS5ReportIdBluetoothState = 0x31,
68} EPS5ReportId;
69
70typedef enum
71{
72 k_EPS5FeatureReportIdCapabilities = 0x03,
73 k_EPS5FeatureReportIdCalibration = 0x05,
74 k_EPS5FeatureReportIdSerialNumber = 0x09,
75 k_EPS5FeatureReportIdFirmwareInfo = 0x20,
76} EPS5FeatureReportId;
77
78typedef struct
79{
80 Uint8 ucLeftJoystickX;
81 Uint8 ucLeftJoystickY;
82 Uint8 ucRightJoystickX;
83 Uint8 ucRightJoystickY;
84 Uint8 rgucButtonsHatAndCounter[3];
85 Uint8 ucTriggerLeft;
86 Uint8 ucTriggerRight;
87} PS5SimpleStatePacket_t;
88
89typedef struct
90{
91 Uint8 ucLeftJoystickX; // 0
92 Uint8 ucLeftJoystickY; // 1
93 Uint8 ucRightJoystickX; // 2
94 Uint8 ucRightJoystickY; // 3
95 Uint8 ucTriggerLeft; // 4
96 Uint8 ucTriggerRight; // 5
97 Uint8 ucCounter; // 6
98 Uint8 rgucButtonsAndHat[4]; // 7
99 Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian
100 Uint8 rgucGyroX[2]; // 15
101 Uint8 rgucGyroY[2]; // 17
102 Uint8 rgucGyroZ[2]; // 19
103 Uint8 rgucAccelX[2]; // 21
104 Uint8 rgucAccelY[2]; // 23
105 Uint8 rgucAccelZ[2]; // 25
106 Uint8 rgucSensorTimestamp[4]; // 27 - 16/32 bit little endian
107
108} PS5StatePacketCommon_t;
109
110typedef struct
111{
112 Uint8 ucLeftJoystickX; // 0
113 Uint8 ucLeftJoystickY; // 1
114 Uint8 ucRightJoystickX; // 2
115 Uint8 ucRightJoystickY; // 3
116 Uint8 ucTriggerLeft; // 4
117 Uint8 ucTriggerRight; // 5
118 Uint8 ucCounter; // 6
119 Uint8 rgucButtonsAndHat[4]; // 7
120 Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian
121 Uint8 rgucGyroX[2]; // 15
122 Uint8 rgucGyroY[2]; // 17
123 Uint8 rgucGyroZ[2]; // 19
124 Uint8 rgucAccelX[2]; // 21
125 Uint8 rgucAccelY[2]; // 23
126 Uint8 rgucAccelZ[2]; // 25
127 Uint8 rgucSensorTimestamp[4]; // 27 - 32 bit little endian
128 Uint8 ucSensorTemp; // 31
129 Uint8 ucTouchpadCounter1; // 32 - high bit clear + counter
130 Uint8 rgucTouchpadData1[3]; // 33 - X/Y, 12 bits per axis
131 Uint8 ucTouchpadCounter2; // 36 - high bit clear + counter
132 Uint8 rgucTouchpadData2[3]; // 37 - X/Y, 12 bits per axis
133 Uint8 rgucUnknown1[8]; // 40
134 Uint8 rgucTimer2[4]; // 48 - 32 bit little endian
135 Uint8 ucBatteryLevel; // 52
136 Uint8 ucConnectState; // 53 - 0x08 = USB, 0x01 = headphone
137
138 // There's more unknown data at the end, and a 32-bit CRC on Bluetooth
139} PS5StatePacket_t;
140
141typedef struct
142{
143 Uint8 ucLeftJoystickX; // 0
144 Uint8 ucLeftJoystickY; // 1
145 Uint8 ucRightJoystickX; // 2
146 Uint8 ucRightJoystickY; // 3
147 Uint8 ucTriggerLeft; // 4
148 Uint8 ucTriggerRight; // 5
149 Uint8 ucCounter; // 6
150 Uint8 rgucButtonsAndHat[4]; // 7
151 Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian
152 Uint8 rgucGyroX[2]; // 15
153 Uint8 rgucGyroY[2]; // 17
154 Uint8 rgucGyroZ[2]; // 19
155 Uint8 rgucAccelX[2]; // 21
156 Uint8 rgucAccelY[2]; // 23
157 Uint8 rgucAccelZ[2]; // 25
158 Uint8 rgucSensorTimestamp[2]; // 27 - 16 bit little endian
159 Uint8 ucBatteryLevel; // 29
160 Uint8 ucUnknown; // 30
161 Uint8 ucTouchpadCounter1; // 31 - high bit clear + counter
162 Uint8 rgucTouchpadData1[3]; // 32 - X/Y, 12 bits per axis
163 Uint8 ucTouchpadCounter2; // 35 - high bit clear + counter
164 Uint8 rgucTouchpadData2[3]; // 36 - X/Y, 12 bits per axis
165
166 // There's more unknown data at the end, and a 32-bit CRC on Bluetooth
167} PS5StatePacketAlt_t;
168
169typedef struct
170{
171 Uint8 ucEnableBits1; // 0
172 Uint8 ucEnableBits2; // 1
173 Uint8 ucRumbleRight; // 2
174 Uint8 ucRumbleLeft; // 3
175 Uint8 ucHeadphoneVolume; // 4
176 Uint8 ucSpeakerVolume; // 5
177 Uint8 ucMicrophoneVolume; // 6
178 Uint8 ucAudioEnableBits; // 7
179 Uint8 ucMicLightMode; // 8
180 Uint8 ucAudioMuteBits; // 9
181 Uint8 rgucRightTriggerEffect[11]; // 10
182 Uint8 rgucLeftTriggerEffect[11]; // 21
183 Uint8 rgucUnknown1[6]; // 32
184 Uint8 ucEnableBits3; // 38
185 Uint8 rgucUnknown2[2]; // 39
186 Uint8 ucLedAnim; // 41
187 Uint8 ucLedBrightness; // 42
188 Uint8 ucPadLights; // 43
189 Uint8 ucLedRed; // 44
190 Uint8 ucLedGreen; // 45
191 Uint8 ucLedBlue; // 46
192} DS5EffectsState_t;
193
194typedef enum
195{
196 k_EDS5EffectRumbleStart = (1 << 0),
197 k_EDS5EffectRumble = (1 << 1),
198 k_EDS5EffectLEDReset = (1 << 2),
199 k_EDS5EffectLED = (1 << 3),
200 k_EDS5EffectPadLights = (1 << 4),
201 k_EDS5EffectMicLight = (1 << 5)
202} EDS5Effect;
203
204typedef enum
205{
206 k_EDS5LEDResetStateNone,
207 k_EDS5LEDResetStatePending,
208 k_EDS5LEDResetStateComplete,
209} EDS5LEDResetState;
210
211typedef struct
212{
213 Sint16 bias;
214 float sensitivity;
215} IMUCalibrationData;
216
217/* Rumble hint mode:
218 * "0": enhanced features are never used
219 * "1": enhanced features are always used
220 * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.
221 */
222typedef enum
223{
224 PS5_ENHANCED_REPORT_HINT_OFF,
225 PS5_ENHANCED_REPORT_HINT_ON,
226 PS5_ENHANCED_REPORT_HINT_AUTO
227} HIDAPI_PS5_EnhancedReportHint;
228
229typedef struct
230{
231 SDL_HIDAPI_Device *device;
232 SDL_Joystick *joystick;
233 bool is_nacon_dongle;
234 bool use_alternate_report;
235 bool sensors_supported;
236 bool lightbar_supported;
237 bool vibration_supported;
238 bool playerled_supported;
239 bool touchpad_supported;
240 bool effects_supported;
241 HIDAPI_PS5_EnhancedReportHint enhanced_report_hint;
242 bool enhanced_reports;
243 bool enhanced_mode;
244 bool enhanced_mode_available;
245 bool report_sensors;
246 bool report_touchpad;
247 bool report_battery;
248 bool hardware_calibration;
249 IMUCalibrationData calibration[6];
250 Uint16 firmware_version;
251 Uint64 last_packet;
252 int player_index;
253 bool player_lights;
254 Uint8 rumble_left;
255 Uint8 rumble_right;
256 bool color_set;
257 Uint8 led_red;
258 Uint8 led_green;
259 Uint8 led_blue;
260 EDS5LEDResetState led_reset_state;
261 Uint64 sensor_ticks;
262 Uint32 last_tick;
263 union
264 {
265 PS5SimpleStatePacket_t simple;
266 PS5StatePacketCommon_t state;
267 PS5StatePacketAlt_t alt_state;
268 PS5StatePacket_t full_state;
269 Uint8 data[64];
270 } last_state;
271} SDL_DriverPS5_Context;
272
273static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage);
274
275static void HIDAPI_DriverPS5_RegisterHints(SDL_HintCallback callback, void *userdata)
276{
277 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata);
278}
279
280static void HIDAPI_DriverPS5_UnregisterHints(SDL_HintCallback callback, void *userdata)
281{
282 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata);
283}
284
285static bool HIDAPI_DriverPS5_IsEnabled(void)
286{
287 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
288}
289
290static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)
291{
292 SDL_memset(report, 0, length);
293 report[0] = report_id;
294 return SDL_hid_get_feature_report(dev, report, length);
295}
296
297static bool HIDAPI_DriverPS5_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
298{
299 Uint8 data[USB_PACKET_LENGTH];
300 int size;
301
302 if (type == SDL_GAMEPAD_TYPE_PS5) {
303 return true;
304 }
305
306 if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) {
307 if (device && device->dev) {
308 size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data));
309 if (size == 48 && data[2] == 0x28) {
310 // Supported third party controller
311 return true;
312 } else {
313 return false;
314 }
315 } else {
316 // Might be supported by this driver, enumerate and find out
317 return true;
318 }
319 }
320 return false;
321}
322
323static void SetLedsForPlayerIndex(DS5EffectsState_t *effects, int player_index)
324{
325 /* This list is the same as what hid-sony.c uses in the Linux kernel.
326 The first 4 values correspond to what the PS4 assigns.
327 */
328 static const Uint8 colors[7][3] = {
329 { 0x00, 0x00, 0x40 }, // Blue
330 { 0x40, 0x00, 0x00 }, // Red
331 { 0x00, 0x40, 0x00 }, // Green
332 { 0x20, 0x00, 0x20 }, // Pink
333 { 0x20, 0x10, 0x00 }, // Orange
334 { 0x00, 0x10, 0x10 }, // Teal
335 { 0x10, 0x10, 0x10 } // White
336 };
337
338 if (player_index >= 0) {
339 player_index %= SDL_arraysize(colors);
340 } else {
341 player_index = 0;
342 }
343
344 effects->ucLedRed = colors[player_index][0];
345 effects->ucLedGreen = colors[player_index][1];
346 effects->ucLedBlue = colors[player_index][2];
347}
348
349static void SetLightsForPlayerIndex(DS5EffectsState_t *effects, int player_index)
350{
351 static const Uint8 lights[] = {
352 0x04,
353 0x0A,
354 0x15,
355 0x1B,
356 0x1F
357 };
358
359 if (player_index >= 0) {
360 // Bitmask, 0x1F enables all lights, 0x20 changes instantly instead of fade
361 player_index %= SDL_arraysize(lights);
362 effects->ucPadLights = lights[player_index] | 0x20;
363 } else {
364 effects->ucPadLights = 0x00;
365 }
366}
367
368static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)
369{
370 SDL_DriverPS5_Context *ctx;
371 Uint8 data[USB_PACKET_LENGTH * 2];
372 int size;
373 char serial[18];
374 SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
375
376 ctx = (SDL_DriverPS5_Context *)SDL_calloc(1, sizeof(*ctx));
377 if (!ctx) {
378 return false;
379 }
380 ctx->device = device;
381
382 device->context = ctx;
383
384 if (device->serial && SDL_strlen(device->serial) == 12) {
385 int i, j;
386
387 j = -1;
388 for (i = 0; i < 12; i += 2) {
389 j += 1;
390 SDL_memmove(&serial[j], &device->serial[i], 2);
391 j += 2;
392 serial[j] = '-';
393 }
394 serial[j] = '\0';
395 } else {
396 serial[0] = '\0';
397 }
398
399 // Read a report to see what mode we're in
400 size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);
401#ifdef DEBUG_PS5_PROTOCOL
402 if (size > 0) {
403 HIDAPI_DumpPacket("PS5 first packet: size = %d", data, size);
404 } else {
405 SDL_Log("PS5 first packet: size = %d", size);
406 }
407#endif
408 if (size == 64) {
409 // Connected over USB
410 ctx->enhanced_reports = true;
411 } else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) {
412 // Connected over Bluetooth, using enhanced reports
413 ctx->enhanced_reports = true;
414 } else {
415 // Connected over Bluetooth, using simple reports (DirectInput enabled)
416 }
417
418 if (device->vendor_id == USB_VENDOR_SONY && ctx->enhanced_reports) {
419 /* Read the serial number (Bluetooth address in reverse byte order)
420 This will also enable enhanced reports over Bluetooth
421 */
422 if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdSerialNumber, data, sizeof(data)) >= 7) {
423 (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
424 data[6], data[5], data[4], data[3], data[2], data[1]);
425 }
426
427 /* Read the firmware version
428 This will also enable enhanced reports over Bluetooth
429 */
430 if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdFirmwareInfo, data, USB_PACKET_LENGTH) >= 46) {
431 ctx->firmware_version = (Uint16)data[44] | ((Uint16)data[45] << 8);
432 }
433 }
434
435 // Get the device capabilities
436 if (device->vendor_id == USB_VENDOR_SONY) {
437 ctx->sensors_supported = true;
438 ctx->lightbar_supported = true;
439 ctx->vibration_supported = true;
440 ctx->playerled_supported = true;
441 ctx->touchpad_supported = true;
442 } else {
443 // Third party controller capability request
444 size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data));
445 if (size == 48 && data[2] == 0x28) {
446 Uint8 capabilities = data[4];
447 Uint8 capabilities2 = data[20];
448 Uint8 device_type = data[5];
449
450#ifdef DEBUG_PS5_PROTOCOL
451 HIDAPI_DumpPacket("PS5 capabilities: size = %d", data, size);
452#endif
453 if (capabilities & 0x02) {
454 ctx->sensors_supported = true;
455 }
456 if (capabilities & 0x04) {
457 ctx->lightbar_supported = true;
458 }
459 if (capabilities & 0x08) {
460 ctx->vibration_supported = true;
461 }
462 if (capabilities & 0x40) {
463 ctx->touchpad_supported = true;
464 }
465 if (capabilities2 & 0x80) {
466 ctx->playerled_supported = true;
467 }
468
469 switch (device_type) {
470 case 0x00:
471 joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
472 break;
473 case 0x01:
474 joystick_type = SDL_JOYSTICK_TYPE_GUITAR;
475 break;
476 case 0x02:
477 joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;
478 break;
479 case 0x06:
480 joystick_type = SDL_JOYSTICK_TYPE_WHEEL;
481 break;
482 case 0x07:
483 joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
484 break;
485 case 0x08:
486 joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;
487 break;
488 default:
489 joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN;
490 break;
491 }
492
493 ctx->use_alternate_report = true;
494
495 if (device->vendor_id == USB_VENDOR_NACON_ALT &&
496 (device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED ||
497 device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS)) {
498 // This doesn't report vibration capability, but it can do rumble
499 ctx->vibration_supported = true;
500 }
501 } else if (device->vendor_id == USB_VENDOR_RAZER &&
502 (device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRED ||
503 device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS)) {
504 // The Razer Wolverine V2 Pro doesn't respond to the detection protocol, but has a touchpad and sensors and no vibration
505 ctx->sensors_supported = true;
506 ctx->touchpad_supported = true;
507 ctx->use_alternate_report = true;
508 } else if (device->vendor_id == USB_VENDOR_RAZER &&
509 device->product_id == USB_PRODUCT_RAZER_KITSUNE) {
510 // The Razer Kitsune doesn't respond to the detection protocol, but has a touchpad
511 joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
512 ctx->touchpad_supported = true;
513 ctx->use_alternate_report = true;
514 }
515 }
516 ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported);
517
518 if (device->vendor_id == USB_VENDOR_NACON_ALT &&
519 device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) {
520 ctx->is_nacon_dongle = true;
521 }
522
523 device->joystick_type = joystick_type;
524 device->type = SDL_GAMEPAD_TYPE_PS5;
525 if (device->vendor_id == USB_VENDOR_SONY) {
526 if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) {
527 HIDAPI_SetDeviceName(device, "DualSense Edge Wireless Controller");
528 } else {
529 HIDAPI_SetDeviceName(device, "DualSense Wireless Controller");
530 }
531 }
532 HIDAPI_SetDeviceSerial(device, serial);
533
534 if (ctx->is_nacon_dongle) {
535 // We don't know if this is connected yet, wait for reports
536 return true;
537 }
538
539 // Prefer the USB device over the Bluetooth device
540 if (device->is_bluetooth) {
541 if (HIDAPI_HasConnectedUSBDevice(device->serial)) {
542 return true;
543 }
544 } else {
545 HIDAPI_DisconnectBluetoothDevice(device->serial);
546 }
547 return HIDAPI_JoystickConnected(device, NULL);
548}
549
550static int HIDAPI_DriverPS5_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
551{
552 return -1;
553}
554
555static void HIDAPI_DriverPS5_LoadCalibrationData(SDL_HIDAPI_Device *device)
556{
557 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
558 int i, size;
559 Uint8 data[USB_PACKET_LENGTH];
560
561 size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCalibration, data, sizeof(data));
562 if (size < 35) {
563#ifdef DEBUG_PS5_CALIBRATION
564 SDL_Log("Short read of calibration data: %d, ignoring calibration", size);
565#endif
566 return;
567 }
568
569 {
570 Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias;
571 Sint16 sGyroPitchPlus, sGyroPitchMinus;
572 Sint16 sGyroYawPlus, sGyroYawMinus;
573 Sint16 sGyroRollPlus, sGyroRollMinus;
574 Sint16 sGyroSpeedPlus, sGyroSpeedMinus;
575
576 Sint16 sAccXPlus, sAccXMinus;
577 Sint16 sAccYPlus, sAccYMinus;
578 Sint16 sAccZPlus, sAccZMinus;
579
580 float flNumerator;
581 Sint16 sRange2g;
582
583#ifdef DEBUG_PS5_CALIBRATION
584 HIDAPI_DumpPacket("PS5 calibration packet: size = %d", data, size);
585#endif
586
587 sGyroPitchBias = LOAD16(data[1], data[2]);
588 sGyroYawBias = LOAD16(data[3], data[4]);
589 sGyroRollBias = LOAD16(data[5], data[6]);
590
591 sGyroPitchPlus = LOAD16(data[7], data[8]);
592 sGyroPitchMinus = LOAD16(data[9], data[10]);
593 sGyroYawPlus = LOAD16(data[11], data[12]);
594 sGyroYawMinus = LOAD16(data[13], data[14]);
595 sGyroRollPlus = LOAD16(data[15], data[16]);
596 sGyroRollMinus = LOAD16(data[17], data[18]);
597
598 sGyroSpeedPlus = LOAD16(data[19], data[20]);
599 sGyroSpeedMinus = LOAD16(data[21], data[22]);
600
601 sAccXPlus = LOAD16(data[23], data[24]);
602 sAccXMinus = LOAD16(data[25], data[26]);
603 sAccYPlus = LOAD16(data[27], data[28]);
604 sAccYMinus = LOAD16(data[29], data[30]);
605 sAccZPlus = LOAD16(data[31], data[32]);
606 sAccZMinus = LOAD16(data[33], data[34]);
607
608 flNumerator = (sGyroSpeedPlus + sGyroSpeedMinus) * GYRO_RES_PER_DEGREE;
609 ctx->calibration[0].bias = sGyroPitchBias;
610 ctx->calibration[0].sensitivity = flNumerator / (sGyroPitchPlus - sGyroPitchMinus);
611
612 ctx->calibration[1].bias = sGyroYawBias;
613 ctx->calibration[1].sensitivity = flNumerator / (sGyroYawPlus - sGyroYawMinus);
614
615 ctx->calibration[2].bias = sGyroRollBias;
616 ctx->calibration[2].sensitivity = flNumerator / (sGyroRollPlus - sGyroRollMinus);
617
618 sRange2g = sAccXPlus - sAccXMinus;
619 ctx->calibration[3].bias = sAccXPlus - sRange2g / 2;
620 ctx->calibration[3].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;
621
622 sRange2g = sAccYPlus - sAccYMinus;
623 ctx->calibration[4].bias = sAccYPlus - sRange2g / 2;
624 ctx->calibration[4].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;
625
626 sRange2g = sAccZPlus - sAccZMinus;
627 ctx->calibration[5].bias = sAccZPlus - sRange2g / 2;
628 ctx->calibration[5].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;
629
630 ctx->hardware_calibration = true;
631 for (i = 0; i < 6; ++i) {
632 float divisor = (i < 3 ? 64.0f : 1.0f);
633#ifdef DEBUG_PS5_CALIBRATION
634 SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].sensitivity);
635#endif
636 // Some controllers have a bad calibration
637 if ((SDL_abs(ctx->calibration[i].bias) > 1024) || (SDL_fabsf(1.0f - ctx->calibration[i].sensitivity / divisor) > 0.5f)) {
638#ifdef DEBUG_PS5_CALIBRATION
639 SDL_Log("invalid calibration, ignoring");
640#endif
641 ctx->hardware_calibration = false;
642 }
643 }
644 }
645}
646
647static float HIDAPI_DriverPS5_ApplyCalibrationData(SDL_DriverPS5_Context *ctx, int index, Sint16 value)
648{
649 float result;
650
651 if (ctx->hardware_calibration) {
652 IMUCalibrationData *calibration = &ctx->calibration[index];
653
654 result = (value - calibration->bias) * calibration->sensitivity;
655 } else if (index < 3) {
656 result = value * 64.f;
657 } else {
658 result = value;
659 }
660
661 // Convert the raw data to the units expected by SDL
662 if (index < 3) {
663 result = (result / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;
664 } else {
665 result = (result / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
666 }
667 return result;
668}
669
670static bool HIDAPI_DriverPS5_UpdateEffects(SDL_DriverPS5_Context *ctx, int effect_mask, bool application_usage)
671{
672 DS5EffectsState_t effects;
673
674 // Make sure the Bluetooth connection sequence has completed before sending LED color change
675 if (ctx->device->is_bluetooth && ctx->enhanced_reports &&
676 (effect_mask & (k_EDS5EffectLED | k_EDS5EffectPadLights)) != 0) {
677 if (ctx->led_reset_state != k_EDS5LEDResetStateComplete) {
678 ctx->led_reset_state = k_EDS5LEDResetStatePending;
679 return true;
680 }
681 }
682
683 SDL_zero(effects);
684
685 if (ctx->vibration_supported) {
686 if (ctx->rumble_left || ctx->rumble_right) {
687 if (ctx->firmware_version < 0x0224) {
688 effects.ucEnableBits1 |= 0x01; // Enable rumble emulation
689
690 // Shift to reduce effective rumble strength to match Xbox controllers
691 effects.ucRumbleLeft = ctx->rumble_left >> 1;
692 effects.ucRumbleRight = ctx->rumble_right >> 1;
693 } else {
694 effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer
695
696 effects.ucRumbleLeft = ctx->rumble_left;
697 effects.ucRumbleRight = ctx->rumble_right;
698 }
699 effects.ucEnableBits1 |= 0x02; // Disable audio haptics
700 } else {
701 // Leaving emulated rumble bits off will restore audio haptics
702 }
703
704 if ((effect_mask & k_EDS5EffectRumbleStart) != 0) {
705 effects.ucEnableBits1 |= 0x02; // Disable audio haptics
706 }
707 if ((effect_mask & k_EDS5EffectRumble) != 0) {
708 // Already handled above
709 }
710 }
711 if (ctx->lightbar_supported) {
712 if ((effect_mask & k_EDS5EffectLEDReset) != 0) {
713 effects.ucEnableBits2 |= 0x08; // Reset LED state
714 }
715 if ((effect_mask & k_EDS5EffectLED) != 0) {
716 effects.ucEnableBits2 |= 0x04; // Enable LED color
717
718 // Populate the LED state with the appropriate color from our lookup table
719 if (ctx->color_set) {
720 effects.ucLedRed = ctx->led_red;
721 effects.ucLedGreen = ctx->led_green;
722 effects.ucLedBlue = ctx->led_blue;
723 } else {
724 SetLedsForPlayerIndex(&effects, ctx->player_index);
725 }
726 }
727 }
728 if (ctx->playerled_supported) {
729 if ((effect_mask & k_EDS5EffectPadLights) != 0) {
730 effects.ucEnableBits2 |= 0x10; // Enable touchpad lights
731
732 if (ctx->player_lights) {
733 SetLightsForPlayerIndex(&effects, ctx->player_index);
734 } else {
735 effects.ucPadLights = 0x00;
736 }
737 }
738 }
739 if ((effect_mask & k_EDS5EffectMicLight) != 0) {
740 effects.ucEnableBits2 |= 0x01; // Enable microphone light
741
742 effects.ucMicLightMode = 0; // Bitmask, 0x00 = off, 0x01 = solid, 0x02 = pulse
743 }
744
745 return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage);
746}
747
748static void HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_DriverPS5_Context *ctx)
749{
750 bool led_reset_complete = false;
751
752 if (ctx->enhanced_reports && ctx->sensors_supported && !ctx->use_alternate_report) {
753 const PS5StatePacketCommon_t *packet = &ctx->last_state.state;
754
755 // Check the timer to make sure the Bluetooth connection LED animation is complete
756 const Uint32 connection_complete = 10200000;
757 Uint32 timestamp = LOAD32(packet->rgucSensorTimestamp[0],
758 packet->rgucSensorTimestamp[1],
759 packet->rgucSensorTimestamp[2],
760 packet->rgucSensorTimestamp[3]);
761 if (timestamp >= connection_complete) {
762 led_reset_complete = true;
763 }
764 } else {
765 // We don't know how to check the timer, just assume it's complete for now
766 led_reset_complete = true;
767 }
768
769 if (led_reset_complete) {
770 HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLEDReset, false);
771
772 ctx->led_reset_state = k_EDS5LEDResetStateComplete;
773
774 HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);
775 }
776}
777
778static void HIDAPI_DriverPS5_TickleBluetooth(SDL_HIDAPI_Device *device)
779{
780 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
781
782 if (ctx->enhanced_reports) {
783 // This is just a dummy packet that should have no effect, since we don't set the CRC
784 Uint8 data[78];
785
786 SDL_zeroa(data);
787
788 data[0] = k_EPS5ReportIdBluetoothEffects;
789 data[1] = 0x02; // Magic value
790
791 if (SDL_HIDAPI_LockRumble()) {
792 SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data));
793 }
794 } else {
795 // We can't even send an invalid effects packet, or it will put the controller in enhanced mode
796 if (device->num_joysticks > 0) {
797 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
798 }
799 }
800}
801
802static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx)
803{
804 if (ctx->enhanced_mode_available) {
805 return;
806 }
807 ctx->enhanced_mode_available = true;
808
809 if (ctx->touchpad_supported) {
810 SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2);
811 ctx->report_touchpad = true;
812 }
813
814 if (ctx->sensors_supported) {
815 if (ctx->device->is_bluetooth) {
816 // Bluetooth sensor update rate appears to be 1000 Hz
817 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 1000.0f);
818 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 1000.0f);
819 } else {
820 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 250.0f);
821 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 250.0f);
822 }
823 }
824
825 ctx->report_battery = true;
826
827 HIDAPI_UpdateDeviceProperties(ctx->device);
828}
829
830static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx)
831{
832 HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);
833
834 if (!ctx->enhanced_mode) {
835 ctx->enhanced_mode = true;
836
837 // Switch into enhanced report mode
838 HIDAPI_DriverPS5_UpdateEffects(ctx, 0, false);
839
840 // Update the light effects
841 HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);
842 }
843}
844
845static void HIDAPI_DriverPS5_SetEnhancedReportHint(SDL_DriverPS5_Context *ctx, HIDAPI_PS5_EnhancedReportHint enhanced_report_hint)
846{
847 switch (enhanced_report_hint) {
848 case PS5_ENHANCED_REPORT_HINT_OFF:
849 // Nothing to do, enhanced mode is a one-way ticket
850 break;
851 case PS5_ENHANCED_REPORT_HINT_ON:
852 HIDAPI_DriverPS5_SetEnhancedMode(ctx);
853 break;
854 case PS5_ENHANCED_REPORT_HINT_AUTO:
855 HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);
856 break;
857 }
858 ctx->enhanced_report_hint = enhanced_report_hint;
859}
860
861static void HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS5_Context *ctx)
862{
863 ctx->enhanced_reports = true;
864
865 if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) {
866 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
867 }
868}
869
870static void HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS5_Context *ctx)
871{
872 if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) {
873 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
874 }
875}
876
877static void SDLCALL SDL_PS5EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
878{
879 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;
880
881 if (ctx->device->is_bluetooth) {
882 if (hint && SDL_strcasecmp(hint, "auto") == 0) {
883 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_AUTO);
884 } else if (SDL_GetStringBoolean(hint, true)) {
885 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
886 } else {
887 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_OFF);
888 }
889 } else {
890 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
891 }
892}
893
894static void SDLCALL SDL_PS5PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
895{
896 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;
897 bool player_lights = SDL_GetStringBoolean(hint, true);
898
899 if (player_lights != ctx->player_lights) {
900 ctx->player_lights = player_lights;
901
902 HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectPadLights, false);
903 }
904}
905
906static void HIDAPI_DriverPS5_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
907{
908 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
909
910 if (!ctx->joystick) {
911 return;
912 }
913
914 ctx->player_index = player_index;
915
916 // This will set the new LED state based on the new player index
917 // SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode
918 HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);
919}
920
921static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
922{
923 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
924
925 SDL_AssertJoysticksLocked();
926
927 ctx->joystick = joystick;
928 ctx->last_packet = SDL_GetTicks();
929 ctx->report_sensors = false;
930 ctx->report_touchpad = false;
931 ctx->rumble_left = 0;
932 ctx->rumble_right = 0;
933 ctx->color_set = false;
934 ctx->led_reset_state = k_EDS5LEDResetStateNone;
935 SDL_zero(ctx->last_state);
936
937 // Initialize player index (needed for setting LEDs)
938 ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
939 ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, true);
940
941 // Initialize the joystick capabilities
942 if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) {
943 joystick->nbuttons = 17; // paddles and touchpad and microphone
944 } else if (ctx->touchpad_supported) {
945 joystick->nbuttons = 13; // touchpad and microphone
946 } else {
947 joystick->nbuttons = 11;
948 }
949 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
950 joystick->nhats = 1;
951 joystick->firmware_version = ctx->firmware_version;
952
953 SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
954 SDL_PS5EnhancedReportsChanged, ctx);
955 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,
956 SDL_PS5PlayerLEDHintChanged, ctx);
957
958 return true;
959}
960
961static bool HIDAPI_DriverPS5_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
962{
963 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
964
965 if (!ctx->vibration_supported) {
966 return SDL_Unsupported();
967 }
968
969 if (!ctx->rumble_left && !ctx->rumble_right) {
970 HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumbleStart, true);
971 }
972
973 ctx->rumble_left = (low_frequency_rumble >> 8);
974 ctx->rumble_right = (high_frequency_rumble >> 8);
975
976 return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumble, true);
977}
978
979static bool HIDAPI_DriverPS5_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
980{
981 return SDL_Unsupported();
982}
983
984static Uint32 HIDAPI_DriverPS5_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
985{
986 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
987 Uint32 result = 0;
988
989 if (ctx->enhanced_mode_available) {
990 if (ctx->lightbar_supported) {
991 result |= SDL_JOYSTICK_CAP_RGB_LED;
992 }
993 if (ctx->playerled_supported) {
994 result |= SDL_JOYSTICK_CAP_PLAYER_LED;
995 }
996 if (ctx->vibration_supported) {
997 result |= SDL_JOYSTICK_CAP_RUMBLE;
998 }
999 }
1000
1001 return result;
1002}
1003
1004static bool HIDAPI_DriverPS5_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1005{
1006 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
1007
1008 if (!ctx->lightbar_supported) {
1009 return SDL_Unsupported();
1010 }
1011
1012 ctx->color_set = true;
1013 ctx->led_red = red;
1014 ctx->led_green = green;
1015 ctx->led_blue = blue;
1016
1017 return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLED, true);
1018}
1019
1020static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage)
1021{
1022 Uint8 data[78];
1023 int report_size, offset;
1024 Uint8 *pending_data;
1025 int *pending_size;
1026 int maximum_size;
1027
1028 if (!ctx->effects_supported) {
1029 // We shouldn't be sending packets to this controller
1030 return SDL_Unsupported();
1031 }
1032
1033 if (!ctx->enhanced_mode) {
1034 if (application_usage) {
1035 HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);
1036 }
1037
1038 if (!ctx->enhanced_mode) {
1039 // We're not in enhanced mode, effects aren't allowed
1040 return SDL_Unsupported();
1041 }
1042 }
1043
1044 SDL_zeroa(data);
1045
1046 if (ctx->device->is_bluetooth) {
1047 data[0] = k_EPS5ReportIdBluetoothEffects;
1048 data[1] = 0x02; // Magic value
1049
1050 report_size = 78;
1051 offset = 2;
1052 } else {
1053 data[0] = k_EPS5ReportIdUsbEffects;
1054
1055 report_size = 48;
1056 offset = 1;
1057 }
1058
1059 SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));
1060
1061 if (ctx->device->is_bluetooth) {
1062 // Bluetooth reports need a CRC at the end of the packet (at least on Linux)
1063 Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation
1064 Uint32 unCRC;
1065 unCRC = SDL_crc32(0, &ubHdr, 1);
1066 unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC)));
1067 SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));
1068 }
1069
1070 if (!SDL_HIDAPI_LockRumble()) {
1071 return false;
1072 }
1073
1074 // See if we can update an existing pending request
1075 if (SDL_HIDAPI_GetPendingRumbleLocked(ctx->device, &pending_data, &pending_size, &maximum_size)) {
1076 DS5EffectsState_t *effects = (DS5EffectsState_t *)&data[offset];
1077 DS5EffectsState_t *pending_effects = (DS5EffectsState_t *)&pending_data[offset];
1078 if (report_size == *pending_size &&
1079 effects->ucEnableBits1 == pending_effects->ucEnableBits1 &&
1080 effects->ucEnableBits2 == pending_effects->ucEnableBits2) {
1081 // We're simply updating the data for this request
1082 SDL_memcpy(pending_data, data, report_size);
1083 SDL_HIDAPI_UnlockRumble();
1084 return true;
1085 }
1086 }
1087
1088 if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, report_size) != report_size) {
1089 return false;
1090 }
1091
1092 return true;
1093}
1094
1095static bool HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
1096{
1097 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
1098
1099 return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, effect, size, true);
1100}
1101
1102static bool HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
1103{
1104 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
1105
1106 HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);
1107
1108 if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) {
1109 return SDL_Unsupported();
1110 }
1111
1112 if (enabled) {
1113 HIDAPI_DriverPS5_LoadCalibrationData(device);
1114 }
1115 ctx->report_sensors = enabled;
1116
1117 return true;
1118}
1119
1120static void HIDAPI_DriverPS5_HandleSimpleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5SimpleStatePacket_t *packet, Uint64 timestamp)
1121{
1122 Sint16 axis;
1123
1124 if (ctx->last_state.simple.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) {
1125 {
1126 Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4);
1127
1128 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));
1129 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));
1130 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));
1131 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));
1132 }
1133 {
1134 Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F);
1135 Uint8 hat;
1136
1137 switch (data) {
1138 case 0:
1139 hat = SDL_HAT_UP;
1140 break;
1141 case 1:
1142 hat = SDL_HAT_RIGHTUP;
1143 break;
1144 case 2:
1145 hat = SDL_HAT_RIGHT;
1146 break;
1147 case 3:
1148 hat = SDL_HAT_RIGHTDOWN;
1149 break;
1150 case 4:
1151 hat = SDL_HAT_DOWN;
1152 break;
1153 case 5:
1154 hat = SDL_HAT_LEFTDOWN;
1155 break;
1156 case 6:
1157 hat = SDL_HAT_LEFT;
1158 break;
1159 case 7:
1160 hat = SDL_HAT_LEFTUP;
1161 break;
1162 default:
1163 hat = SDL_HAT_CENTERED;
1164 break;
1165 }
1166 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1167 }
1168 }
1169
1170 if (ctx->last_state.simple.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) {
1171 Uint8 data = packet->rgucButtonsHatAndCounter[1];
1172
1173 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));
1174 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));
1175 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));
1176 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));
1177 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));
1178 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));
1179 }
1180
1181 if (ctx->last_state.simple.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) {
1182 Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03);
1183
1184 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));
1185 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0));
1186 }
1187
1188 if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x04)) {
1189 axis = SDL_JOYSTICK_AXIS_MAX;
1190 } else {
1191 axis = ((int)packet->ucTriggerLeft * 257) - 32768;
1192 }
1193 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
1194 if (packet->ucTriggerRight == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x08)) {
1195 axis = SDL_JOYSTICK_AXIS_MAX;
1196 } else {
1197 axis = ((int)packet->ucTriggerRight * 257) - 32768;
1198 }
1199 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
1200 axis = ((int)packet->ucLeftJoystickX * 257) - 32768;
1201 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
1202 axis = ((int)packet->ucLeftJoystickY * 257) - 32768;
1203 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
1204 axis = ((int)packet->ucRightJoystickX * 257) - 32768;
1205 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
1206 axis = ((int)packet->ucRightJoystickY * 257) - 32768;
1207 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
1208
1209 SDL_memcpy(&ctx->last_state.simple, packet, sizeof(ctx->last_state.simple));
1210}
1211
1212static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketCommon_t *packet, Uint64 timestamp)
1213{
1214 Sint16 axis;
1215
1216 if (ctx->last_state.state.rgucButtonsAndHat[0] != packet->rgucButtonsAndHat[0]) {
1217 {
1218 Uint8 data = (packet->rgucButtonsAndHat[0] >> 4);
1219
1220 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));
1221 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));
1222 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));
1223 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));
1224 }
1225 {
1226 Uint8 data = (packet->rgucButtonsAndHat[0] & 0x0F);
1227 Uint8 hat;
1228
1229 switch (data) {
1230 case 0:
1231 hat = SDL_HAT_UP;
1232 break;
1233 case 1:
1234 hat = SDL_HAT_RIGHTUP;
1235 break;
1236 case 2:
1237 hat = SDL_HAT_RIGHT;
1238 break;
1239 case 3:
1240 hat = SDL_HAT_RIGHTDOWN;
1241 break;
1242 case 4:
1243 hat = SDL_HAT_DOWN;
1244 break;
1245 case 5:
1246 hat = SDL_HAT_LEFTDOWN;
1247 break;
1248 case 6:
1249 hat = SDL_HAT_LEFT;
1250 break;
1251 case 7:
1252 hat = SDL_HAT_LEFTUP;
1253 break;
1254 default:
1255 hat = SDL_HAT_CENTERED;
1256 break;
1257 }
1258 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1259 }
1260 }
1261
1262 if (ctx->last_state.state.rgucButtonsAndHat[1] != packet->rgucButtonsAndHat[1]) {
1263 Uint8 data = packet->rgucButtonsAndHat[1];
1264
1265 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));
1266 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));
1267 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));
1268 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));
1269 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));
1270 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));
1271 }
1272
1273 if (ctx->last_state.state.rgucButtonsAndHat[2] != packet->rgucButtonsAndHat[2]) {
1274 Uint8 data = packet->rgucButtonsAndHat[2];
1275
1276 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));
1277 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0));
1278 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_MICROPHONE, ((data & 0x04) != 0));
1279 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION, ((data & 0x10) != 0));
1280 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION, ((data & 0x20) != 0));
1281 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE, ((data & 0x40) != 0));
1282 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE, ((data & 0x80) != 0));
1283 }
1284
1285 if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsAndHat[1] & 0x04)) {
1286 axis = SDL_JOYSTICK_AXIS_MAX;
1287 } else {
1288 axis = ((int)packet->ucTriggerLeft * 257) - 32768;
1289 }
1290 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
1291 if (packet->ucTriggerRight == 0 && (packet->rgucButtonsAndHat[1] & 0x08)) {
1292 axis = SDL_JOYSTICK_AXIS_MAX;
1293 } else {
1294 axis = ((int)packet->ucTriggerRight * 257) - 32768;
1295 }
1296 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
1297 axis = ((int)packet->ucLeftJoystickX * 257) - 32768;
1298 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
1299 axis = ((int)packet->ucLeftJoystickY * 257) - 32768;
1300 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
1301 axis = ((int)packet->ucRightJoystickX * 257) - 32768;
1302 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
1303 axis = ((int)packet->ucRightJoystickY * 257) - 32768;
1304 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
1305
1306 if (ctx->report_sensors) {
1307 Uint64 sensor_timestamp;
1308 float data[3];
1309
1310 if (ctx->use_alternate_report) {
1311 // 16-bit timestamp
1312 Uint32 delta;
1313 Uint16 tick = LOAD16(packet->rgucSensorTimestamp[0],
1314 packet->rgucSensorTimestamp[1]);
1315 if (ctx->last_tick < tick) {
1316 delta = (tick - ctx->last_tick);
1317 } else {
1318 delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);
1319 }
1320 ctx->last_tick = tick;
1321 ctx->sensor_ticks += delta;
1322
1323 // Sensor timestamp is in 1us units
1324 sensor_timestamp = SDL_US_TO_NS(ctx->sensor_ticks);
1325 } else {
1326 // 32-bit timestamp
1327 Uint32 delta;
1328 Uint32 tick = LOAD32(packet->rgucSensorTimestamp[0],
1329 packet->rgucSensorTimestamp[1],
1330 packet->rgucSensorTimestamp[2],
1331 packet->rgucSensorTimestamp[3]);
1332 if (ctx->last_tick < tick) {
1333 delta = (tick - ctx->last_tick);
1334 } else {
1335 delta = (SDL_MAX_UINT32 - ctx->last_tick + tick + 1);
1336 }
1337 ctx->last_tick = tick;
1338 ctx->sensor_ticks += delta;
1339
1340 // Sensor timestamp is in 0.33us units
1341 sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US) / 3;
1342 }
1343
1344 data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));
1345 data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));
1346 data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));
1347 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3);
1348
1349 data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));
1350 data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));
1351 data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));
1352 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3);
1353 }
1354}
1355
1356static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacket_t *packet, Uint64 timestamp)
1357{
1358 static const float TOUCHPAD_SCALEX = 1.0f / 1920;
1359 static const float TOUCHPAD_SCALEY = 1.0f / 1070;
1360 bool touchpad_down;
1361 int touchpad_x, touchpad_y;
1362
1363 if (ctx->report_touchpad) {
1364 touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);
1365 touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);
1366 touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);
1367 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1368
1369 touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);
1370 touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);
1371 touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);
1372 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1373 }
1374
1375 if (ctx->report_battery) {
1376 SDL_PowerState state;
1377 int percent;
1378 Uint8 status = (packet->ucBatteryLevel >> 4) & 0x0F;
1379 Uint8 level = (packet->ucBatteryLevel & 0x0F);
1380
1381 switch (status) {
1382 case 0:
1383 state = SDL_POWERSTATE_ON_BATTERY;
1384 percent = SDL_min(level * 10 + 5, 100);
1385 break;
1386 case 1:
1387 state = SDL_POWERSTATE_CHARGING;
1388 percent = SDL_min(level * 10 + 5, 100);
1389 break;
1390 case 2:
1391 state = SDL_POWERSTATE_CHARGED;
1392 percent = 100;
1393 break;
1394 default:
1395 state = SDL_POWERSTATE_UNKNOWN;
1396 percent = 0;
1397 break;
1398 }
1399 SDL_SendJoystickPowerInfo(joystick, state, percent);
1400 }
1401
1402 HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp);
1403
1404 SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));
1405}
1406
1407static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketAlt_t *packet, Uint64 timestamp)
1408{
1409 static const float TOUCHPAD_SCALEX = 1.0f / 1920;
1410 static const float TOUCHPAD_SCALEY = 1.0f / 1070;
1411 bool touchpad_down;
1412 int touchpad_x, touchpad_y;
1413
1414 if (ctx->report_touchpad) {
1415 touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);
1416 touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);
1417 touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);
1418 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1419
1420 touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);
1421 touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);
1422 touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);
1423 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1424 }
1425
1426 HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp);
1427
1428 SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));
1429}
1430
1431static bool VerifyCRC(Uint8 *data, int size)
1432{
1433 Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation
1434 Uint32 unCRC, unPacketCRC;
1435 Uint8 *packetCRC = data + size - sizeof(unPacketCRC);
1436 unCRC = SDL_crc32(0, &ubHdr, 1);
1437 unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC)));
1438
1439 unPacketCRC = LOAD32(packetCRC[0],
1440 packetCRC[1],
1441 packetCRC[2],
1442 packetCRC[3]);
1443 return (unCRC == unPacketCRC);
1444}
1445
1446static bool HIDAPI_DriverPS5_IsPacketValid(SDL_DriverPS5_Context *ctx, Uint8 *data, int size)
1447{
1448 switch (data[0]) {
1449 case k_EPS5ReportIdState:
1450 if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) {
1451 // The report timestamp doesn't change when the controller isn't connected
1452 PS5StatePacketAlt_t *packet = (PS5StatePacketAlt_t *)&data[1];
1453 if (SDL_memcmp(packet->rgucPacketSequence, ctx->last_state.state.rgucPacketSequence, sizeof(packet->rgucPacketSequence)) == 0) {
1454 return false;
1455 }
1456 if (ctx->last_state.alt_state.rgucAccelX[0] == 0 && ctx->last_state.alt_state.rgucAccelX[1] == 0 &&
1457 ctx->last_state.alt_state.rgucAccelY[0] == 0 && ctx->last_state.alt_state.rgucAccelY[1] == 0 &&
1458 ctx->last_state.alt_state.rgucAccelZ[0] == 0 && ctx->last_state.alt_state.rgucAccelZ[1] == 0) {
1459 // We don't have any state to compare yet, go ahead and copy it
1460 SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS5StatePacketAlt_t));
1461 return false;
1462 }
1463 }
1464 return true;
1465
1466 case k_EPS5ReportIdBluetoothState:
1467 if (VerifyCRC(data, size)) {
1468 return true;
1469 }
1470 break;
1471 default:
1472 break;
1473 }
1474 return false;
1475}
1476
1477static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device)
1478{
1479 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
1480 SDL_Joystick *joystick = NULL;
1481 Uint8 data[USB_PACKET_LENGTH * 2];
1482 int size;
1483 int packet_count = 0;
1484 Uint64 now = SDL_GetTicks();
1485
1486 if (device->num_joysticks > 0) {
1487 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1488 }
1489
1490 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
1491 Uint64 timestamp = SDL_GetTicksNS();
1492
1493#ifdef DEBUG_PS5_PROTOCOL
1494 HIDAPI_DumpPacket("PS5 packet: size = %d", data, size);
1495#endif
1496 if (!HIDAPI_DriverPS5_IsPacketValid(ctx, data, size)) {
1497 continue;
1498 }
1499
1500 ++packet_count;
1501 ctx->last_packet = now;
1502
1503 if (!joystick) {
1504 continue;
1505 }
1506
1507 switch (data[0]) {
1508 case k_EPS5ReportIdState:
1509 if (size == 10 || size == 78) {
1510 HIDAPI_DriverPS5_HandleSimpleStatePacket(joystick, device->dev, ctx, (PS5SimpleStatePacket_t *)&data[1], timestamp);
1511 } else {
1512 if (ctx->use_alternate_report) {
1513 HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[1], timestamp);
1514 } else {
1515 HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[1], timestamp);
1516 }
1517 }
1518 break;
1519 case k_EPS5ReportIdBluetoothState:
1520 // This is the extended report, we can enable effects now in auto mode
1521 HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(ctx);
1522
1523 if (ctx->use_alternate_report) {
1524 HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[2], timestamp);
1525 } else {
1526 HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[2], timestamp);
1527 }
1528 if (ctx->led_reset_state == k_EDS5LEDResetStatePending) {
1529 HIDAPI_DriverPS5_CheckPendingLEDReset(ctx);
1530 }
1531 break;
1532 default:
1533#ifdef DEBUG_JOYSTICK
1534 SDL_Log("Unknown PS5 packet: 0x%.2x", data[0]);
1535#endif
1536 break;
1537 }
1538 }
1539
1540 if (device->is_bluetooth) {
1541 if (packet_count == 0) {
1542 // Check to see if it looks like the device disconnected
1543 if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {
1544 // Send an empty output report to tickle the Bluetooth stack
1545 HIDAPI_DriverPS5_TickleBluetooth(device);
1546 ctx->last_packet = now;
1547 }
1548 } else {
1549 // Reconnect the Bluetooth device once the USB device is gone
1550 if (device->num_joysticks == 0 &&
1551 !HIDAPI_HasConnectedUSBDevice(device->serial)) {
1552 HIDAPI_JoystickConnected(device, NULL);
1553 }
1554 }
1555 }
1556
1557 if (ctx->is_nacon_dongle) {
1558 if (packet_count == 0) {
1559 if (device->num_joysticks > 0) {
1560 // Check to see if it looks like the device disconnected
1561 if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {
1562 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1563 }
1564 }
1565 } else {
1566 if (device->num_joysticks == 0) {
1567 HIDAPI_JoystickConnected(device, NULL);
1568 }
1569 }
1570 }
1571
1572 if (packet_count == 0 && size < 0 && device->num_joysticks > 0) {
1573 // Read error, device is disconnected
1574 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1575 }
1576 return (size >= 0);
1577}
1578
1579static void HIDAPI_DriverPS5_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1580{
1581 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
1582
1583 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
1584 SDL_PS5EnhancedReportsChanged, ctx);
1585
1586 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,
1587 SDL_PS5PlayerLEDHintChanged, ctx);
1588
1589 ctx->joystick = NULL;
1590
1591 ctx->report_sensors = false;
1592 ctx->enhanced_mode = false;
1593 ctx->enhanced_mode_available = false;
1594}
1595
1596static void HIDAPI_DriverPS5_FreeDevice(SDL_HIDAPI_Device *device)
1597{
1598}
1599
1600SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5 = {
1601 SDL_HINT_JOYSTICK_HIDAPI_PS5,
1602 true,
1603 HIDAPI_DriverPS5_RegisterHints,
1604 HIDAPI_DriverPS5_UnregisterHints,
1605 HIDAPI_DriverPS5_IsEnabled,
1606 HIDAPI_DriverPS5_IsSupportedDevice,
1607 HIDAPI_DriverPS5_InitDevice,
1608 HIDAPI_DriverPS5_GetDevicePlayerIndex,
1609 HIDAPI_DriverPS5_SetDevicePlayerIndex,
1610 HIDAPI_DriverPS5_UpdateDevice,
1611 HIDAPI_DriverPS5_OpenJoystick,
1612 HIDAPI_DriverPS5_RumbleJoystick,
1613 HIDAPI_DriverPS5_RumbleJoystickTriggers,
1614 HIDAPI_DriverPS5_GetJoystickCapabilities,
1615 HIDAPI_DriverPS5_SetJoystickLED,
1616 HIDAPI_DriverPS5_SendJoystickEffect,
1617 HIDAPI_DriverPS5_SetJoystickSensorsEnabled,
1618 HIDAPI_DriverPS5_CloseJoystick,
1619 HIDAPI_DriverPS5_FreeDevice,
1620};
1621
1622#endif // SDL_JOYSTICK_HIDAPI_PS5
1623
1624#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.c
new file mode 100644
index 0000000..5fd93dc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.c
@@ -0,0 +1,285 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25// Handle rumble on a separate thread so it doesn't block the application
26
27#include "SDL_hidapijoystick_c.h"
28#include "SDL_hidapi_rumble.h"
29#include "../../thread/SDL_systhread.h"
30
31typedef struct SDL_HIDAPI_RumbleRequest
32{
33 SDL_HIDAPI_Device *device;
34 Uint8 data[2 * USB_PACKET_LENGTH]; // need enough space for the biggest report: dualshock4 is 78 bytes
35 int size;
36 SDL_HIDAPI_RumbleSentCallback callback;
37 void *userdata;
38 struct SDL_HIDAPI_RumbleRequest *prev;
39
40} SDL_HIDAPI_RumbleRequest;
41
42typedef struct SDL_HIDAPI_RumbleContext
43{
44 SDL_AtomicInt initialized;
45 SDL_AtomicInt running;
46 SDL_Thread *thread;
47 SDL_Semaphore *request_sem;
48 SDL_HIDAPI_RumbleRequest *requests_head;
49 SDL_HIDAPI_RumbleRequest *requests_tail;
50} SDL_HIDAPI_RumbleContext;
51
52#ifndef SDL_THREAD_SAFETY_ANALYSIS
53static
54#endif
55SDL_Mutex *SDL_HIDAPI_rumble_lock;
56static SDL_HIDAPI_RumbleContext rumble_context SDL_GUARDED_BY(SDL_HIDAPI_rumble_lock);
57
58static int SDLCALL SDL_HIDAPI_RumbleThread(void *data)
59{
60 SDL_HIDAPI_RumbleContext *ctx = (SDL_HIDAPI_RumbleContext *)data;
61
62 SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH);
63
64 while (SDL_GetAtomicInt(&ctx->running)) {
65 SDL_HIDAPI_RumbleRequest *request = NULL;
66
67 SDL_WaitSemaphore(ctx->request_sem);
68
69 SDL_LockMutex(SDL_HIDAPI_rumble_lock);
70 request = ctx->requests_tail;
71 if (request) {
72 if (request == ctx->requests_head) {
73 ctx->requests_head = NULL;
74 }
75 ctx->requests_tail = request->prev;
76 }
77 SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);
78
79 if (request) {
80 SDL_LockMutex(request->device->dev_lock);
81 if (request->device->dev) {
82#ifdef DEBUG_RUMBLE
83 HIDAPI_DumpPacket("Rumble packet: size = %d", request->data, request->size);
84#endif
85 SDL_hid_write(request->device->dev, request->data, request->size);
86 }
87 SDL_UnlockMutex(request->device->dev_lock);
88 if (request->callback) {
89 request->callback(request->userdata);
90 }
91 (void)SDL_AtomicDecRef(&request->device->rumble_pending);
92 SDL_free(request);
93
94 // Make sure we're not starving report reads when there's lots of rumble
95 SDL_Delay(10);
96 }
97 }
98 return 0;
99}
100
101static void SDL_HIDAPI_StopRumbleThread(SDL_HIDAPI_RumbleContext *ctx)
102{
103 SDL_HIDAPI_RumbleRequest *request;
104
105 SDL_SetAtomicInt(&ctx->running, false);
106
107 if (ctx->thread) {
108 int result;
109
110 SDL_SignalSemaphore(ctx->request_sem);
111 SDL_WaitThread(ctx->thread, &result);
112 ctx->thread = NULL;
113 }
114
115 SDL_LockMutex(SDL_HIDAPI_rumble_lock);
116 while (ctx->requests_tail) {
117 request = ctx->requests_tail;
118 if (request == ctx->requests_head) {
119 ctx->requests_head = NULL;
120 }
121 ctx->requests_tail = request->prev;
122
123 if (request->callback) {
124 request->callback(request->userdata);
125 }
126 (void)SDL_AtomicDecRef(&request->device->rumble_pending);
127 SDL_free(request);
128 }
129 SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);
130
131 if (ctx->request_sem) {
132 SDL_DestroySemaphore(ctx->request_sem);
133 ctx->request_sem = NULL;
134 }
135
136 if (SDL_HIDAPI_rumble_lock) {
137 SDL_DestroyMutex(SDL_HIDAPI_rumble_lock);
138 SDL_HIDAPI_rumble_lock = NULL;
139 }
140
141 SDL_SetAtomicInt(&ctx->initialized, false);
142}
143
144static bool SDL_HIDAPI_StartRumbleThread(SDL_HIDAPI_RumbleContext *ctx)
145{
146 SDL_HIDAPI_rumble_lock = SDL_CreateMutex();
147 if (!SDL_HIDAPI_rumble_lock) {
148 SDL_HIDAPI_StopRumbleThread(ctx);
149 return false;
150 }
151
152 ctx->request_sem = SDL_CreateSemaphore(0);
153 if (!ctx->request_sem) {
154 SDL_HIDAPI_StopRumbleThread(ctx);
155 return false;
156 }
157
158 SDL_SetAtomicInt(&ctx->running, true);
159 ctx->thread = SDL_CreateThread(SDL_HIDAPI_RumbleThread, "HIDAPI Rumble", ctx);
160 if (!ctx->thread) {
161 SDL_HIDAPI_StopRumbleThread(ctx);
162 return false;
163 }
164 return true;
165}
166
167bool SDL_HIDAPI_LockRumble(void)
168{
169 SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
170
171 if (SDL_CompareAndSwapAtomicInt(&ctx->initialized, false, true)) {
172 if (!SDL_HIDAPI_StartRumbleThread(ctx)) {
173 return false;
174 }
175 }
176
177 SDL_LockMutex(SDL_HIDAPI_rumble_lock);
178 return true;
179}
180
181bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size)
182{
183 SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
184 SDL_HIDAPI_RumbleRequest *request, *found;
185
186 found = NULL;
187 for (request = ctx->requests_tail; request; request = request->prev) {
188 if (request->device == device) {
189 found = request;
190 }
191 }
192 if (found) {
193 *data = found->data;
194 *size = &found->size;
195 *maximum_size = sizeof(found->data);
196 return true;
197 }
198 return false;
199}
200
201int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size)
202{
203 return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, data, size, NULL, NULL);
204}
205
206int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata)
207{
208 SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
209 SDL_HIDAPI_RumbleRequest *request;
210
211 if (size > sizeof(request->data)) {
212 SDL_HIDAPI_UnlockRumble();
213 SDL_SetError("Couldn't send rumble, size %d is greater than %d", size, (int)sizeof(request->data));
214 return -1;
215 }
216
217 request = (SDL_HIDAPI_RumbleRequest *)SDL_calloc(1, sizeof(*request));
218 if (!request) {
219 SDL_HIDAPI_UnlockRumble();
220 return -1;
221 }
222 request->device = device;
223 SDL_memcpy(request->data, data, size);
224 request->size = size;
225 request->callback = callback;
226 request->userdata = userdata;
227
228 SDL_AtomicIncRef(&device->rumble_pending);
229
230 if (ctx->requests_head) {
231 ctx->requests_head->prev = request;
232 } else {
233 ctx->requests_tail = request;
234 }
235 ctx->requests_head = request;
236
237 // Make sure we unlock before posting the semaphore so the rumble thread can run immediately
238 SDL_HIDAPI_UnlockRumble();
239
240 SDL_SignalSemaphore(ctx->request_sem);
241
242 return size;
243}
244
245void SDL_HIDAPI_UnlockRumble(void)
246{
247 SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);
248}
249
250int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size)
251{
252 Uint8 *pending_data;
253 int *pending_size;
254 int maximum_size;
255
256 if (size <= 0) {
257 SDL_SetError("Tried to send rumble with invalid size");
258 return -1;
259 }
260
261 if (!SDL_HIDAPI_LockRumble()) {
262 return -1;
263 }
264
265 // check if there is a pending request for the device and update it
266 if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size) &&
267 size == *pending_size && data[0] == pending_data[0]) {
268 SDL_memcpy(pending_data, data, size);
269 SDL_HIDAPI_UnlockRumble();
270 return size;
271 }
272
273 return SDL_HIDAPI_SendRumbleAndUnlock(device, data, size);
274}
275
276void SDL_HIDAPI_QuitRumble(void)
277{
278 SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
279
280 if (SDL_GetAtomicInt(&ctx->running)) {
281 SDL_HIDAPI_StopRumbleThread(ctx);
282 }
283}
284
285#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.h b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.h
new file mode 100644
index 0000000..ede061e
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_rumble.h
@@ -0,0 +1,42 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25// Handle rumble on a separate thread so it doesn't block the application
26
27// Advanced API
28#ifdef SDL_THREAD_SAFETY_ANALYSIS
29extern SDL_Mutex *SDL_HIDAPI_rumble_lock;
30#endif
31bool SDL_HIDAPI_LockRumble(void) SDL_TRY_ACQUIRE(0, SDL_HIDAPI_rumble_lock);
32bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size);
33int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) SDL_RELEASE(SDL_HIDAPI_rumble_lock);
34typedef void (*SDL_HIDAPI_RumbleSentCallback)(void *userdata);
35int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata) SDL_RELEASE(SDL_HIDAPI_rumble_lock);
36void SDL_HIDAPI_UnlockRumble(void) SDL_RELEASE(SDL_HIDAPI_rumble_lock);
37
38// Simple API, will replace any pending rumble with the new data
39int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size);
40void SDL_HIDAPI_QuitRumble(void);
41
42#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_shield.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_shield.c
new file mode 100644
index 0000000..10dcea3
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_shield.c
@@ -0,0 +1,578 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../SDL_sysjoystick.h"
26#include "SDL_hidapijoystick_c.h"
27#include "SDL_hidapi_rumble.h"
28
29#ifdef SDL_JOYSTICK_HIDAPI_SHIELD
30
31// Define this if you want to log all packets from the controller
32// #define DEBUG_SHIELD_PROTOCOL
33
34#define CMD_BATTERY_STATE 0x07
35#define CMD_RUMBLE 0x39
36#define CMD_CHARGE_STATE 0x3A
37
38// Milliseconds between polls of battery state
39#define BATTERY_POLL_INTERVAL_MS 60000
40
41// Milliseconds between retransmission of rumble to keep motors running
42#define RUMBLE_REFRESH_INTERVAL_MS 500
43
44// Reports that are too small are dropped over Bluetooth
45#define HID_REPORT_SIZE 33
46
47enum
48{
49 SDL_GAMEPAD_BUTTON_SHIELD_SHARE = 11,
50 SDL_GAMEPAD_BUTTON_SHIELD_V103_TOUCHPAD,
51 SDL_GAMEPAD_BUTTON_SHIELD_V103_MINUS,
52 SDL_GAMEPAD_BUTTON_SHIELD_V103_PLUS,
53 SDL_GAMEPAD_NUM_SHIELD_V103_BUTTONS,
54
55 SDL_GAMEPAD_NUM_SHIELD_V104_BUTTONS = SDL_GAMEPAD_BUTTON_SHIELD_SHARE + 1,
56};
57
58typedef enum
59{
60 k_ShieldReportIdControllerState = 0x01,
61 k_ShieldReportIdControllerTouch = 0x02,
62 k_ShieldReportIdCommandResponse = 0x03,
63 k_ShieldReportIdCommandRequest = 0x04,
64} EShieldReportId;
65
66// This same report structure is used for both requests and responses
67typedef struct
68{
69 Uint8 report_id;
70 Uint8 cmd;
71 Uint8 seq_num;
72 Uint8 payload[HID_REPORT_SIZE - 3];
73} ShieldCommandReport_t;
74SDL_COMPILE_TIME_ASSERT(ShieldCommandReport_t, sizeof(ShieldCommandReport_t) == HID_REPORT_SIZE);
75
76typedef struct
77{
78 Uint8 seq_num;
79
80 bool has_charging;
81 Uint8 charging;
82 bool has_battery_level;
83 Uint8 battery_level;
84 Uint64 last_battery_query_time;
85
86 bool rumble_report_pending;
87 bool rumble_update_pending;
88 Uint8 left_motor_amplitude;
89 Uint8 right_motor_amplitude;
90 Uint64 last_rumble_time;
91
92 Uint8 last_state[USB_PACKET_LENGTH];
93} SDL_DriverShield_Context;
94
95static void HIDAPI_DriverShield_RegisterHints(SDL_HintCallback callback, void *userdata)
96{
97 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, callback, userdata);
98}
99
100static void HIDAPI_DriverShield_UnregisterHints(SDL_HintCallback callback, void *userdata)
101{
102 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, callback, userdata);
103}
104
105static bool HIDAPI_DriverShield_IsEnabled(void)
106{
107 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SHIELD, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
108}
109
110static bool HIDAPI_DriverShield_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
111{
112 return SDL_IsJoystickNVIDIASHIELDController(vendor_id, product_id);
113}
114
115static bool HIDAPI_DriverShield_InitDevice(SDL_HIDAPI_Device *device)
116{
117 SDL_DriverShield_Context *ctx;
118
119 ctx = (SDL_DriverShield_Context *)SDL_calloc(1, sizeof(*ctx));
120 if (!ctx) {
121 return false;
122 }
123 device->context = ctx;
124
125 HIDAPI_SetDeviceName(device, "NVIDIA SHIELD Controller");
126
127 return HIDAPI_JoystickConnected(device, NULL);
128}
129
130static int HIDAPI_DriverShield_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
131{
132 return -1;
133}
134
135static void HIDAPI_DriverShield_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
136{
137}
138
139static bool HIDAPI_DriverShield_SendCommand(SDL_HIDAPI_Device *device, Uint8 cmd, const void *data, int size)
140{
141 SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
142 ShieldCommandReport_t cmd_pkt;
143
144 if (size > sizeof(cmd_pkt.payload)) {
145 return SDL_SetError("Command data exceeds HID report size");
146 }
147
148 if (!SDL_HIDAPI_LockRumble()) {
149 return false;
150 }
151
152 cmd_pkt.report_id = k_ShieldReportIdCommandRequest;
153 cmd_pkt.cmd = cmd;
154 cmd_pkt.seq_num = ctx->seq_num++;
155 if (data) {
156 SDL_memcpy(cmd_pkt.payload, data, size);
157 }
158
159 // Zero unused data in the payload
160 if (size != sizeof(cmd_pkt.payload)) {
161 SDL_memset(&cmd_pkt.payload[size], 0, sizeof(cmd_pkt.payload) - size);
162 }
163
164 if (SDL_HIDAPI_SendRumbleAndUnlock(device, (Uint8 *)&cmd_pkt, sizeof(cmd_pkt)) != sizeof(cmd_pkt)) {
165 return SDL_SetError("Couldn't send command packet");
166 }
167
168 return true;
169}
170
171static bool HIDAPI_DriverShield_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
172{
173 SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
174
175 SDL_AssertJoysticksLocked();
176
177 ctx->rumble_report_pending = false;
178 ctx->rumble_update_pending = false;
179 ctx->left_motor_amplitude = 0;
180 ctx->right_motor_amplitude = 0;
181 ctx->last_rumble_time = 0;
182 SDL_zeroa(ctx->last_state);
183
184 // Initialize the joystick capabilities
185 if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) {
186 joystick->nbuttons = SDL_GAMEPAD_NUM_SHIELD_V103_BUTTONS;
187 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
188 joystick->nhats = 1;
189
190 SDL_PrivateJoystickAddTouchpad(joystick, 1);
191 } else {
192 joystick->nbuttons = SDL_GAMEPAD_NUM_SHIELD_V104_BUTTONS;
193 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
194 joystick->nhats = 1;
195 }
196
197 // Request battery and charging info
198 ctx->last_battery_query_time = SDL_GetTicks();
199 HIDAPI_DriverShield_SendCommand(device, CMD_CHARGE_STATE, NULL, 0);
200 HIDAPI_DriverShield_SendCommand(device, CMD_BATTERY_STATE, NULL, 0);
201
202 return true;
203}
204
205static bool HIDAPI_DriverShield_SendNextRumble(SDL_HIDAPI_Device *device)
206{
207 SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
208 Uint8 rumble_data[3];
209
210 if (!ctx->rumble_update_pending) {
211 return true;
212 }
213
214 rumble_data[0] = 0x01; // enable
215 rumble_data[1] = ctx->left_motor_amplitude;
216 rumble_data[2] = ctx->right_motor_amplitude;
217
218 ctx->rumble_update_pending = false;
219 ctx->last_rumble_time = SDL_GetTicks();
220
221 return HIDAPI_DriverShield_SendCommand(device, CMD_RUMBLE, rumble_data, sizeof(rumble_data));
222}
223
224static bool HIDAPI_DriverShield_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
225{
226 if (device->product_id == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) {
227 Uint8 rumble_packet[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
228
229 rumble_packet[2] = (low_frequency_rumble >> 8);
230 rumble_packet[4] = (high_frequency_rumble >> 8);
231
232 if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
233 return SDL_SetError("Couldn't send rumble packet");
234 }
235 return true;
236
237 } else {
238 SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
239
240 // The rumble motors are quite intense, so tone down the intensity like the official driver does
241 ctx->left_motor_amplitude = low_frequency_rumble >> 11;
242 ctx->right_motor_amplitude = high_frequency_rumble >> 11;
243 ctx->rumble_update_pending = true;
244
245 if (ctx->rumble_report_pending) {
246 // We will service this after the hardware acknowledges the previous request
247 return true;
248 }
249
250 return HIDAPI_DriverShield_SendNextRumble(device);
251 }
252}
253
254static bool HIDAPI_DriverShield_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
255{
256 return SDL_Unsupported();
257}
258
259static Uint32 HIDAPI_DriverShield_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
260{
261 return SDL_JOYSTICK_CAP_RUMBLE;
262}
263
264static bool HIDAPI_DriverShield_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
265{
266 return SDL_Unsupported();
267}
268
269static bool HIDAPI_DriverShield_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
270{
271 const Uint8 *data_bytes = (const Uint8 *)data;
272
273 if (size > 1) {
274 // Single command byte followed by a variable length payload
275 return HIDAPI_DriverShield_SendCommand(device, data_bytes[0], &data_bytes[1], size - 1);
276 } else if (size == 1) {
277 // Single command byte with no payload
278 return HIDAPI_DriverShield_SendCommand(device, data_bytes[0], NULL, 0);
279 } else {
280 return SDL_SetError("Effect data must at least contain a command byte");
281 }
282}
283
284static bool HIDAPI_DriverShield_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
285{
286 return SDL_Unsupported();
287}
288
289static void HIDAPI_DriverShield_HandleStatePacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size)
290{
291 Uint64 timestamp = SDL_GetTicksNS();
292
293 if (ctx->last_state[3] != data[3]) {
294 Uint8 hat;
295
296 switch (data[3]) {
297 case 0:
298 hat = SDL_HAT_UP;
299 break;
300 case 1:
301 hat = SDL_HAT_RIGHTUP;
302 break;
303 case 2:
304 hat = SDL_HAT_RIGHT;
305 break;
306 case 3:
307 hat = SDL_HAT_RIGHTDOWN;
308 break;
309 case 4:
310 hat = SDL_HAT_DOWN;
311 break;
312 case 5:
313 hat = SDL_HAT_LEFTDOWN;
314 break;
315 case 6:
316 hat = SDL_HAT_LEFT;
317 break;
318 case 7:
319 hat = SDL_HAT_LEFTUP;
320 break;
321 default:
322 hat = SDL_HAT_CENTERED;
323 break;
324 }
325 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
326 }
327
328 if (ctx->last_state[1] != data[1]) {
329 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[1] & 0x01) != 0));
330 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[1] & 0x02) != 0));
331 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[1] & 0x04) != 0));
332 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[1] & 0x08) != 0));
333 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0));
334 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0));
335 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x40) != 0));
336 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x80) != 0));
337 }
338
339 if (ctx->last_state[2] != data[2]) {
340 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x02) != 0));
341 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_PLUS, ((data[2] & 0x08) != 0));
342 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_MINUS, ((data[2] & 0x10) != 0));
343 //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x20) != 0));
344 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x40) != 0));
345 //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[2] & 0x80) != 0));
346 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x80) != 0));
347 }
348
349 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[4]) - 0x8000);
350 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[6]) - 0x8000);
351
352 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[8]) - 0x8000);
353 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[10]) - 0x8000);
354
355 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[12]) - 0x8000);
356 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[14]) - 0x8000);
357
358 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
359}
360
361#undef clamp
362#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
363
364static void HIDAPI_DriverShield_HandleTouchPacketV103(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, const Uint8 *data, int size)
365{
366 bool touchpad_down;
367 float touchpad_x, touchpad_y;
368 Uint64 timestamp = SDL_GetTicksNS();
369
370 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_V103_TOUCHPAD, ((data[1] & 0x01) != 0));
371
372 // It's a triangular pad, but just use the center as the usable touch area
373 touchpad_down = ((data[1] & 0x80) == 0);
374 touchpad_x = clamp((float)(data[2] - 0x70) / 0x50, 0.0f, 1.0f);
375 touchpad_y = clamp((float)(data[4] - 0x40) / 0x15, 0.0f, 1.0f);
376 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x, touchpad_y, touchpad_down ? 1.0f : 0.0f);
377}
378
379static void HIDAPI_DriverShield_HandleStatePacketV104(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx, Uint8 *data, int size)
380{
381 Uint64 timestamp = SDL_GetTicksNS();
382
383 if (size < 23) {
384 return;
385 }
386
387 if (ctx->last_state[2] != data[2]) {
388 Uint8 hat;
389
390 switch (data[2]) {
391 case 0:
392 hat = SDL_HAT_UP;
393 break;
394 case 1:
395 hat = SDL_HAT_RIGHTUP;
396 break;
397 case 2:
398 hat = SDL_HAT_RIGHT;
399 break;
400 case 3:
401 hat = SDL_HAT_RIGHTDOWN;
402 break;
403 case 4:
404 hat = SDL_HAT_DOWN;
405 break;
406 case 5:
407 hat = SDL_HAT_LEFTDOWN;
408 break;
409 case 6:
410 hat = SDL_HAT_LEFT;
411 break;
412 case 7:
413 hat = SDL_HAT_LEFTUP;
414 break;
415 default:
416 hat = SDL_HAT_CENTERED;
417 break;
418 }
419 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
420 }
421
422 if (ctx->last_state[3] != data[3]) {
423 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x01) != 0));
424 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x02) != 0));
425 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x04) != 0));
426 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0));
427 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x10) != 0));
428 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x20) != 0));
429 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x40) != 0));
430 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[3] & 0x80) != 0));
431 }
432
433 if (ctx->last_state[4] != data[4]) {
434 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[4] & 0x01) != 0));
435 }
436
437 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_Swap16LE(*(Sint16 *)&data[9]) - 0x8000);
438 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_Swap16LE(*(Sint16 *)&data[11]) - 0x8000);
439
440 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_Swap16LE(*(Sint16 *)&data[13]) - 0x8000);
441 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_Swap16LE(*(Sint16 *)&data[15]) - 0x8000);
442
443 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[19]) - 0x8000);
444 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, SDL_Swap16LE(*(Sint16 *)&data[21]) - 0x8000);
445
446 if (ctx->last_state[17] != data[17]) {
447 //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SHIELD_SHARE, ((data[17] & 0x01) != 0));
448 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[17] & 0x02) != 0));
449 //SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[17] & 0x04) != 0));
450 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[17] & 0x01) != 0));
451 }
452
453 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
454}
455
456static void HIDAPI_DriverShield_UpdatePowerInfo(SDL_Joystick *joystick, SDL_DriverShield_Context *ctx)
457{
458 if (!ctx->has_charging || !ctx->has_battery_level) {
459 return;
460 }
461
462 SDL_PowerState state = ctx->charging ? SDL_POWERSTATE_CHARGING : SDL_POWERSTATE_ON_BATTERY;
463 int percent = ctx->battery_level * 20;
464 SDL_SendJoystickPowerInfo(joystick, state, percent);
465}
466
467static bool HIDAPI_DriverShield_UpdateDevice(SDL_HIDAPI_Device *device)
468{
469 SDL_DriverShield_Context *ctx = (SDL_DriverShield_Context *)device->context;
470 SDL_Joystick *joystick = NULL;
471 Uint8 data[USB_PACKET_LENGTH];
472 int size = 0;
473 ShieldCommandReport_t *cmd_resp_report;
474
475 if (device->num_joysticks > 0) {
476 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
477 } else {
478 return false;
479 }
480
481 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
482#ifdef DEBUG_SHIELD_PROTOCOL
483 HIDAPI_DumpPacket("NVIDIA SHIELD packet: size = %d", data, size);
484#endif
485
486 // Byte 0 is HID report ID
487 switch (data[0]) {
488 case k_ShieldReportIdControllerState:
489 if (!joystick) {
490 break;
491 }
492 if (size == 16) {
493 HIDAPI_DriverShield_HandleStatePacketV103(joystick, ctx, data, size);
494 } else {
495 HIDAPI_DriverShield_HandleStatePacketV104(joystick, ctx, data, size);
496 }
497 break;
498 case k_ShieldReportIdControllerTouch:
499 if (!joystick) {
500 break;
501 }
502 HIDAPI_DriverShield_HandleTouchPacketV103(joystick, ctx, data, size);
503 break;
504 case k_ShieldReportIdCommandResponse:
505 cmd_resp_report = (ShieldCommandReport_t *)data;
506 switch (cmd_resp_report->cmd) {
507 case CMD_RUMBLE:
508 ctx->rumble_report_pending = false;
509 HIDAPI_DriverShield_SendNextRumble(device);
510 break;
511 case CMD_CHARGE_STATE:
512 ctx->has_charging = true;
513 ctx->charging = cmd_resp_report->payload[0];
514 HIDAPI_DriverShield_UpdatePowerInfo(joystick, ctx);
515 break;
516 case CMD_BATTERY_STATE:
517 ctx->has_battery_level = true;
518 ctx->battery_level = cmd_resp_report->payload[2];
519 HIDAPI_DriverShield_UpdatePowerInfo(joystick, ctx);
520 break;
521 }
522 break;
523 }
524 }
525
526 // Ask for battery state again if we're due for an update
527 if (joystick && SDL_GetTicks() >= (ctx->last_battery_query_time + BATTERY_POLL_INTERVAL_MS)) {
528 ctx->last_battery_query_time = SDL_GetTicks();
529 HIDAPI_DriverShield_SendCommand(device, CMD_BATTERY_STATE, NULL, 0);
530 }
531
532 // Retransmit rumble packets if they've lasted longer than the hardware supports
533 if ((ctx->left_motor_amplitude != 0 || ctx->right_motor_amplitude != 0) &&
534 SDL_GetTicks() >= (ctx->last_rumble_time + RUMBLE_REFRESH_INTERVAL_MS)) {
535 ctx->rumble_update_pending = true;
536 HIDAPI_DriverShield_SendNextRumble(device);
537 }
538
539 if (size < 0) {
540 // Read error, device is disconnected
541 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
542 }
543 return (size >= 0);
544}
545
546static void HIDAPI_DriverShield_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
547{
548}
549
550static void HIDAPI_DriverShield_FreeDevice(SDL_HIDAPI_Device *device)
551{
552}
553
554SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield = {
555 SDL_HINT_JOYSTICK_HIDAPI_SHIELD,
556 true,
557 HIDAPI_DriverShield_RegisterHints,
558 HIDAPI_DriverShield_UnregisterHints,
559 HIDAPI_DriverShield_IsEnabled,
560 HIDAPI_DriverShield_IsSupportedDevice,
561 HIDAPI_DriverShield_InitDevice,
562 HIDAPI_DriverShield_GetDevicePlayerIndex,
563 HIDAPI_DriverShield_SetDevicePlayerIndex,
564 HIDAPI_DriverShield_UpdateDevice,
565 HIDAPI_DriverShield_OpenJoystick,
566 HIDAPI_DriverShield_RumbleJoystick,
567 HIDAPI_DriverShield_RumbleJoystickTriggers,
568 HIDAPI_DriverShield_GetJoystickCapabilities,
569 HIDAPI_DriverShield_SetJoystickLED,
570 HIDAPI_DriverShield_SendJoystickEffect,
571 HIDAPI_DriverShield_SetJoystickSensorsEnabled,
572 HIDAPI_DriverShield_CloseJoystick,
573 HIDAPI_DriverShield_FreeDevice,
574};
575
576#endif // SDL_JOYSTICK_HIDAPI_SHIELD
577
578#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_stadia.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_stadia.c
new file mode 100644
index 0000000..487bf41
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_stadia.c
@@ -0,0 +1,324 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../SDL_sysjoystick.h"
26#include "SDL_hidapijoystick_c.h"
27#include "SDL_hidapi_rumble.h"
28
29#ifdef SDL_JOYSTICK_HIDAPI_STADIA
30
31// Define this if you want to log all packets from the controller
32// #define DEBUG_STADIA_PROTOCOL
33
34enum
35{
36 SDL_GAMEPAD_BUTTON_STADIA_CAPTURE = 11,
37 SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT,
38 SDL_GAMEPAD_NUM_STADIA_BUTTONS,
39};
40
41typedef struct
42{
43 bool rumble_supported;
44 Uint8 last_state[USB_PACKET_LENGTH];
45} SDL_DriverStadia_Context;
46
47static void HIDAPI_DriverStadia_RegisterHints(SDL_HintCallback callback, void *userdata)
48{
49 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);
50}
51
52static void HIDAPI_DriverStadia_UnregisterHints(SDL_HintCallback callback, void *userdata)
53{
54 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);
55}
56
57static bool HIDAPI_DriverStadia_IsEnabled(void)
58{
59 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STADIA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
60}
61
62static bool HIDAPI_DriverStadia_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
63{
64 return SDL_IsJoystickGoogleStadiaController(vendor_id, product_id);
65}
66
67static bool HIDAPI_DriverStadia_InitDevice(SDL_HIDAPI_Device *device)
68{
69 SDL_DriverStadia_Context *ctx;
70
71 ctx = (SDL_DriverStadia_Context *)SDL_calloc(1, sizeof(*ctx));
72 if (!ctx) {
73 return false;
74 }
75 device->context = ctx;
76
77 // Check whether rumble is supported
78 {
79 Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
80
81 if (SDL_hid_write(device->dev, rumble_packet, sizeof(rumble_packet)) >= 0) {
82 ctx->rumble_supported = true;
83 }
84 }
85
86 HIDAPI_SetDeviceName(device, "Google Stadia Controller");
87
88 return HIDAPI_JoystickConnected(device, NULL);
89}
90
91static int HIDAPI_DriverStadia_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
92{
93 return -1;
94}
95
96static void HIDAPI_DriverStadia_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
97{
98}
99
100static bool HIDAPI_DriverStadia_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
101{
102 SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
103
104 SDL_AssertJoysticksLocked();
105
106 SDL_zeroa(ctx->last_state);
107
108 // Initialize the joystick capabilities
109 joystick->nbuttons = SDL_GAMEPAD_NUM_STADIA_BUTTONS;
110 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
111 joystick->nhats = 1;
112
113 return true;
114}
115
116static bool HIDAPI_DriverStadia_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
117{
118 SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
119
120 if (ctx->rumble_supported) {
121 Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
122
123
124 rumble_packet[1] = (low_frequency_rumble & 0xFF);
125 rumble_packet[2] = (low_frequency_rumble >> 8);
126 rumble_packet[3] = (high_frequency_rumble & 0xFF);
127 rumble_packet[4] = (high_frequency_rumble >> 8);
128
129 if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
130 return SDL_SetError("Couldn't send rumble packet");
131 }
132 return true;
133 } else {
134 return SDL_Unsupported();
135 }
136}
137
138static bool HIDAPI_DriverStadia_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
139{
140 return SDL_Unsupported();
141}
142
143static Uint32 HIDAPI_DriverStadia_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
144{
145 SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
146 Uint32 caps = 0;
147
148 if (ctx->rumble_supported) {
149 caps |= SDL_JOYSTICK_CAP_RUMBLE;
150 }
151 return caps;
152}
153
154static bool HIDAPI_DriverStadia_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
155{
156 return SDL_Unsupported();
157}
158
159static bool HIDAPI_DriverStadia_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
160{
161 return SDL_Unsupported();
162}
163
164static bool HIDAPI_DriverStadia_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
165{
166 return SDL_Unsupported();
167}
168
169static void HIDAPI_DriverStadia_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverStadia_Context *ctx, Uint8 *data, int size)
170{
171 Sint16 axis;
172 Uint64 timestamp = SDL_GetTicksNS();
173
174 // The format is the same but the original FW will send 10 bytes and January '21 FW update will send 11
175 if (size < 10 || data[0] != 0x03) {
176 // We don't know how to handle this report
177 return;
178 }
179
180 if (ctx->last_state[1] != data[1]) {
181 Uint8 hat;
182
183 switch (data[1]) {
184 case 0:
185 hat = SDL_HAT_UP;
186 break;
187 case 1:
188 hat = SDL_HAT_RIGHTUP;
189 break;
190 case 2:
191 hat = SDL_HAT_RIGHT;
192 break;
193 case 3:
194 hat = SDL_HAT_RIGHTDOWN;
195 break;
196 case 4:
197 hat = SDL_HAT_DOWN;
198 break;
199 case 5:
200 hat = SDL_HAT_LEFTDOWN;
201 break;
202 case 6:
203 hat = SDL_HAT_LEFT;
204 break;
205 case 7:
206 hat = SDL_HAT_LEFTUP;
207 break;
208 default:
209 hat = SDL_HAT_CENTERED;
210 break;
211 }
212 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
213 }
214
215 if (ctx->last_state[2] != data[2]) {
216 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x40) != 0));
217 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x10) != 0));
218 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x20) != 0));
219 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));
220 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_CAPTURE, ((data[2] & 0x01) != 0));
221 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, ((data[2] & 0x02) != 0));
222 }
223
224 if (ctx->last_state[3] != data[3]) {
225 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0));
226 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));
227 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x10) != 0));
228 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0));
229 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0));
230 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));
231 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x01) != 0));
232 }
233
234#define READ_STICK_AXIS(offset) \
235 (data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), 0x01 - 0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16))
236 {
237 axis = READ_STICK_AXIS(4);
238 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
239 axis = READ_STICK_AXIS(5);
240 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
241 axis = READ_STICK_AXIS(6);
242 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
243 axis = READ_STICK_AXIS(7);
244 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
245 }
246#undef READ_STICK_AXIS
247
248#define READ_TRIGGER_AXIS(offset) \
249 (Sint16)(((int)data[offset] * 257) - 32768)
250 {
251 axis = READ_TRIGGER_AXIS(8);
252 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
253 axis = READ_TRIGGER_AXIS(9);
254 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
255 }
256#undef READ_TRIGGER_AXIS
257
258 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
259}
260
261static bool HIDAPI_DriverStadia_UpdateDevice(SDL_HIDAPI_Device *device)
262{
263 SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
264 SDL_Joystick *joystick = NULL;
265 Uint8 data[USB_PACKET_LENGTH];
266 int size = 0;
267
268 if (device->num_joysticks > 0) {
269 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
270 } else {
271 return false;
272 }
273
274 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
275#ifdef DEBUG_STADIA_PROTOCOL
276 HIDAPI_DumpPacket("Google Stadia packet: size = %d", data, size);
277#endif
278 if (!joystick) {
279 continue;
280 }
281
282 HIDAPI_DriverStadia_HandleStatePacket(joystick, ctx, data, size);
283 }
284
285 if (size < 0) {
286 // Read error, device is disconnected
287 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
288 }
289 return (size >= 0);
290}
291
292static void HIDAPI_DriverStadia_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
293{
294}
295
296static void HIDAPI_DriverStadia_FreeDevice(SDL_HIDAPI_Device *device)
297{
298}
299
300SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia = {
301 SDL_HINT_JOYSTICK_HIDAPI_STADIA,
302 true,
303 HIDAPI_DriverStadia_RegisterHints,
304 HIDAPI_DriverStadia_UnregisterHints,
305 HIDAPI_DriverStadia_IsEnabled,
306 HIDAPI_DriverStadia_IsSupportedDevice,
307 HIDAPI_DriverStadia_InitDevice,
308 HIDAPI_DriverStadia_GetDevicePlayerIndex,
309 HIDAPI_DriverStadia_SetDevicePlayerIndex,
310 HIDAPI_DriverStadia_UpdateDevice,
311 HIDAPI_DriverStadia_OpenJoystick,
312 HIDAPI_DriverStadia_RumbleJoystick,
313 HIDAPI_DriverStadia_RumbleJoystickTriggers,
314 HIDAPI_DriverStadia_GetJoystickCapabilities,
315 HIDAPI_DriverStadia_SetJoystickLED,
316 HIDAPI_DriverStadia_SendJoystickEffect,
317 HIDAPI_DriverStadia_SetJoystickSensorsEnabled,
318 HIDAPI_DriverStadia_CloseJoystick,
319 HIDAPI_DriverStadia_FreeDevice,
320};
321
322#endif // SDL_JOYSTICK_HIDAPI_STADIA
323
324#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam.c
new file mode 100644
index 0000000..b48d353
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam.c
@@ -0,0 +1,1534 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28
29#ifdef SDL_JOYSTICK_HIDAPI_STEAM
30
31// Define this if you want to log all packets from the controller
32// #define DEBUG_STEAM_PROTOCOL
33
34#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED "SDL_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED"
35
36#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
37// This requires prompting for Bluetooth permissions, so make sure the application really wants it
38#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT false
39#else
40#define SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)
41#endif
42
43#define PAIRING_STATE_DURATION_SECONDS 60
44
45
46/*****************************************************************************************************/
47
48#include "steam/controller_constants.h"
49#include "steam/controller_structs.h"
50
51enum
52{
53 SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE = 11,
54 SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE,
55 SDL_GAMEPAD_NUM_STEAM_BUTTONS,
56};
57
58typedef struct SteamControllerStateInternal_t
59{
60 // Controller Type for this Controller State
61 Uint32 eControllerType;
62
63 // If packet num matches that on your prior call, then the controller state hasn't been changed since
64 // your last call and there is no need to process it
65 Uint32 unPacketNum;
66
67 // bit flags for each of the buttons
68 Uint64 ulButtons;
69
70 // Left pad coordinates
71 short sLeftPadX;
72 short sLeftPadY;
73
74 // Right pad coordinates
75 short sRightPadX;
76 short sRightPadY;
77
78 // Center pad coordinates
79 short sCenterPadX;
80 short sCenterPadY;
81
82 // Left analog stick coordinates
83 short sLeftStickX;
84 short sLeftStickY;
85
86 // Right analog stick coordinates
87 short sRightStickX;
88 short sRightStickY;
89
90 unsigned short sTriggerL;
91 unsigned short sTriggerR;
92
93 short sAccelX;
94 short sAccelY;
95 short sAccelZ;
96
97 short sGyroX;
98 short sGyroY;
99 short sGyroZ;
100
101 float sGyroQuatW;
102 float sGyroQuatX;
103 float sGyroQuatY;
104 float sGyroQuatZ;
105
106 short sGyroSteeringAngle;
107
108 unsigned short sBatteryLevel;
109
110 // Pressure sensor data.
111 unsigned short sPressurePadLeft;
112 unsigned short sPressurePadRight;
113
114 unsigned short sPressureBumperLeft;
115 unsigned short sPressureBumperRight;
116
117 // Internal state data
118 short sPrevLeftPad[2];
119 short sPrevLeftStick[2];
120} SteamControllerStateInternal_t;
121
122// Defines for ulButtons in SteamControllerStateInternal_t
123#define STEAM_RIGHT_TRIGGER_MASK 0x00000001
124#define STEAM_LEFT_TRIGGER_MASK 0x00000002
125#define STEAM_RIGHT_BUMPER_MASK 0x00000004
126#define STEAM_LEFT_BUMPER_MASK 0x00000008
127#define STEAM_BUTTON_NORTH_MASK 0x00000010 // Y
128#define STEAM_BUTTON_EAST_MASK 0x00000020 // B
129#define STEAM_BUTTON_WEST_MASK 0x00000040 // X
130#define STEAM_BUTTON_SOUTH_MASK 0x00000080 // A
131#define STEAM_DPAD_UP_MASK 0x00000100 // DPAD UP
132#define STEAM_DPAD_RIGHT_MASK 0x00000200 // DPAD RIGHT
133#define STEAM_DPAD_LEFT_MASK 0x00000400 // DPAD LEFT
134#define STEAM_DPAD_DOWN_MASK 0x00000800 // DPAD DOWN
135#define STEAM_BUTTON_MENU_MASK 0x00001000 // SELECT
136#define STEAM_BUTTON_STEAM_MASK 0x00002000 // GUIDE
137#define STEAM_BUTTON_ESCAPE_MASK 0x00004000 // START
138#define STEAM_BUTTON_BACK_LEFT_MASK 0x00008000
139#define STEAM_BUTTON_BACK_RIGHT_MASK 0x00010000
140#define STEAM_BUTTON_LEFTPAD_CLICKED_MASK 0x00020000
141#define STEAM_BUTTON_RIGHTPAD_CLICKED_MASK 0x00040000
142#define STEAM_LEFTPAD_FINGERDOWN_MASK 0x00080000
143#define STEAM_RIGHTPAD_FINGERDOWN_MASK 0x00100000
144#define STEAM_JOYSTICK_BUTTON_MASK 0x00400000
145#define STEAM_LEFTPAD_AND_JOYSTICK_MASK 0x00800000
146
147// Look for report version 0x0001, type WIRELESS (3), length >= 1 byte
148#define D0G_IS_VALID_WIRELESS_EVENT(data, len) ((len) >= 5 && (data)[0] == 1 && (data)[1] == 0 && (data)[2] == 3 && (data)[3] >= 1)
149#define D0G_GET_WIRELESS_EVENT_TYPE(data) ((data)[4])
150#define D0G_WIRELESS_DISCONNECTED 1
151#define D0G_WIRELESS_ESTABLISHED 2
152#define D0G_WIRELESS_NEWLYPAIRED 3
153
154#define D0G_IS_WIRELESS_DISCONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) == D0G_WIRELESS_DISCONNECTED)
155#define D0G_IS_WIRELESS_CONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) != D0G_WIRELESS_DISCONNECTED)
156
157
158#define MAX_REPORT_SEGMENT_PAYLOAD_SIZE 18
159/*
160 * SteamControllerPacketAssembler has to be used when reading output repots from controllers.
161 */
162typedef struct
163{
164 uint8_t uBuffer[MAX_REPORT_SEGMENT_PAYLOAD_SIZE * 8 + 1];
165 int nExpectedSegmentNumber;
166 bool bIsBle;
167} SteamControllerPacketAssembler;
168
169#undef clamp
170#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
171
172#undef offsetof
173#define offsetof(s, m) (size_t) & (((s *)0)->m)
174
175#ifdef DEBUG_STEAM_CONTROLLER
176#define DPRINTF(format, ...) printf(format, ##__VA_ARGS__)
177#define HEXDUMP(ptr, len) hexdump(ptr, len)
178#else
179#define DPRINTF(format, ...)
180#define HEXDUMP(ptr, len)
181#endif
182#define printf SDL_Log
183
184#define MAX_REPORT_SEGMENT_SIZE (MAX_REPORT_SEGMENT_PAYLOAD_SIZE + 2)
185#define CALC_REPORT_SEGMENT_NUM(index) ((index / MAX_REPORT_SEGMENT_PAYLOAD_SIZE) & 0x07)
186#define REPORT_SEGMENT_DATA_FLAG 0x80
187#define REPORT_SEGMENT_LAST_FLAG 0x40
188#define BLE_REPORT_NUMBER 0x03
189
190#define STEAMCONTROLLER_TRIGGER_MAX_ANALOG 26000
191
192// Enable mouse mode when using the Steam Controller locally
193#undef ENABLE_MOUSE_MODE
194
195// Wireless firmware quirk: the firmware intentionally signals "failure" when performing
196// SET_FEATURE / GET_FEATURE when it actually means "pending radio roundtrip". The only
197// way to make SET_FEATURE / GET_FEATURE work is to loop several times with a sleep. If
198// it takes more than 50ms to get the response for SET_FEATURE / GET_FEATURE, we assume
199// that the controller has failed.
200#define RADIO_WORKAROUND_SLEEP_ATTEMPTS 50
201#define RADIO_WORKAROUND_SLEEP_DURATION_US 500
202
203// This was defined by experimentation. 2000 seemed to work but to give that extra bit of margin, set to 3ms.
204#define CONTROLLER_CONFIGURATION_DELAY_US 3000
205
206static uint8_t GetSegmentHeader(int nSegmentNumber, bool bLastPacket)
207{
208 uint8_t header = REPORT_SEGMENT_DATA_FLAG;
209 header |= nSegmentNumber;
210 if (bLastPacket) {
211 header |= REPORT_SEGMENT_LAST_FLAG;
212 }
213
214 return header;
215}
216
217static void hexdump(const uint8_t *ptr, int len)
218{
219 HIDAPI_DumpPacket("Data", ptr, len);
220}
221
222static void ResetSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler)
223{
224 SDL_memset(pAssembler->uBuffer, 0, sizeof(pAssembler->uBuffer));
225 pAssembler->nExpectedSegmentNumber = 0;
226}
227
228static void InitializeSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, bool bIsBle)
229{
230 pAssembler->bIsBle = bIsBle;
231 ResetSteamControllerPacketAssembler(pAssembler);
232}
233
234// Returns:
235// <0 on error
236// 0 on not ready
237// Complete packet size on completion
238static int WriteSegmentToSteamControllerPacketAssembler(SteamControllerPacketAssembler *pAssembler, const uint8_t *pSegment, int nSegmentLength)
239{
240 if (pAssembler->bIsBle) {
241 uint8_t uSegmentHeader = pSegment[1];
242 int nSegmentNumber = uSegmentHeader & 0x07;
243
244 HEXDUMP(pSegment, nSegmentLength);
245
246 if (pSegment[0] != BLE_REPORT_NUMBER) {
247 // We may get keyboard/mouse input events until controller stops sending them
248 return 0;
249 }
250
251 if (nSegmentLength != MAX_REPORT_SEGMENT_SIZE) {
252 printf("Bad segment size! %d\n", nSegmentLength);
253 hexdump(pSegment, nSegmentLength);
254 ResetSteamControllerPacketAssembler(pAssembler);
255 return -1;
256 }
257
258 DPRINTF("GOT PACKET HEADER = 0x%x\n", uSegmentHeader);
259
260 if (!(uSegmentHeader & REPORT_SEGMENT_DATA_FLAG)) {
261 // We get empty segments, just ignore them
262 return 0;
263 }
264
265 if (nSegmentNumber != pAssembler->nExpectedSegmentNumber) {
266 ResetSteamControllerPacketAssembler(pAssembler);
267
268 if (nSegmentNumber) {
269 // This happens occasionally
270 DPRINTF("Bad segment number, got %d, expected %d\n",
271 nSegmentNumber, pAssembler->nExpectedSegmentNumber);
272 return -1;
273 }
274 }
275
276 SDL_memcpy(pAssembler->uBuffer + nSegmentNumber * MAX_REPORT_SEGMENT_PAYLOAD_SIZE,
277 pSegment + 2, // ignore header and report number
278 MAX_REPORT_SEGMENT_PAYLOAD_SIZE);
279
280 if (uSegmentHeader & REPORT_SEGMENT_LAST_FLAG) {
281 pAssembler->nExpectedSegmentNumber = 0;
282 return (nSegmentNumber + 1) * MAX_REPORT_SEGMENT_PAYLOAD_SIZE;
283 }
284
285 pAssembler->nExpectedSegmentNumber++;
286 } else {
287 // Just pass through
288 SDL_memcpy(pAssembler->uBuffer,
289 pSegment,
290 nSegmentLength);
291 return nSegmentLength;
292 }
293
294 return 0;
295}
296
297#define BLE_MAX_READ_RETRIES 8
298
299static int SetFeatureReport(SDL_HIDAPI_Device *dev, const unsigned char uBuffer[65], int nActualDataLen)
300{
301 int nRet = -1;
302
303 DPRINTF("SetFeatureReport %p %p %d\n", dev, uBuffer, nActualDataLen);
304
305 if (dev->is_bluetooth) {
306 int nSegmentNumber = 0;
307 uint8_t uPacketBuffer[MAX_REPORT_SEGMENT_SIZE];
308 const unsigned char *pBufferPtr = uBuffer + 1;
309
310 if (nActualDataLen < 1) {
311 return -1;
312 }
313
314 // Skip report number in data
315 nActualDataLen--;
316
317 while (nActualDataLen > 0) {
318 int nBytesInPacket = nActualDataLen > MAX_REPORT_SEGMENT_PAYLOAD_SIZE ? MAX_REPORT_SEGMENT_PAYLOAD_SIZE : nActualDataLen;
319
320 nActualDataLen -= nBytesInPacket;
321
322 // Construct packet
323 SDL_memset(uPacketBuffer, 0, sizeof(uPacketBuffer));
324 uPacketBuffer[0] = BLE_REPORT_NUMBER;
325 uPacketBuffer[1] = GetSegmentHeader(nSegmentNumber, nActualDataLen == 0);
326 SDL_memcpy(&uPacketBuffer[2], pBufferPtr, nBytesInPacket);
327
328 pBufferPtr += nBytesInPacket;
329 nSegmentNumber++;
330
331 nRet = SDL_hid_send_feature_report(dev->dev, uPacketBuffer, sizeof(uPacketBuffer));
332 }
333 } else {
334 for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) {
335 nRet = SDL_hid_send_feature_report(dev->dev, uBuffer, 65);
336 if (nRet >= 0) {
337 break;
338 }
339
340 SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000);
341 }
342 }
343
344 DPRINTF("SetFeatureReport() ret = %d\n", nRet);
345
346 return nRet;
347}
348
349static int GetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65])
350{
351 int nRet = -1;
352
353 DPRINTF("GetFeatureReport( %p %p )\n", dev, uBuffer);
354
355 if (dev->is_bluetooth) {
356 int nRetries = 0;
357 uint8_t uSegmentBuffer[MAX_REPORT_SEGMENT_SIZE + 1];
358 uint8_t ucBytesToRead = MAX_REPORT_SEGMENT_SIZE;
359 uint8_t ucDataStartOffset = 0;
360
361 SteamControllerPacketAssembler assembler;
362 InitializeSteamControllerPacketAssembler(&assembler, dev->is_bluetooth);
363
364 // On Windows and macOS, BLE devices get 2 copies of the feature report ID, one that is removed by ReadFeatureReport,
365 // and one that's included in the buffer we receive. We pad the bytes to read and skip over the report ID
366 // if necessary.
367#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_MACOS)
368 ++ucBytesToRead;
369 ++ucDataStartOffset;
370#endif
371
372 while (nRetries < BLE_MAX_READ_RETRIES) {
373 SDL_memset(uSegmentBuffer, 0, sizeof(uSegmentBuffer));
374 uSegmentBuffer[0] = BLE_REPORT_NUMBER;
375 nRet = SDL_hid_get_feature_report(dev->dev, uSegmentBuffer, ucBytesToRead);
376
377 DPRINTF("GetFeatureReport ble ret=%d\n", nRet);
378 HEXDUMP(uSegmentBuffer, nRet);
379
380 // Zero retry counter if we got data
381 if (nRet > 2 && (uSegmentBuffer[ucDataStartOffset + 1] & REPORT_SEGMENT_DATA_FLAG)) {
382 nRetries = 0;
383 } else {
384 nRetries++;
385 }
386
387 if (nRet > 0) {
388 int nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&assembler,
389 uSegmentBuffer + ucDataStartOffset,
390 nRet - ucDataStartOffset);
391
392 if (nPacketLength > 0 && nPacketLength < 65) {
393 // Leave space for "report number"
394 uBuffer[0] = 0;
395 SDL_memcpy(uBuffer + 1, assembler.uBuffer, nPacketLength);
396 return nPacketLength;
397 }
398 }
399 }
400 printf("Could not get a full ble packet after %d retries\n", nRetries);
401 return -1;
402 } else {
403 SDL_memset(uBuffer, 0, 65);
404
405 for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) {
406 nRet = SDL_hid_get_feature_report(dev->dev, uBuffer, 65);
407 if (nRet >= 0) {
408 break;
409 }
410
411 SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000);
412 }
413
414 DPRINTF("GetFeatureReport USB ret=%d\n", nRet);
415 HEXDUMP(uBuffer, nRet);
416 }
417
418 return nRet;
419}
420
421static int ReadResponse(SDL_HIDAPI_Device *dev, uint8_t uBuffer[65], int nExpectedResponse)
422{
423 for (int nRetries = 0; nRetries < 10; nRetries++) {
424 int nRet = GetFeatureReport(dev, uBuffer);
425
426 DPRINTF("ReadResponse( %p %p 0x%x )\n", dev, uBuffer, nExpectedResponse);
427
428 if (nRet < 0) {
429 continue;
430 }
431
432 DPRINTF("ReadResponse got %d bytes of data: ", nRet);
433 HEXDUMP(uBuffer, nRet);
434
435 if (uBuffer[1] != nExpectedResponse) {
436 continue;
437 }
438
439 return nRet;
440 }
441 return -1;
442}
443
444//---------------------------------------------------------------------------
445// Reset steam controller (unmap buttons and pads) and re-fetch capability bits
446//---------------------------------------------------------------------------
447static bool ResetSteamController(SDL_HIDAPI_Device *dev, bool bSuppressErrorSpew, uint32_t *punUpdateRateUS)
448{
449 // Firmware quirk: Set Feature and Get Feature requests always require a 65-byte buffer.
450 unsigned char buf[65];
451 unsigned int i;
452 int res = -1;
453 int nSettings = 0;
454 int nAttributesLength;
455 FeatureReportMsg *msg;
456 uint32_t unUpdateRateUS = 9000; // Good default rate
457
458 DPRINTF("ResetSteamController hid=%p\n", dev);
459
460 buf[0] = 0;
461 buf[1] = ID_GET_ATTRIBUTES_VALUES;
462 res = SetFeatureReport(dev, buf, 2);
463 if (res < 0) {
464 if (!bSuppressErrorSpew) {
465 printf("GET_ATTRIBUTES_VALUES failed for controller %p\n", dev);
466 }
467 return false;
468 }
469
470 // Retrieve GET_ATTRIBUTES_VALUES result
471 // Wireless controller endpoints without a connected controller will return nAttrs == 0
472 res = ReadResponse(dev, buf, ID_GET_ATTRIBUTES_VALUES);
473 if (res < 0 || buf[1] != ID_GET_ATTRIBUTES_VALUES) {
474 HEXDUMP(buf, res);
475 if (!bSuppressErrorSpew) {
476 printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev);
477 }
478 return false;
479 }
480
481 nAttributesLength = buf[2];
482 if (nAttributesLength > res) {
483 if (!bSuppressErrorSpew) {
484 printf("Bad GET_ATTRIBUTES_VALUES response for controller %p\n", dev);
485 }
486 return false;
487 }
488
489 msg = (FeatureReportMsg *)&buf[1];
490 for (i = 0; i < (int)msg->header.length / sizeof(ControllerAttribute); ++i) {
491 uint8_t unAttribute = msg->payload.getAttributes.attributes[i].attributeTag;
492 uint32_t unValue = msg->payload.getAttributes.attributes[i].attributeValue;
493
494 switch (unAttribute) {
495 case ATTRIB_UNIQUE_ID:
496 break;
497 case ATTRIB_PRODUCT_ID:
498 break;
499 case ATTRIB_CAPABILITIES:
500 break;
501 case ATTRIB_CONNECTION_INTERVAL_IN_US:
502 unUpdateRateUS = unValue;
503 break;
504 default:
505 break;
506 }
507 }
508 if (punUpdateRateUS) {
509 *punUpdateRateUS = unUpdateRateUS;
510 }
511
512 // Clear digital button mappings
513 buf[0] = 0;
514 buf[1] = ID_CLEAR_DIGITAL_MAPPINGS;
515 res = SetFeatureReport(dev, buf, 2);
516 if (res < 0) {
517 if (!bSuppressErrorSpew) {
518 printf("CLEAR_DIGITAL_MAPPINGS failed for controller %p\n", dev);
519 }
520 return false;
521 }
522
523 // Reset the default settings
524 SDL_memset(buf, 0, 65);
525 buf[1] = ID_LOAD_DEFAULT_SETTINGS;
526 buf[2] = 0;
527 res = SetFeatureReport(dev, buf, 3);
528 if (res < 0) {
529 if (!bSuppressErrorSpew) {
530 printf("LOAD_DEFAULT_SETTINGS failed for controller %p\n", dev);
531 }
532 return false;
533 }
534
535 // Apply custom settings - clear trackpad modes (cancel mouse emulation), etc
536#define ADD_SETTING(SETTING, VALUE) \
537 buf[3 + nSettings * 3] = SETTING; \
538 buf[3 + nSettings * 3 + 1] = ((uint16_t)VALUE) & 0xFF; \
539 buf[3 + nSettings * 3 + 2] = ((uint16_t)VALUE) >> 8; \
540 ++nSettings;
541
542 SDL_memset(buf, 0, 65);
543 buf[1] = ID_SET_SETTINGS_VALUES;
544 ADD_SETTING(SETTING_WIRELESS_PACKET_VERSION, 2);
545 ADD_SETTING(SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE);
546#ifdef ENABLE_MOUSE_MODE
547 ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE);
548 ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 1);
549 ADD_SETTING(SETTING_MOMENTUM_MAXIMUM_VELOCITY, 20000); // [0-20000] default 8000
550 ADD_SETTING(SETTING_MOMENTUM_DECAY_AMOUNT, 50); // [0-50] default 5
551#else
552 ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE);
553 ADD_SETTING(SETTING_SMOOTH_ABSOLUTE_MOUSE, 0);
554#endif
555 buf[2] = (unsigned char)(nSettings * 3);
556
557 res = SetFeatureReport(dev, buf, 3 + nSettings * 3);
558 if (res < 0) {
559 if (!bSuppressErrorSpew) {
560 printf("SET_SETTINGS failed for controller %p\n", dev);
561 }
562 return false;
563 }
564
565#ifdef ENABLE_MOUSE_MODE
566 // Wait for ID_CLEAR_DIGITAL_MAPPINGS to be processed on the controller
567 bool bMappingsCleared = false;
568 int iRetry;
569 for (iRetry = 0; iRetry < 2; ++iRetry) {
570 SDL_memset(buf, 0, 65);
571 buf[1] = ID_GET_DIGITAL_MAPPINGS;
572 buf[2] = 1; // one byte - requesting from index 0
573 buf[3] = 0;
574 res = SetFeatureReport(dev, buf, 4);
575 if (res < 0) {
576 printf("GET_DIGITAL_MAPPINGS failed for controller %p\n", dev);
577 return false;
578 }
579
580 res = ReadResponse(dev, buf, ID_GET_DIGITAL_MAPPINGS);
581 if (res < 0 || buf[1] != ID_GET_DIGITAL_MAPPINGS) {
582 printf("Bad GET_DIGITAL_MAPPINGS response for controller %p\n", dev);
583 return false;
584 }
585
586 // If the length of the digital mappings result is not 1 (index byte, no mappings) then clearing hasn't executed
587 if (buf[2] == 1 && buf[3] == 0xFF) {
588 bMappingsCleared = true;
589 break;
590 }
591 usleep(CONTROLLER_CONFIGURATION_DELAY_US);
592 }
593
594 if (!bMappingsCleared && !bSuppressErrorSpew) {
595 printf("Warning: CLEAR_DIGITAL_MAPPINGS never completed for controller %p\n", dev);
596 }
597
598 // Set our new mappings
599 SDL_memset(buf, 0, 65);
600 buf[1] = ID_SET_DIGITAL_MAPPINGS;
601 buf[2] = 6; // 2 settings x 3 bytes
602 buf[3] = IO_DIGITAL_BUTTON_RIGHT_TRIGGER;
603 buf[4] = DEVICE_MOUSE;
604 buf[5] = MOUSE_BTN_LEFT;
605 buf[6] = IO_DIGITAL_BUTTON_LEFT_TRIGGER;
606 buf[7] = DEVICE_MOUSE;
607 buf[8] = MOUSE_BTN_RIGHT;
608
609 res = SetFeatureReport(dev, buf, 9);
610 if (res < 0) {
611 if (!bSuppressErrorSpew) {
612 printf("SET_DIGITAL_MAPPINGS failed for controller %p\n", dev);
613 }
614 return false;
615 }
616#endif // ENABLE_MOUSE_MODE
617
618 return true;
619}
620
621//---------------------------------------------------------------------------
622// Read from a Steam Controller
623//---------------------------------------------------------------------------
624static int ReadSteamController(SDL_hid_device *dev, uint8_t *pData, int nDataSize)
625{
626 SDL_memset(pData, 0, nDataSize);
627 pData[0] = BLE_REPORT_NUMBER; // hid_read will also overwrite this with the same value, 0x03
628 return SDL_hid_read(dev, pData, nDataSize);
629}
630
631//---------------------------------------------------------------------------
632// Set Steam Controller pairing state
633//---------------------------------------------------------------------------
634static void SetPairingState(SDL_HIDAPI_Device *dev, bool bEnablePairing)
635{
636 unsigned char buf[65];
637 SDL_memset(buf, 0, 65);
638 buf[1] = ID_ENABLE_PAIRING;
639 buf[2] = 2; // 2 payload bytes: bool + timeout
640 buf[3] = bEnablePairing ? 1 : 0;
641 buf[4] = bEnablePairing ? PAIRING_STATE_DURATION_SECONDS : 0;
642 SetFeatureReport(dev, buf, 5);
643}
644
645//---------------------------------------------------------------------------
646// Commit Steam Controller pairing
647//---------------------------------------------------------------------------
648static void CommitPairing(SDL_HIDAPI_Device *dev)
649{
650 unsigned char buf[65];
651 SDL_memset(buf, 0, 65);
652 buf[1] = ID_DONGLE_COMMIT_DEVICE;
653 SetFeatureReport(dev, buf, 2);
654}
655
656//---------------------------------------------------------------------------
657// Close a Steam Controller
658//---------------------------------------------------------------------------
659static void CloseSteamController(SDL_HIDAPI_Device *dev)
660{
661 // Switch the Steam Controller back to lizard mode so it works with the OS
662 unsigned char buf[65];
663 int nSettings = 0;
664
665 // Reset digital button mappings
666 SDL_memset(buf, 0, 65);
667 buf[1] = ID_SET_DEFAULT_DIGITAL_MAPPINGS;
668 SetFeatureReport(dev, buf, 2);
669
670 // Reset the default settings
671 SDL_memset(buf, 0, 65);
672 buf[1] = ID_LOAD_DEFAULT_SETTINGS;
673 buf[2] = 0;
674 SetFeatureReport(dev, buf, 3);
675
676 // Reset mouse mode for lizard mode
677 SDL_memset(buf, 0, 65);
678 buf[1] = ID_SET_SETTINGS_VALUES;
679 ADD_SETTING(SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_ABSOLUTE_MOUSE);
680 buf[2] = (unsigned char)(nSettings * 3);
681 SetFeatureReport(dev, buf, 3 + nSettings * 3);
682}
683
684//---------------------------------------------------------------------------
685// Scale and clamp values to a range
686//---------------------------------------------------------------------------
687static float RemapValClamped(float val, float A, float B, float C, float D)
688{
689 if (A == B) {
690 return (val - B) >= 0.0f ? D : C;
691 } else {
692 float cVal = (val - A) / (B - A);
693 cVal = clamp(cVal, 0.0f, 1.0f);
694
695 return C + (D - C) * cVal;
696 }
697}
698
699//---------------------------------------------------------------------------
700// Rotate the pad coordinates
701//---------------------------------------------------------------------------
702static void RotatePad(int *pX, int *pY, float flAngleInRad)
703{
704 int origX = *pX, origY = *pY;
705
706 *pX = (int)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY);
707 *pY = (int)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY);
708}
709static void RotatePadShort(short *pX, short *pY, float flAngleInRad)
710{
711 int origX = *pX, origY = *pY;
712
713 *pX = (short)(SDL_cosf(flAngleInRad) * origX - SDL_sinf(flAngleInRad) * origY);
714 *pY = (short)(SDL_sinf(flAngleInRad) * origX + SDL_cosf(flAngleInRad) * origY);
715}
716
717//---------------------------------------------------------------------------
718// Format the first part of the state packet
719//---------------------------------------------------------------------------
720static void FormatStatePacketUntilGyro(SteamControllerStateInternal_t *pState, ValveControllerStatePacket_t *pStatePacket)
721{
722 int nLeftPadX;
723 int nLeftPadY;
724 int nRightPadX;
725 int nRightPadY;
726 int nPadOffset;
727
728 // 15 degrees in rad
729 const float flRotationAngle = 0.261799f;
730
731 SDL_memset(pState, 0, offsetof(SteamControllerStateInternal_t, sBatteryLevel));
732
733 // pState->eControllerType = m_eControllerType;
734 pState->eControllerType = 2; // k_eControllerType_SteamController;
735 pState->unPacketNum = pStatePacket->unPacketNum;
736
737 // We have a chunk of trigger data in the packet format here, so zero it out afterwards
738 SDL_memcpy(&pState->ulButtons, &pStatePacket->ButtonTriggerData.ulButtons, 8);
739 pState->ulButtons &= ~0xFFFF000000LL;
740
741 // The firmware uses this bit to tell us what kind of data is packed into the left two axes
742 if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {
743 // Finger-down bit not set; "left pad" is actually trackpad
744 pState->sLeftPadX = pState->sPrevLeftPad[0] = pStatePacket->sLeftPadX;
745 pState->sLeftPadY = pState->sPrevLeftPad[1] = pStatePacket->sLeftPadY;
746
747 if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {
748 // The controller is interleaving both stick and pad data, both are active
749 pState->sLeftStickX = pState->sPrevLeftStick[0];
750 pState->sLeftStickY = pState->sPrevLeftStick[1];
751 } else {
752 // The stick is not active
753 pState->sPrevLeftStick[0] = 0;
754 pState->sPrevLeftStick[1] = 0;
755 }
756 } else {
757 // Finger-down bit not set; "left pad" is actually joystick
758
759 // XXX there's a firmware bug where sometimes padX is 0 and padY is a large number (actually the battery voltage)
760 // If that happens skip this packet and report last frames stick
761 /*
762 if ( m_eControllerType == k_eControllerType_SteamControllerV2 && pStatePacket->sLeftPadY > 900 ) {
763 pState->sLeftStickX = pState->sPrevLeftStick[0];
764 pState->sLeftStickY = pState->sPrevLeftStick[1];
765 } else
766 */
767 {
768 pState->sPrevLeftStick[0] = pState->sLeftStickX = pStatePacket->sLeftPadX;
769 pState->sPrevLeftStick[1] = pState->sLeftStickY = pStatePacket->sLeftPadY;
770 }
771 /*
772 if (m_eControllerType == k_eControllerType_SteamControllerV2) {
773 UpdateV2JoystickCap(&state);
774 }
775 */
776
777 if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {
778 // The controller is interleaving both stick and pad data, both are active
779 pState->sLeftPadX = pState->sPrevLeftPad[0];
780 pState->sLeftPadY = pState->sPrevLeftPad[1];
781 } else {
782 // The trackpad is not active
783 pState->sPrevLeftPad[0] = 0;
784 pState->sPrevLeftPad[1] = 0;
785
786 // Old controllers send trackpad click for joystick button when trackpad is not active
787 if (pState->ulButtons & STEAM_BUTTON_LEFTPAD_CLICKED_MASK) {
788 pState->ulButtons &= ~STEAM_BUTTON_LEFTPAD_CLICKED_MASK;
789 pState->ulButtons |= STEAM_JOYSTICK_BUTTON_MASK;
790 }
791 }
792 }
793
794 // Fingerdown bit indicates if the packed left axis data was joystick or pad,
795 // but if we are interleaving both, the left finger is definitely on the pad.
796 if (pStatePacket->ButtonTriggerData.ulButtons & STEAM_LEFTPAD_AND_JOYSTICK_MASK) {
797 pState->ulButtons |= STEAM_LEFTPAD_FINGERDOWN_MASK;
798 }
799
800 pState->sRightPadX = pStatePacket->sRightPadX;
801 pState->sRightPadY = pStatePacket->sRightPadY;
802
803 nLeftPadX = pState->sLeftPadX;
804 nLeftPadY = pState->sLeftPadY;
805 nRightPadX = pState->sRightPadX;
806 nRightPadY = pState->sRightPadY;
807
808 RotatePad(&nLeftPadX, &nLeftPadY, -flRotationAngle);
809 RotatePad(&nRightPadX, &nRightPadY, flRotationAngle);
810
811 if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {
812 nPadOffset = 1000;
813 } else {
814 nPadOffset = 0;
815 }
816
817 pState->sLeftPadX = (short)clamp(nLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
818 pState->sLeftPadY = (short)clamp(nLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
819
820 nPadOffset = 0;
821 if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) {
822 nPadOffset = 1000;
823 } else {
824 nPadOffset = 0;
825 }
826
827 pState->sRightPadX = (short)clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
828 pState->sRightPadY = (short)clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
829
830 pState->sTriggerL = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nLeft << 7) | pStatePacket->ButtonTriggerData.Triggers.nLeft), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
831 pState->sTriggerR = (unsigned short)RemapValClamped((float)((pStatePacket->ButtonTriggerData.Triggers.nRight << 7) | pStatePacket->ButtonTriggerData.Triggers.nRight), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
832}
833
834//---------------------------------------------------------------------------
835// Update Steam Controller state from a BLE data packet, returns true if it parsed data
836//---------------------------------------------------------------------------
837static bool UpdateBLESteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState)
838{
839 const float flRotationAngle = 0.261799f;
840 uint32_t ucOptionDataMask;
841
842 pState->unPacketNum++;
843 ucOptionDataMask = (*pData++ & 0xF0);
844 ucOptionDataMask |= (uint32_t)(*pData++) << 8;
845 if (ucOptionDataMask & k_EBLEButtonChunk1) {
846 SDL_memcpy(&pState->ulButtons, pData, 3);
847 pData += 3;
848 }
849 if (ucOptionDataMask & k_EBLEButtonChunk2) {
850 // The middle 2 bytes of the button bits over the wire are triggers when over the wire and non-SC buttons in the internal controller state packet
851 pState->sTriggerL = (unsigned short)RemapValClamped((float)((pData[0] << 7) | pData[0]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
852 pState->sTriggerR = (unsigned short)RemapValClamped((float)((pData[1] << 7) | pData[1]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16);
853 pData += 2;
854 }
855 if (ucOptionDataMask & k_EBLEButtonChunk3) {
856 uint8_t *pButtonByte = (uint8_t *)&pState->ulButtons;
857 pButtonByte[5] = *pData++;
858 pButtonByte[6] = *pData++;
859 pButtonByte[7] = *pData++;
860 }
861 if (ucOptionDataMask & k_EBLELeftJoystickChunk) {
862 // This doesn't handle any of the special headcrab stuff for raw joystick which is OK for now since that FW doesn't support
863 // this protocol yet either
864 int nLength = sizeof(pState->sLeftStickX) + sizeof(pState->sLeftStickY);
865 SDL_memcpy(&pState->sLeftStickX, pData, nLength);
866 pData += nLength;
867 }
868 if (ucOptionDataMask & k_EBLELeftTrackpadChunk) {
869 int nLength = sizeof(pState->sLeftPadX) + sizeof(pState->sLeftPadY);
870 int nPadOffset;
871 SDL_memcpy(&pState->sLeftPadX, pData, nLength);
872 if (pState->ulButtons & STEAM_LEFTPAD_FINGERDOWN_MASK) {
873 nPadOffset = 1000;
874 } else {
875 nPadOffset = 0;
876 }
877
878 RotatePadShort(&pState->sLeftPadX, &pState->sLeftPadY, -flRotationAngle);
879 pState->sLeftPadX = (short)clamp(pState->sLeftPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
880 pState->sLeftPadY = (short)clamp(pState->sLeftPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
881 pData += nLength;
882 }
883 if (ucOptionDataMask & k_EBLERightTrackpadChunk) {
884 int nLength = sizeof(pState->sRightPadX) + sizeof(pState->sRightPadY);
885 int nPadOffset = 0;
886
887 SDL_memcpy(&pState->sRightPadX, pData, nLength);
888
889 if (pState->ulButtons & STEAM_RIGHTPAD_FINGERDOWN_MASK) {
890 nPadOffset = 1000;
891 } else {
892 nPadOffset = 0;
893 }
894
895 RotatePadShort(&pState->sRightPadX, &pState->sRightPadY, flRotationAngle);
896 pState->sRightPadX = (short)clamp(pState->sRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
897 pState->sRightPadY = (short)clamp(pState->sRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16);
898 pData += nLength;
899 }
900 if (ucOptionDataMask & k_EBLEIMUAccelChunk) {
901 int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ);
902 SDL_memcpy(&pState->sAccelX, pData, nLength);
903 pData += nLength;
904 }
905 if (ucOptionDataMask & k_EBLEIMUGyroChunk) {
906 int nLength = sizeof(pState->sAccelX) + sizeof(pState->sAccelY) + sizeof(pState->sAccelZ);
907 SDL_memcpy(&pState->sGyroX, pData, nLength);
908 pData += nLength;
909 }
910 if (ucOptionDataMask & k_EBLEIMUQuatChunk) {
911 int nLength = sizeof(pState->sGyroQuatW) + sizeof(pState->sGyroQuatX) + sizeof(pState->sGyroQuatY) + sizeof(pState->sGyroQuatZ);
912 SDL_memcpy(&pState->sGyroQuatW, pData, nLength);
913 pData += nLength;
914 }
915 return true;
916}
917
918//---------------------------------------------------------------------------
919// Update Steam Controller state from a data packet, returns true if it parsed data
920//---------------------------------------------------------------------------
921static bool UpdateSteamControllerState(const uint8_t *pData, int nDataSize, SteamControllerStateInternal_t *pState)
922{
923 ValveInReport_t *pInReport = (ValveInReport_t *)pData;
924
925 if (pInReport->header.unReportVersion != k_ValveInReportMsgVersion) {
926 if ((pData[0] & 0x0F) == k_EBLEReportState) {
927 return UpdateBLESteamControllerState(pData, nDataSize, pState);
928 }
929 return false;
930 }
931
932 if ((pInReport->header.ucType != ID_CONTROLLER_STATE) &&
933 (pInReport->header.ucType != ID_CONTROLLER_BLE_STATE)) {
934 return false;
935 }
936
937 if (pInReport->header.ucType == ID_CONTROLLER_STATE) {
938 ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState;
939
940 // No new data to process; indicate that we received a state packet, but otherwise do nothing.
941 if (pState->unPacketNum == pStatePacket->unPacketNum) {
942 return true;
943 }
944
945 FormatStatePacketUntilGyro(pState, pStatePacket);
946
947 pState->sAccelX = pStatePacket->sAccelX;
948 pState->sAccelY = pStatePacket->sAccelY;
949 pState->sAccelZ = pStatePacket->sAccelZ;
950
951 pState->sGyroQuatW = pStatePacket->sGyroQuatW;
952 pState->sGyroQuatX = pStatePacket->sGyroQuatX;
953 pState->sGyroQuatY = pStatePacket->sGyroQuatY;
954 pState->sGyroQuatZ = pStatePacket->sGyroQuatZ;
955
956 pState->sGyroX = pStatePacket->sGyroX;
957 pState->sGyroY = pStatePacket->sGyroY;
958 pState->sGyroZ = pStatePacket->sGyroZ;
959
960 } else if (pInReport->header.ucType == ID_CONTROLLER_BLE_STATE) {
961 ValveControllerBLEStatePacket_t *pBLEStatePacket = &pInReport->payload.controllerBLEState;
962 ValveControllerStatePacket_t *pStatePacket = &pInReport->payload.controllerState;
963
964 // No new data to process; indicate that we received a state packet, but otherwise do nothing.
965 if (pState->unPacketNum == pStatePacket->unPacketNum) {
966 return true;
967 }
968
969 FormatStatePacketUntilGyro(pState, pStatePacket);
970
971 switch (pBLEStatePacket->ucGyroDataType) {
972 case 1:
973 pState->sGyroQuatW = ((float)pBLEStatePacket->sGyro[0]);
974 pState->sGyroQuatX = ((float)pBLEStatePacket->sGyro[1]);
975 pState->sGyroQuatY = ((float)pBLEStatePacket->sGyro[2]);
976 pState->sGyroQuatZ = ((float)pBLEStatePacket->sGyro[3]);
977 break;
978
979 case 2:
980 pState->sAccelX = pBLEStatePacket->sGyro[0];
981 pState->sAccelY = pBLEStatePacket->sGyro[1];
982 pState->sAccelZ = pBLEStatePacket->sGyro[2];
983 break;
984
985 case 3:
986 pState->sGyroX = pBLEStatePacket->sGyro[0];
987 pState->sGyroY = pBLEStatePacket->sGyro[1];
988 pState->sGyroZ = pBLEStatePacket->sGyro[2];
989 break;
990
991 default:
992 break;
993 }
994 }
995
996 return true;
997}
998
999/*****************************************************************************************************/
1000
1001typedef struct
1002{
1003 SDL_HIDAPI_Device *device;
1004 bool connected;
1005 bool report_sensors;
1006 uint32_t update_rate_in_us;
1007 Uint64 sensor_timestamp;
1008 Uint64 pairing_time;
1009
1010 SteamControllerPacketAssembler m_assembler;
1011 SteamControllerStateInternal_t m_state;
1012 SteamControllerStateInternal_t m_last_state;
1013} SDL_DriverSteam_Context;
1014
1015static bool IsDongle(Uint16 product_id)
1016{
1017 return (product_id == USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE);
1018}
1019
1020static void HIDAPI_DriverSteam_RegisterHints(SDL_HintCallback callback, void *userdata)
1021{
1022 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);
1023}
1024
1025static void HIDAPI_DriverSteam_UnregisterHints(SDL_HintCallback callback, void *userdata)
1026{
1027 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);
1028}
1029
1030static bool HIDAPI_DriverSteam_IsEnabled(void)
1031{
1032 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_HINT_JOYSTICK_HIDAPI_STEAM_DEFAULT);
1033}
1034
1035static bool HIDAPI_DriverSteam_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
1036{
1037 if (!SDL_IsJoystickSteamController(vendor_id, product_id)) {
1038 return false;
1039 }
1040
1041 if (device->is_bluetooth) {
1042 return true;
1043 }
1044
1045 if (IsDongle(product_id)) {
1046 if (interface_number >= 1 && interface_number <= 4) {
1047 // This is one of the wireless controller interfaces
1048 return true;
1049 }
1050 } else {
1051 if (interface_number == 2) {
1052 // This is the controller interface (not mouse or keyboard)
1053 return true;
1054 }
1055 }
1056 return false;
1057}
1058
1059static void HIDAPI_DriverSteam_SetPairingState(SDL_DriverSteam_Context *ctx, bool enabled)
1060{
1061 // Only have one dongle in pairing mode at a time
1062 static SDL_DriverSteam_Context *s_PairingContext = NULL;
1063
1064 if (enabled && s_PairingContext != NULL) {
1065 return;
1066 }
1067
1068 if (!enabled && s_PairingContext != ctx) {
1069 return;
1070 }
1071
1072 if (ctx->connected) {
1073 return;
1074 }
1075
1076 SetPairingState(ctx->device, enabled);
1077
1078 if (enabled) {
1079 ctx->pairing_time = SDL_GetTicks();
1080 s_PairingContext = ctx;
1081 } else {
1082 ctx->pairing_time = 0;
1083 s_PairingContext = NULL;
1084 }
1085}
1086
1087static void HIDAPI_DriverSteam_RenewPairingState(SDL_DriverSteam_Context *ctx)
1088{
1089 Uint64 now = SDL_GetTicks();
1090
1091 if (now >= ctx->pairing_time + PAIRING_STATE_DURATION_SECONDS * 1000) {
1092 SetPairingState(ctx->device, true);
1093 ctx->pairing_time = now;
1094 }
1095}
1096
1097static void HIDAPI_DriverSteam_CommitPairing(SDL_DriverSteam_Context *ctx)
1098{
1099 CommitPairing(ctx->device);
1100}
1101
1102static void SDLCALL SDL_PairingEnabledHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
1103{
1104 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata;
1105 bool enabled = SDL_GetStringBoolean(hint, false);
1106
1107 HIDAPI_DriverSteam_SetPairingState(ctx, enabled);
1108}
1109
1110static bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device)
1111{
1112 SDL_DriverSteam_Context *ctx;
1113
1114 ctx = (SDL_DriverSteam_Context *)SDL_calloc(1, sizeof(*ctx));
1115 if (!ctx) {
1116 return false;
1117 }
1118 ctx->device = device;
1119 device->context = ctx;
1120
1121#ifdef SDL_PLATFORM_WIN32
1122 if (device->serial) {
1123 // We get a garbage serial number on Windows
1124 SDL_free(device->serial);
1125 device->serial = NULL;
1126 }
1127#endif // SDL_PLATFORM_WIN32
1128
1129 HIDAPI_SetDeviceName(device, "Steam Controller");
1130
1131 // If this is a wireless dongle, request a wireless state update
1132 if (IsDongle(device->product_id)) {
1133 unsigned char buf[65];
1134 int res;
1135
1136 buf[0] = 0;
1137 buf[1] = ID_DONGLE_GET_WIRELESS_STATE;
1138 res = SetFeatureReport(device, buf, 2);
1139 if (res < 0) {
1140 return SDL_SetError("Failed to send ID_DONGLE_GET_WIRELESS_STATE request");
1141 }
1142
1143 for (int attempt = 0; attempt < 5; ++attempt) {
1144 uint8_t data[128];
1145
1146 res = ReadSteamController(device->dev, data, sizeof(data));
1147 if (res == 0) {
1148 SDL_Delay(1);
1149 continue;
1150 }
1151 if (res < 0) {
1152 break;
1153 }
1154
1155#ifdef DEBUG_STEAM_PROTOCOL
1156 HIDAPI_DumpPacket("Initial dongle packet: size = %d", data, res);
1157#endif
1158
1159 if (D0G_IS_WIRELESS_CONNECT(data, res)) {
1160 ctx->connected = true;
1161 break;
1162 } else if (D0G_IS_WIRELESS_DISCONNECT(data, res)) {
1163 ctx->connected = false;
1164 break;
1165 }
1166 }
1167
1168 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED,
1169 SDL_PairingEnabledHintChanged, ctx);
1170 } else {
1171 // Wired and BLE controllers are always connected if HIDAPI can see them
1172 ctx->connected = true;
1173 }
1174
1175 if (ctx->connected) {
1176 return HIDAPI_JoystickConnected(device, NULL);
1177 } else {
1178 // We will enumerate any attached controllers in UpdateDevice()
1179 return true;
1180 }
1181}
1182
1183static int HIDAPI_DriverSteam_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
1184{
1185 return -1;
1186}
1187
1188static void HIDAPI_DriverSteam_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
1189{
1190}
1191
1192static bool SetHomeLED(SDL_HIDAPI_Device *device, Uint8 value)
1193{
1194 unsigned char buf[65];
1195 int nSettings = 0;
1196
1197 SDL_memset(buf, 0, 65);
1198 buf[1] = ID_SET_SETTINGS_VALUES;
1199 ADD_SETTING(SETTING_LED_USER_BRIGHTNESS, value);
1200 buf[2] = (unsigned char)(nSettings * 3);
1201 if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) {
1202 return SDL_SetError("Couldn't write feature report");
1203 }
1204 return true;
1205}
1206
1207static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
1208{
1209 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)userdata;
1210
1211 if (hint && *hint) {
1212 int value;
1213
1214 if (SDL_strchr(hint, '.') != NULL) {
1215 value = (int)(100.0f * SDL_atof(hint));
1216 if (value > 255) {
1217 value = 255;
1218 }
1219 } else if (SDL_GetStringBoolean(hint, true)) {
1220 value = 100;
1221 } else {
1222 value = 0;
1223 }
1224 SetHomeLED(ctx->device, (Uint8)value);
1225 }
1226}
1227
1228static bool HIDAPI_DriverSteam_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1229{
1230 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1231 float update_rate_in_hz = 0.0f;
1232
1233 SDL_AssertJoysticksLocked();
1234
1235 ctx->report_sensors = false;
1236 SDL_zero(ctx->m_assembler);
1237 SDL_zero(ctx->m_state);
1238 SDL_zero(ctx->m_last_state);
1239
1240 if (!ResetSteamController(device, false, &ctx->update_rate_in_us)) {
1241 SDL_SetError("Couldn't reset controller");
1242 return false;
1243 }
1244 if (ctx->update_rate_in_us > 0) {
1245 update_rate_in_hz = 1000000.0f / ctx->update_rate_in_us;
1246 }
1247
1248 InitializeSteamControllerPacketAssembler(&ctx->m_assembler, device->is_bluetooth);
1249
1250 // Initialize the joystick capabilities
1251 joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_BUTTONS;
1252 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
1253 joystick->nhats = 1;
1254
1255 if (IsDongle(device->product_id)) {
1256 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
1257 }
1258
1259 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);
1260 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);
1261
1262 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED,
1263 SDL_HomeLEDHintChanged, ctx);
1264
1265 return true;
1266}
1267
1268static bool HIDAPI_DriverSteam_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1269{
1270 // You should use the full Steam Input API for rumble support
1271 return SDL_Unsupported();
1272}
1273
1274static bool HIDAPI_DriverSteam_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
1275{
1276 return SDL_Unsupported();
1277}
1278
1279static Uint32 HIDAPI_DriverSteam_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1280{
1281 // You should use the full Steam Input API for extended capabilities
1282 return 0;
1283}
1284
1285static bool HIDAPI_DriverSteam_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1286{
1287 // You should use the full Steam Input API for LED support
1288 return SDL_Unsupported();
1289}
1290
1291static bool HIDAPI_DriverSteam_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
1292{
1293 if (size == 65) {
1294 if (SetFeatureReport(device, data, size) < 0) {
1295 return SDL_SetError("Couldn't write feature report");
1296 }
1297 return true;
1298 }
1299 return SDL_Unsupported();
1300}
1301
1302static bool HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
1303{
1304 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1305 unsigned char buf[65];
1306 int nSettings = 0;
1307
1308 SDL_memset(buf, 0, 65);
1309 buf[1] = ID_SET_SETTINGS_VALUES;
1310 if (enabled) {
1311 ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO);
1312 } else {
1313 ADD_SETTING(SETTING_IMU_MODE, SETTING_GYRO_MODE_OFF);
1314 }
1315 buf[2] = (unsigned char)(nSettings * 3);
1316 if (SetFeatureReport(device, buf, 3 + nSettings * 3) < 0) {
1317 return SDL_SetError("Couldn't write feature report");
1318 }
1319
1320 ctx->report_sensors = enabled;
1321
1322 return true;
1323}
1324
1325static bool ControllerConnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick)
1326{
1327 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1328
1329 if (!HIDAPI_JoystickConnected(device, NULL)) {
1330 return false;
1331 }
1332
1333 // We'll automatically accept this controller if we're in pairing mode
1334 HIDAPI_DriverSteam_CommitPairing(ctx);
1335
1336 *joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1337 ctx->connected = true;
1338 return true;
1339}
1340
1341static void ControllerDisconnected(SDL_HIDAPI_Device *device, SDL_Joystick **joystick)
1342{
1343 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1344
1345 if (device->joysticks) {
1346 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1347 }
1348 ctx->connected = false;
1349 *joystick = NULL;
1350}
1351
1352static bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device)
1353{
1354 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1355 SDL_Joystick *joystick = NULL;
1356
1357 if (device->num_joysticks > 0) {
1358 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1359 }
1360
1361 if (ctx->pairing_time) {
1362 HIDAPI_DriverSteam_RenewPairingState(ctx);
1363 }
1364
1365 for (;;) {
1366 uint8_t data[128];
1367 int r, nPacketLength;
1368 const Uint8 *pPacket;
1369
1370 r = ReadSteamController(device->dev, data, sizeof(data));
1371 if (r == 0) {
1372 break;
1373 }
1374 if (r < 0) {
1375 // Failed to read from controller
1376 ControllerDisconnected(device, &joystick);
1377 return false;
1378 }
1379
1380#ifdef DEBUG_STEAM_PROTOCOL
1381 HIDAPI_DumpPacket("Steam Controller packet: size = %d", data, r);
1382#endif
1383
1384 nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&ctx->m_assembler, data, r);
1385 pPacket = ctx->m_assembler.uBuffer;
1386
1387 if (nPacketLength > 0 && UpdateSteamControllerState(pPacket, nPacketLength, &ctx->m_state)) {
1388 Uint64 timestamp = SDL_GetTicksNS();
1389
1390 if (!ctx->connected) {
1391 // Maybe we missed a wireless status packet?
1392 ControllerConnected(device, &joystick);
1393 }
1394
1395 if (!joystick) {
1396 continue;
1397 }
1398
1399 if (ctx->m_state.ulButtons != ctx->m_last_state.ulButtons) {
1400 Uint8 hat = 0;
1401
1402 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,
1403 ((ctx->m_state.ulButtons & STEAM_BUTTON_SOUTH_MASK) != 0));
1404
1405 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,
1406 ((ctx->m_state.ulButtons & STEAM_BUTTON_EAST_MASK) != 0));
1407
1408 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,
1409 ((ctx->m_state.ulButtons & STEAM_BUTTON_WEST_MASK) != 0));
1410
1411 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,
1412 ((ctx->m_state.ulButtons & STEAM_BUTTON_NORTH_MASK) != 0));
1413
1414 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
1415 ((ctx->m_state.ulButtons & STEAM_LEFT_BUMPER_MASK) != 0));
1416
1417 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
1418 ((ctx->m_state.ulButtons & STEAM_RIGHT_BUMPER_MASK) != 0));
1419
1420 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,
1421 ((ctx->m_state.ulButtons & STEAM_BUTTON_MENU_MASK) != 0));
1422
1423 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,
1424 ((ctx->m_state.ulButtons & STEAM_BUTTON_ESCAPE_MASK) != 0));
1425
1426 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,
1427 ((ctx->m_state.ulButtons & STEAM_BUTTON_STEAM_MASK) != 0));
1428
1429 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,
1430 ((ctx->m_state.ulButtons & STEAM_JOYSTICK_BUTTON_MASK) != 0));
1431 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_LEFT_PADDLE,
1432 ((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_LEFT_MASK) != 0));
1433 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_RIGHT_PADDLE,
1434 ((ctx->m_state.ulButtons & STEAM_BUTTON_BACK_RIGHT_MASK) != 0));
1435
1436 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,
1437 ((ctx->m_state.ulButtons & STEAM_BUTTON_RIGHTPAD_CLICKED_MASK) != 0));
1438
1439 if (ctx->m_state.ulButtons & STEAM_DPAD_UP_MASK) {
1440 hat |= SDL_HAT_UP;
1441 }
1442 if (ctx->m_state.ulButtons & STEAM_DPAD_DOWN_MASK) {
1443 hat |= SDL_HAT_DOWN;
1444 }
1445 if (ctx->m_state.ulButtons & STEAM_DPAD_LEFT_MASK) {
1446 hat |= SDL_HAT_LEFT;
1447 }
1448 if (ctx->m_state.ulButtons & STEAM_DPAD_RIGHT_MASK) {
1449 hat |= SDL_HAT_RIGHT;
1450 }
1451 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1452 }
1453
1454 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (int)ctx->m_state.sTriggerL * 2 - 32768);
1455 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (int)ctx->m_state.sTriggerR * 2 - 32768);
1456
1457 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, ctx->m_state.sLeftStickX);
1458 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~ctx->m_state.sLeftStickY);
1459 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, ctx->m_state.sRightPadX);
1460 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~ctx->m_state.sRightPadY);
1461
1462 if (ctx->report_sensors) {
1463 float values[3];
1464
1465 ctx->sensor_timestamp += SDL_US_TO_NS(ctx->update_rate_in_us);
1466
1467 values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
1468 values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
1469 values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
1470 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp, values, 3);
1471
1472 values[0] = (ctx->m_state.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
1473 values[1] = (ctx->m_state.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
1474 values[2] = (-ctx->m_state.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
1475 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp, values, 3);
1476 }
1477
1478 ctx->m_last_state = ctx->m_state;
1479 } else if (!ctx->connected && D0G_IS_WIRELESS_CONNECT(pPacket, nPacketLength)) {
1480 ControllerConnected(device, &joystick);
1481 } else if (ctx->connected && D0G_IS_WIRELESS_DISCONNECT(pPacket, nPacketLength)) {
1482 ControllerDisconnected(device, &joystick);
1483 }
1484 }
1485 return true;
1486}
1487
1488static void HIDAPI_DriverSteam_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1489{
1490 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1491
1492 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED,
1493 SDL_HomeLEDHintChanged, ctx);
1494
1495 CloseSteamController(device);
1496}
1497
1498static void HIDAPI_DriverSteam_FreeDevice(SDL_HIDAPI_Device *device)
1499{
1500 SDL_DriverSteam_Context *ctx = (SDL_DriverSteam_Context *)device->context;
1501
1502 if (IsDongle(device->product_id)) {
1503 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_PAIRING_ENABLED,
1504 SDL_PairingEnabledHintChanged, ctx);
1505
1506 HIDAPI_DriverSteam_SetPairingState(ctx, false);
1507 }
1508}
1509
1510SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam = {
1511 SDL_HINT_JOYSTICK_HIDAPI_STEAM,
1512 true,
1513 HIDAPI_DriverSteam_RegisterHints,
1514 HIDAPI_DriverSteam_UnregisterHints,
1515 HIDAPI_DriverSteam_IsEnabled,
1516 HIDAPI_DriverSteam_IsSupportedDevice,
1517 HIDAPI_DriverSteam_InitDevice,
1518 HIDAPI_DriverSteam_GetDevicePlayerIndex,
1519 HIDAPI_DriverSteam_SetDevicePlayerIndex,
1520 HIDAPI_DriverSteam_UpdateDevice,
1521 HIDAPI_DriverSteam_OpenJoystick,
1522 HIDAPI_DriverSteam_RumbleJoystick,
1523 HIDAPI_DriverSteam_RumbleJoystickTriggers,
1524 HIDAPI_DriverSteam_GetJoystickCapabilities,
1525 HIDAPI_DriverSteam_SetJoystickLED,
1526 HIDAPI_DriverSteam_SendJoystickEffect,
1527 HIDAPI_DriverSteam_SetSensorsEnabled,
1528 HIDAPI_DriverSteam_CloseJoystick,
1529 HIDAPI_DriverSteam_FreeDevice,
1530};
1531
1532#endif // SDL_JOYSTICK_HIDAPI_STEAM
1533
1534#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam_hori.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam_hori.c
new file mode 100644
index 0000000..0cd192d
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steam_hori.c
@@ -0,0 +1,415 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../SDL_sysjoystick.h"
26#include "SDL_hidapijoystick_c.h"
27#include "SDL_hidapi_rumble.h"
28#include "../SDL_joystick_c.h"
29
30#ifdef SDL_JOYSTICK_HIDAPI_STEAM_HORI
31
32/* Define this if you want to log all packets from the controller */
33/*#define DEBUG_HORI_PROTOCOL*/
34
35#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
36
37enum
38{
39 SDL_GAMEPAD_BUTTON_HORI_QAM = 11,
40 SDL_GAMEPAD_BUTTON_HORI_FR,
41 SDL_GAMEPAD_BUTTON_HORI_FL,
42 SDL_GAMEPAD_BUTTON_HORI_M1,
43 SDL_GAMEPAD_BUTTON_HORI_M2,
44 SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L,
45 SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R,
46 SDL_GAMEPAD_NUM_HORI_BUTTONS
47};
48
49typedef struct
50{
51 Uint8 last_state[USB_PACKET_LENGTH];
52 Uint64 sensor_ticks;
53 Uint32 last_tick;
54 bool wireless;
55 bool serial_needs_init;
56} SDL_DriverSteamHori_Context;
57
58static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device);
59
60static void HIDAPI_DriverSteamHori_RegisterHints(SDL_HintCallback callback, void *userdata)
61{
62 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata);
63}
64
65static void HIDAPI_DriverSteamHori_UnregisterHints(SDL_HintCallback callback, void *userdata)
66{
67 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata);
68}
69
70static bool HIDAPI_DriverSteamHori_IsEnabled(void)
71{
72 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
73}
74
75static bool HIDAPI_DriverSteamHori_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
76{
77 return SDL_IsJoystickHoriSteamController(vendor_id, product_id);
78}
79
80static bool HIDAPI_DriverSteamHori_InitDevice(SDL_HIDAPI_Device *device)
81{
82 SDL_DriverSteamHori_Context *ctx;
83
84 ctx = (SDL_DriverSteamHori_Context *)SDL_calloc(1, sizeof(*ctx));
85 if (!ctx) {
86 return false;
87 }
88
89 device->context = ctx;
90 ctx->serial_needs_init = true;
91
92 HIDAPI_SetDeviceName(device, "Wireless HORIPAD For Steam");
93
94 return HIDAPI_JoystickConnected(device, NULL);
95}
96
97static int HIDAPI_DriverSteamHori_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
98{
99 return -1;
100}
101
102static void HIDAPI_DriverSteamHori_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
103{
104}
105
106static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
107{
108 SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context;
109
110 SDL_AssertJoysticksLocked();
111
112 SDL_zeroa(ctx->last_state);
113
114 /* Initialize the joystick capabilities */
115 joystick->nbuttons = SDL_GAMEPAD_NUM_HORI_BUTTONS;
116 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
117 joystick->nhats = 1;
118
119 ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT;
120
121 if (ctx->wireless && device->serial) {
122 joystick->serial = SDL_strdup(device->serial);
123 ctx->serial_needs_init = false;
124 } else if (!ctx->wireless) {
125 // Need to actual read from the device to init the serial
126 HIDAPI_DriverSteamHori_UpdateDevice(device);
127 }
128
129 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
130 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
131
132 return true;
133}
134
135static bool HIDAPI_DriverSteamHori_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
136{
137 // Device doesn't support rumble
138 return SDL_Unsupported();
139}
140
141static bool HIDAPI_DriverSteamHori_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
142{
143 return SDL_Unsupported();
144}
145
146static Uint32 HIDAPI_DriverSteamHori_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
147{
148 return 0;
149}
150
151static bool HIDAPI_DriverSteamHori_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
152{
153 return SDL_Unsupported();
154}
155
156static bool HIDAPI_DriverSteamHori_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
157{
158 return SDL_Unsupported();
159}
160
161static bool HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
162{
163 return true;
164}
165
166#undef clamp
167#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
168
169#ifndef DEG2RAD
170#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))
171#endif
172
173//---------------------------------------------------------------------------
174// Scale and clamp values to a range
175//---------------------------------------------------------------------------
176static float RemapValClamped(float val, float A, float B, float C, float D)
177{
178 if (A == B) {
179 return (val - B) >= 0.0f ? D : C;
180 } else {
181 float cVal = (val - A) / (B - A);
182 cVal = clamp(cVal, 0.0f, 1.0f);
183
184 return C + (D - C) * cVal;
185 }
186}
187
188#define REPORT_HEADER_USB 0x07
189#define REPORT_HEADER_BT 0x00
190
191static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSteamHori_Context *ctx, Uint8 *data, int size)
192{
193 Sint16 axis;
194 Uint64 timestamp = SDL_GetTicksNS();
195
196 // Make sure it's gamepad state and not OTA FW update info
197 if (data[0] != REPORT_HEADER_USB && data[0] != REPORT_HEADER_BT) {
198 /* We don't know how to handle this report */
199 return;
200 }
201
202 #define READ_STICK_AXIS(offset) \
203 (data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), -0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16))
204 {
205 axis = READ_STICK_AXIS(1);
206 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
207 axis = READ_STICK_AXIS(2);
208 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
209 axis = READ_STICK_AXIS(3);
210 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
211 axis = READ_STICK_AXIS(4);
212 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
213 }
214#undef READ_STICK_AXIS
215
216 if (ctx->last_state[5] != data[5]) {
217 Uint8 hat;
218
219 switch (data[5] & 0xF) {
220 case 0:
221 hat = SDL_HAT_UP;
222 break;
223 case 1:
224 hat = SDL_HAT_RIGHTUP;
225 break;
226 case 2:
227 hat = SDL_HAT_RIGHT;
228 break;
229 case 3:
230 hat = SDL_HAT_RIGHTDOWN;
231 break;
232 case 4:
233 hat = SDL_HAT_DOWN;
234 break;
235 case 5:
236 hat = SDL_HAT_LEFTDOWN;
237 break;
238 case 6:
239 hat = SDL_HAT_LEFT;
240 break;
241 case 7:
242 hat = SDL_HAT_LEFTUP;
243 break;
244 default:
245 hat = SDL_HAT_CENTERED;
246 break;
247 }
248 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
249 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x10) != 0));
250 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x20) != 0));
251 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_QAM, ((data[5] & 0x40) != 0));
252 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x80) != 0));
253
254 }
255
256 if (ctx->last_state[6] != data[6]) {
257 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[6] & 0x01) != 0));
258 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M1 /* M1 */, ((data[6] & 0x02) != 0));
259 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[6] & 0x04) != 0));
260 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[6] & 0x08) != 0));
261
262 // TODO: can we handle the digital trigger mode? The data seems to come through analog regardless of the trigger state
263 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x40) != 0));
264 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x80) != 0));
265 }
266
267 if (ctx->last_state[7] != data[7]) {
268 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[7] & 0x01) != 0));
269 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[7] & 0x02) != 0));
270 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[7] & 0x04) != 0));
271 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M2, ((data[7] & 0x08) != 0));
272 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, ((data[7] & 0x10) != 0));
273 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, ((data[7] & 0x20) != 0));
274 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FR, ((data[7] & 0x40) != 0));
275 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0));
276 }
277
278 if (!ctx->wireless && ctx->serial_needs_init) {
279 char serial[18];
280 (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
281 data[38], data[39], data[40], data[41], data[42], data[43]);
282
283 joystick->serial = SDL_strdup(serial);
284 ctx->serial_needs_init = false;
285 }
286
287#define READ_TRIGGER_AXIS(offset) \
288 (Sint16)(((int)data[offset] * 257) - 32768)
289 {
290 axis = READ_TRIGGER_AXIS(8);
291 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
292 axis = READ_TRIGGER_AXIS(9);
293 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
294 }
295#undef READ_TRIGGER_AXIS
296
297 if (1) {
298 Uint64 sensor_timestamp;
299 float imu_data[3];
300
301 /* 16-bit timestamp */
302 Uint32 delta;
303 Uint16 tick = LOAD16(data[10],
304 data[11]);
305 if (ctx->last_tick < tick) {
306 delta = (tick - ctx->last_tick);
307 } else {
308 delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);
309 }
310
311 ctx->last_tick = tick;
312 ctx->sensor_ticks += delta;
313
314 /* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */
315 sensor_timestamp = timestamp; // if the values were good we woudl call SDL_US_TO_NS(ctx->sensor_ticks);
316
317 const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f;
318 const float gyroScale = DEG2RAD(2048);
319
320 imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
321 imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
322 imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
323
324
325 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3);
326
327 // SDL_Log("%u %f, %f, %f ", data[0], imu_data[0], imu_data[1], imu_data[2] );
328 imu_data[2] = LOAD16(data[18], data[19]) * accelScale;
329 imu_data[1] = -1 * LOAD16(data[20], data[21]) * accelScale;
330 imu_data[0] = LOAD16(data[22], data[23]) * accelScale;
331 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, imu_data, 3);
332 }
333
334 if (ctx->last_state[24] != data[24]) {
335 bool bCharging = (data[24] & 0x10) != 0;
336 int percent = (data[24] & 0xF) * 10;
337 SDL_PowerState state;
338 if (bCharging) {
339 state = SDL_POWERSTATE_CHARGING;
340 } else if (ctx->wireless) {
341 state = SDL_POWERSTATE_ON_BATTERY;
342 } else {
343 state = SDL_POWERSTATE_CHARGED;
344 }
345
346 SDL_SendJoystickPowerInfo(joystick, state, percent);
347 }
348
349 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
350}
351
352static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device)
353{
354 SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context;
355 SDL_Joystick *joystick = NULL;
356 Uint8 data[USB_PACKET_LENGTH];
357 int size = 0;
358
359 if (device->num_joysticks > 0) {
360 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
361 } else {
362 return false;
363 }
364
365 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
366#ifdef DEBUG_HORI_PROTOCOL
367 HIDAPI_DumpPacket("Google Hori packet: size = %d", data, size);
368#endif
369 if (!joystick) {
370 continue;
371 }
372
373 HIDAPI_DriverSteamHori_HandleStatePacket(joystick, ctx, data, size);
374 }
375
376 if (size < 0) {
377 /* Read error, device is disconnected */
378 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
379 }
380 return (size >= 0);
381}
382
383static void HIDAPI_DriverSteamHori_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
384{
385}
386
387static void HIDAPI_DriverSteamHori_FreeDevice(SDL_HIDAPI_Device *device)
388{
389}
390
391SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori = {
392 SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI,
393 true,
394 HIDAPI_DriverSteamHori_RegisterHints,
395 HIDAPI_DriverSteamHori_UnregisterHints,
396 HIDAPI_DriverSteamHori_IsEnabled,
397 HIDAPI_DriverSteamHori_IsSupportedDevice,
398 HIDAPI_DriverSteamHori_InitDevice,
399 HIDAPI_DriverSteamHori_GetDevicePlayerIndex,
400 HIDAPI_DriverSteamHori_SetDevicePlayerIndex,
401 HIDAPI_DriverSteamHori_UpdateDevice,
402 HIDAPI_DriverSteamHori_OpenJoystick,
403 HIDAPI_DriverSteamHori_RumbleJoystick,
404 HIDAPI_DriverSteamHori_RumbleJoystickTriggers,
405 HIDAPI_DriverSteamHori_GetJoystickCapabilities,
406 HIDAPI_DriverSteamHori_SetJoystickLED,
407 HIDAPI_DriverSteamHori_SendJoystickEffect,
408 HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled,
409 HIDAPI_DriverSteamHori_CloseJoystick,
410 HIDAPI_DriverSteamHori_FreeDevice,
411};
412
413#endif /* SDL_JOYSTICK_HIDAPI_STEAM_HORI */
414
415#endif /* SDL_JOYSTICK_HIDAPI */
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steamdeck.c
new file mode 100644
index 0000000..75a13cc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_steamdeck.c
@@ -0,0 +1,451 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 2023 Max Maisel <max.maisel@posteo.de>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../SDL_sysjoystick.h"
26#include "SDL_hidapijoystick_c.h"
27
28#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK
29
30/*****************************************************************************************************/
31
32#include "steam/controller_constants.h"
33#include "steam/controller_structs.h"
34
35enum
36{
37 SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM = 11,
38 SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,
39 SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,
40 SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,
41 SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
42 SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS,
43};
44
45typedef enum
46{
47 STEAMDECK_LBUTTON_R2 = 0x00000001,
48 STEAMDECK_LBUTTON_L2 = 0x00000002,
49 STEAMDECK_LBUTTON_R = 0x00000004,
50 STEAMDECK_LBUTTON_L = 0x00000008,
51 STEAMDECK_LBUTTON_Y = 0x00000010,
52 STEAMDECK_LBUTTON_B = 0x00000020,
53 STEAMDECK_LBUTTON_X = 0x00000040,
54 STEAMDECK_LBUTTON_A = 0x00000080,
55 STEAMDECK_LBUTTON_DPAD_UP = 0x00000100,
56 STEAMDECK_LBUTTON_DPAD_RIGHT = 0x00000200,
57 STEAMDECK_LBUTTON_DPAD_LEFT = 0x00000400,
58 STEAMDECK_LBUTTON_DPAD_DOWN = 0x00000800,
59 STEAMDECK_LBUTTON_VIEW = 0x00001000,
60 STEAMDECK_LBUTTON_STEAM = 0x00002000,
61 STEAMDECK_LBUTTON_MENU = 0x00004000,
62 STEAMDECK_LBUTTON_L5 = 0x00008000,
63 STEAMDECK_LBUTTON_R5 = 0x00010000,
64 STEAMDECK_LBUTTON_LEFT_PAD = 0x00020000,
65 STEAMDECK_LBUTTON_RIGHT_PAD = 0x00040000,
66 STEAMDECK_LBUTTON_L3 = 0x00400000,
67 STEAMDECK_LBUTTON_R3 = 0x04000000,
68
69 STEAMDECK_HBUTTON_L4 = 0x00000200,
70 STEAMDECK_HBUTTON_R4 = 0x00000400,
71 STEAMDECK_HBUTTON_QAM = 0x00040000,
72} SteamDeckButtons;
73
74typedef struct
75{
76 Uint32 update_rate_us;
77 Uint32 sensor_timestamp_us;
78 Uint64 last_button_state;
79 Uint8 watchdog_counter;
80} SDL_DriverSteamDeck_Context;
81
82static bool DisableDeckLizardMode(SDL_hid_device *dev)
83{
84 int rc;
85 Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
86 FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
87
88 msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS;
89
90 rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
91 if (rc != sizeof(buffer))
92 return false;
93
94 msg->header.type = ID_SET_SETTINGS_VALUES;
95 msg->header.length = 5 * sizeof(ControllerSetting);
96 msg->payload.setSettingsValues.settings[0].settingNum = SETTING_SMOOTH_ABSOLUTE_MOUSE;
97 msg->payload.setSettingsValues.settings[0].settingValue = 0;
98 msg->payload.setSettingsValues.settings[1].settingNum = SETTING_LEFT_TRACKPAD_MODE;
99 msg->payload.setSettingsValues.settings[1].settingValue = TRACKPAD_NONE;
100 msg->payload.setSettingsValues.settings[2].settingNum = SETTING_RIGHT_TRACKPAD_MODE; // disable mouse
101 msg->payload.setSettingsValues.settings[2].settingValue = TRACKPAD_NONE;
102 msg->payload.setSettingsValues.settings[3].settingNum = SETTING_LEFT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad
103 msg->payload.setSettingsValues.settings[3].settingValue = 0xFFFF;
104 msg->payload.setSettingsValues.settings[4].settingNum = SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE; // disable clicky pad
105 msg->payload.setSettingsValues.settings[4].settingValue = 0xFFFF;
106
107 rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
108 if (rc != sizeof(buffer))
109 return false;
110
111 // There may be a lingering report read back after changing settings.
112 // Discard it.
113 SDL_hid_get_feature_report(dev, buffer, sizeof(buffer));
114
115 return true;
116}
117
118static bool FeedDeckLizardWatchdog(SDL_hid_device *dev)
119{
120 int rc;
121 Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
122 FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
123
124 msg->header.type = ID_CLEAR_DIGITAL_MAPPINGS;
125
126 rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
127 if (rc != sizeof(buffer))
128 return false;
129
130 msg->header.type = ID_SET_SETTINGS_VALUES;
131 msg->header.length = 1 * sizeof(ControllerSetting);
132 msg->payload.setSettingsValues.settings[0].settingNum = SETTING_RIGHT_TRACKPAD_MODE;
133 msg->payload.setSettingsValues.settings[0].settingValue = TRACKPAD_NONE;
134
135 rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
136 if (rc != sizeof(buffer))
137 return false;
138
139 // There may be a lingering report read back after changing settings.
140 // Discard it.
141 SDL_hid_get_feature_report(dev, buffer, sizeof(buffer));
142
143 return true;
144}
145
146static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device,
147 SDL_Joystick *joystick,
148 ValveInReport_t *pInReport)
149{
150 float values[3];
151 SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;
152 Uint64 timestamp = SDL_GetTicksNS();
153
154 if (pInReport->payload.deckState.ulButtons != ctx->last_button_state) {
155 Uint8 hat = 0;
156
157 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,
158 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_A) != 0));
159 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,
160 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_B) != 0));
161 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,
162 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_X) != 0));
163 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,
164 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_Y) != 0));
165
166 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
167 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L) != 0));
168 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
169 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R) != 0));
170
171 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,
172 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_VIEW) != 0));
173 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,
174 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_MENU) != 0));
175 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,
176 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_STEAM) != 0));
177 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM,
178 ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_QAM) != 0));
179
180 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,
181 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L3) != 0));
182 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,
183 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R3) != 0));
184
185 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,
186 ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_R4) != 0));
187 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,
188 ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_L4) != 0));
189 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,
190 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_R5) != 0));
191 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
192 ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_L5) != 0));
193
194 if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) {
195 hat |= SDL_HAT_UP;
196 }
197 if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_DOWN) {
198 hat |= SDL_HAT_DOWN;
199 }
200 if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_LEFT) {
201 hat |= SDL_HAT_LEFT;
202 }
203 if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_RIGHT) {
204 hat |= SDL_HAT_RIGHT;
205 }
206 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
207
208 ctx->last_button_state = pInReport->payload.deckState.ulButtons;
209 }
210
211 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
212 (int)pInReport->payload.deckState.sTriggerRawL * 2 - 32768);
213 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
214 (int)pInReport->payload.deckState.sTriggerRawR * 2 - 32768);
215
216 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX,
217 pInReport->payload.deckState.sLeftStickX);
218 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY,
219 -pInReport->payload.deckState.sLeftStickY);
220 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX,
221 pInReport->payload.deckState.sRightStickX);
222 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY,
223 -pInReport->payload.deckState.sRightStickY);
224
225 ctx->sensor_timestamp_us += ctx->update_rate_us;
226
227 values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
228 values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
229 values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
230 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3);
231
232 values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
233 values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
234 values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
235 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3);
236}
237
238/*****************************************************************************************************/
239
240static void HIDAPI_DriverSteamDeck_RegisterHints(SDL_HintCallback callback, void *userdata)
241{
242 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata);
243}
244
245static void HIDAPI_DriverSteamDeck_UnregisterHints(SDL_HintCallback callback, void *userdata)
246{
247 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, callback, userdata);
248}
249
250static bool HIDAPI_DriverSteamDeck_IsEnabled(void)
251{
252 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,
253 SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
254}
255
256static bool HIDAPI_DriverSteamDeck_IsSupportedDevice(
257 SDL_HIDAPI_Device *device,
258 const char *name,
259 SDL_GamepadType type,
260 Uint16 vendor_id,
261 Uint16 product_id,
262 Uint16 version,
263 int interface_number,
264 int interface_class,
265 int interface_subclass,
266 int interface_protocol)
267{
268 return SDL_IsJoystickSteamDeck(vendor_id, product_id);
269}
270
271static bool HIDAPI_DriverSteamDeck_InitDevice(SDL_HIDAPI_Device *device)
272{
273 int size;
274 Uint8 data[64];
275 SDL_DriverSteamDeck_Context *ctx;
276
277 ctx = (SDL_DriverSteamDeck_Context *)SDL_calloc(1, sizeof(*ctx));
278 if (ctx == NULL) {
279 return false;
280 }
281
282 // Always 1kHz according to USB descriptor, but actually about 4 ms.
283 ctx->update_rate_us = 4000;
284
285 device->context = ctx;
286
287 // Read a report to see if this is the correct endpoint.
288 // Mouse, Keyboard and Controller have the same VID/PID but
289 // only the controller hidraw device receives hid reports.
290 size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);
291 if (size == 0)
292 return false;
293
294 if (!DisableDeckLizardMode(device->dev))
295 return false;
296
297 HIDAPI_SetDeviceName(device, "Steam Deck");
298
299 return HIDAPI_JoystickConnected(device, NULL);
300}
301
302static int HIDAPI_DriverSteamDeck_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
303{
304 return -1;
305}
306
307static void HIDAPI_DriverSteamDeck_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
308{
309}
310
311static bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device)
312{
313 SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;
314 SDL_Joystick *joystick = NULL;
315 int r;
316 uint8_t data[64];
317 ValveInReport_t *pInReport = (ValveInReport_t *)data;
318
319 if (device->num_joysticks > 0) {
320 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
321 if (joystick == NULL) {
322 return false;
323 }
324 } else {
325 return false;
326 }
327
328 if (ctx->watchdog_counter++ > 200) {
329 ctx->watchdog_counter = 0;
330 if (!FeedDeckLizardWatchdog(device->dev))
331 return false;
332 }
333
334 SDL_memset(data, 0, sizeof(data));
335
336 do {
337 r = SDL_hid_read(device->dev, data, sizeof(data));
338
339 if (r < 0) {
340 // Failed to read from controller
341 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
342 return false;
343 } else if (r == 64 &&
344 pInReport->header.unReportVersion == k_ValveInReportMsgVersion &&
345 pInReport->header.ucType == ID_CONTROLLER_DECK_STATE &&
346 pInReport->header.ucLength == 64) {
347 HIDAPI_DriverSteamDeck_HandleState(device, joystick, pInReport);
348 }
349 } while (r > 0);
350
351 return true;
352}
353
354static bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
355{
356 SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;
357 float update_rate_in_hz = 1.0f / (float)(ctx->update_rate_us) * 1.0e6f;
358
359 SDL_AssertJoysticksLocked();
360
361 // Initialize the joystick capabilities
362 joystick->nbuttons = SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS;
363 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
364 joystick->nhats = 1;
365
366 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);
367 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);
368
369 return true;
370}
371
372static bool HIDAPI_DriverSteamDeck_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
373{
374 int rc;
375 Uint8 buffer[HID_FEATURE_REPORT_BYTES + 1] = { 0 };
376 FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
377
378 msg->header.type = ID_TRIGGER_RUMBLE_CMD;
379 msg->payload.simpleRumble.unRumbleType = 0;
380 msg->payload.simpleRumble.unIntensity = HAPTIC_INTENSITY_SYSTEM;
381 msg->payload.simpleRumble.unLeftMotorSpeed = low_frequency_rumble;
382 msg->payload.simpleRumble.unRightMotorSpeed = high_frequency_rumble;
383 msg->payload.simpleRumble.nLeftGain = 2;
384 msg->payload.simpleRumble.nRightGain = 0;
385
386 rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer));
387 if (rc != sizeof(buffer))
388 return false;
389 return true;
390}
391
392static bool HIDAPI_DriverSteamDeck_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
393{
394 return SDL_Unsupported();
395}
396
397static Uint32 HIDAPI_DriverSteamDeck_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
398{
399 return SDL_JOYSTICK_CAP_RUMBLE;
400}
401
402static bool HIDAPI_DriverSteamDeck_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
403{
404 return SDL_Unsupported();
405}
406
407static bool HIDAPI_DriverSteamDeck_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
408{
409 return SDL_Unsupported();
410}
411
412static bool HIDAPI_DriverSteamDeck_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
413{
414 // On steam deck, sensors are enabled by default. Nothing to do here.
415 return true;
416}
417
418static void HIDAPI_DriverSteamDeck_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
419{
420 // Lizard mode id automatically re-enabled by watchdog. Nothing to do here.
421}
422
423static void HIDAPI_DriverSteamDeck_FreeDevice(SDL_HIDAPI_Device *device)
424{
425}
426
427SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck = {
428 SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK,
429 true,
430 HIDAPI_DriverSteamDeck_RegisterHints,
431 HIDAPI_DriverSteamDeck_UnregisterHints,
432 HIDAPI_DriverSteamDeck_IsEnabled,
433 HIDAPI_DriverSteamDeck_IsSupportedDevice,
434 HIDAPI_DriverSteamDeck_InitDevice,
435 HIDAPI_DriverSteamDeck_GetDevicePlayerIndex,
436 HIDAPI_DriverSteamDeck_SetDevicePlayerIndex,
437 HIDAPI_DriverSteamDeck_UpdateDevice,
438 HIDAPI_DriverSteamDeck_OpenJoystick,
439 HIDAPI_DriverSteamDeck_RumbleJoystick,
440 HIDAPI_DriverSteamDeck_RumbleJoystickTriggers,
441 HIDAPI_DriverSteamDeck_GetJoystickCapabilities,
442 HIDAPI_DriverSteamDeck_SetJoystickLED,
443 HIDAPI_DriverSteamDeck_SendJoystickEffect,
444 HIDAPI_DriverSteamDeck_SetSensorsEnabled,
445 HIDAPI_DriverSteamDeck_CloseJoystick,
446 HIDAPI_DriverSteamDeck_FreeDevice,
447};
448
449#endif // SDL_JOYSTICK_HIDAPI_STEAMDECK
450
451#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_switch.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_switch.c
new file mode 100644
index 0000000..0e7b823
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -0,0 +1,2859 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21/* This driver supports the Nintendo Switch Pro controller.
22 Code and logic contributed by Valve Corporation under the SDL zlib license.
23*/
24#include "SDL_internal.h"
25
26#ifdef SDL_JOYSTICK_HIDAPI
27
28#include "../../SDL_hints_c.h"
29#include "../SDL_sysjoystick.h"
30#include "SDL_hidapijoystick_c.h"
31#include "SDL_hidapi_rumble.h"
32#include "SDL_hidapi_nintendo.h"
33
34#ifdef SDL_JOYSTICK_HIDAPI_SWITCH
35
36// Define this if you want to log all packets from the controller
37// #define DEBUG_SWITCH_PROTOCOL
38
39// Define this to get log output for rumble logic
40// #define DEBUG_RUMBLE
41
42/* The initialization sequence doesn't appear to work correctly on Windows unless
43 the reads and writes are on the same thread.
44
45 ... and now I can't reproduce this, so I'm leaving it in, but disabled for now.
46 */
47// #define SWITCH_SYNCHRONOUS_WRITES
48
49/* How often you can write rumble commands to the controller.
50 If you send commands more frequently than this, you can turn off the controller
51 in Bluetooth mode, or the motors can miss the command in USB mode.
52 */
53#define RUMBLE_WRITE_FREQUENCY_MS 30
54
55// How often you have to refresh a long duration rumble to keep the motors running
56#define RUMBLE_REFRESH_FREQUENCY_MS 50
57
58#define SWITCH_GYRO_SCALE 14.2842f
59#define SWITCH_ACCEL_SCALE 4096.f
60
61#define SWITCH_GYRO_SCALE_OFFSET 13371.0f
62#define SWITCH_GYRO_SCALE_MULT 936.0f
63#define SWITCH_ACCEL_SCALE_OFFSET 16384.0f
64#define SWITCH_ACCEL_SCALE_MULT 4.0f
65
66enum
67{
68 SDL_GAMEPAD_BUTTON_SWITCH_SHARE = 11,
69 SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1,
70 SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1,
71 SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2,
72 SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2,
73 SDL_GAMEPAD_NUM_SWITCH_BUTTONS,
74};
75
76typedef enum
77{
78 k_eSwitchInputReportIDs_SubcommandReply = 0x21,
79 k_eSwitchInputReportIDs_FullControllerState = 0x30,
80 k_eSwitchInputReportIDs_FullControllerAndMcuState = 0x31,
81 k_eSwitchInputReportIDs_SimpleControllerState = 0x3F,
82 k_eSwitchInputReportIDs_CommandAck = 0x81,
83} ESwitchInputReportIDs;
84
85typedef enum
86{
87 k_eSwitchOutputReportIDs_RumbleAndSubcommand = 0x01,
88 k_eSwitchOutputReportIDs_Rumble = 0x10,
89 k_eSwitchOutputReportIDs_Proprietary = 0x80,
90} ESwitchOutputReportIDs;
91
92typedef enum
93{
94 k_eSwitchSubcommandIDs_BluetoothManualPair = 0x01,
95 k_eSwitchSubcommandIDs_RequestDeviceInfo = 0x02,
96 k_eSwitchSubcommandIDs_SetInputReportMode = 0x03,
97 k_eSwitchSubcommandIDs_SetHCIState = 0x06,
98 k_eSwitchSubcommandIDs_SPIFlashRead = 0x10,
99 k_eSwitchSubcommandIDs_SetPlayerLights = 0x30,
100 k_eSwitchSubcommandIDs_SetHomeLight = 0x38,
101 k_eSwitchSubcommandIDs_EnableIMU = 0x40,
102 k_eSwitchSubcommandIDs_SetIMUSensitivity = 0x41,
103 k_eSwitchSubcommandIDs_EnableVibration = 0x48,
104} ESwitchSubcommandIDs;
105
106typedef enum
107{
108 k_eSwitchProprietaryCommandIDs_Status = 0x01,
109 k_eSwitchProprietaryCommandIDs_Handshake = 0x02,
110 k_eSwitchProprietaryCommandIDs_HighSpeed = 0x03,
111 k_eSwitchProprietaryCommandIDs_ForceUSB = 0x04,
112 k_eSwitchProprietaryCommandIDs_ClearUSB = 0x05,
113 k_eSwitchProprietaryCommandIDs_ResetMCU = 0x06,
114} ESwitchProprietaryCommandIDs;
115
116#define k_unSwitchOutputPacketDataLength 49
117#define k_unSwitchMaxOutputPacketLength 64
118#define k_unSwitchBluetoothPacketLength k_unSwitchOutputPacketDataLength
119#define k_unSwitchUSBPacketLength k_unSwitchMaxOutputPacketLength
120
121#define k_unSPIStickFactoryCalibrationStartOffset 0x603D
122#define k_unSPIStickFactoryCalibrationEndOffset 0x604E
123#define k_unSPIStickFactoryCalibrationLength (k_unSPIStickFactoryCalibrationEndOffset - k_unSPIStickFactoryCalibrationStartOffset + 1)
124
125#define k_unSPIStickUserCalibrationStartOffset 0x8010
126#define k_unSPIStickUserCalibrationEndOffset 0x8025
127#define k_unSPIStickUserCalibrationLength (k_unSPIStickUserCalibrationEndOffset - k_unSPIStickUserCalibrationStartOffset + 1)
128
129#define k_unSPIIMUScaleStartOffset 0x6020
130#define k_unSPIIMUScaleEndOffset 0x6037
131#define k_unSPIIMUScaleLength (k_unSPIIMUScaleEndOffset - k_unSPIIMUScaleStartOffset + 1)
132
133#define k_unSPIIMUUserScaleStartOffset 0x8026
134#define k_unSPIIMUUserScaleEndOffset 0x8039
135#define k_unSPIIMUUserScaleLength (k_unSPIIMUUserScaleEndOffset - k_unSPIIMUUserScaleStartOffset + 1)
136
137#pragma pack(1)
138typedef struct
139{
140 Uint8 rgucButtons[2];
141 Uint8 ucStickHat;
142 Uint8 rgucJoystickLeft[2];
143 Uint8 rgucJoystickRight[2];
144} SwitchInputOnlyControllerStatePacket_t;
145
146typedef struct
147{
148 Uint8 rgucButtons[2];
149 Uint8 ucStickHat;
150 Sint16 sJoystickLeft[2];
151 Sint16 sJoystickRight[2];
152} SwitchSimpleStatePacket_t;
153
154typedef struct
155{
156 Uint8 ucCounter;
157 Uint8 ucBatteryAndConnection;
158 Uint8 rgucButtons[3];
159 Uint8 rgucJoystickLeft[3];
160 Uint8 rgucJoystickRight[3];
161 Uint8 ucVibrationCode;
162} SwitchControllerStatePacket_t;
163
164typedef struct
165{
166 SwitchControllerStatePacket_t controllerState;
167
168 struct
169 {
170 Sint16 sAccelX;
171 Sint16 sAccelY;
172 Sint16 sAccelZ;
173
174 Sint16 sGyroX;
175 Sint16 sGyroY;
176 Sint16 sGyroZ;
177 } imuState[3];
178} SwitchStatePacket_t;
179
180typedef struct
181{
182 Uint32 unAddress;
183 Uint8 ucLength;
184} SwitchSPIOpData_t;
185
186typedef struct
187{
188 SwitchControllerStatePacket_t m_controllerState;
189
190 Uint8 ucSubcommandAck;
191 Uint8 ucSubcommandID;
192
193#define k_unSubcommandDataBytes 35
194 union
195 {
196 Uint8 rgucSubcommandData[k_unSubcommandDataBytes];
197
198 struct
199 {
200 SwitchSPIOpData_t opData;
201 Uint8 rgucReadData[k_unSubcommandDataBytes - sizeof(SwitchSPIOpData_t)];
202 } spiReadData;
203
204 struct
205 {
206 Uint8 rgucFirmwareVersion[2];
207 Uint8 ucDeviceType;
208 Uint8 ucFiller1;
209 Uint8 rgucMACAddress[6];
210 Uint8 ucFiller2;
211 Uint8 ucColorLocation;
212 } deviceInfo;
213
214 struct
215 {
216 SwitchSPIOpData_t opData;
217 Uint8 rgucLeftCalibration[9];
218 Uint8 rgucRightCalibration[9];
219 } stickFactoryCalibration;
220
221 struct
222 {
223 SwitchSPIOpData_t opData;
224 Uint8 rgucLeftMagic[2];
225 Uint8 rgucLeftCalibration[9];
226 Uint8 rgucRightMagic[2];
227 Uint8 rgucRightCalibration[9];
228 } stickUserCalibration;
229 };
230} SwitchSubcommandInputPacket_t;
231
232typedef struct
233{
234 Uint8 ucPacketType;
235 Uint8 ucCommandID;
236 Uint8 ucFiller;
237
238 Uint8 ucDeviceType;
239 Uint8 rgucMACAddress[6];
240} SwitchProprietaryStatusPacket_t;
241
242typedef struct
243{
244 Uint8 rgucData[4];
245} SwitchRumbleData_t;
246
247typedef struct
248{
249 Uint8 ucPacketType;
250 Uint8 ucPacketNumber;
251 SwitchRumbleData_t rumbleData[2];
252} SwitchCommonOutputPacket_t;
253
254typedef struct
255{
256 SwitchCommonOutputPacket_t commonData;
257
258 Uint8 ucSubcommandID;
259 Uint8 rgucSubcommandData[k_unSwitchOutputPacketDataLength - sizeof(SwitchCommonOutputPacket_t) - 1];
260} SwitchSubcommandOutputPacket_t;
261
262typedef struct
263{
264 Uint8 ucPacketType;
265 Uint8 ucProprietaryID;
266
267 Uint8 rgucProprietaryData[k_unSwitchOutputPacketDataLength - 1 - 1];
268} SwitchProprietaryOutputPacket_t;
269#pragma pack()
270
271/* Enhanced report hint mode:
272 * "0": enhanced features are never used
273 * "1": enhanced features are always used
274 * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.
275 */
276typedef enum
277{
278 SWITCH_ENHANCED_REPORT_HINT_OFF,
279 SWITCH_ENHANCED_REPORT_HINT_ON,
280 SWITCH_ENHANCED_REPORT_HINT_AUTO
281} HIDAPI_Switch_EnhancedReportHint;
282
283typedef struct
284{
285 SDL_HIDAPI_Device *device;
286 SDL_Joystick *joystick;
287 bool m_bInputOnly;
288 bool m_bUseButtonLabels;
289 bool m_bPlayerLights;
290 int m_nPlayerIndex;
291 bool m_bSyncWrite;
292 int m_nMaxWriteAttempts;
293 ESwitchDeviceInfoControllerType m_eControllerType;
294 Uint8 m_nInitialInputMode;
295 Uint8 m_nCurrentInputMode;
296 Uint8 m_rgucMACAddress[6];
297 Uint8 m_nCommandNumber;
298 HIDAPI_Switch_EnhancedReportHint m_eEnhancedReportHint;
299 bool m_bEnhancedMode;
300 bool m_bEnhancedModeAvailable;
301 SwitchCommonOutputPacket_t m_RumblePacket;
302 Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength];
303 bool m_bRumbleActive;
304 Uint64 m_ulRumbleSent;
305 bool m_bRumblePending;
306 bool m_bRumbleZeroPending;
307 Uint32 m_unRumblePending;
308 bool m_bSensorsSupported;
309 bool m_bReportSensors;
310 bool m_bHasSensorData;
311 Uint64 m_ulLastInput;
312 Uint64 m_ulLastIMUReset;
313 Uint64 m_ulIMUSampleTimestampNS;
314 Uint32 m_unIMUSamples;
315 Uint64 m_ulIMUUpdateIntervalNS;
316 Uint64 m_ulTimestampNS;
317 bool m_bVerticalMode;
318
319 SwitchInputOnlyControllerStatePacket_t m_lastInputOnlyState;
320 SwitchSimpleStatePacket_t m_lastSimpleState;
321 SwitchStatePacket_t m_lastFullState;
322
323 struct StickCalibrationData
324 {
325 struct
326 {
327 Sint16 sCenter;
328 Sint16 sMin;
329 Sint16 sMax;
330 } axis[2];
331 } m_StickCalData[2];
332
333 struct StickExtents
334 {
335 struct
336 {
337 Sint16 sMin;
338 Sint16 sMax;
339 } axis[2];
340 } m_StickExtents[2], m_SimpleStickExtents[2];
341
342 struct IMUScaleData
343 {
344 float fAccelScaleX;
345 float fAccelScaleY;
346 float fAccelScaleZ;
347
348 float fGyroScaleX;
349 float fGyroScaleY;
350 float fGyroScaleZ;
351 } m_IMUScaleData;
352} SDL_DriverSwitch_Context;
353
354static int ReadInput(SDL_DriverSwitch_Context *ctx)
355{
356 int result;
357
358 // Make sure we don't try to read at the same time a write is happening
359 if (SDL_GetAtomicInt(&ctx->device->rumble_pending) > 0) {
360 return 0;
361 }
362
363 result = SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0);
364
365 // See if we can guess the initial input mode
366 if (result > 0 && !ctx->m_bInputOnly && !ctx->m_nInitialInputMode) {
367 switch (ctx->m_rgucReadBuffer[0]) {
368 case k_eSwitchInputReportIDs_FullControllerState:
369 case k_eSwitchInputReportIDs_FullControllerAndMcuState:
370 case k_eSwitchInputReportIDs_SimpleControllerState:
371 ctx->m_nInitialInputMode = ctx->m_rgucReadBuffer[0];
372 break;
373 default:
374 break;
375 }
376 }
377 return result;
378}
379
380static int WriteOutput(SDL_DriverSwitch_Context *ctx, const Uint8 *data, int size)
381{
382#ifdef SWITCH_SYNCHRONOUS_WRITES
383 return SDL_hid_write(ctx->device->dev, data, size);
384#else
385 // Use the rumble thread for general asynchronous writes
386 if (!SDL_HIDAPI_LockRumble()) {
387 return -1;
388 }
389 return SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size);
390#endif // SWITCH_SYNCHRONOUS_WRITES
391}
392
393static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID)
394{
395 // Average response time for messages is ~30ms
396 Uint64 endTicks = SDL_GetTicks() + 100;
397
398 int nRead = 0;
399 while ((nRead = ReadInput(ctx)) != -1) {
400 if (nRead > 0) {
401 if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) {
402 SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[1];
403 if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) {
404 return reply;
405 }
406 }
407 } else {
408 SDL_Delay(1);
409 }
410
411 if (SDL_GetTicks() >= endTicks) {
412 break;
413 }
414 }
415 return NULL;
416}
417
418static bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs expectedID)
419{
420 // Average response time for messages is ~30ms
421 Uint64 endTicks = SDL_GetTicks() + 100;
422
423 int nRead = 0;
424 while ((nRead = ReadInput(ctx)) != -1) {
425 if (nRead > 0) {
426 if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_CommandAck && ctx->m_rgucReadBuffer[1] == expectedID) {
427 return true;
428 }
429 } else {
430 SDL_Delay(1);
431 }
432
433 if (SDL_GetTicks() >= endTicks) {
434 break;
435 }
436 }
437 return false;
438}
439
440static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandOutputPacket_t *outPacket)
441{
442 SDL_memset(outPacket, 0, sizeof(*outPacket));
443
444 outPacket->commonData.ucPacketType = k_eSwitchOutputReportIDs_RumbleAndSubcommand;
445 outPacket->commonData.ucPacketNumber = ctx->m_nCommandNumber;
446
447 SDL_memcpy(outPacket->commonData.rumbleData, ctx->m_RumblePacket.rumbleData, sizeof(ctx->m_RumblePacket.rumbleData));
448
449 outPacket->ucSubcommandID = ucCommandID;
450 if (pBuf) {
451 SDL_memcpy(outPacket->rgucSubcommandData, pBuf, ucLen);
452 }
453
454 ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF;
455}
456
457static bool WritePacket(SDL_DriverSwitch_Context *ctx, void *pBuf, Uint8 ucLen)
458{
459 Uint8 rgucBuf[k_unSwitchMaxOutputPacketLength];
460 const size_t unWriteSize = ctx->device->is_bluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength;
461
462 if (ucLen > k_unSwitchOutputPacketDataLength) {
463 return false;
464 }
465
466 if (ucLen < unWriteSize) {
467 SDL_memcpy(rgucBuf, pBuf, ucLen);
468 SDL_memset(rgucBuf + ucLen, 0, unWriteSize - ucLen);
469 pBuf = rgucBuf;
470 ucLen = (Uint8)unWriteSize;
471 }
472 if (ctx->m_bSyncWrite) {
473 return SDL_hid_write(ctx->device->dev, (Uint8 *)pBuf, ucLen) >= 0;
474 } else {
475 return WriteOutput(ctx, (Uint8 *)pBuf, ucLen) >= 0;
476 }
477}
478
479static bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, const Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandInputPacket_t **ppReply)
480{
481 SwitchSubcommandInputPacket_t *reply = NULL;
482 int nTries;
483
484 for (nTries = 1; !reply && nTries <= ctx->m_nMaxWriteAttempts; ++nTries) {
485 SwitchSubcommandOutputPacket_t commandPacket;
486 ConstructSubcommand(ctx, ucCommandID, pBuf, ucLen, &commandPacket);
487
488 if (!WritePacket(ctx, &commandPacket, sizeof(commandPacket))) {
489 continue;
490 }
491
492 reply = ReadSubcommandReply(ctx, ucCommandID);
493 }
494
495 if (ppReply) {
496 *ppReply = reply;
497 }
498 return reply != NULL;
499}
500
501static bool WriteProprietary(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs ucCommand, Uint8 *pBuf, Uint8 ucLen, bool waitForReply)
502{
503 int nTries;
504
505 for (nTries = 1; nTries <= ctx->m_nMaxWriteAttempts; ++nTries) {
506 SwitchProprietaryOutputPacket_t packet;
507
508 if ((!pBuf && ucLen > 0) || ucLen > sizeof(packet.rgucProprietaryData)) {
509 return false;
510 }
511
512 SDL_zero(packet);
513 packet.ucPacketType = k_eSwitchOutputReportIDs_Proprietary;
514 packet.ucProprietaryID = ucCommand;
515 if (pBuf) {
516 SDL_memcpy(packet.rgucProprietaryData, pBuf, ucLen);
517 }
518
519 if (!WritePacket(ctx, &packet, sizeof(packet))) {
520 continue;
521 }
522
523 if (!waitForReply || ReadProprietaryReply(ctx, ucCommand)) {
524 // SDL_Log("Succeeded%s after %d tries", ctx->m_bSyncWrite ? " (sync)" : "", nTries);
525 return true;
526 }
527 }
528 // SDL_Log("Failed%s after %d tries", ctx->m_bSyncWrite ? " (sync)" : "", nTries);
529 return false;
530}
531
532static Uint8 EncodeRumbleHighAmplitude(Uint16 amplitude)
533{
534 /* More information about these values can be found here:
535 * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
536 */
537 Uint16 hfa[101][2] = { { 0, 0x0 }, { 514, 0x2 }, { 775, 0x4 }, { 921, 0x6 }, { 1096, 0x8 }, { 1303, 0x0a }, { 1550, 0x0c }, { 1843, 0x0e }, { 2192, 0x10 }, { 2606, 0x12 }, { 3100, 0x14 }, { 3686, 0x16 }, { 4383, 0x18 }, { 5213, 0x1a }, { 6199, 0x1c }, { 7372, 0x1e }, { 7698, 0x20 }, { 8039, 0x22 }, { 8395, 0x24 }, { 8767, 0x26 }, { 9155, 0x28 }, { 9560, 0x2a }, { 9984, 0x2c }, { 10426, 0x2e }, { 10887, 0x30 }, { 11369, 0x32 }, { 11873, 0x34 }, { 12398, 0x36 }, { 12947, 0x38 }, { 13520, 0x3a }, { 14119, 0x3c }, { 14744, 0x3e }, { 15067, 0x40 }, { 15397, 0x42 }, { 15734, 0x44 }, { 16079, 0x46 }, { 16431, 0x48 }, { 16790, 0x4a }, { 17158, 0x4c }, { 17534, 0x4e }, { 17918, 0x50 }, { 18310, 0x52 }, { 18711, 0x54 }, { 19121, 0x56 }, { 19540, 0x58 }, { 19967, 0x5a }, { 20405, 0x5c }, { 20851, 0x5e }, { 21308, 0x60 }, { 21775, 0x62 }, { 22251, 0x64 }, { 22739, 0x66 }, { 23236, 0x68 }, { 23745, 0x6a }, { 24265, 0x6c }, { 24797, 0x6e }, { 25340, 0x70 }, { 25894, 0x72 }, { 26462, 0x74 }, { 27041, 0x76 }, { 27633, 0x78 }, { 28238, 0x7a }, { 28856, 0x7c }, { 29488, 0x7e }, { 30134, 0x80 }, { 30794, 0x82 }, { 31468, 0x84 }, { 32157, 0x86 }, { 32861, 0x88 }, { 33581, 0x8a }, { 34316, 0x8c }, { 35068, 0x8e }, { 35836, 0x90 }, { 36620, 0x92 }, { 37422, 0x94 }, { 38242, 0x96 }, { 39079, 0x98 }, { 39935, 0x9a }, { 40809, 0x9c }, { 41703, 0x9e }, { 42616, 0xa0 }, { 43549, 0xa2 }, { 44503, 0xa4 }, { 45477, 0xa6 }, { 46473, 0xa8 }, { 47491, 0xaa }, { 48531, 0xac }, { 49593, 0xae }, { 50679, 0xb0 }, { 51789, 0xb2 }, { 52923, 0xb4 }, { 54082, 0xb6 }, { 55266, 0xb8 }, { 56476, 0xba }, { 57713, 0xbc }, { 58977, 0xbe }, { 60268, 0xc0 }, { 61588, 0xc2 }, { 62936, 0xc4 }, { 64315, 0xc6 }, { 65535, 0xc8 } };
538 int index = 0;
539 for (; index < 101; index++) {
540 if (amplitude <= hfa[index][0]) {
541 return (Uint8)hfa[index][1];
542 }
543 }
544 return (Uint8)hfa[100][1];
545}
546
547static Uint16 EncodeRumbleLowAmplitude(Uint16 amplitude)
548{
549 /* More information about these values can be found here:
550 * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
551 */
552 Uint16 lfa[101][2] = { { 0, 0x0040 }, { 514, 0x8040 }, { 775, 0x0041 }, { 921, 0x8041 }, { 1096, 0x0042 }, { 1303, 0x8042 }, { 1550, 0x0043 }, { 1843, 0x8043 }, { 2192, 0x0044 }, { 2606, 0x8044 }, { 3100, 0x0045 }, { 3686, 0x8045 }, { 4383, 0x0046 }, { 5213, 0x8046 }, { 6199, 0x0047 }, { 7372, 0x8047 }, { 7698, 0x0048 }, { 8039, 0x8048 }, { 8395, 0x0049 }, { 8767, 0x8049 }, { 9155, 0x004a }, { 9560, 0x804a }, { 9984, 0x004b }, { 10426, 0x804b }, { 10887, 0x004c }, { 11369, 0x804c }, { 11873, 0x004d }, { 12398, 0x804d }, { 12947, 0x004e }, { 13520, 0x804e }, { 14119, 0x004f }, { 14744, 0x804f }, { 15067, 0x0050 }, { 15397, 0x8050 }, { 15734, 0x0051 }, { 16079, 0x8051 }, { 16431, 0x0052 }, { 16790, 0x8052 }, { 17158, 0x0053 }, { 17534, 0x8053 }, { 17918, 0x0054 }, { 18310, 0x8054 }, { 18711, 0x0055 }, { 19121, 0x8055 }, { 19540, 0x0056 }, { 19967, 0x8056 }, { 20405, 0x0057 }, { 20851, 0x8057 }, { 21308, 0x0058 }, { 21775, 0x8058 }, { 22251, 0x0059 }, { 22739, 0x8059 }, { 23236, 0x005a }, { 23745, 0x805a }, { 24265, 0x005b }, { 24797, 0x805b }, { 25340, 0x005c }, { 25894, 0x805c }, { 26462, 0x005d }, { 27041, 0x805d }, { 27633, 0x005e }, { 28238, 0x805e }, { 28856, 0x005f }, { 29488, 0x805f }, { 30134, 0x0060 }, { 30794, 0x8060 }, { 31468, 0x0061 }, { 32157, 0x8061 }, { 32861, 0x0062 }, { 33581, 0x8062 }, { 34316, 0x0063 }, { 35068, 0x8063 }, { 35836, 0x0064 }, { 36620, 0x8064 }, { 37422, 0x0065 }, { 38242, 0x8065 }, { 39079, 0x0066 }, { 39935, 0x8066 }, { 40809, 0x0067 }, { 41703, 0x8067 }, { 42616, 0x0068 }, { 43549, 0x8068 }, { 44503, 0x0069 }, { 45477, 0x8069 }, { 46473, 0x006a }, { 47491, 0x806a }, { 48531, 0x006b }, { 49593, 0x806b }, { 50679, 0x006c }, { 51789, 0x806c }, { 52923, 0x006d }, { 54082, 0x806d }, { 55266, 0x006e }, { 56476, 0x806e }, { 57713, 0x006f }, { 58977, 0x806f }, { 60268, 0x0070 }, { 61588, 0x8070 }, { 62936, 0x0071 }, { 64315, 0x8071 }, { 65535, 0x0072 } };
553 int index = 0;
554 for (; index < 101; index++) {
555 if (amplitude <= lfa[index][0]) {
556 return lfa[index][1];
557 }
558 }
559 return lfa[100][1];
560}
561
562static void SetNeutralRumble(SwitchRumbleData_t *pRumble)
563{
564 pRumble->rgucData[0] = 0x00;
565 pRumble->rgucData[1] = 0x01;
566 pRumble->rgucData[2] = 0x40;
567 pRumble->rgucData[3] = 0x40;
568}
569
570static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp)
571{
572 if (ucHighFreqAmp > 0 || usLowFreqAmp > 0) {
573 // High-band frequency and low-band amplitude are actually nine-bits each so they
574 // take a bit from the high-band amplitude and low-band frequency bytes respectively
575 pRumble->rgucData[0] = usHighFreq & 0xFF;
576 pRumble->rgucData[1] = ucHighFreqAmp | ((usHighFreq >> 8) & 0x01);
577
578 pRumble->rgucData[2] = ucLowFreq | ((usLowFreqAmp >> 8) & 0x80);
579 pRumble->rgucData[3] = usLowFreqAmp & 0xFF;
580
581#ifdef DEBUG_RUMBLE
582 SDL_Log("Freq: %.2X %.2X %.2X, Amp: %.2X %.2X %.2X",
583 usHighFreq & 0xFF, ((usHighFreq >> 8) & 0x01), ucLowFreq,
584 ucHighFreqAmp, ((usLowFreqAmp >> 8) & 0x80), usLowFreqAmp & 0xFF);
585#endif
586 } else {
587 SetNeutralRumble(pRumble);
588 }
589}
590
591static bool WriteRumble(SDL_DriverSwitch_Context *ctx)
592{
593 /* Write into m_RumblePacket rather than a temporary buffer to allow the current rumble state
594 * to be retained for subsequent rumble or subcommand packets sent to the controller
595 */
596 ctx->m_RumblePacket.ucPacketType = k_eSwitchOutputReportIDs_Rumble;
597 ctx->m_RumblePacket.ucPacketNumber = ctx->m_nCommandNumber;
598 ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF;
599
600 // Refresh the rumble state periodically
601 ctx->m_ulRumbleSent = SDL_GetTicks();
602
603 return WritePacket(ctx, (Uint8 *)&ctx->m_RumblePacket, sizeof(ctx->m_RumblePacket));
604}
605
606static ESwitchDeviceInfoControllerType CalculateControllerType(SDL_DriverSwitch_Context *ctx, ESwitchDeviceInfoControllerType eControllerType)
607{
608 SDL_HIDAPI_Device *device = ctx->device;
609
610 // The N64 controller reports as a Pro controller over USB
611 if (eControllerType == k_eSwitchDeviceInfoControllerType_ProController &&
612 device->product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) {
613 eControllerType = k_eSwitchDeviceInfoControllerType_N64;
614 }
615
616 if (eControllerType == k_eSwitchDeviceInfoControllerType_Unknown) {
617 // This might be a Joy-Con that's missing from a charging grip slot
618 if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) {
619 if (device->interface_number == 1) {
620 eControllerType = k_eSwitchDeviceInfoControllerType_JoyConLeft;
621 } else {
622 eControllerType = k_eSwitchDeviceInfoControllerType_JoyConRight;
623 }
624 }
625 }
626 return eControllerType;
627}
628
629static bool BReadDeviceInfo(SDL_DriverSwitch_Context *ctx)
630{
631 SwitchSubcommandInputPacket_t *reply = NULL;
632
633 if (ctx->device->is_bluetooth) {
634 if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_RequestDeviceInfo, NULL, 0, &reply)) {
635 // Byte 2: Controller ID (1=LJC, 2=RJC, 3=Pro)
636 ctx->m_eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)reply->deviceInfo.ucDeviceType);
637
638 // Bytes 4-9: MAC address (big-endian)
639 SDL_memcpy(ctx->m_rgucMACAddress, reply->deviceInfo.rgucMACAddress, sizeof(ctx->m_rgucMACAddress));
640
641 return true;
642 }
643 } else {
644 if (WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Status, NULL, 0, true)) {
645 SwitchProprietaryStatusPacket_t *status = (SwitchProprietaryStatusPacket_t *)&ctx->m_rgucReadBuffer[0];
646 size_t i;
647
648 ctx->m_eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)status->ucDeviceType);
649
650 for (i = 0; i < sizeof(ctx->m_rgucMACAddress); ++i) {
651 ctx->m_rgucMACAddress[i] = status->rgucMACAddress[sizeof(ctx->m_rgucMACAddress) - i - 1];
652 }
653
654 return true;
655 }
656 }
657 return false;
658}
659
660static bool BTrySetupUSB(SDL_DriverSwitch_Context *ctx)
661{
662 /* We have to send a connection handshake to the controller when communicating over USB
663 * before we're able to send it other commands. Luckily this command is not supported
664 * over Bluetooth, so we can use the controller's lack of response as a way to
665 * determine if the connection is over USB or Bluetooth
666 */
667 if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, true)) {
668 return false;
669 }
670 if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_HighSpeed, NULL, 0, true)) {
671 // The 8BitDo M30 and SF30 Pro don't respond to this command, but otherwise work correctly
672 // return false;
673 }
674 if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, true)) {
675 // This fails on the right Joy-Con when plugged into the charging grip
676 // return false;
677 }
678 if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false)) {
679 return false;
680 }
681 return true;
682}
683
684static bool SetVibrationEnabled(SDL_DriverSwitch_Context *ctx, Uint8 enabled)
685{
686 return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableVibration, &enabled, sizeof(enabled), NULL);
687}
688static bool SetInputMode(SDL_DriverSwitch_Context *ctx, Uint8 input_mode)
689{
690#ifdef FORCE_SIMPLE_REPORTS
691 input_mode = k_eSwitchInputReportIDs_SimpleControllerState;
692#endif
693#ifdef FORCE_FULL_REPORTS
694 input_mode = k_eSwitchInputReportIDs_FullControllerState;
695#endif
696
697 if (input_mode == ctx->m_nCurrentInputMode) {
698 return true;
699 } else {
700 ctx->m_nCurrentInputMode = input_mode;
701
702 return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetInputReportMode, &input_mode, sizeof(input_mode), NULL);
703 }
704}
705
706static bool SetHomeLED(SDL_DriverSwitch_Context *ctx, Uint8 brightness)
707{
708 Uint8 ucLedIntensity = 0;
709 Uint8 rgucBuffer[4];
710
711 if (brightness > 0) {
712 if (brightness < 65) {
713 ucLedIntensity = (brightness + 5) / 10;
714 } else {
715 ucLedIntensity = (Uint8)SDL_ceilf(0xF * SDL_powf((float)brightness / 100.f, 2.13f));
716 }
717 }
718
719 rgucBuffer[0] = (0x0 << 4) | 0x1; // 0 mini cycles (besides first), cycle duration 8ms
720 rgucBuffer[1] = ((ucLedIntensity & 0xF) << 4) | 0x0; // LED start intensity (0x0-0xF), 0 cycles (LED stays on at start intensity after first cycle)
721 rgucBuffer[2] = ((ucLedIntensity & 0xF) << 4) | 0x0; // First cycle LED intensity, 0x0 intensity for second cycle
722 rgucBuffer[3] = (0x0 << 4) | 0x0; // 8ms fade transition to first cycle, 8ms first cycle LED duration
723
724 return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetHomeLight, rgucBuffer, sizeof(rgucBuffer), NULL);
725}
726
727static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
728{
729 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata;
730
731 if (hint && *hint) {
732 int value;
733
734 if (SDL_strchr(hint, '.') != NULL) {
735 value = (int)(100.0f * SDL_atof(hint));
736 if (value > 255) {
737 value = 255;
738 }
739 } else if (SDL_GetStringBoolean(hint, true)) {
740 value = 100;
741 } else {
742 value = 0;
743 }
744 SetHomeLED(ctx, (Uint8)value);
745 }
746}
747
748static void UpdateSlotLED(SDL_DriverSwitch_Context *ctx)
749{
750 if (!ctx->m_bInputOnly) {
751 Uint8 led_data = 0;
752
753 if (ctx->m_bPlayerLights && ctx->m_nPlayerIndex >= 0) {
754 led_data = (1 << (ctx->m_nPlayerIndex % 4));
755 }
756 WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetPlayerLights, &led_data, sizeof(led_data), NULL);
757 }
758}
759
760static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
761{
762 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata;
763 bool bPlayerLights = SDL_GetStringBoolean(hint, true);
764
765 if (bPlayerLights != ctx->m_bPlayerLights) {
766 ctx->m_bPlayerLights = bPlayerLights;
767
768 UpdateSlotLED(ctx);
769 HIDAPI_UpdateDeviceProperties(ctx->device);
770 }
771}
772
773static void GetInitialInputMode(SDL_DriverSwitch_Context *ctx)
774{
775 if (!ctx->m_nInitialInputMode) {
776 // This will set the initial input mode if it can
777 ReadInput(ctx);
778 }
779}
780
781static Uint8 GetDefaultInputMode(SDL_DriverSwitch_Context *ctx)
782{
783 Uint8 input_mode;
784
785 // Determine the desired input mode
786 if (ctx->m_nInitialInputMode) {
787 input_mode = ctx->m_nInitialInputMode;
788 } else {
789 if (ctx->device->is_bluetooth) {
790 input_mode = k_eSwitchInputReportIDs_SimpleControllerState;
791 } else {
792 input_mode = k_eSwitchInputReportIDs_FullControllerState;
793 }
794 }
795
796 switch (ctx->m_eEnhancedReportHint) {
797 case SWITCH_ENHANCED_REPORT_HINT_OFF:
798 input_mode = k_eSwitchInputReportIDs_SimpleControllerState;
799 break;
800 case SWITCH_ENHANCED_REPORT_HINT_ON:
801 if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState) {
802 input_mode = k_eSwitchInputReportIDs_FullControllerState;
803 }
804 break;
805 case SWITCH_ENHANCED_REPORT_HINT_AUTO:
806 /* Joy-Con controllers switch their thumbsticks into D-pad mode in simple mode,
807 * so let's enable full controller state for them.
808 */
809 if (ctx->device->vendor_id == USB_VENDOR_NINTENDO &&
810 (ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT ||
811 ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT)) {
812 input_mode = k_eSwitchInputReportIDs_FullControllerState;
813 }
814 break;
815 }
816
817 // Wired controllers break if they are put into simple controller state
818 if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState &&
819 !ctx->device->is_bluetooth) {
820 input_mode = k_eSwitchInputReportIDs_FullControllerState;
821 }
822 return input_mode;
823}
824
825static Uint8 GetSensorInputMode(SDL_DriverSwitch_Context *ctx)
826{
827 Uint8 input_mode;
828
829 // Determine the desired input mode
830 if (!ctx->m_nInitialInputMode ||
831 ctx->m_nInitialInputMode == k_eSwitchInputReportIDs_SimpleControllerState) {
832 input_mode = k_eSwitchInputReportIDs_FullControllerState;
833 } else {
834 input_mode = ctx->m_nInitialInputMode;
835 }
836 return input_mode;
837}
838
839static void UpdateInputMode(SDL_DriverSwitch_Context *ctx)
840{
841 Uint8 input_mode;
842
843 if (ctx->m_bReportSensors) {
844 input_mode = GetSensorInputMode(ctx);
845 } else {
846 input_mode = GetDefaultInputMode(ctx);
847 }
848 SetInputMode(ctx, input_mode);
849}
850
851static void SetEnhancedModeAvailable(SDL_DriverSwitch_Context *ctx)
852{
853 if (ctx->m_bEnhancedModeAvailable) {
854 return;
855 }
856 ctx->m_bEnhancedModeAvailable = true;
857
858 if (ctx->m_bSensorsSupported) {
859 // Use the right sensor in the combined Joy-Con pair
860 if (!ctx->device->parent ||
861 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
862 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 200.0f);
863 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 200.0f);
864 }
865 if (ctx->device->parent &&
866 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
867 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_L, 200.0f);
868 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_L, 200.0f);
869 }
870 if (ctx->device->parent &&
871 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
872 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_R, 200.0f);
873 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_R, 200.0f);
874 }
875 }
876}
877
878static void SetEnhancedReportHint(SDL_DriverSwitch_Context *ctx, HIDAPI_Switch_EnhancedReportHint eEnhancedReportHint)
879{
880 ctx->m_eEnhancedReportHint = eEnhancedReportHint;
881
882 switch (eEnhancedReportHint) {
883 case SWITCH_ENHANCED_REPORT_HINT_OFF:
884 ctx->m_bEnhancedMode = false;
885 break;
886 case SWITCH_ENHANCED_REPORT_HINT_ON:
887 SetEnhancedModeAvailable(ctx);
888 ctx->m_bEnhancedMode = true;
889 break;
890 case SWITCH_ENHANCED_REPORT_HINT_AUTO:
891 SetEnhancedModeAvailable(ctx);
892 break;
893 }
894
895 UpdateInputMode(ctx);
896}
897
898static void UpdateEnhancedModeOnEnhancedReport(SDL_DriverSwitch_Context *ctx)
899{
900 if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) {
901 SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON);
902 }
903}
904
905static void UpdateEnhancedModeOnApplicationUsage(SDL_DriverSwitch_Context *ctx)
906{
907 if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) {
908 SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON);
909 }
910}
911
912static void SDLCALL SDL_EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
913{
914 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata;
915
916 if (hint && SDL_strcasecmp(hint, "auto") == 0) {
917 SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_AUTO);
918 } else if (SDL_GetStringBoolean(hint, true)) {
919 SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON);
920 } else {
921 SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_OFF);
922 }
923}
924
925static bool SetIMUEnabled(SDL_DriverSwitch_Context *ctx, bool enabled)
926{
927 Uint8 imu_data = enabled ? 1 : 0;
928 return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableIMU, &imu_data, sizeof(imu_data), NULL);
929}
930
931static bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx)
932{
933 Uint8 *pLeftStickCal;
934 Uint8 *pRightStickCal;
935 size_t stick, axis;
936 SwitchSubcommandInputPacket_t *user_reply = NULL;
937 SwitchSubcommandInputPacket_t *factory_reply = NULL;
938 SwitchSPIOpData_t readUserParams;
939 SwitchSPIOpData_t readFactoryParams;
940
941 // Read User Calibration Info
942 readUserParams.unAddress = k_unSPIStickUserCalibrationStartOffset;
943 readUserParams.ucLength = k_unSPIStickUserCalibrationLength;
944
945 // This isn't readable on all controllers, so ignore failure
946 WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readUserParams, sizeof(readUserParams), &user_reply);
947
948 // Read Factory Calibration Info
949 readFactoryParams.unAddress = k_unSPIStickFactoryCalibrationStartOffset;
950 readFactoryParams.ucLength = k_unSPIStickFactoryCalibrationLength;
951
952 const int MAX_ATTEMPTS = 3;
953 for (int attempt = 0; ; ++attempt) {
954 if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readFactoryParams, sizeof(readFactoryParams), &factory_reply)) {
955 return false;
956 }
957
958 if (factory_reply->stickFactoryCalibration.opData.unAddress == k_unSPIStickFactoryCalibrationStartOffset) {
959 // We successfully read the calibration data
960 break;
961 }
962
963 if (attempt == MAX_ATTEMPTS) {
964 return false;
965 }
966 }
967
968 // Automatically select the user calibration if magic bytes are set
969 if (user_reply && user_reply->stickUserCalibration.rgucLeftMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucLeftMagic[1] == 0xA1) {
970 pLeftStickCal = user_reply->stickUserCalibration.rgucLeftCalibration;
971 } else {
972 pLeftStickCal = factory_reply->stickFactoryCalibration.rgucLeftCalibration;
973 }
974
975 if (user_reply && user_reply->stickUserCalibration.rgucRightMagic[0] == 0xB2 && user_reply->stickUserCalibration.rgucRightMagic[1] == 0xA1) {
976 pRightStickCal = user_reply->stickUserCalibration.rgucRightCalibration;
977 } else {
978 pRightStickCal = factory_reply->stickFactoryCalibration.rgucRightCalibration;
979 }
980
981 /* Stick calibration values are 12-bits each and are packed by bit
982 * For whatever reason the fields are in a different order for each stick
983 * Left: X-Max, Y-Max, X-Center, Y-Center, X-Min, Y-Min
984 * Right: X-Center, Y-Center, X-Min, Y-Min, X-Max, Y-Max
985 */
986
987 // Left stick
988 ctx->m_StickCalData[0].axis[0].sMax = ((pLeftStickCal[1] << 8) & 0xF00) | pLeftStickCal[0]; // X Axis max above center
989 ctx->m_StickCalData[0].axis[1].sMax = (pLeftStickCal[2] << 4) | (pLeftStickCal[1] >> 4); // Y Axis max above center
990 ctx->m_StickCalData[0].axis[0].sCenter = ((pLeftStickCal[4] << 8) & 0xF00) | pLeftStickCal[3]; // X Axis center
991 ctx->m_StickCalData[0].axis[1].sCenter = (pLeftStickCal[5] << 4) | (pLeftStickCal[4] >> 4); // Y Axis center
992 ctx->m_StickCalData[0].axis[0].sMin = ((pLeftStickCal[7] << 8) & 0xF00) | pLeftStickCal[6]; // X Axis min below center
993 ctx->m_StickCalData[0].axis[1].sMin = (pLeftStickCal[8] << 4) | (pLeftStickCal[7] >> 4); // Y Axis min below center
994
995 // Right stick
996 ctx->m_StickCalData[1].axis[0].sCenter = ((pRightStickCal[1] << 8) & 0xF00) | pRightStickCal[0]; // X Axis center
997 ctx->m_StickCalData[1].axis[1].sCenter = (pRightStickCal[2] << 4) | (pRightStickCal[1] >> 4); // Y Axis center
998 ctx->m_StickCalData[1].axis[0].sMin = ((pRightStickCal[4] << 8) & 0xF00) | pRightStickCal[3]; // X Axis min below center
999 ctx->m_StickCalData[1].axis[1].sMin = (pRightStickCal[5] << 4) | (pRightStickCal[4] >> 4); // Y Axis min below center
1000 ctx->m_StickCalData[1].axis[0].sMax = ((pRightStickCal[7] << 8) & 0xF00) | pRightStickCal[6]; // X Axis max above center
1001 ctx->m_StickCalData[1].axis[1].sMax = (pRightStickCal[8] << 4) | (pRightStickCal[7] >> 4); // Y Axis max above center
1002
1003 // Filter out any values that were uninitialized (0xFFF) in the SPI read
1004 for (stick = 0; stick < 2; ++stick) {
1005 for (axis = 0; axis < 2; ++axis) {
1006 if (ctx->m_StickCalData[stick].axis[axis].sCenter == 0xFFF) {
1007 ctx->m_StickCalData[stick].axis[axis].sCenter = 2048;
1008 }
1009 if (ctx->m_StickCalData[stick].axis[axis].sMax == 0xFFF) {
1010 ctx->m_StickCalData[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sCenter * 0.7f);
1011 }
1012 if (ctx->m_StickCalData[stick].axis[axis].sMin == 0xFFF) {
1013 ctx->m_StickCalData[stick].axis[axis].sMin = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sCenter * 0.7f);
1014 }
1015 }
1016 }
1017
1018 for (stick = 0; stick < 2; ++stick) {
1019 for (axis = 0; axis < 2; ++axis) {
1020 ctx->m_StickExtents[stick].axis[axis].sMin = -(Sint16)(ctx->m_StickCalData[stick].axis[axis].sMin * 0.7f);
1021 ctx->m_StickExtents[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sMax * 0.7f);
1022 }
1023 }
1024
1025 for (stick = 0; stick < 2; ++stick) {
1026 for (axis = 0; axis < 2; ++axis) {
1027 ctx->m_SimpleStickExtents[stick].axis[axis].sMin = (Sint16)(SDL_MIN_SINT16 * 0.5f);
1028 ctx->m_SimpleStickExtents[stick].axis[axis].sMax = (Sint16)(SDL_MAX_SINT16 * 0.5f);
1029 }
1030 }
1031
1032 return true;
1033}
1034
1035static bool LoadIMUCalibration(SDL_DriverSwitch_Context *ctx)
1036{
1037 SwitchSubcommandInputPacket_t *reply = NULL;
1038
1039 // Read Calibration Info
1040 SwitchSPIOpData_t readParams;
1041 readParams.unAddress = k_unSPIIMUScaleStartOffset;
1042 readParams.ucLength = k_unSPIIMUScaleLength;
1043
1044 if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply)) {
1045 Uint8 *pIMUScale;
1046 Sint16 sAccelRawX, sAccelRawY, sAccelRawZ, sGyroRawX, sGyroRawY, sGyroRawZ;
1047
1048 // IMU scale gives us multipliers for converting raw values to real world values
1049 pIMUScale = reply->spiReadData.rgucReadData;
1050
1051 sAccelRawX = (pIMUScale[1] << 8) | pIMUScale[0];
1052 sAccelRawY = (pIMUScale[3] << 8) | pIMUScale[2];
1053 sAccelRawZ = (pIMUScale[5] << 8) | pIMUScale[4];
1054
1055 sGyroRawX = (pIMUScale[13] << 8) | pIMUScale[12];
1056 sGyroRawY = (pIMUScale[15] << 8) | pIMUScale[14];
1057 sGyroRawZ = (pIMUScale[17] << 8) | pIMUScale[16];
1058
1059 // Check for user calibration data. If it's present and set, it'll override the factory settings
1060 readParams.unAddress = k_unSPIIMUUserScaleStartOffset;
1061 readParams.ucLength = k_unSPIIMUUserScaleLength;
1062 if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply) && (pIMUScale[0] | pIMUScale[1] << 8) == 0xA1B2) {
1063 pIMUScale = reply->spiReadData.rgucReadData;
1064
1065 sAccelRawX = (pIMUScale[3] << 8) | pIMUScale[2];
1066 sAccelRawY = (pIMUScale[5] << 8) | pIMUScale[4];
1067 sAccelRawZ = (pIMUScale[7] << 8) | pIMUScale[6];
1068
1069 sGyroRawX = (pIMUScale[15] << 8) | pIMUScale[14];
1070 sGyroRawY = (pIMUScale[17] << 8) | pIMUScale[16];
1071 sGyroRawZ = (pIMUScale[19] << 8) | pIMUScale[18];
1072 }
1073
1074 // Accelerometer scale
1075 ctx->m_IMUScaleData.fAccelScaleX = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawX) * SDL_STANDARD_GRAVITY;
1076 ctx->m_IMUScaleData.fAccelScaleY = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawY) * SDL_STANDARD_GRAVITY;
1077 ctx->m_IMUScaleData.fAccelScaleZ = SWITCH_ACCEL_SCALE_MULT / (SWITCH_ACCEL_SCALE_OFFSET - (float)sAccelRawZ) * SDL_STANDARD_GRAVITY;
1078
1079 // Gyro scale
1080 ctx->m_IMUScaleData.fGyroScaleX = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawX) * SDL_PI_F / 180.0f;
1081 ctx->m_IMUScaleData.fGyroScaleY = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawY) * SDL_PI_F / 180.0f;
1082 ctx->m_IMUScaleData.fGyroScaleZ = SWITCH_GYRO_SCALE_MULT / (SWITCH_GYRO_SCALE_OFFSET - (float)sGyroRawZ) * SDL_PI_F / 180.0f;
1083
1084 } else {
1085 // Use default values
1086 const float accelScale = SDL_STANDARD_GRAVITY / SWITCH_ACCEL_SCALE;
1087 const float gyroScale = SDL_PI_F / 180.0f / SWITCH_GYRO_SCALE;
1088
1089 ctx->m_IMUScaleData.fAccelScaleX = accelScale;
1090 ctx->m_IMUScaleData.fAccelScaleY = accelScale;
1091 ctx->m_IMUScaleData.fAccelScaleZ = accelScale;
1092
1093 ctx->m_IMUScaleData.fGyroScaleX = gyroScale;
1094 ctx->m_IMUScaleData.fGyroScaleY = gyroScale;
1095 ctx->m_IMUScaleData.fGyroScaleZ = gyroScale;
1096 }
1097 return true;
1098}
1099
1100static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue)
1101{
1102 sRawValue -= ctx->m_StickCalData[nStick].axis[nAxis].sCenter;
1103
1104 if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) {
1105 ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue;
1106 }
1107 if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) {
1108 ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue;
1109 }
1110
1111 return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, ctx->m_StickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16);
1112}
1113
1114static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue)
1115{
1116 // 0x8000 is the neutral value for all joystick axes
1117 const Uint16 usJoystickCenter = 0x8000;
1118
1119 sRawValue -= usJoystickCenter;
1120
1121 if (sRawValue > ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax) {
1122 ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax = sRawValue;
1123 }
1124 if (sRawValue < ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin) {
1125 ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue;
1126 }
1127
1128 return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16);
1129}
1130
1131static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button)
1132{
1133 if (ctx->m_bUseButtonLabels) {
1134 // Use button labels instead of positions, e.g. Nintendo Online Classic controllers
1135 switch (button) {
1136 case SDL_GAMEPAD_BUTTON_SOUTH:
1137 return SDL_GAMEPAD_BUTTON_EAST;
1138 case SDL_GAMEPAD_BUTTON_EAST:
1139 return SDL_GAMEPAD_BUTTON_SOUTH;
1140 case SDL_GAMEPAD_BUTTON_WEST:
1141 return SDL_GAMEPAD_BUTTON_NORTH;
1142 case SDL_GAMEPAD_BUTTON_NORTH:
1143 return SDL_GAMEPAD_BUTTON_WEST;
1144 default:
1145 break;
1146 }
1147 }
1148 return button;
1149}
1150
1151static int GetMaxWriteAttempts(SDL_HIDAPI_Device *device)
1152{
1153 if (device->vendor_id == USB_VENDOR_NINTENDO &&
1154 device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) {
1155 // This device is a little slow and we know we're always on USB
1156 return 20;
1157 } else {
1158 return 5;
1159 }
1160}
1161
1162static ESwitchDeviceInfoControllerType ReadJoyConControllerType(SDL_HIDAPI_Device *device)
1163{
1164 ESwitchDeviceInfoControllerType eControllerType = k_eSwitchDeviceInfoControllerType_Unknown;
1165 const int MAX_ATTEMPTS = 1; // Don't try too long, in case this is a zombie Bluetooth controller
1166 int attempts = 0;
1167
1168 // Create enough of a context to read the controller type from the device
1169 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx));
1170 if (ctx) {
1171 ctx->device = device;
1172 ctx->m_bSyncWrite = true;
1173 ctx->m_nMaxWriteAttempts = GetMaxWriteAttempts(device);
1174
1175 for ( ; ; ) {
1176 ++attempts;
1177 if (device->is_bluetooth) {
1178 SwitchSubcommandInputPacket_t *reply = NULL;
1179
1180 if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_RequestDeviceInfo, NULL, 0, &reply)) {
1181 eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)reply->deviceInfo.ucDeviceType);
1182 }
1183 } else {
1184 if (WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Status, NULL, 0, true)) {
1185 SwitchProprietaryStatusPacket_t *status = (SwitchProprietaryStatusPacket_t *)&ctx->m_rgucReadBuffer[0];
1186
1187 eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)status->ucDeviceType);
1188 }
1189 }
1190 if (eControllerType == k_eSwitchDeviceInfoControllerType_Unknown && attempts < MAX_ATTEMPTS) {
1191 // Wait a bit and try again
1192 SDL_Delay(100);
1193 continue;
1194 }
1195 break;
1196 }
1197 SDL_free(ctx);
1198 }
1199 return eControllerType;
1200}
1201
1202static bool HasHomeLED(SDL_DriverSwitch_Context *ctx)
1203{
1204 Uint16 vendor_id = ctx->device->vendor_id;
1205 Uint16 product_id = ctx->device->product_id;
1206
1207 // The Power A Nintendo Switch Pro controllers don't have a Home LED
1208 if (vendor_id == 0 && product_id == 0) {
1209 return false;
1210 }
1211
1212 // HORI Wireless Switch Pad
1213 if (vendor_id == 0x0f0d && product_id == 0x00f6) {
1214 return false;
1215 }
1216
1217 // Third party controllers don't have a home LED and will shut off if we try to set it
1218 if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_Unknown ||
1219 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_LicProController) {
1220 return false;
1221 }
1222
1223 // The Nintendo Online classic controllers don't have a Home LED
1224 if (vendor_id == USB_VENDOR_NINTENDO &&
1225 ctx->m_eControllerType > k_eSwitchDeviceInfoControllerType_ProController) {
1226 return false;
1227 }
1228
1229 return true;
1230}
1231
1232static bool AlwaysUsesLabels(Uint16 vendor_id, Uint16 product_id, ESwitchDeviceInfoControllerType eControllerType)
1233{
1234 // Some controllers don't have a diamond button configuration, so should always use labels
1235 if (SDL_IsJoystickGameCube(vendor_id, product_id)) {
1236 return true;
1237 }
1238 switch (eControllerType) {
1239 case k_eSwitchDeviceInfoControllerType_HVCLeft:
1240 case k_eSwitchDeviceInfoControllerType_HVCRight:
1241 case k_eSwitchDeviceInfoControllerType_NESLeft:
1242 case k_eSwitchDeviceInfoControllerType_NESRight:
1243 case k_eSwitchDeviceInfoControllerType_N64:
1244 case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:
1245 return true;
1246 default:
1247 return false;
1248 }
1249}
1250
1251static void HIDAPI_DriverNintendoClassic_RegisterHints(SDL_HintCallback callback, void *userdata)
1252{
1253 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, callback, userdata);
1254}
1255
1256static void HIDAPI_DriverNintendoClassic_UnregisterHints(SDL_HintCallback callback, void *userdata)
1257{
1258 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, callback, userdata);
1259}
1260
1261static bool HIDAPI_DriverNintendoClassic_IsEnabled(void)
1262{
1263 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
1264}
1265
1266static bool HIDAPI_DriverNintendoClassic_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
1267{
1268 if (vendor_id == USB_VENDOR_NINTENDO) {
1269 if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) {
1270 if (SDL_strncmp(name, "NES Controller", 14) == 0 ||
1271 SDL_strncmp(name, "HVC Controller", 14) == 0) {
1272 return true;
1273 }
1274 }
1275
1276 if (product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) {
1277 return true;
1278 }
1279
1280 if (product_id == USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER) {
1281 return true;
1282 }
1283
1284 if (product_id == USB_PRODUCT_NINTENDO_SNES_CONTROLLER) {
1285 return true;
1286 }
1287 }
1288
1289 return false;
1290}
1291
1292static void HIDAPI_DriverJoyCons_RegisterHints(SDL_HintCallback callback, void *userdata)
1293{
1294 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, callback, userdata);
1295}
1296
1297static void HIDAPI_DriverJoyCons_UnregisterHints(SDL_HintCallback callback, void *userdata)
1298{
1299 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, callback, userdata);
1300}
1301
1302static bool HIDAPI_DriverJoyCons_IsEnabled(void)
1303{
1304 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
1305}
1306
1307static bool HIDAPI_DriverJoyCons_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
1308{
1309 if (vendor_id == USB_VENDOR_NINTENDO) {
1310 if (product_id == USB_PRODUCT_NINTENDO_SWITCH_PRO && device && device->dev) {
1311 // This might be a Kinvoca Joy-Con that reports VID/PID as a Switch Pro controller
1312 ESwitchDeviceInfoControllerType eControllerType = ReadJoyConControllerType(device);
1313 if (eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft ||
1314 eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
1315 return true;
1316 }
1317 }
1318
1319 if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT ||
1320 product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT ||
1321 product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) {
1322 return true;
1323 }
1324 }
1325 return false;
1326}
1327
1328static void HIDAPI_DriverSwitch_RegisterHints(SDL_HintCallback callback, void *userdata)
1329{
1330 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, callback, userdata);
1331}
1332
1333static void HIDAPI_DriverSwitch_UnregisterHints(SDL_HintCallback callback, void *userdata)
1334{
1335 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, callback, userdata);
1336}
1337
1338static bool HIDAPI_DriverSwitch_IsEnabled(void)
1339{
1340 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
1341}
1342
1343static bool HIDAPI_DriverSwitch_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
1344{
1345 /* The HORI Wireless Switch Pad enumerates as a HID device when connected via USB
1346 with the same VID/PID as when connected over Bluetooth but doesn't actually
1347 support communication over USB. The most reliable way to block this without allowing the
1348 controller to continually attempt to reconnect is to filter it out by manufacturer/product string.
1349 Note that the controller does have a different product string when connected over Bluetooth.
1350 */
1351 if (SDL_strcmp(name, "HORI Wireless Switch Pad") == 0) {
1352 return false;
1353 }
1354
1355 // If it's handled by another driver, it's not handled here
1356 if (HIDAPI_DriverNintendoClassic_IsSupportedDevice(device, name, type, vendor_id, product_id, version, interface_number, interface_class, interface_subclass, interface_protocol) ||
1357 HIDAPI_DriverJoyCons_IsSupportedDevice(device, name, type, vendor_id, product_id, version, interface_number, interface_class, interface_subclass, interface_protocol)) {
1358 return false;
1359 }
1360
1361 return (type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO);
1362}
1363
1364static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
1365{
1366 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
1367
1368 if (ctx->m_bInputOnly) {
1369 if (SDL_IsJoystickGameCube(device->vendor_id, device->product_id)) {
1370 device->type = SDL_GAMEPAD_TYPE_STANDARD;
1371 }
1372 } else {
1373 char serial[18];
1374
1375 switch (ctx->m_eControllerType) {
1376 case k_eSwitchDeviceInfoControllerType_JoyConLeft:
1377 HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)");
1378 HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT);
1379 device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT;
1380 break;
1381 case k_eSwitchDeviceInfoControllerType_JoyConRight:
1382 HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)");
1383 HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT);
1384 device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;
1385 break;
1386 case k_eSwitchDeviceInfoControllerType_ProController:
1387 case k_eSwitchDeviceInfoControllerType_LicProController:
1388 HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller");
1389 HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_PRO);
1390 device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
1391 break;
1392 case k_eSwitchDeviceInfoControllerType_HVCLeft:
1393 HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (1)");
1394 device->type = SDL_GAMEPAD_TYPE_STANDARD;
1395 break;
1396 case k_eSwitchDeviceInfoControllerType_HVCRight:
1397 HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (2)");
1398 device->type = SDL_GAMEPAD_TYPE_STANDARD;
1399 break;
1400 case k_eSwitchDeviceInfoControllerType_NESLeft:
1401 HIDAPI_SetDeviceName(device, "Nintendo NES Controller (L)");
1402 device->type = SDL_GAMEPAD_TYPE_STANDARD;
1403 break;
1404 case k_eSwitchDeviceInfoControllerType_NESRight:
1405 HIDAPI_SetDeviceName(device, "Nintendo NES Controller (R)");
1406 device->type = SDL_GAMEPAD_TYPE_STANDARD;
1407 break;
1408 case k_eSwitchDeviceInfoControllerType_SNES:
1409 HIDAPI_SetDeviceName(device, "Nintendo SNES Controller");
1410 HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SNES_CONTROLLER);
1411 device->type = SDL_GAMEPAD_TYPE_STANDARD;
1412 break;
1413 case k_eSwitchDeviceInfoControllerType_N64:
1414 HIDAPI_SetDeviceName(device, "Nintendo N64 Controller");
1415 HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_N64_CONTROLLER);
1416 device->type = SDL_GAMEPAD_TYPE_STANDARD;
1417 break;
1418 case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:
1419 HIDAPI_SetDeviceName(device, "Nintendo SEGA Genesis Controller");
1420 HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER);
1421 device->type = SDL_GAMEPAD_TYPE_STANDARD;
1422 break;
1423 case k_eSwitchDeviceInfoControllerType_Unknown:
1424 // We couldn't read the device info for this controller, might not be fully compliant
1425 if (device->vendor_id == USB_VENDOR_NINTENDO) {
1426 switch (device->product_id) {
1427 case USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT:
1428 ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_JoyConLeft;
1429 HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)");
1430 device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT;
1431 break;
1432 case USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT:
1433 ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_JoyConRight;
1434 HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)");
1435 device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;
1436 break;
1437 case USB_PRODUCT_NINTENDO_SWITCH_PRO:
1438 ctx->m_eControllerType = k_eSwitchDeviceInfoControllerType_ProController;
1439 HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller");
1440 device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
1441 break;
1442 default:
1443 break;
1444 }
1445 }
1446 return;
1447 default:
1448 device->type = SDL_GAMEPAD_TYPE_STANDARD;
1449 break;
1450 }
1451 device->guid.data[15] = ctx->m_eControllerType;
1452
1453 (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
1454 ctx->m_rgucMACAddress[0],
1455 ctx->m_rgucMACAddress[1],
1456 ctx->m_rgucMACAddress[2],
1457 ctx->m_rgucMACAddress[3],
1458 ctx->m_rgucMACAddress[4],
1459 ctx->m_rgucMACAddress[5]);
1460 HIDAPI_SetDeviceSerial(device, serial);
1461 }
1462}
1463
1464static bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)
1465{
1466 SDL_DriverSwitch_Context *ctx;
1467
1468 ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx));
1469 if (!ctx) {
1470 return false;
1471 }
1472 ctx->device = device;
1473 device->context = ctx;
1474
1475 ctx->m_nMaxWriteAttempts = GetMaxWriteAttempts(device);
1476 ctx->m_bSyncWrite = true;
1477
1478 // Find out whether or not we can send output reports
1479 ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id);
1480 if (!ctx->m_bInputOnly) {
1481 // Initialize rumble data, important for reading device info on the MOBAPAD M073
1482 SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
1483 SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
1484
1485 BReadDeviceInfo(ctx);
1486 }
1487 UpdateDeviceIdentity(device);
1488
1489 // Prefer the USB device over the Bluetooth device
1490 if (device->is_bluetooth) {
1491 if (HIDAPI_HasConnectedUSBDevice(device->serial)) {
1492 return true;
1493 }
1494 } else {
1495 HIDAPI_DisconnectBluetoothDevice(device->serial);
1496 }
1497 return HIDAPI_JoystickConnected(device, NULL);
1498}
1499
1500static int HIDAPI_DriverSwitch_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
1501{
1502 return -1;
1503}
1504
1505static void HIDAPI_DriverSwitch_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
1506{
1507 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
1508
1509 if (!ctx->joystick) {
1510 return;
1511 }
1512
1513 ctx->m_nPlayerIndex = player_index;
1514
1515 UpdateSlotLED(ctx);
1516}
1517
1518static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1519{
1520 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
1521
1522 SDL_AssertJoysticksLocked();
1523
1524 ctx->joystick = joystick;
1525
1526 ctx->m_bSyncWrite = true;
1527
1528 if (!ctx->m_bInputOnly) {
1529#ifdef SDL_PLATFORM_MACOS
1530 // Wait for the OS to finish its handshake with the controller
1531 SDL_Delay(250);
1532#endif
1533 GetInitialInputMode(ctx);
1534 ctx->m_nCurrentInputMode = ctx->m_nInitialInputMode;
1535
1536 // Initialize rumble data
1537 SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
1538 SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
1539
1540 if (!device->is_bluetooth) {
1541 if (!BTrySetupUSB(ctx)) {
1542 SDL_SetError("Couldn't setup USB mode");
1543 return false;
1544 }
1545 }
1546
1547 if (!LoadStickCalibration(ctx)) {
1548 SDL_SetError("Couldn't load stick calibration");
1549 return false;
1550 }
1551
1552 if (ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_HVCLeft &&
1553 ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_HVCRight &&
1554 ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESLeft &&
1555 ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESRight &&
1556 ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SNES &&
1557 ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_N64 &&
1558 ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis) {
1559 if (LoadIMUCalibration(ctx)) {
1560 ctx->m_bSensorsSupported = true;
1561 }
1562 }
1563
1564 // Enable vibration
1565 SetVibrationEnabled(ctx, 1);
1566
1567 // Set desired input mode
1568 SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
1569 SDL_EnhancedReportsChanged, ctx);
1570
1571 // Start sending USB reports
1572 if (!device->is_bluetooth) {
1573 // ForceUSB doesn't generate an ACK, so don't wait for a reply
1574 if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false)) {
1575 SDL_SetError("Couldn't start USB reports");
1576 return false;
1577 }
1578 }
1579
1580 // Set the LED state
1581 if (HasHomeLED(ctx)) {
1582 if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft ||
1583 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
1584 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED,
1585 SDL_HomeLEDHintChanged, ctx);
1586 } else {
1587 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED,
1588 SDL_HomeLEDHintChanged, ctx);
1589 }
1590 }
1591 }
1592
1593 if (AlwaysUsesLabels(device->vendor_id, device->product_id, ctx->m_eControllerType)) {
1594 ctx->m_bUseButtonLabels = true;
1595 }
1596
1597 // Initialize player index (needed for setting LEDs)
1598 ctx->m_nPlayerIndex = SDL_GetJoystickPlayerIndex(joystick);
1599 ctx->m_bPlayerLights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, true);
1600 UpdateSlotLED(ctx);
1601
1602 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED,
1603 SDL_PlayerLEDHintChanged, ctx);
1604
1605 // Initialize the joystick capabilities
1606 joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH_BUTTONS;
1607 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
1608 joystick->nhats = 1;
1609
1610 // Set up for input
1611 ctx->m_bSyncWrite = false;
1612 ctx->m_ulLastIMUReset = ctx->m_ulLastInput = SDL_GetTicks();
1613 ctx->m_ulIMUUpdateIntervalNS = SDL_MS_TO_NS(5); // Start off at 5 ms update rate
1614
1615 // Set up for vertical mode
1616 ctx->m_bVerticalMode = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false);
1617
1618 return true;
1619}
1620
1621static bool HIDAPI_DriverSwitch_ActuallyRumbleJoystick(SDL_DriverSwitch_Context *ctx, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1622{
1623 /* Experimentally determined rumble values. These will only matter on some controllers as tested ones
1624 * seem to disregard these and just use any non-zero rumble values as a binary flag for constant rumble
1625 *
1626 * More information about these values can be found here:
1627 * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
1628 */
1629 const Uint16 k_usHighFreq = 0x0074;
1630 const Uint8 k_ucHighFreqAmp = EncodeRumbleHighAmplitude(high_frequency_rumble);
1631 const Uint8 k_ucLowFreq = 0x3D;
1632 const Uint16 k_usLowFreqAmp = EncodeRumbleLowAmplitude(low_frequency_rumble);
1633
1634 if (low_frequency_rumble || high_frequency_rumble) {
1635 EncodeRumble(&ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);
1636 EncodeRumble(&ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);
1637 } else {
1638 SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
1639 SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
1640 }
1641
1642 ctx->m_bRumbleActive = (low_frequency_rumble || high_frequency_rumble);
1643
1644 if (!WriteRumble(ctx)) {
1645 return SDL_SetError("Couldn't send rumble packet");
1646 }
1647 return true;
1648}
1649
1650static bool HIDAPI_DriverSwitch_SendPendingRumble(SDL_DriverSwitch_Context *ctx)
1651{
1652 if (SDL_GetTicks() < (ctx->m_ulRumbleSent + RUMBLE_WRITE_FREQUENCY_MS)) {
1653 return true;
1654 }
1655
1656 if (ctx->m_bRumblePending) {
1657 Uint16 low_frequency_rumble = (Uint16)(ctx->m_unRumblePending >> 16);
1658 Uint16 high_frequency_rumble = (Uint16)ctx->m_unRumblePending;
1659
1660#ifdef DEBUG_RUMBLE
1661 SDL_Log("Sent pending rumble %d/%d, %d ms after previous rumble", low_frequency_rumble, high_frequency_rumble, SDL_GetTicks() - ctx->m_ulRumbleSent);
1662#endif
1663 ctx->m_bRumblePending = false;
1664 ctx->m_unRumblePending = 0;
1665
1666 return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, low_frequency_rumble, high_frequency_rumble);
1667 }
1668
1669 if (ctx->m_bRumbleZeroPending) {
1670 ctx->m_bRumbleZeroPending = false;
1671
1672#ifdef DEBUG_RUMBLE
1673 SDL_Log("Sent pending zero rumble, %d ms after previous rumble", SDL_GetTicks() - ctx->m_ulRumbleSent);
1674#endif
1675 return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, 0, 0);
1676 }
1677
1678 return true;
1679}
1680
1681static bool HIDAPI_DriverSwitch_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1682{
1683 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
1684
1685 if (ctx->m_bInputOnly) {
1686 return SDL_Unsupported();
1687 }
1688
1689 if (device->parent) {
1690 if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
1691 // Just handle low frequency rumble
1692 high_frequency_rumble = 0;
1693 } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
1694 // Just handle high frequency rumble
1695 low_frequency_rumble = 0;
1696 }
1697 }
1698
1699 if (ctx->m_bRumblePending) {
1700 if (!HIDAPI_DriverSwitch_SendPendingRumble(ctx)) {
1701 return false;
1702 }
1703 }
1704
1705 if (SDL_GetTicks() < (ctx->m_ulRumbleSent + RUMBLE_WRITE_FREQUENCY_MS)) {
1706 if (low_frequency_rumble || high_frequency_rumble) {
1707 Uint32 unRumblePending = ((Uint32)low_frequency_rumble << 16) | high_frequency_rumble;
1708
1709 // Keep the highest rumble intensity in the given interval
1710 if (unRumblePending > ctx->m_unRumblePending) {
1711 ctx->m_unRumblePending = unRumblePending;
1712 }
1713 ctx->m_bRumblePending = true;
1714 ctx->m_bRumbleZeroPending = false;
1715 } else {
1716 // When rumble is complete, turn it off
1717 ctx->m_bRumbleZeroPending = true;
1718 }
1719 return true;
1720 }
1721
1722#ifdef DEBUG_RUMBLE
1723 SDL_Log("Sent rumble %d/%d", low_frequency_rumble, high_frequency_rumble);
1724#endif
1725
1726 return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, low_frequency_rumble, high_frequency_rumble);
1727}
1728
1729static bool HIDAPI_DriverSwitch_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
1730{
1731 return SDL_Unsupported();
1732}
1733
1734static Uint32 HIDAPI_DriverSwitch_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1735{
1736 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
1737 Uint32 result = 0;
1738
1739 if (ctx->m_bPlayerLights && !ctx->m_bInputOnly) {
1740 result |= SDL_JOYSTICK_CAP_PLAYER_LED;
1741 }
1742
1743 if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_ProController && !ctx->m_bInputOnly) {
1744 // Doesn't have an RGB LED, so don't return SDL_JOYSTICK_CAP_RGB_LED here
1745 result |= SDL_JOYSTICK_CAP_RUMBLE;
1746 } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft ||
1747 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
1748 result |= SDL_JOYSTICK_CAP_RUMBLE;
1749 }
1750 return result;
1751}
1752
1753static bool HIDAPI_DriverSwitch_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1754{
1755 return SDL_Unsupported();
1756}
1757
1758static bool HIDAPI_DriverSwitch_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
1759{
1760 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
1761
1762 if (size == sizeof(SwitchCommonOutputPacket_t)) {
1763 const SwitchCommonOutputPacket_t *packet = (SwitchCommonOutputPacket_t *)data;
1764
1765 if (packet->ucPacketType != k_eSwitchOutputReportIDs_Rumble) {
1766 return SDL_SetError("Unknown Nintendo Switch Pro effect type");
1767 }
1768
1769 SDL_copyp(&ctx->m_RumblePacket.rumbleData[0], &packet->rumbleData[0]);
1770 SDL_copyp(&ctx->m_RumblePacket.rumbleData[1], &packet->rumbleData[1]);
1771 if (!WriteRumble(ctx)) {
1772 return false;
1773 }
1774
1775 // This overwrites any internal rumble
1776 ctx->m_bRumblePending = false;
1777 ctx->m_bRumbleZeroPending = false;
1778 return true;
1779 } else if (size >= 2 && size <= 256) {
1780 const Uint8 *payload = (const Uint8 *)data;
1781 ESwitchSubcommandIDs cmd = (ESwitchSubcommandIDs)payload[0];
1782
1783 if (cmd == k_eSwitchSubcommandIDs_SetInputReportMode && !device->is_bluetooth) {
1784 // Going into simple mode over USB disables input reports, so don't do that
1785 return true;
1786 }
1787 if (cmd == k_eSwitchSubcommandIDs_SetHomeLight && !HasHomeLED(ctx)) {
1788 // Setting the home LED when it's not supported can cause the controller to reset
1789 return true;
1790 }
1791
1792 if (!WriteSubcommand(ctx, cmd, &payload[1], (Uint8)(size - 1), NULL)) {
1793 return false;
1794 }
1795 return true;
1796 }
1797 return SDL_Unsupported();
1798}
1799
1800static bool HIDAPI_DriverSwitch_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
1801{
1802 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
1803
1804 UpdateEnhancedModeOnApplicationUsage(ctx);
1805
1806 if (!ctx->m_bSensorsSupported || (enabled && !ctx->m_bEnhancedMode)) {
1807 return SDL_Unsupported();
1808 }
1809
1810 ctx->m_bReportSensors = enabled;
1811 ctx->m_unIMUSamples = 0;
1812 ctx->m_ulIMUSampleTimestampNS = SDL_GetTicksNS();
1813
1814 UpdateInputMode(ctx);
1815 SetIMUEnabled(ctx, enabled);
1816
1817 return true;
1818}
1819
1820static void HandleInputOnlyControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchInputOnlyControllerStatePacket_t *packet)
1821{
1822 Sint16 axis;
1823 Uint64 timestamp = SDL_GetTicksNS();
1824
1825 if (packet->rgucButtons[0] != ctx->m_lastInputOnlyState.rgucButtons[0]) {
1826 Uint8 data = packet->rgucButtons[0];
1827 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x02) != 0));
1828 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x04) != 0));
1829 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0));
1830 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0));
1831 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0));
1832 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0));
1833 }
1834
1835 if (packet->rgucButtons[1] != ctx->m_lastInputOnlyState.rgucButtons[1]) {
1836 Uint8 data = packet->rgucButtons[1];
1837 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0));
1838 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));
1839 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0));
1840 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0));
1841 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));
1842 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));
1843 }
1844
1845 if (packet->ucStickHat != ctx->m_lastInputOnlyState.ucStickHat) {
1846 Uint8 hat;
1847
1848 switch (packet->ucStickHat) {
1849 case 0:
1850 hat = SDL_HAT_UP;
1851 break;
1852 case 1:
1853 hat = SDL_HAT_RIGHTUP;
1854 break;
1855 case 2:
1856 hat = SDL_HAT_RIGHT;
1857 break;
1858 case 3:
1859 hat = SDL_HAT_RIGHTDOWN;
1860 break;
1861 case 4:
1862 hat = SDL_HAT_DOWN;
1863 break;
1864 case 5:
1865 hat = SDL_HAT_LEFTDOWN;
1866 break;
1867 case 6:
1868 hat = SDL_HAT_LEFT;
1869 break;
1870 case 7:
1871 hat = SDL_HAT_LEFTUP;
1872 break;
1873 default:
1874 hat = SDL_HAT_CENTERED;
1875 break;
1876 }
1877 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1878 }
1879
1880 axis = (packet->rgucButtons[0] & 0x40) ? 32767 : -32768;
1881 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
1882
1883 axis = (packet->rgucButtons[0] & 0x80) ? 32767 : -32768;
1884 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
1885
1886 if (packet->rgucJoystickLeft[0] != ctx->m_lastInputOnlyState.rgucJoystickLeft[0]) {
1887 axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickLeft[0], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16);
1888 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
1889 }
1890
1891 if (packet->rgucJoystickLeft[1] != ctx->m_lastInputOnlyState.rgucJoystickLeft[1]) {
1892 axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickLeft[1], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16);
1893 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
1894 }
1895
1896 if (packet->rgucJoystickRight[0] != ctx->m_lastInputOnlyState.rgucJoystickRight[0]) {
1897 axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickRight[0], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16);
1898 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
1899 }
1900
1901 if (packet->rgucJoystickRight[1] != ctx->m_lastInputOnlyState.rgucJoystickRight[1]) {
1902 axis = (Sint16)HIDAPI_RemapVal(packet->rgucJoystickRight[1], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16);
1903 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
1904 }
1905
1906 ctx->m_lastInputOnlyState = *packet;
1907}
1908
1909static void HandleCombinedSimpleControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)
1910{
1911 if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {
1912 Uint8 data = packet->rgucButtons[0];
1913 Uint8 hat = 0;
1914
1915 if (data & 0x01) {
1916 hat |= SDL_HAT_LEFT;
1917 }
1918 if (data & 0x02) {
1919 hat |= SDL_HAT_DOWN;
1920 }
1921 if (data & 0x04) {
1922 hat |= SDL_HAT_UP;
1923 }
1924 if (data & 0x08) {
1925 hat |= SDL_HAT_RIGHT;
1926 }
1927 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1928
1929 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x10) != 0));
1930 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x20) != 0));
1931 }
1932
1933 if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {
1934 Uint8 data = packet->rgucButtons[1];
1935 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0));
1936 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0));
1937 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));
1938 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0));
1939 }
1940
1941 Sint16 axis = (packet->rgucButtons[1] & 0x80) ? 32767 : -32768;
1942 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
1943
1944 if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {
1945 switch (packet->ucStickHat) {
1946 case 0:
1947 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);
1948 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);
1949 break;
1950 case 1:
1951 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);
1952 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);
1953 break;
1954 case 2:
1955 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);
1956 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);
1957 break;
1958 case 3:
1959 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);
1960 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);
1961 break;
1962 case 4:
1963 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);
1964 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);
1965 break;
1966 case 5:
1967 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);
1968 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);
1969 break;
1970 case 6:
1971 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);
1972 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);
1973 break;
1974 case 7:
1975 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);
1976 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);
1977 break;
1978 default:
1979 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);
1980 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);
1981 break;
1982 }
1983 }
1984}
1985
1986static void HandleCombinedSimpleControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)
1987{
1988 if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {
1989 Uint8 data = packet->rgucButtons[0];
1990 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x01) != 0));
1991 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0));
1992 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0));
1993 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x08) != 0));
1994 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x10) != 0));
1995 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x20) != 0));
1996 }
1997
1998 if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {
1999 Uint8 data = packet->rgucButtons[1];
2000 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));
2001 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0));
2002 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));
2003 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0));
2004 }
2005
2006 Sint16 axis = (packet->rgucButtons[1] & 0x80) ? 32767 : -32768;
2007 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
2008
2009 if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {
2010 switch (packet->ucStickHat) {
2011 case 0:
2012 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN);
2013 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0);
2014 break;
2015 case 1:
2016 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN);
2017 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN);
2018 break;
2019 case 2:
2020 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0);
2021 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN);
2022 break;
2023 case 3:
2024 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX);
2025 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MIN);
2026 break;
2027 case 4:
2028 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX);
2029 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0);
2030 break;
2031 case 5:
2032 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MAX);
2033 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX);
2034 break;
2035 case 6:
2036 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0);
2037 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX);
2038 break;
2039 case 7:
2040 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, SDL_JOYSTICK_AXIS_MIN);
2041 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, SDL_JOYSTICK_AXIS_MAX);
2042 break;
2043 default:
2044 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, 0);
2045 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, 0);
2046 break;
2047 }
2048 }
2049}
2050
2051static void HandleMiniSimpleControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)
2052{
2053 if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {
2054 Uint8 data = packet->rgucButtons[0];
2055 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0));
2056 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0));
2057 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0));
2058 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0));
2059 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0));
2060 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0));
2061 }
2062
2063 if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {
2064 Uint8 data = packet->rgucButtons[1];
2065 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x01) != 0));
2066 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0));
2067 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x20) != 0));
2068 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x40) != 0));
2069 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x80) != 0));
2070 }
2071
2072 if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {
2073 switch (packet->ucStickHat) {
2074 case 0:
2075 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);
2076 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);
2077 break;
2078 case 1:
2079 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);
2080 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);
2081 break;
2082 case 2:
2083 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);
2084 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);
2085 break;
2086 case 3:
2087 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);
2088 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);
2089 break;
2090 case 4:
2091 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);
2092 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);
2093 break;
2094 case 5:
2095 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);
2096 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);
2097 break;
2098 case 6:
2099 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);
2100 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);
2101 break;
2102 case 7:
2103 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);
2104 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);
2105 break;
2106 default:
2107 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);
2108 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);
2109 break;
2110 }
2111 }
2112}
2113
2114static void HandleMiniSimpleControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)
2115{
2116 if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {
2117 Uint8 data = packet->rgucButtons[0];
2118 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0));
2119 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0));
2120 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0));
2121 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0));
2122 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0));
2123 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0));
2124 }
2125
2126 if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {
2127 Uint8 data = packet->rgucButtons[1];
2128 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));
2129 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0));
2130 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));
2131 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));
2132 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x40) != 0));
2133 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x80) != 0));
2134 }
2135
2136 if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {
2137 switch (packet->ucStickHat) {
2138 case 0:
2139 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);
2140 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);
2141 break;
2142 case 1:
2143 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);
2144 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);
2145 break;
2146 case 2:
2147 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);
2148 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);
2149 break;
2150 case 3:
2151 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MAX);
2152 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);
2153 break;
2154 case 4:
2155 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);
2156 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);
2157 break;
2158 case 5:
2159 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);
2160 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MAX);
2161 break;
2162 case 6:
2163 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);
2164 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);
2165 break;
2166 case 7:
2167 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, SDL_JOYSTICK_AXIS_MIN);
2168 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, SDL_JOYSTICK_AXIS_MIN);
2169 break;
2170 default:
2171 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, 0);
2172 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, 0);
2173 break;
2174 }
2175 }
2176}
2177
2178static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)
2179{
2180 Uint64 timestamp = SDL_GetTicksNS();
2181
2182 if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
2183 if (ctx->device->parent || ctx->m_bVerticalMode) {
2184 HandleCombinedSimpleControllerStateL(timestamp, joystick, ctx, packet);
2185 } else {
2186 HandleMiniSimpleControllerStateL(timestamp, joystick, ctx, packet);
2187 }
2188 } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
2189 if (ctx->device->parent || ctx->m_bVerticalMode) {
2190 HandleCombinedSimpleControllerStateR(timestamp, joystick, ctx, packet);
2191 } else {
2192 HandleMiniSimpleControllerStateR(timestamp, joystick, ctx, packet);
2193 }
2194 } else {
2195 Sint16 axis;
2196
2197 if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {
2198 Uint8 data = packet->rgucButtons[0];
2199 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x01) != 0));
2200 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0));
2201 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0));
2202 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x08) != 0));
2203 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x10) != 0));
2204 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x20) != 0));
2205 }
2206
2207 if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {
2208 Uint8 data = packet->rgucButtons[1];
2209 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0));
2210 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));
2211 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0));
2212 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x08) != 0));
2213 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));
2214 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));
2215 }
2216
2217 if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {
2218 Uint8 hat;
2219
2220 switch (packet->ucStickHat) {
2221 case 0:
2222 hat = SDL_HAT_UP;
2223 break;
2224 case 1:
2225 hat = SDL_HAT_RIGHTUP;
2226 break;
2227 case 2:
2228 hat = SDL_HAT_RIGHT;
2229 break;
2230 case 3:
2231 hat = SDL_HAT_RIGHTDOWN;
2232 break;
2233 case 4:
2234 hat = SDL_HAT_DOWN;
2235 break;
2236 case 5:
2237 hat = SDL_HAT_LEFTDOWN;
2238 break;
2239 case 6:
2240 hat = SDL_HAT_LEFT;
2241 break;
2242 case 7:
2243 hat = SDL_HAT_LEFTUP;
2244 break;
2245 default:
2246 hat = SDL_HAT_CENTERED;
2247 break;
2248 }
2249 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
2250 }
2251
2252 axis = (packet->rgucButtons[0] & 0x40) ? 32767 : -32768;
2253 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
2254
2255 axis = ((packet->rgucButtons[0] & 0x80) || (packet->rgucButtons[1] & 0x80)) ? 32767 : -32768;
2256 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
2257
2258 axis = ApplySimpleStickCalibration(ctx, 0, 0, packet->sJoystickLeft[0]);
2259 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
2260
2261 axis = ApplySimpleStickCalibration(ctx, 0, 1, packet->sJoystickLeft[1]);
2262 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
2263
2264 axis = ApplySimpleStickCalibration(ctx, 1, 0, packet->sJoystickRight[0]);
2265 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
2266
2267 axis = ApplySimpleStickCalibration(ctx, 1, 1, packet->sJoystickRight[1]);
2268 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
2269 }
2270
2271 ctx->m_lastSimpleState = *packet;
2272}
2273
2274static void SendSensorUpdate(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SDL_SensorType type, Uint64 sensor_timestamp, const Sint16 *values)
2275{
2276 float data[3];
2277
2278 /* Note the order of components has been shuffled to match PlayStation controllers,
2279 * since that's our de facto standard from already supporting those controllers, and
2280 * users will want consistent axis mappings across devices.
2281 */
2282 if (type == SDL_SENSOR_GYRO || type == SDL_SENSOR_GYRO_L || type == SDL_SENSOR_GYRO_R) {
2283 data[0] = -(ctx->m_IMUScaleData.fGyroScaleY * (float)values[1]);
2284 data[1] = ctx->m_IMUScaleData.fGyroScaleZ * (float)values[2];
2285 data[2] = -(ctx->m_IMUScaleData.fGyroScaleX * (float)values[0]);
2286 } else {
2287 data[0] = -(ctx->m_IMUScaleData.fAccelScaleY * (float)values[1]);
2288 data[1] = ctx->m_IMUScaleData.fAccelScaleZ * (float)values[2];
2289 data[2] = -(ctx->m_IMUScaleData.fAccelScaleX * (float)values[0]);
2290 }
2291
2292 // Right Joy-Con flips some axes, so let's flip them back for consistency
2293 if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
2294 data[0] = -data[0];
2295 data[1] = -data[1];
2296 }
2297
2298 if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft &&
2299 !ctx->device->parent && !ctx->m_bVerticalMode) {
2300 // Mini-gamepad mode, swap some axes around
2301 float tmp = data[2];
2302 data[2] = -data[0];
2303 data[0] = tmp;
2304 }
2305
2306 if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight &&
2307 !ctx->device->parent && !ctx->m_bVerticalMode) {
2308 // Mini-gamepad mode, swap some axes around
2309 float tmp = data[2];
2310 data[2] = data[0];
2311 data[0] = -tmp;
2312 }
2313
2314 SDL_SendJoystickSensor(timestamp, joystick, type, sensor_timestamp, data, 3);
2315}
2316
2317static void HandleCombinedControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)
2318{
2319 Sint16 axis;
2320
2321 if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {
2322 Uint8 data = packet->controllerState.rgucButtons[1];
2323 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0));
2324 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0));
2325 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));
2326 }
2327
2328 if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) {
2329 Uint8 data = packet->controllerState.rgucButtons[2];
2330 Uint8 hat = 0;
2331
2332 if (data & 0x01) {
2333 hat |= SDL_HAT_DOWN;
2334 }
2335 if (data & 0x02) {
2336 hat |= SDL_HAT_UP;
2337 }
2338 if (data & 0x04) {
2339 hat |= SDL_HAT_RIGHT;
2340 }
2341 if (data & 0x08) {
2342 hat |= SDL_HAT_LEFT;
2343 }
2344 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
2345
2346 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x10) != 0));
2347 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x20) != 0));
2348 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0));
2349 axis = (data & 0x80) ? 32767 : -32768;
2350 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
2351 }
2352
2353 axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8);
2354 axis = ApplyStickCalibration(ctx, 0, 0, axis);
2355 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
2356
2357 axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4);
2358 axis = ApplyStickCalibration(ctx, 0, 1, axis);
2359 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);
2360}
2361
2362static void HandleCombinedControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)
2363{
2364 Sint16 axis;
2365
2366 if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) {
2367 Uint8 data = packet->controllerState.rgucButtons[0];
2368 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0));
2369 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x08) != 0));
2370 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0));
2371 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0));
2372 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x10) != 0));
2373 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x20) != 0));
2374 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0));
2375 axis = (data & 0x80) ? 32767 : -32768;
2376 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
2377 }
2378
2379 if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {
2380 Uint8 data = packet->controllerState.rgucButtons[1];
2381 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));
2382 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x04) != 0));
2383 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));
2384 }
2385
2386 axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8);
2387 axis = ApplyStickCalibration(ctx, 1, 0, axis);
2388 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
2389
2390 axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4);
2391 axis = ApplyStickCalibration(ctx, 1, 1, axis);
2392 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis);
2393}
2394
2395static void HandleMiniControllerStateL(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)
2396{
2397 Sint16 axis;
2398
2399 if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {
2400 Uint8 data = packet->controllerState.rgucButtons[1];
2401 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x01) != 0));
2402 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0));
2403 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x20) != 0));
2404 }
2405
2406 if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) {
2407 Uint8 data = packet->controllerState.rgucButtons[2];
2408 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x08) != 0));
2409 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x01) != 0));
2410 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x02) != 0));
2411 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x04) != 0));
2412 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x10) != 0));
2413 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x20) != 0));
2414 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE1, ((data & 0x40) != 0));
2415 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_LEFT_PADDLE2, ((data & 0x80) != 0));
2416 }
2417
2418 axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8);
2419 axis = ApplyStickCalibration(ctx, 0, 0, axis);
2420 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);
2421
2422 axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4);
2423 axis = ApplyStickCalibration(ctx, 0, 1, axis);
2424 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, ~axis);
2425}
2426
2427static void HandleMiniControllerStateR(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)
2428{
2429 Sint16 axis;
2430
2431 if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) {
2432 Uint8 data = packet->controllerState.rgucButtons[0];
2433 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x08) != 0));
2434 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x02) != 0));
2435 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x04) != 0));
2436 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x01) != 0));
2437 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x10) != 0));
2438 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x20) != 0));
2439 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE1, ((data & 0x40) != 0));
2440 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_RIGHT_PADDLE2, ((data & 0x80) != 0));
2441 }
2442
2443 if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {
2444 Uint8 data = packet->controllerState.rgucButtons[1];
2445 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));
2446 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x04) != 0));
2447 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));
2448 }
2449
2450 axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8);
2451 axis = ApplyStickCalibration(ctx, 1, 0, axis);
2452 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
2453
2454 axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4);
2455 axis = ApplyStickCalibration(ctx, 1, 1, axis);
2456 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
2457}
2458
2459static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) SDL_NO_THREAD_SAFETY_ANALYSIS // We unlock and lock the device lock to be able to change IMU state
2460{
2461 Uint64 timestamp = SDL_GetTicksNS();
2462
2463 if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
2464 if (ctx->device->parent || ctx->m_bVerticalMode) {
2465 HandleCombinedControllerStateL(timestamp, joystick, ctx, packet);
2466 } else {
2467 HandleMiniControllerStateL(timestamp, joystick, ctx, packet);
2468 }
2469 } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
2470 if (ctx->device->parent || ctx->m_bVerticalMode) {
2471 HandleCombinedControllerStateR(timestamp, joystick, ctx, packet);
2472 } else {
2473 HandleMiniControllerStateR(timestamp, joystick, ctx, packet);
2474 }
2475 } else {
2476 Sint16 axis;
2477
2478 if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) {
2479 Uint8 data = packet->controllerState.rgucButtons[0];
2480 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_SOUTH), ((data & 0x04) != 0));
2481 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_EAST), ((data & 0x08) != 0));
2482 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_WEST), ((data & 0x01) != 0));
2483 SDL_SendJoystickButton(timestamp, joystick, RemapButton(ctx, SDL_GAMEPAD_BUTTON_NORTH), ((data & 0x02) != 0));
2484 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x40) != 0));
2485 }
2486
2487 if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {
2488 Uint8 data = packet->controllerState.rgucButtons[1];
2489 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x01) != 0));
2490 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x02) != 0));
2491 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x04) != 0));
2492 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x08) != 0));
2493
2494 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));
2495 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));
2496 }
2497
2498 if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) {
2499 Uint8 data = packet->controllerState.rgucButtons[2];
2500 Uint8 hat = 0;
2501
2502 if (data & 0x01) {
2503 hat |= SDL_HAT_DOWN;
2504 }
2505 if (data & 0x02) {
2506 hat |= SDL_HAT_UP;
2507 }
2508 if (data & 0x04) {
2509 hat |= SDL_HAT_RIGHT;
2510 }
2511 if (data & 0x08) {
2512 hat |= SDL_HAT_LEFT;
2513 }
2514 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
2515
2516 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x40) != 0));
2517 }
2518
2519 axis = (packet->controllerState.rgucButtons[0] & 0x80) ? 32767 : -32768;
2520 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
2521
2522 axis = (packet->controllerState.rgucButtons[2] & 0x80) ? 32767 : -32768;
2523 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
2524
2525 axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8);
2526 axis = ApplyStickCalibration(ctx, 0, 0, axis);
2527 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
2528
2529 axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4);
2530 axis = ApplyStickCalibration(ctx, 0, 1, axis);
2531 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);
2532
2533 axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8);
2534 axis = ApplyStickCalibration(ctx, 1, 0, axis);
2535 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
2536
2537 axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4);
2538 axis = ApplyStickCalibration(ctx, 1, 1, axis);
2539 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis);
2540 }
2541
2542 /* High nibble of battery/connection byte is battery level, low nibble is connection status (always 0 on 8BitDo Pro 2)
2543 * LSB of connection nibble is USB/Switch connection status
2544 * LSB of the battery nibble is used to report charging.
2545 * The battery level is reported from 0(empty)-8(full)
2546 */
2547 SDL_PowerState state;
2548 int charging = (packet->controllerState.ucBatteryAndConnection & 0x10);
2549 int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4;
2550 int percent = (int)SDL_roundf((level / 8.0f) * 100.0f);
2551
2552 if (charging) {
2553 if (level == 8) {
2554 state = SDL_POWERSTATE_CHARGED;
2555 } else {
2556 state = SDL_POWERSTATE_CHARGING;
2557 }
2558 } else {
2559 state = SDL_POWERSTATE_ON_BATTERY;
2560 }
2561 SDL_SendJoystickPowerInfo(joystick, state, percent);
2562
2563 if (ctx->m_bReportSensors) {
2564 bool bHasSensorData = (packet->imuState[0].sAccelZ != 0 ||
2565 packet->imuState[0].sAccelY != 0 ||
2566 packet->imuState[0].sAccelX != 0);
2567 if (bHasSensorData) {
2568 const Uint32 IMU_UPDATE_RATE_SAMPLE_FREQUENCY = 1000;
2569 Uint64 sensor_timestamp[3];
2570
2571 ctx->m_bHasSensorData = true;
2572
2573 // We got three IMU samples, calculate the IMU update rate and timestamps
2574 ctx->m_unIMUSamples += 3;
2575 if (ctx->m_unIMUSamples >= IMU_UPDATE_RATE_SAMPLE_FREQUENCY) {
2576 Uint64 now = SDL_GetTicksNS();
2577 Uint64 elapsed = (now - ctx->m_ulIMUSampleTimestampNS);
2578
2579 if (elapsed > 0) {
2580 ctx->m_ulIMUUpdateIntervalNS = elapsed / ctx->m_unIMUSamples;
2581 }
2582 ctx->m_unIMUSamples = 0;
2583 ctx->m_ulIMUSampleTimestampNS = now;
2584 }
2585
2586 ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS;
2587 sensor_timestamp[0] = ctx->m_ulTimestampNS;
2588 ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS;
2589 sensor_timestamp[1] = ctx->m_ulTimestampNS;
2590 ctx->m_ulTimestampNS += ctx->m_ulIMUUpdateIntervalNS;
2591 sensor_timestamp[2] = ctx->m_ulTimestampNS;
2592
2593 if (!ctx->device->parent ||
2594 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
2595 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[0], &packet->imuState[2].sGyroX);
2596 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[0], &packet->imuState[2].sAccelX);
2597
2598 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[1], &packet->imuState[1].sGyroX);
2599 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[1], &packet->imuState[1].sAccelX);
2600
2601 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO, sensor_timestamp[2], &packet->imuState[0].sGyroX);
2602 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL, sensor_timestamp[2], &packet->imuState[0].sAccelX);
2603 }
2604
2605 if (ctx->device->parent &&
2606 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
2607 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[0], &packet->imuState[2].sGyroX);
2608 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[0], &packet->imuState[2].sAccelX);
2609
2610 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[1], &packet->imuState[1].sGyroX);
2611 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[1], &packet->imuState[1].sAccelX);
2612
2613 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_L, sensor_timestamp[2], &packet->imuState[0].sGyroX);
2614 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_L, sensor_timestamp[2], &packet->imuState[0].sAccelX);
2615 }
2616 if (ctx->device->parent &&
2617 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
2618 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[0], &packet->imuState[2].sGyroX);
2619 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[0], &packet->imuState[2].sAccelX);
2620
2621 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[1], &packet->imuState[1].sGyroX);
2622 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[1], &packet->imuState[1].sAccelX);
2623
2624 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_GYRO_R, sensor_timestamp[2], &packet->imuState[0].sGyroX);
2625 SendSensorUpdate(timestamp, joystick, ctx, SDL_SENSOR_ACCEL_R, sensor_timestamp[2], &packet->imuState[0].sAccelX);
2626 }
2627
2628 } else if (ctx->m_bHasSensorData) {
2629 // Uh oh, someone turned off the IMU?
2630 const int IMU_RESET_DELAY_MS = 3000;
2631 Uint64 now = SDL_GetTicks();
2632
2633 if (now >= (ctx->m_ulLastIMUReset + IMU_RESET_DELAY_MS)) {
2634 SDL_HIDAPI_Device *device = ctx->device;
2635
2636 if (device->updating) {
2637 SDL_UnlockMutex(device->dev_lock);
2638 }
2639
2640 SetIMUEnabled(ctx, true);
2641
2642 if (device->updating) {
2643 SDL_LockMutex(device->dev_lock);
2644 }
2645 ctx->m_ulLastIMUReset = now;
2646 }
2647
2648 } else {
2649 // We have never gotten IMU data, probably not supported on this device
2650 }
2651 }
2652
2653 ctx->m_lastFullState = *packet;
2654}
2655
2656static bool HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device)
2657{
2658 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
2659 SDL_Joystick *joystick = NULL;
2660 int size;
2661 int packet_count = 0;
2662 Uint64 now = SDL_GetTicks();
2663
2664 if (device->num_joysticks > 0) {
2665 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
2666 }
2667
2668 while ((size = ReadInput(ctx)) > 0) {
2669#ifdef DEBUG_SWITCH_PROTOCOL
2670 HIDAPI_DumpPacket("Nintendo Switch packet: size = %d", ctx->m_rgucReadBuffer, size);
2671#endif
2672 ++packet_count;
2673 ctx->m_ulLastInput = now;
2674
2675 if (!joystick) {
2676 continue;
2677 }
2678
2679 if (ctx->m_bInputOnly) {
2680 HandleInputOnlyControllerState(joystick, ctx, (SwitchInputOnlyControllerStatePacket_t *)&ctx->m_rgucReadBuffer[0]);
2681 } else {
2682 if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) {
2683 continue;
2684 }
2685
2686 ctx->m_nCurrentInputMode = ctx->m_rgucReadBuffer[0];
2687
2688 switch (ctx->m_rgucReadBuffer[0]) {
2689 case k_eSwitchInputReportIDs_SimpleControllerState:
2690 HandleSimpleControllerState(joystick, ctx, (SwitchSimpleStatePacket_t *)&ctx->m_rgucReadBuffer[1]);
2691 break;
2692 case k_eSwitchInputReportIDs_FullControllerState:
2693 case k_eSwitchInputReportIDs_FullControllerAndMcuState:
2694 // This is the extended report, we can enable sensors now in auto mode
2695 UpdateEnhancedModeOnEnhancedReport(ctx);
2696
2697 HandleFullControllerState(joystick, ctx, (SwitchStatePacket_t *)&ctx->m_rgucReadBuffer[1]);
2698 break;
2699 default:
2700 break;
2701 }
2702 }
2703 }
2704
2705 if (joystick) {
2706 if (packet_count == 0) {
2707 if (!ctx->m_bInputOnly && !device->is_bluetooth &&
2708 ctx->device->product_id != USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) {
2709 const int INPUT_WAIT_TIMEOUT_MS = 100;
2710 if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) {
2711 // Steam may have put the controller back into non-reporting mode
2712 bool wasSyncWrite = ctx->m_bSyncWrite;
2713
2714 ctx->m_bSyncWrite = true;
2715 WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, false);
2716 ctx->m_bSyncWrite = wasSyncWrite;
2717 }
2718 } else if (device->is_bluetooth &&
2719 ctx->m_nCurrentInputMode != k_eSwitchInputReportIDs_SimpleControllerState) {
2720 const int INPUT_WAIT_TIMEOUT_MS = 3000;
2721 if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) {
2722 // Bluetooth may have disconnected, try reopening the controller
2723 size = -1;
2724 }
2725 }
2726 }
2727
2728 if (ctx->m_bRumblePending || ctx->m_bRumbleZeroPending) {
2729 HIDAPI_DriverSwitch_SendPendingRumble(ctx);
2730 } else if (ctx->m_bRumbleActive &&
2731 now >= (ctx->m_ulRumbleSent + RUMBLE_REFRESH_FREQUENCY_MS)) {
2732#ifdef DEBUG_RUMBLE
2733 SDL_Log("Sent continuing rumble, %d ms after previous rumble", now - ctx->m_ulRumbleSent);
2734#endif
2735 WriteRumble(ctx);
2736 }
2737 }
2738
2739 // Reconnect the Bluetooth device once the USB device is gone
2740 if (device->num_joysticks == 0 && device->is_bluetooth && packet_count > 0 &&
2741 !device->parent &&
2742 !HIDAPI_HasConnectedUSBDevice(device->serial)) {
2743 HIDAPI_JoystickConnected(device, NULL);
2744 }
2745
2746 if (size < 0 && device->num_joysticks > 0) {
2747 // Read error, device is disconnected
2748 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
2749 }
2750 return (size >= 0);
2751}
2752
2753static void HIDAPI_DriverSwitch_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
2754{
2755 SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
2756
2757 if (!ctx->m_bInputOnly) {
2758 // Restore simple input mode for other applications
2759 if (!ctx->m_nInitialInputMode ||
2760 ctx->m_nInitialInputMode == k_eSwitchInputReportIDs_SimpleControllerState) {
2761 SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState);
2762 }
2763 }
2764
2765 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
2766 SDL_EnhancedReportsChanged, ctx);
2767
2768 if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft ||
2769 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
2770 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED,
2771 SDL_HomeLEDHintChanged, ctx);
2772 } else {
2773 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED,
2774 SDL_HomeLEDHintChanged, ctx);
2775 }
2776
2777 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED,
2778 SDL_PlayerLEDHintChanged, ctx);
2779
2780 ctx->joystick = NULL;
2781
2782 ctx->m_bReportSensors = false;
2783 ctx->m_bEnhancedMode = false;
2784 ctx->m_bEnhancedModeAvailable = false;
2785}
2786
2787static void HIDAPI_DriverSwitch_FreeDevice(SDL_HIDAPI_Device *device)
2788{
2789}
2790
2791SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic = {
2792 SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC,
2793 true,
2794 HIDAPI_DriverNintendoClassic_RegisterHints,
2795 HIDAPI_DriverNintendoClassic_UnregisterHints,
2796 HIDAPI_DriverNintendoClassic_IsEnabled,
2797 HIDAPI_DriverNintendoClassic_IsSupportedDevice,
2798 HIDAPI_DriverSwitch_InitDevice,
2799 HIDAPI_DriverSwitch_GetDevicePlayerIndex,
2800 HIDAPI_DriverSwitch_SetDevicePlayerIndex,
2801 HIDAPI_DriverSwitch_UpdateDevice,
2802 HIDAPI_DriverSwitch_OpenJoystick,
2803 HIDAPI_DriverSwitch_RumbleJoystick,
2804 HIDAPI_DriverSwitch_RumbleJoystickTriggers,
2805 HIDAPI_DriverSwitch_GetJoystickCapabilities,
2806 HIDAPI_DriverSwitch_SetJoystickLED,
2807 HIDAPI_DriverSwitch_SendJoystickEffect,
2808 HIDAPI_DriverSwitch_SetJoystickSensorsEnabled,
2809 HIDAPI_DriverSwitch_CloseJoystick,
2810 HIDAPI_DriverSwitch_FreeDevice,
2811};
2812
2813SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons = {
2814 SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS,
2815 true,
2816 HIDAPI_DriverJoyCons_RegisterHints,
2817 HIDAPI_DriverJoyCons_UnregisterHints,
2818 HIDAPI_DriverJoyCons_IsEnabled,
2819 HIDAPI_DriverJoyCons_IsSupportedDevice,
2820 HIDAPI_DriverSwitch_InitDevice,
2821 HIDAPI_DriverSwitch_GetDevicePlayerIndex,
2822 HIDAPI_DriverSwitch_SetDevicePlayerIndex,
2823 HIDAPI_DriverSwitch_UpdateDevice,
2824 HIDAPI_DriverSwitch_OpenJoystick,
2825 HIDAPI_DriverSwitch_RumbleJoystick,
2826 HIDAPI_DriverSwitch_RumbleJoystickTriggers,
2827 HIDAPI_DriverSwitch_GetJoystickCapabilities,
2828 HIDAPI_DriverSwitch_SetJoystickLED,
2829 HIDAPI_DriverSwitch_SendJoystickEffect,
2830 HIDAPI_DriverSwitch_SetJoystickSensorsEnabled,
2831 HIDAPI_DriverSwitch_CloseJoystick,
2832 HIDAPI_DriverSwitch_FreeDevice,
2833};
2834
2835SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch = {
2836 SDL_HINT_JOYSTICK_HIDAPI_SWITCH,
2837 true,
2838 HIDAPI_DriverSwitch_RegisterHints,
2839 HIDAPI_DriverSwitch_UnregisterHints,
2840 HIDAPI_DriverSwitch_IsEnabled,
2841 HIDAPI_DriverSwitch_IsSupportedDevice,
2842 HIDAPI_DriverSwitch_InitDevice,
2843 HIDAPI_DriverSwitch_GetDevicePlayerIndex,
2844 HIDAPI_DriverSwitch_SetDevicePlayerIndex,
2845 HIDAPI_DriverSwitch_UpdateDevice,
2846 HIDAPI_DriverSwitch_OpenJoystick,
2847 HIDAPI_DriverSwitch_RumbleJoystick,
2848 HIDAPI_DriverSwitch_RumbleJoystickTriggers,
2849 HIDAPI_DriverSwitch_GetJoystickCapabilities,
2850 HIDAPI_DriverSwitch_SetJoystickLED,
2851 HIDAPI_DriverSwitch_SendJoystickEffect,
2852 HIDAPI_DriverSwitch_SetJoystickSensorsEnabled,
2853 HIDAPI_DriverSwitch_CloseJoystick,
2854 HIDAPI_DriverSwitch_FreeDevice,
2855};
2856
2857#endif // SDL_JOYSTICK_HIDAPI_SWITCH
2858
2859#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_wii.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_wii.c
new file mode 100644
index 0000000..fb3e164
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_wii.c
@@ -0,0 +1,1617 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28#include "SDL_hidapi_rumble.h"
29#include "SDL_hidapi_nintendo.h"
30
31#ifdef SDL_JOYSTICK_HIDAPI_WII
32
33// Define this if you want to log all packets from the controller
34// #define DEBUG_WII_PROTOCOL
35
36#define ENABLE_CONTINUOUS_REPORTING true
37
38#define INPUT_WAIT_TIMEOUT_MS (3 * 1000)
39#define MOTION_PLUS_UPDATE_TIME_MS (8 * 1000)
40#define STATUS_UPDATE_TIME_MS (15 * 60 * 1000)
41
42#define WII_EXTENSION_NONE 0x2E2E
43#define WII_EXTENSION_UNINITIALIZED 0xFFFF
44#define WII_EXTENSION_NUNCHUK 0x0000
45#define WII_EXTENSION_GAMEPAD 0x0101
46#define WII_EXTENSION_WIIUPRO 0x0120
47#define WII_EXTENSION_MOTIONPLUS_MASK 0xF0FF
48#define WII_EXTENSION_MOTIONPLUS_ID 0x0005
49
50#define WII_MOTIONPLUS_MODE_NONE 0x00
51#define WII_MOTIONPLUS_MODE_STANDARD 0x04
52#define WII_MOTIONPLUS_MODE_NUNCHUK 0x05
53#define WII_MOTIONPLUS_MODE_GAMEPAD 0x07
54
55typedef enum
56{
57 k_eWiiInputReportIDs_Status = 0x20,
58 k_eWiiInputReportIDs_ReadMemory = 0x21,
59 k_eWiiInputReportIDs_Acknowledge = 0x22,
60 k_eWiiInputReportIDs_ButtonData0 = 0x30,
61 k_eWiiInputReportIDs_ButtonData1 = 0x31,
62 k_eWiiInputReportIDs_ButtonData2 = 0x32,
63 k_eWiiInputReportIDs_ButtonData3 = 0x33,
64 k_eWiiInputReportIDs_ButtonData4 = 0x34,
65 k_eWiiInputReportIDs_ButtonData5 = 0x35,
66 k_eWiiInputReportIDs_ButtonData6 = 0x36,
67 k_eWiiInputReportIDs_ButtonData7 = 0x37,
68 k_eWiiInputReportIDs_ButtonDataD = 0x3D,
69 k_eWiiInputReportIDs_ButtonDataE = 0x3E,
70 k_eWiiInputReportIDs_ButtonDataF = 0x3F,
71} EWiiInputReportIDs;
72
73typedef enum
74{
75 k_eWiiOutputReportIDs_Rumble = 0x10,
76 k_eWiiOutputReportIDs_LEDs = 0x11,
77 k_eWiiOutputReportIDs_DataReportingMode = 0x12,
78 k_eWiiOutputReportIDs_IRCameraEnable = 0x13,
79 k_eWiiOutputReportIDs_SpeakerEnable = 0x14,
80 k_eWiiOutputReportIDs_StatusRequest = 0x15,
81 k_eWiiOutputReportIDs_WriteMemory = 0x16,
82 k_eWiiOutputReportIDs_ReadMemory = 0x17,
83 k_eWiiOutputReportIDs_SpeakerData = 0x18,
84 k_eWiiOutputReportIDs_SpeakerMute = 0x19,
85 k_eWiiOutputReportIDs_IRCameraEnable2 = 0x1a,
86} EWiiOutputReportIDs;
87
88typedef enum
89{
90 k_eWiiPlayerLEDs_P1 = 0x10,
91 k_eWiiPlayerLEDs_P2 = 0x20,
92 k_eWiiPlayerLEDs_P3 = 0x40,
93 k_eWiiPlayerLEDs_P4 = 0x80,
94} EWiiPlayerLEDs;
95
96typedef enum
97{
98 k_eWiiCommunicationState_None, // No special communications happening
99 k_eWiiCommunicationState_CheckMotionPlusStage1, // Sent standard extension identify request
100 k_eWiiCommunicationState_CheckMotionPlusStage2, // Sent Motion Plus extension identify request
101} EWiiCommunicationState;
102
103typedef enum
104{
105 k_eWiiButtons_A = SDL_GAMEPAD_BUTTON_MISC1,
106 k_eWiiButtons_B,
107 k_eWiiButtons_One,
108 k_eWiiButtons_Two,
109 k_eWiiButtons_Plus,
110 k_eWiiButtons_Minus,
111 k_eWiiButtons_Home,
112 k_eWiiButtons_DPad_Up,
113 k_eWiiButtons_DPad_Down,
114 k_eWiiButtons_DPad_Left,
115 k_eWiiButtons_DPad_Right,
116 k_eWiiButtons_Max
117} EWiiButtons;
118
119#define k_unWiiPacketDataLength 22
120
121typedef struct
122{
123 Uint8 rgucBaseButtons[2];
124 Uint8 rgucAccelerometer[3];
125 Uint8 rgucExtension[21];
126 bool hasBaseButtons;
127 bool hasAccelerometer;
128 Uint8 ucNExtensionBytes;
129} WiiButtonData;
130
131typedef struct
132{
133 Uint16 min;
134 Uint16 max;
135 Uint16 center;
136 Uint16 deadzone;
137} StickCalibrationData;
138
139typedef struct
140{
141 SDL_HIDAPI_Device *device;
142 SDL_Joystick *joystick;
143 Uint64 timestamp;
144 EWiiCommunicationState m_eCommState;
145 EWiiExtensionControllerType m_eExtensionControllerType;
146 bool m_bPlayerLights;
147 int m_nPlayerIndex;
148 bool m_bRumbleActive;
149 bool m_bMotionPlusPresent;
150 Uint8 m_ucMotionPlusMode;
151 bool m_bReportSensors;
152 Uint8 m_rgucReadBuffer[k_unWiiPacketDataLength];
153 Uint64 m_ulLastInput;
154 Uint64 m_ulLastStatus;
155 Uint64 m_ulNextMotionPlusCheck;
156 bool m_bDisconnected;
157
158 StickCalibrationData m_StickCalibrationData[6];
159} SDL_DriverWii_Context;
160
161static void HIDAPI_DriverWii_RegisterHints(SDL_HintCallback callback, void *userdata)
162{
163 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata);
164}
165
166static void HIDAPI_DriverWii_UnregisterHints(SDL_HintCallback callback, void *userdata)
167{
168 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII, callback, userdata);
169}
170
171static bool HIDAPI_DriverWii_IsEnabled(void)
172{
173#if 1 // This doesn't work with the dolphinbar, so don't enable by default right now
174 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII, false);
175#else
176 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII,
177 SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,
178 SDL_HIDAPI_DEFAULT));
179#endif
180}
181
182static bool HIDAPI_DriverWii_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
183{
184 if (vendor_id == USB_VENDOR_NINTENDO &&
185 (product_id == USB_PRODUCT_NINTENDO_WII_REMOTE ||
186 product_id == USB_PRODUCT_NINTENDO_WII_REMOTE2)) {
187 return true;
188 }
189 return false;
190}
191
192static int ReadInput(SDL_DriverWii_Context *ctx)
193{
194 int size;
195
196 // Make sure we don't try to read at the same time a write is happening
197 if (SDL_GetAtomicInt(&ctx->device->rumble_pending) > 0) {
198 return 0;
199 }
200
201 size = SDL_hid_read_timeout(ctx->device->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0);
202#ifdef DEBUG_WII_PROTOCOL
203 if (size > 0) {
204 HIDAPI_DumpPacket("Wii packet: size = %d", ctx->m_rgucReadBuffer, size);
205 }
206#endif
207 return size;
208}
209
210static bool WriteOutput(SDL_DriverWii_Context *ctx, const Uint8 *data, int size, bool sync)
211{
212#ifdef DEBUG_WII_PROTOCOL
213 if (size > 0) {
214 HIDAPI_DumpPacket("Wii write packet: size = %d", data, size);
215 }
216#endif
217 if (sync) {
218 return SDL_hid_write(ctx->device->dev, data, size) >= 0;
219 } else {
220 // Use the rumble thread for general asynchronous writes
221 if (!SDL_HIDAPI_LockRumble()) {
222 return false;
223 }
224 return SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) >= 0;
225 }
226}
227
228static bool ReadInputSync(SDL_DriverWii_Context *ctx, EWiiInputReportIDs expectedID, bool (*isMine)(const Uint8 *))
229{
230 Uint64 endTicks = SDL_GetTicks() + 250; // Seeing successful reads after about 200 ms
231
232 int nRead = 0;
233 while ((nRead = ReadInput(ctx)) != -1) {
234 if (nRead > 0) {
235 if (ctx->m_rgucReadBuffer[0] == expectedID && (!isMine || isMine(ctx->m_rgucReadBuffer))) {
236 return true;
237 }
238 } else {
239 if (SDL_GetTicks() >= endTicks) {
240 break;
241 }
242 SDL_Delay(1);
243 }
244 }
245 SDL_SetError("Read timed out");
246 return false;
247}
248
249static bool IsWriteMemoryResponse(const Uint8 *data)
250{
251 return data[3] == k_eWiiOutputReportIDs_WriteMemory;
252}
253
254static bool WriteRegister(SDL_DriverWii_Context *ctx, Uint32 address, const Uint8 *data, int size, bool sync)
255{
256 Uint8 writeRequest[k_unWiiPacketDataLength];
257
258 SDL_zeroa(writeRequest);
259 writeRequest[0] = k_eWiiOutputReportIDs_WriteMemory;
260 writeRequest[1] = (Uint8)(0x04 | (Uint8)ctx->m_bRumbleActive);
261 writeRequest[2] = (address >> 16) & 0xff;
262 writeRequest[3] = (address >> 8) & 0xff;
263 writeRequest[4] = address & 0xff;
264 writeRequest[5] = (Uint8)size;
265 SDL_assert(size > 0 && size <= 16);
266 SDL_memcpy(writeRequest + 6, data, size);
267
268 if (!WriteOutput(ctx, writeRequest, sizeof(writeRequest), sync)) {
269 return false;
270 }
271 if (sync) {
272 // Wait for response
273 if (!ReadInputSync(ctx, k_eWiiInputReportIDs_Acknowledge, IsWriteMemoryResponse)) {
274 return false;
275 }
276 if (ctx->m_rgucReadBuffer[4]) {
277 SDL_SetError("Write memory failed: %u", ctx->m_rgucReadBuffer[4]);
278 return false;
279 }
280 }
281 return true;
282}
283
284static bool ReadRegister(SDL_DriverWii_Context *ctx, Uint32 address, int size, bool sync)
285{
286 Uint8 readRequest[7];
287
288 readRequest[0] = k_eWiiOutputReportIDs_ReadMemory;
289 readRequest[1] = (Uint8)(0x04 | (Uint8)ctx->m_bRumbleActive);
290 readRequest[2] = (address >> 16) & 0xff;
291 readRequest[3] = (address >> 8) & 0xff;
292 readRequest[4] = address & 0xff;
293 readRequest[5] = (size >> 8) & 0xff;
294 readRequest[6] = size & 0xff;
295
296 SDL_assert(size > 0 && size <= 0xffff);
297
298 if (!WriteOutput(ctx, readRequest, sizeof(readRequest), sync)) {
299 return false;
300 }
301 if (sync) {
302 SDL_assert(size <= 16); // Only waiting for one packet is supported right now
303 // Wait for response
304 if (!ReadInputSync(ctx, k_eWiiInputReportIDs_ReadMemory, NULL)) {
305 return false;
306 }
307 }
308 return true;
309}
310
311static bool SendExtensionIdentify(SDL_DriverWii_Context *ctx, bool sync)
312{
313 return ReadRegister(ctx, 0xA400FE, 2, sync);
314}
315
316static bool ParseExtensionIdentifyResponse(SDL_DriverWii_Context *ctx, Uint16 *extension)
317{
318 int i;
319
320 if (ctx->m_rgucReadBuffer[0] != k_eWiiInputReportIDs_ReadMemory) {
321 SDL_SetError("Unexpected extension response type");
322 return false;
323 }
324
325 if (ctx->m_rgucReadBuffer[4] != 0x00 || ctx->m_rgucReadBuffer[5] != 0xFE) {
326 SDL_SetError("Unexpected extension response address");
327 return false;
328 }
329
330 if (ctx->m_rgucReadBuffer[3] != 0x10) {
331 Uint8 error = (ctx->m_rgucReadBuffer[3] & 0xF);
332
333 if (error == 7) {
334 // The extension memory isn't mapped
335 *extension = WII_EXTENSION_NONE;
336 return true;
337 }
338
339 if (error) {
340 SDL_SetError("Failed to read extension type: %u", error);
341 } else {
342 SDL_SetError("Unexpected read length when reading extension type: %d", (ctx->m_rgucReadBuffer[3] >> 4) + 1);
343 }
344 return false;
345 }
346
347 *extension = 0;
348 for (i = 6; i < 8; i++) {
349 *extension = *extension << 8 | ctx->m_rgucReadBuffer[i];
350 }
351 return true;
352}
353
354static EWiiExtensionControllerType GetExtensionType(Uint16 extension_id)
355{
356 switch (extension_id) {
357 case WII_EXTENSION_NONE:
358 return k_eWiiExtensionControllerType_None;
359 case WII_EXTENSION_NUNCHUK:
360 return k_eWiiExtensionControllerType_Nunchuk;
361 case WII_EXTENSION_GAMEPAD:
362 return k_eWiiExtensionControllerType_Gamepad;
363 case WII_EXTENSION_WIIUPRO:
364 return k_eWiiExtensionControllerType_WiiUPro;
365 default:
366 return k_eWiiExtensionControllerType_Unknown;
367 }
368}
369
370static bool SendExtensionReset(SDL_DriverWii_Context *ctx, bool sync)
371{
372 bool result = true;
373 {
374 Uint8 data = 0x55;
375 result = result && WriteRegister(ctx, 0xA400F0, &data, sizeof(data), sync);
376 }
377 // This write will fail if there is no extension connected, that's fine
378 {
379 Uint8 data = 0x00;
380 (void)WriteRegister(ctx, 0xA400FB, &data, sizeof(data), sync);
381 }
382 return result;
383}
384
385static bool GetMotionPlusState(SDL_DriverWii_Context *ctx, bool *connected, Uint8 *mode)
386{
387 Uint16 extension;
388
389 if (connected) {
390 *connected = false;
391 }
392 if (mode) {
393 *mode = 0;
394 }
395
396 if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {
397 // The Wii U Pro controller never has the Motion Plus extension
398 return true;
399 }
400
401 if (SendExtensionIdentify(ctx, true) &&
402 ParseExtensionIdentifyResponse(ctx, &extension)) {
403 if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {
404 // Motion Plus is currently active
405 if (connected) {
406 *connected = true;
407 }
408 if (mode) {
409 *mode = (extension >> 8);
410 }
411 return true;
412 }
413 }
414
415 if (ReadRegister(ctx, 0xA600FE, 2, true) &&
416 ParseExtensionIdentifyResponse(ctx, &extension)) {
417 if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {
418 // Motion Plus is currently connected
419 if (connected) {
420 *connected = true;
421 }
422 }
423 return true;
424 }
425
426 // Failed to read the register or parse the response
427 return false;
428}
429
430static bool NeedsPeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx, bool status_update)
431{
432 if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {
433 // The Wii U Pro controller never has the Motion Plus extension
434 return false;
435 }
436
437 if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE && !status_update) {
438 // We'll get a status update when Motion Plus is disconnected
439 return false;
440 }
441
442 return true;
443}
444
445static void SchedulePeriodicMotionPlusCheck(SDL_DriverWii_Context *ctx)
446{
447 ctx->m_ulNextMotionPlusCheck = SDL_GetTicks() + MOTION_PLUS_UPDATE_TIME_MS;
448}
449
450static void CheckMotionPlusConnection(SDL_DriverWii_Context *ctx)
451{
452 SendExtensionIdentify(ctx, false);
453
454 ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage1;
455}
456
457static void ActivateMotionPlusWithMode(SDL_DriverWii_Context *ctx, Uint8 mode)
458{
459#ifdef SDL_PLATFORM_LINUX
460 /* Linux drivers maintain a lot of state around the Motion Plus
461 * extension, so don't mess with it here.
462 */
463#else
464 WriteRegister(ctx, 0xA600FE, &mode, sizeof(mode), true);
465
466 ctx->m_ucMotionPlusMode = mode;
467#endif // LINUX
468}
469
470static void ActivateMotionPlus(SDL_DriverWii_Context *ctx)
471{
472 Uint8 mode = WII_MOTIONPLUS_MODE_STANDARD;
473
474 // Pick the pass-through mode based on the connected controller
475 if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {
476 mode = WII_MOTIONPLUS_MODE_NUNCHUK;
477 } else if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Gamepad) {
478 mode = WII_MOTIONPLUS_MODE_GAMEPAD;
479 }
480 ActivateMotionPlusWithMode(ctx, mode);
481}
482
483static void DeactivateMotionPlus(SDL_DriverWii_Context *ctx)
484{
485 Uint8 data = 0x55;
486 WriteRegister(ctx, 0xA400F0, &data, sizeof(data), true);
487
488 // Wait for the deactivation status message
489 ReadInputSync(ctx, k_eWiiInputReportIDs_Status, NULL);
490
491 ctx->m_ucMotionPlusMode = WII_MOTIONPLUS_MODE_NONE;
492}
493
494static void UpdatePowerLevelWii(SDL_Joystick *joystick, Uint8 batteryLevelByte)
495{
496 int percent;
497 if (batteryLevelByte > 178) {
498 percent = 100;
499 } else if (batteryLevelByte > 51) {
500 percent = 70;
501 } else if (batteryLevelByte > 13) {
502 percent = 20;
503 } else {
504 percent = 5;
505 }
506 SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent);
507}
508
509static void UpdatePowerLevelWiiU(SDL_Joystick *joystick, Uint8 extensionBatteryByte)
510{
511 bool charging = !(extensionBatteryByte & 0x08);
512 bool pluggedIn = !(extensionBatteryByte & 0x04);
513 Uint8 batteryLevel = extensionBatteryByte >> 4;
514
515 if (pluggedIn) {
516 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
517 } else {
518 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
519 }
520
521 /* Not sure if all Wii U Pro controllers act like this, but on mine
522 * 4, 3, and 2 are held for about 20 hours each
523 * 1 is held for about 6 hours
524 * 0 is held for about 2 hours
525 * No value above 4 has been observed.
526 */
527 SDL_PowerState state;
528 int percent;
529 if (charging) {
530 state = SDL_POWERSTATE_CHARGING;
531 } else if (pluggedIn) {
532 state = SDL_POWERSTATE_CHARGED;
533 } else {
534 state = SDL_POWERSTATE_ON_BATTERY;
535 }
536 if (batteryLevel >= 4) {
537 percent = 100;
538 } else if (batteryLevel == 3) {
539 percent = 70;
540 } else if (batteryLevel == 2) {
541 percent = 40;
542 } else if (batteryLevel == 1) {
543 percent = 10;
544 } else {
545 percent = 3;
546 }
547 SDL_SendJoystickPowerInfo(joystick, state, percent);
548}
549
550static EWiiInputReportIDs GetButtonPacketType(SDL_DriverWii_Context *ctx)
551{
552 switch (ctx->m_eExtensionControllerType) {
553 case k_eWiiExtensionControllerType_WiiUPro:
554 return k_eWiiInputReportIDs_ButtonDataD;
555 case k_eWiiExtensionControllerType_Nunchuk:
556 case k_eWiiExtensionControllerType_Gamepad:
557 if (ctx->m_bReportSensors) {
558 return k_eWiiInputReportIDs_ButtonData5;
559 } else {
560 return k_eWiiInputReportIDs_ButtonData2;
561 }
562 default:
563 if (ctx->m_bReportSensors) {
564 return k_eWiiInputReportIDs_ButtonData5;
565 } else {
566 return k_eWiiInputReportIDs_ButtonData0;
567 }
568 }
569}
570
571static bool RequestButtonPacketType(SDL_DriverWii_Context *ctx, EWiiInputReportIDs type)
572{
573 Uint8 data[3];
574 Uint8 tt = (Uint8)ctx->m_bRumbleActive;
575
576 // Continuous reporting off, tt & 4 == 0
577 if (ENABLE_CONTINUOUS_REPORTING) {
578 tt |= 4;
579 }
580
581 data[0] = k_eWiiOutputReportIDs_DataReportingMode;
582 data[1] = tt;
583 data[2] = type;
584 return WriteOutput(ctx, data, sizeof(data), false);
585}
586
587static void ResetButtonPacketType(SDL_DriverWii_Context *ctx)
588{
589 RequestButtonPacketType(ctx, GetButtonPacketType(ctx));
590}
591
592static void InitStickCalibrationData(SDL_DriverWii_Context *ctx)
593{
594 int i;
595 switch (ctx->m_eExtensionControllerType) {
596 case k_eWiiExtensionControllerType_WiiUPro:
597 for (i = 0; i < 4; i++) {
598 ctx->m_StickCalibrationData[i].min = 1000;
599 ctx->m_StickCalibrationData[i].max = 3000;
600 ctx->m_StickCalibrationData[i].center = 0;
601 ctx->m_StickCalibrationData[i].deadzone = 100;
602 }
603 break;
604 case k_eWiiExtensionControllerType_Gamepad:
605 for (i = 0; i < 4; i++) {
606 ctx->m_StickCalibrationData[i].min = i < 2 ? 9 : 5;
607 ctx->m_StickCalibrationData[i].max = i < 2 ? 54 : 26;
608 ctx->m_StickCalibrationData[i].center = 0;
609 ctx->m_StickCalibrationData[i].deadzone = i < 2 ? 4 : 2;
610 }
611 break;
612 case k_eWiiExtensionControllerType_Nunchuk:
613 for (i = 0; i < 2; i++) {
614 ctx->m_StickCalibrationData[i].min = 40;
615 ctx->m_StickCalibrationData[i].max = 215;
616 ctx->m_StickCalibrationData[i].center = 0;
617 ctx->m_StickCalibrationData[i].deadzone = 10;
618 }
619 break;
620 default:
621 break;
622 }
623}
624
625static void InitializeExtension(SDL_DriverWii_Context *ctx)
626{
627 SendExtensionReset(ctx, true);
628 InitStickCalibrationData(ctx);
629 ResetButtonPacketType(ctx);
630}
631
632static void UpdateSlotLED(SDL_DriverWii_Context *ctx)
633{
634 Uint8 leds;
635 Uint8 data[2];
636
637 // The lowest bit needs to have the rumble status
638 leds = (Uint8)ctx->m_bRumbleActive;
639
640 if (ctx->m_bPlayerLights) {
641 // Use the same LED codes as Smash 8-player for 5-7
642 if (ctx->m_nPlayerIndex == 0 || ctx->m_nPlayerIndex > 3) {
643 leds |= k_eWiiPlayerLEDs_P1;
644 }
645 if (ctx->m_nPlayerIndex == 1 || ctx->m_nPlayerIndex == 4) {
646 leds |= k_eWiiPlayerLEDs_P2;
647 }
648 if (ctx->m_nPlayerIndex == 2 || ctx->m_nPlayerIndex == 5) {
649 leds |= k_eWiiPlayerLEDs_P3;
650 }
651 if (ctx->m_nPlayerIndex == 3 || ctx->m_nPlayerIndex == 6) {
652 leds |= k_eWiiPlayerLEDs_P4;
653 }
654 // Turn on all lights for other player indexes
655 if (ctx->m_nPlayerIndex < 0 || ctx->m_nPlayerIndex > 6) {
656 leds |= k_eWiiPlayerLEDs_P1 | k_eWiiPlayerLEDs_P2 | k_eWiiPlayerLEDs_P3 | k_eWiiPlayerLEDs_P4;
657 }
658 }
659
660 data[0] = k_eWiiOutputReportIDs_LEDs;
661 data[1] = leds;
662 WriteOutput(ctx, data, sizeof(data), false);
663}
664
665static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
666{
667 SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)userdata;
668 bool bPlayerLights = SDL_GetStringBoolean(hint, true);
669
670 if (bPlayerLights != ctx->m_bPlayerLights) {
671 ctx->m_bPlayerLights = bPlayerLights;
672
673 UpdateSlotLED(ctx);
674 }
675}
676
677static EWiiExtensionControllerType ReadExtensionControllerType(SDL_HIDAPI_Device *device)
678{
679 SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
680 EWiiExtensionControllerType eExtensionControllerType = k_eWiiExtensionControllerType_Unknown;
681 const int MAX_ATTEMPTS = 20;
682 int attempts = 0;
683
684 // Create enough of a context to read the controller type from the device
685 for (attempts = 0; attempts < MAX_ATTEMPTS; ++attempts) {
686 Uint16 extension;
687 if (SendExtensionIdentify(ctx, true) &&
688 ParseExtensionIdentifyResponse(ctx, &extension)) {
689 Uint8 motion_plus_mode = 0;
690 if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {
691 motion_plus_mode = (Uint8)(extension >> 8);
692 }
693 if (motion_plus_mode || extension == WII_EXTENSION_UNINITIALIZED) {
694 SendExtensionReset(ctx, true);
695 if (SendExtensionIdentify(ctx, true)) {
696 ParseExtensionIdentifyResponse(ctx, &extension);
697 }
698 }
699
700 eExtensionControllerType = GetExtensionType(extension);
701
702 // Reset the Motion Plus controller if needed
703 if (motion_plus_mode) {
704 ActivateMotionPlusWithMode(ctx, motion_plus_mode);
705 }
706 break;
707 }
708 }
709 return eExtensionControllerType;
710}
711
712static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
713{
714 SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
715
716 switch (ctx->m_eExtensionControllerType) {
717 case k_eWiiExtensionControllerType_None:
718 HIDAPI_SetDeviceName(device, "Nintendo Wii Remote");
719 break;
720 case k_eWiiExtensionControllerType_Nunchuk:
721 HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Nunchuk");
722 break;
723 case k_eWiiExtensionControllerType_Gamepad:
724 HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Classic Controller");
725 break;
726 case k_eWiiExtensionControllerType_WiiUPro:
727 HIDAPI_SetDeviceName(device, "Nintendo Wii U Pro Controller");
728 break;
729 default:
730 HIDAPI_SetDeviceName(device, "Nintendo Wii Remote with Unknown Extension");
731 break;
732 }
733 device->guid.data[15] = ctx->m_eExtensionControllerType;
734}
735
736static bool HIDAPI_DriverWii_InitDevice(SDL_HIDAPI_Device *device)
737{
738 SDL_DriverWii_Context *ctx;
739
740 ctx = (SDL_DriverWii_Context *)SDL_calloc(1, sizeof(*ctx));
741 if (!ctx) {
742 return false;
743 }
744 ctx->device = device;
745 device->context = ctx;
746
747 if (device->vendor_id == USB_VENDOR_NINTENDO) {
748 ctx->m_eExtensionControllerType = ReadExtensionControllerType(device);
749
750 UpdateDeviceIdentity(device);
751 }
752 return HIDAPI_JoystickConnected(device, NULL);
753}
754
755static int HIDAPI_DriverWii_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
756{
757 return -1;
758}
759
760static void HIDAPI_DriverWii_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
761{
762 SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
763
764 if (!ctx->joystick) {
765 return;
766 }
767
768 ctx->m_nPlayerIndex = player_index;
769
770 UpdateSlotLED(ctx);
771}
772
773static bool HIDAPI_DriverWii_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
774{
775 SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
776
777 SDL_AssertJoysticksLocked();
778
779 ctx->joystick = joystick;
780
781 InitializeExtension(ctx);
782
783 GetMotionPlusState(ctx, &ctx->m_bMotionPlusPresent, &ctx->m_ucMotionPlusMode);
784
785 if (NeedsPeriodicMotionPlusCheck(ctx, false)) {
786 SchedulePeriodicMotionPlusCheck(ctx);
787 }
788
789 if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None ||
790 ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {
791 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 100.0f);
792 if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_Nunchuk) {
793 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 100.0f);
794 }
795
796 if (ctx->m_bMotionPlusPresent) {
797 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 100.0f);
798 }
799 }
800
801 // Initialize player index (needed for setting LEDs)
802 ctx->m_nPlayerIndex = SDL_GetJoystickPlayerIndex(joystick);
803 ctx->m_bPlayerLights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED, true);
804 UpdateSlotLED(ctx);
805
806 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED,
807 SDL_PlayerLEDHintChanged, ctx);
808
809 // Initialize the joystick capabilities
810 if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {
811 joystick->nbuttons = 15;
812 } else {
813 // Maximum is Classic Controller + Wiimote
814 joystick->nbuttons = k_eWiiButtons_Max;
815 }
816 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
817
818 ctx->m_ulLastInput = SDL_GetTicks();
819
820 return true;
821}
822
823static bool HIDAPI_DriverWii_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
824{
825 SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
826 bool active = (low_frequency_rumble || high_frequency_rumble);
827
828 if (active != ctx->m_bRumbleActive) {
829 Uint8 data[2];
830
831 data[0] = k_eWiiOutputReportIDs_Rumble;
832 data[1] = (Uint8)active;
833 WriteOutput(ctx, data, sizeof(data), false);
834
835 ctx->m_bRumbleActive = active;
836 }
837 return true;
838}
839
840static bool HIDAPI_DriverWii_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
841{
842 return SDL_Unsupported();
843}
844
845static Uint32 HIDAPI_DriverWii_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
846{
847 return SDL_JOYSTICK_CAP_RUMBLE;
848}
849
850static bool HIDAPI_DriverWii_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
851{
852 return SDL_Unsupported();
853}
854
855static bool HIDAPI_DriverWii_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
856{
857 return SDL_Unsupported();
858}
859
860static bool HIDAPI_DriverWii_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
861{
862 SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
863
864 if (enabled != ctx->m_bReportSensors) {
865 ctx->m_bReportSensors = enabled;
866
867 if (ctx->m_bMotionPlusPresent) {
868 if (enabled) {
869 ActivateMotionPlus(ctx);
870 } else {
871 DeactivateMotionPlus(ctx);
872 }
873 }
874
875 ResetButtonPacketType(ctx);
876 }
877 return true;
878}
879
880static void PostStickCalibrated(Uint64 timestamp, SDL_Joystick *joystick, StickCalibrationData *calibration, Uint8 axis, Uint16 data)
881{
882 Sint16 value = 0;
883 if (!calibration->center) {
884 // Center on first read
885 calibration->center = data;
886 return;
887 }
888 if (data < calibration->min) {
889 calibration->min = data;
890 }
891 if (data > calibration->max) {
892 calibration->max = data;
893 }
894 if (data < calibration->center - calibration->deadzone) {
895 Uint16 zero = calibration->center - calibration->deadzone;
896 Uint16 range = zero - calibration->min;
897 Uint16 distance = zero - data;
898 float fvalue = (float)distance / (float)range;
899 value = (Sint16)(fvalue * SDL_JOYSTICK_AXIS_MIN);
900 } else if (data > calibration->center + calibration->deadzone) {
901 Uint16 zero = calibration->center + calibration->deadzone;
902 Uint16 range = calibration->max - zero;
903 Uint16 distance = data - zero;
904 float fvalue = (float)distance / (float)range;
905 value = (Sint16)(fvalue * SDL_JOYSTICK_AXIS_MAX);
906 }
907 if (axis == SDL_GAMEPAD_AXIS_LEFTY || axis == SDL_GAMEPAD_AXIS_RIGHTY) {
908 if (value) {
909 value = ~value;
910 }
911 }
912 SDL_SendJoystickAxis(timestamp, joystick, axis, value);
913}
914
915/* Send button data to SDL
916 *`defs` is a mapping for each bit to which button it represents. 0xFF indicates an unused bit
917 *`data` is the button data from the controller
918 *`size` is the number of bytes in `data` and the number of arrays of 8 mappings in `defs`
919 *`on` is the joystick value to be sent if a bit is on
920 *`off` is the joystick value to be sent if a bit is off
921 */
922static void PostPackedButtonData(Uint64 timestamp, SDL_Joystick *joystick, const Uint8 defs[][8], const Uint8 *data, int size, bool on, bool off)
923{
924 int i, j;
925
926 for (i = 0; i < size; i++) {
927 for (j = 0; j < 8; j++) {
928 Uint8 button = defs[i][j];
929 if (button != 0xFF) {
930 bool down = (data[i] >> j) & 1 ? on : off;
931 SDL_SendJoystickButton(timestamp, joystick, button, down);
932 }
933 }
934 }
935}
936
937static const Uint8 GAMEPAD_BUTTON_DEFS[3][8] = {
938 {
939 0xFF /* Unused */,
940 SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
941 SDL_GAMEPAD_BUTTON_START,
942 SDL_GAMEPAD_BUTTON_GUIDE,
943 SDL_GAMEPAD_BUTTON_BACK,
944 SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
945 SDL_GAMEPAD_BUTTON_DPAD_DOWN,
946 SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
947 },
948 {
949 SDL_GAMEPAD_BUTTON_DPAD_UP,
950 SDL_GAMEPAD_BUTTON_DPAD_LEFT,
951 0xFF /* ZR */,
952 SDL_GAMEPAD_BUTTON_NORTH,
953 SDL_GAMEPAD_BUTTON_EAST,
954 SDL_GAMEPAD_BUTTON_WEST,
955 SDL_GAMEPAD_BUTTON_SOUTH,
956 0xFF /*ZL*/,
957 },
958 {
959 SDL_GAMEPAD_BUTTON_RIGHT_STICK,
960 SDL_GAMEPAD_BUTTON_LEFT_STICK,
961 0xFF /* Charging */,
962 0xFF /* Plugged In */,
963 0xFF /* Unused */,
964 0xFF /* Unused */,
965 0xFF /* Unused */,
966 0xFF /* Unused */,
967 }
968};
969
970static const Uint8 MP_GAMEPAD_BUTTON_DEFS[3][8] = {
971 {
972 0xFF /* Unused */,
973 SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
974 SDL_GAMEPAD_BUTTON_START,
975 SDL_GAMEPAD_BUTTON_GUIDE,
976 SDL_GAMEPAD_BUTTON_BACK,
977 SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
978 SDL_GAMEPAD_BUTTON_DPAD_DOWN,
979 SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
980 },
981 {
982 0xFF /* Motion Plus data */,
983 0xFF /* Motion Plus data */,
984 0xFF /* ZR */,
985 SDL_GAMEPAD_BUTTON_NORTH,
986 SDL_GAMEPAD_BUTTON_EAST,
987 SDL_GAMEPAD_BUTTON_WEST,
988 SDL_GAMEPAD_BUTTON_SOUTH,
989 0xFF /*ZL*/,
990 },
991 {
992 SDL_GAMEPAD_BUTTON_RIGHT_STICK,
993 SDL_GAMEPAD_BUTTON_LEFT_STICK,
994 0xFF /* Charging */,
995 0xFF /* Plugged In */,
996 0xFF /* Unused */,
997 0xFF /* Unused */,
998 0xFF /* Unused */,
999 0xFF /* Unused */,
1000 }
1001};
1002
1003static const Uint8 MP_FIXUP_DPAD_BUTTON_DEFS[2][8] = {
1004 {
1005 SDL_GAMEPAD_BUTTON_DPAD_UP,
1006 0xFF,
1007 0xFF,
1008 0xFF,
1009 0xFF,
1010 0xFF,
1011 0xFF,
1012 0xFF,
1013 },
1014 {
1015 SDL_GAMEPAD_BUTTON_DPAD_LEFT,
1016 0xFF,
1017 0xFF,
1018 0xFF,
1019 0xFF,
1020 0xFF,
1021 0xFF,
1022 0xFF,
1023 }
1024};
1025
1026static void HandleWiiUProButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1027{
1028 static const Uint8 axes[] = { SDL_GAMEPAD_AXIS_LEFTX, SDL_GAMEPAD_AXIS_RIGHTX, SDL_GAMEPAD_AXIS_LEFTY, SDL_GAMEPAD_AXIS_RIGHTY };
1029 const Uint8(*buttons)[8] = GAMEPAD_BUTTON_DEFS;
1030 Uint8 zl, zr;
1031 int i;
1032
1033 if (data->ucNExtensionBytes < 11) {
1034 return;
1035 }
1036
1037 // Buttons
1038 PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucExtension + 8, 3, false, true);
1039
1040 // Triggers
1041 zl = data->rgucExtension[9] & 0x80;
1042 zr = data->rgucExtension[9] & 0x04;
1043 SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, zl ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);
1044 SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, zr ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);
1045
1046 // Sticks
1047 for (i = 0; i < 4; i++) {
1048 Uint16 value = data->rgucExtension[i * 2] | (data->rgucExtension[i * 2 + 1] << 8);
1049 PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[i], axes[i], value);
1050 }
1051
1052 // Power
1053 UpdatePowerLevelWiiU(joystick, data->rgucExtension[10]);
1054}
1055
1056static void HandleGamepadControllerButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1057{
1058 const Uint8(*buttons)[8] = (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) ? MP_GAMEPAD_BUTTON_DEFS : GAMEPAD_BUTTON_DEFS;
1059 Uint8 lx, ly, rx, ry, zl, zr;
1060
1061 if (data->ucNExtensionBytes < 6) {
1062 return;
1063 }
1064
1065 // Buttons
1066 PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucExtension + 4, 2, false, true);
1067 if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) {
1068 PostPackedButtonData(ctx->timestamp, joystick, MP_FIXUP_DPAD_BUTTON_DEFS, data->rgucExtension, 2, false, true);
1069 }
1070
1071 // Triggers
1072 zl = data->rgucExtension[5] & 0x80;
1073 zr = data->rgucExtension[5] & 0x04;
1074 SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, zl ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);
1075 SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, zr ? SDL_JOYSTICK_AXIS_MIN : SDL_JOYSTICK_AXIS_MAX);
1076
1077 // Sticks
1078 if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_GAMEPAD) {
1079 lx = data->rgucExtension[0] & 0x3E;
1080 ly = data->rgucExtension[1] & 0x3E;
1081 } else {
1082 lx = data->rgucExtension[0] & 0x3F;
1083 ly = data->rgucExtension[1] & 0x3F;
1084 }
1085 rx = (data->rgucExtension[2] >> 7) | ((data->rgucExtension[1] >> 5) & 0x06) | ((data->rgucExtension[0] >> 3) & 0x18);
1086 ry = data->rgucExtension[2] & 0x1F;
1087 PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[0], SDL_GAMEPAD_AXIS_LEFTX, lx);
1088 PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[1], SDL_GAMEPAD_AXIS_LEFTY, ly);
1089 PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[2], SDL_GAMEPAD_AXIS_RIGHTX, rx);
1090 PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[3], SDL_GAMEPAD_AXIS_RIGHTY, ry);
1091}
1092
1093static void HandleWiiRemoteButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1094{
1095 static const Uint8 buttons[2][8] = {
1096 {
1097 k_eWiiButtons_DPad_Left,
1098 k_eWiiButtons_DPad_Right,
1099 k_eWiiButtons_DPad_Down,
1100 k_eWiiButtons_DPad_Up,
1101 k_eWiiButtons_Plus,
1102 0xFF /* Unused */,
1103 0xFF /* Unused */,
1104 0xFF /* Unused */,
1105 },
1106 {
1107 k_eWiiButtons_Two,
1108 k_eWiiButtons_One,
1109 k_eWiiButtons_B,
1110 k_eWiiButtons_A,
1111 k_eWiiButtons_Minus,
1112 0xFF /* Unused */,
1113 0xFF /* Unused */,
1114 k_eWiiButtons_Home,
1115 }
1116 };
1117 if (data->hasBaseButtons) {
1118 PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucBaseButtons, 2, true, false);
1119 }
1120}
1121
1122static void HandleWiiRemoteButtonDataAsMainController(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1123{
1124 /* Wii remote maps really badly to a normal controller
1125 * Mapped 1 and 2 as X and Y
1126 * Not going to attempt positional mapping
1127 */
1128 static const Uint8 buttons[2][8] = {
1129 {
1130 SDL_GAMEPAD_BUTTON_DPAD_LEFT,
1131 SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
1132 SDL_GAMEPAD_BUTTON_DPAD_DOWN,
1133 SDL_GAMEPAD_BUTTON_DPAD_UP,
1134 SDL_GAMEPAD_BUTTON_START,
1135 0xFF /* Unused */,
1136 0xFF /* Unused */,
1137 0xFF /* Unused */,
1138 },
1139 {
1140 SDL_GAMEPAD_BUTTON_NORTH,
1141 SDL_GAMEPAD_BUTTON_WEST,
1142 SDL_GAMEPAD_BUTTON_SOUTH,
1143 SDL_GAMEPAD_BUTTON_EAST,
1144 SDL_GAMEPAD_BUTTON_BACK,
1145 0xFF /* Unused */,
1146 0xFF /* Unused */,
1147 SDL_GAMEPAD_BUTTON_GUIDE,
1148 }
1149 };
1150 if (data->hasBaseButtons) {
1151 PostPackedButtonData(ctx->timestamp, joystick, buttons, data->rgucBaseButtons, 2, true, false);
1152 }
1153}
1154
1155static void HandleNunchuckButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1156{
1157 bool c_button, z_button;
1158
1159 if (data->ucNExtensionBytes < 6) {
1160 return;
1161 }
1162
1163 if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_NUNCHUK) {
1164 c_button = (data->rgucExtension[5] & 0x08) ? false : true;
1165 z_button = (data->rgucExtension[5] & 0x04) ? false : true;
1166 } else {
1167 c_button = (data->rgucExtension[5] & 0x02) ? false : true;
1168 z_button = (data->rgucExtension[5] & 0x01) ? false : true;
1169 }
1170 SDL_SendJoystickButton(ctx->timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, c_button);
1171 SDL_SendJoystickAxis(ctx->timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, z_button ? SDL_JOYSTICK_AXIS_MAX : SDL_JOYSTICK_AXIS_MIN);
1172 PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[0], SDL_GAMEPAD_AXIS_LEFTX, data->rgucExtension[0]);
1173 PostStickCalibrated(ctx->timestamp, joystick, &ctx->m_StickCalibrationData[1], SDL_GAMEPAD_AXIS_LEFTY, data->rgucExtension[1]);
1174
1175 if (ctx->m_bReportSensors) {
1176 const float ACCEL_RES_PER_G = 200.0f;
1177 Sint16 x, y, z;
1178 float values[3];
1179
1180 x = (data->rgucExtension[2] << 2);
1181 y = (data->rgucExtension[3] << 2);
1182 z = (data->rgucExtension[4] << 2);
1183
1184 if (ctx->m_ucMotionPlusMode == WII_MOTIONPLUS_MODE_NUNCHUK) {
1185 x |= ((data->rgucExtension[5] >> 3) & 0x02);
1186 y |= ((data->rgucExtension[5] >> 4) & 0x02);
1187 z &= ~0x04;
1188 z |= ((data->rgucExtension[5] >> 5) & 0x06);
1189 } else {
1190 x |= ((data->rgucExtension[5] >> 2) & 0x03);
1191 y |= ((data->rgucExtension[5] >> 4) & 0x03);
1192 z |= ((data->rgucExtension[5] >> 6) & 0x03);
1193 }
1194
1195 x -= 0x200;
1196 y -= 0x200;
1197 z -= 0x200;
1198
1199 values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1200 values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1201 values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1202 SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_ACCEL_L, ctx->timestamp, values, 3);
1203 }
1204}
1205
1206static void HandleMotionPlusData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1207{
1208 if (ctx->m_bReportSensors) {
1209 const float GYRO_RES_PER_DEGREE = 8192.0f;
1210 int x, y, z;
1211 float values[3];
1212
1213 x = (data->rgucExtension[0] | ((data->rgucExtension[3] << 6) & 0xFF00)) - 8192;
1214 y = (data->rgucExtension[1] | ((data->rgucExtension[4] << 6) & 0xFF00)) - 8192;
1215 z = (data->rgucExtension[2] | ((data->rgucExtension[5] << 6) & 0xFF00)) - 8192;
1216
1217 if (data->rgucExtension[3] & 0x02) {
1218 // Slow rotation rate: 8192/440 units per deg/s
1219 x *= 440;
1220 } else {
1221 // Fast rotation rate: 8192/2000 units per deg/s
1222 x *= 2000;
1223 }
1224 if (data->rgucExtension[4] & 0x02) {
1225 // Slow rotation rate: 8192/440 units per deg/s
1226 y *= 440;
1227 } else {
1228 // Fast rotation rate: 8192/2000 units per deg/s
1229 y *= 2000;
1230 }
1231 if (data->rgucExtension[3] & 0x01) {
1232 // Slow rotation rate: 8192/440 units per deg/s
1233 z *= 440;
1234 } else {
1235 // Fast rotation rate: 8192/2000 units per deg/s
1236 z *= 2000;
1237 }
1238
1239 values[0] = -((float)z / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;
1240 values[1] = ((float)x / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;
1241 values[2] = ((float)y / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;
1242 SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_GYRO, ctx->timestamp, values, 3);
1243 }
1244}
1245
1246static void HandleWiiRemoteAccelData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, const WiiButtonData *data)
1247{
1248 const float ACCEL_RES_PER_G = 100.0f;
1249 Sint16 x, y, z;
1250 float values[3];
1251
1252 if (!ctx->m_bReportSensors) {
1253 return;
1254 }
1255
1256 x = ((data->rgucAccelerometer[0] << 2) | ((data->rgucBaseButtons[0] >> 5) & 0x03)) - 0x200;
1257 y = ((data->rgucAccelerometer[1] << 2) | ((data->rgucBaseButtons[1] >> 4) & 0x02)) - 0x200;
1258 z = ((data->rgucAccelerometer[2] << 2) | ((data->rgucBaseButtons[1] >> 5) & 0x02)) - 0x200;
1259
1260 values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1261 values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1262 values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
1263 SDL_SendJoystickSensor(ctx->timestamp, joystick, SDL_SENSOR_ACCEL, ctx->timestamp, values, 3);
1264}
1265
1266static void HandleButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, WiiButtonData *data)
1267{
1268 if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_WiiUPro) {
1269 HandleWiiUProButtonData(ctx, joystick, data);
1270 return;
1271 }
1272
1273 if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE &&
1274 data->ucNExtensionBytes > 5) {
1275 if (data->rgucExtension[5] & 0x01) {
1276 // The data is invalid, possibly during a hotplug
1277 return;
1278 }
1279
1280 if (data->rgucExtension[4] & 0x01) {
1281 if (ctx->m_eExtensionControllerType == k_eWiiExtensionControllerType_None) {
1282 // Something was plugged into the extension port, reinitialize to get new state
1283 ctx->m_bDisconnected = true;
1284 }
1285 } else {
1286 if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None) {
1287 // Something was removed from the extension port, reinitialize to get new state
1288 ctx->m_bDisconnected = true;
1289 }
1290 }
1291
1292 if (data->rgucExtension[5] & 0x02) {
1293 HandleMotionPlusData(ctx, joystick, data);
1294
1295 // The extension data is consumed
1296 data->ucNExtensionBytes = 0;
1297 }
1298 }
1299
1300 HandleWiiRemoteButtonData(ctx, joystick, data);
1301 switch (ctx->m_eExtensionControllerType) {
1302 case k_eWiiExtensionControllerType_Nunchuk:
1303 HandleNunchuckButtonData(ctx, joystick, data);
1304 SDL_FALLTHROUGH;
1305 case k_eWiiExtensionControllerType_None:
1306 HandleWiiRemoteButtonDataAsMainController(ctx, joystick, data);
1307 break;
1308 case k_eWiiExtensionControllerType_Gamepad:
1309 HandleGamepadControllerButtonData(ctx, joystick, data);
1310 break;
1311 default:
1312 break;
1313 }
1314 HandleWiiRemoteAccelData(ctx, joystick, data);
1315}
1316
1317static void GetBaseButtons(WiiButtonData *dst, const Uint8 *src)
1318{
1319 SDL_memcpy(dst->rgucBaseButtons, src, 2);
1320 dst->hasBaseButtons = true;
1321}
1322
1323static void GetAccelerometer(WiiButtonData *dst, const Uint8 *src)
1324{
1325 SDL_memcpy(dst->rgucAccelerometer, src, 3);
1326 dst->hasAccelerometer = true;
1327}
1328
1329static void GetExtensionData(WiiButtonData *dst, const Uint8 *src, int size)
1330{
1331 bool valid_data = false;
1332 int i;
1333
1334 if (size > sizeof(dst->rgucExtension)) {
1335 size = sizeof(dst->rgucExtension);
1336 }
1337
1338 for (i = 0; i < size; ++i) {
1339 if (src[i] != 0xFF) {
1340 valid_data = true;
1341 break;
1342 }
1343 }
1344 if (valid_data) {
1345 SDL_memcpy(dst->rgucExtension, src, size);
1346 dst->ucNExtensionBytes = (Uint8)size;
1347 }
1348}
1349
1350static void HandleStatus(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
1351{
1352 bool hadExtension = ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_None;
1353 bool hasExtension = (ctx->m_rgucReadBuffer[3] & 2) ? true : false;
1354 WiiButtonData data;
1355 SDL_zero(data);
1356 GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1357 HandleButtonData(ctx, joystick, &data);
1358
1359 if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_WiiUPro) {
1360 // Wii U has separate battery level tracking
1361 UpdatePowerLevelWii(joystick, ctx->m_rgucReadBuffer[6]);
1362 }
1363
1364 // The report data format has been reset, need to update it
1365 ResetButtonPacketType(ctx);
1366
1367 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Status update, extension %s", hasExtension ? "CONNECTED" : "DISCONNECTED");
1368
1369 /* When Motion Plus is active, we get extension connect/disconnect status
1370 * through the Motion Plus packets. Otherwise we can use the status here.
1371 */
1372 if (ctx->m_ucMotionPlusMode != WII_MOTIONPLUS_MODE_NONE) {
1373 /* Check to make sure the Motion Plus extension state hasn't changed,
1374 * otherwise we'll get extension connect/disconnect status through
1375 * Motion Plus packets.
1376 */
1377 if (NeedsPeriodicMotionPlusCheck(ctx, true)) {
1378 ctx->m_ulNextMotionPlusCheck = SDL_GetTicks();
1379 }
1380
1381 } else if (hadExtension != hasExtension) {
1382 // Reinitialize to get new state
1383 ctx->m_bDisconnected = true;
1384 }
1385}
1386
1387static void HandleResponse(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
1388{
1389 EWiiInputReportIDs type = (EWiiInputReportIDs)ctx->m_rgucReadBuffer[0];
1390 WiiButtonData data;
1391 SDL_assert(type == k_eWiiInputReportIDs_Acknowledge || type == k_eWiiInputReportIDs_ReadMemory);
1392 SDL_zero(data);
1393 GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1394 HandleButtonData(ctx, joystick, &data);
1395
1396 switch (ctx->m_eCommState) {
1397 case k_eWiiCommunicationState_None:
1398 break;
1399
1400 case k_eWiiCommunicationState_CheckMotionPlusStage1:
1401 case k_eWiiCommunicationState_CheckMotionPlusStage2:
1402 {
1403 Uint16 extension = 0;
1404 if (ParseExtensionIdentifyResponse(ctx, &extension)) {
1405 if ((extension & WII_EXTENSION_MOTIONPLUS_MASK) == WII_EXTENSION_MOTIONPLUS_ID) {
1406 // Motion Plus is currently active
1407 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Motion Plus CONNECTED (stage %d)", ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1 ? 1 : 2);
1408
1409 if (!ctx->m_bMotionPlusPresent) {
1410 // Reinitialize to get new sensor availability
1411 ctx->m_bDisconnected = true;
1412 }
1413 ctx->m_eCommState = k_eWiiCommunicationState_None;
1414
1415 } else if (ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1) {
1416 // Check to see if Motion Plus is present
1417 ReadRegister(ctx, 0xA600FE, 2, false);
1418
1419 ctx->m_eCommState = k_eWiiCommunicationState_CheckMotionPlusStage2;
1420
1421 } else {
1422 // Motion Plus is not present
1423 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Motion Plus DISCONNECTED (stage %d)", ctx->m_eCommState == k_eWiiCommunicationState_CheckMotionPlusStage1 ? 1 : 2);
1424
1425 if (ctx->m_bMotionPlusPresent) {
1426 // Reinitialize to get new sensor availability
1427 ctx->m_bDisconnected = true;
1428 }
1429 ctx->m_eCommState = k_eWiiCommunicationState_None;
1430 }
1431 }
1432 } break;
1433 default:
1434 // Should never happen
1435 break;
1436 }
1437}
1438
1439static void HandleButtonPacket(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
1440{
1441 EWiiInputReportIDs eExpectedReport = GetButtonPacketType(ctx);
1442 WiiButtonData data;
1443
1444 // FIXME: This should see if the data format is compatible rather than equal
1445 if (eExpectedReport != ctx->m_rgucReadBuffer[0]) {
1446 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Resetting report mode to %d", eExpectedReport);
1447 RequestButtonPacketType(ctx, eExpectedReport);
1448 }
1449
1450 // IR camera data is not supported
1451 SDL_zero(data);
1452 switch (ctx->m_rgucReadBuffer[0]) {
1453 case k_eWiiInputReportIDs_ButtonData0: // 30 BB BB
1454 GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1455 break;
1456 case k_eWiiInputReportIDs_ButtonData1: // 31 BB BB AA AA AA
1457 case k_eWiiInputReportIDs_ButtonData3: // 33 BB BB AA AA AA II II II II II II II II II II II II
1458 GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1459 GetAccelerometer(&data, ctx->m_rgucReadBuffer + 3);
1460 break;
1461 case k_eWiiInputReportIDs_ButtonData2: // 32 BB BB EE EE EE EE EE EE EE EE
1462 GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1463 GetExtensionData(&data, ctx->m_rgucReadBuffer + 3, 8);
1464 break;
1465 case k_eWiiInputReportIDs_ButtonData4: // 34 BB BB EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE
1466 GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1467 GetExtensionData(&data, ctx->m_rgucReadBuffer + 3, 19);
1468 break;
1469 case k_eWiiInputReportIDs_ButtonData5: // 35 BB BB AA AA AA EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE
1470 GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1471 GetAccelerometer(&data, ctx->m_rgucReadBuffer + 3);
1472 GetExtensionData(&data, ctx->m_rgucReadBuffer + 6, 16);
1473 break;
1474 case k_eWiiInputReportIDs_ButtonData6: // 36 BB BB II II II II II II II II II II EE EE EE EE EE EE EE EE EE
1475 GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1476 GetExtensionData(&data, ctx->m_rgucReadBuffer + 13, 9);
1477 break;
1478 case k_eWiiInputReportIDs_ButtonData7: // 37 BB BB AA AA AA II II II II II II II II II II EE EE EE EE EE EE
1479 GetBaseButtons(&data, ctx->m_rgucReadBuffer + 1);
1480 GetExtensionData(&data, ctx->m_rgucReadBuffer + 16, 6);
1481 break;
1482 case k_eWiiInputReportIDs_ButtonDataD: // 3d EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE
1483 GetExtensionData(&data, ctx->m_rgucReadBuffer + 1, 21);
1484 break;
1485 case k_eWiiInputReportIDs_ButtonDataE:
1486 case k_eWiiInputReportIDs_ButtonDataF:
1487 default:
1488 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Unsupported button data type %02x", ctx->m_rgucReadBuffer[0]);
1489 return;
1490 }
1491 HandleButtonData(ctx, joystick, &data);
1492}
1493
1494static void HandleInput(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick)
1495{
1496 EWiiInputReportIDs type = (EWiiInputReportIDs)ctx->m_rgucReadBuffer[0];
1497
1498 // Set up for handling input
1499 ctx->timestamp = SDL_GetTicksNS();
1500
1501 if (type == k_eWiiInputReportIDs_Status) {
1502 HandleStatus(ctx, joystick);
1503 } else if (type == k_eWiiInputReportIDs_Acknowledge || type == k_eWiiInputReportIDs_ReadMemory) {
1504 HandleResponse(ctx, joystick);
1505 } else if (type >= k_eWiiInputReportIDs_ButtonData0 && type <= k_eWiiInputReportIDs_ButtonDataF) {
1506 HandleButtonPacket(ctx, joystick);
1507 } else {
1508 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Unexpected input packet of type %x", type);
1509 }
1510}
1511
1512static bool HIDAPI_DriverWii_UpdateDevice(SDL_HIDAPI_Device *device)
1513{
1514 SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
1515 SDL_Joystick *joystick = NULL;
1516 int size;
1517 Uint64 now;
1518
1519 if (device->num_joysticks > 0) {
1520 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1521 } else {
1522 return false;
1523 }
1524
1525 now = SDL_GetTicks();
1526
1527 while ((size = ReadInput(ctx)) > 0) {
1528 if (joystick) {
1529 HandleInput(ctx, joystick);
1530 }
1531 ctx->m_ulLastInput = now;
1532 }
1533
1534 /* Check to see if we've lost connection to the controller.
1535 * We have continuous reporting enabled, so this should be reliable now.
1536 */
1537 {
1538 SDL_COMPILE_TIME_ASSERT(ENABLE_CONTINUOUS_REPORTING, ENABLE_CONTINUOUS_REPORTING);
1539 }
1540 if (now >= (ctx->m_ulLastInput + INPUT_WAIT_TIMEOUT_MS)) {
1541 // Bluetooth may have disconnected, try reopening the controller
1542 size = -1;
1543 }
1544
1545 if (joystick) {
1546 // These checks aren't needed on the Wii U Pro Controller
1547 if (ctx->m_eExtensionControllerType != k_eWiiExtensionControllerType_WiiUPro) {
1548
1549 // Check to see if the Motion Plus extension status has changed
1550 if (ctx->m_ulNextMotionPlusCheck && now >= ctx->m_ulNextMotionPlusCheck) {
1551 CheckMotionPlusConnection(ctx);
1552 if (NeedsPeriodicMotionPlusCheck(ctx, false)) {
1553 SchedulePeriodicMotionPlusCheck(ctx);
1554 } else {
1555 ctx->m_ulNextMotionPlusCheck = 0;
1556 }
1557 }
1558
1559 // Request a status update periodically to make sure our battery value is up to date
1560 if (!ctx->m_ulLastStatus || now >= (ctx->m_ulLastStatus + STATUS_UPDATE_TIME_MS)) {
1561 Uint8 data[2];
1562
1563 data[0] = k_eWiiOutputReportIDs_StatusRequest;
1564 data[1] = (Uint8)ctx->m_bRumbleActive;
1565 WriteOutput(ctx, data, sizeof(data), false);
1566
1567 ctx->m_ulLastStatus = now;
1568 }
1569 }
1570 }
1571
1572 if (size < 0 || ctx->m_bDisconnected) {
1573 // Read error, device is disconnected
1574 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1575 }
1576 return (size >= 0);
1577}
1578
1579static void HIDAPI_DriverWii_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1580{
1581 SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
1582
1583 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED,
1584 SDL_PlayerLEDHintChanged, ctx);
1585
1586 ctx->joystick = NULL;
1587}
1588
1589static void HIDAPI_DriverWii_FreeDevice(SDL_HIDAPI_Device *device)
1590{
1591}
1592
1593SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii = {
1594 SDL_HINT_JOYSTICK_HIDAPI_WII,
1595 true,
1596 HIDAPI_DriverWii_RegisterHints,
1597 HIDAPI_DriverWii_UnregisterHints,
1598 HIDAPI_DriverWii_IsEnabled,
1599 HIDAPI_DriverWii_IsSupportedDevice,
1600 HIDAPI_DriverWii_InitDevice,
1601 HIDAPI_DriverWii_GetDevicePlayerIndex,
1602 HIDAPI_DriverWii_SetDevicePlayerIndex,
1603 HIDAPI_DriverWii_UpdateDevice,
1604 HIDAPI_DriverWii_OpenJoystick,
1605 HIDAPI_DriverWii_RumbleJoystick,
1606 HIDAPI_DriverWii_RumbleJoystickTriggers,
1607 HIDAPI_DriverWii_GetJoystickCapabilities,
1608 HIDAPI_DriverWii_SetJoystickLED,
1609 HIDAPI_DriverWii_SendJoystickEffect,
1610 HIDAPI_DriverWii_SetJoystickSensorsEnabled,
1611 HIDAPI_DriverWii_CloseJoystick,
1612 HIDAPI_DriverWii_FreeDevice,
1613};
1614
1615#endif // SDL_JOYSTICK_HIDAPI_WII
1616
1617#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360.c
new file mode 100644
index 0000000..49be08a
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360.c
@@ -0,0 +1,379 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28#include "SDL_hidapi_rumble.h"
29
30#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
31
32// Define this if you want to log all packets from the controller
33// #define DEBUG_XBOX_PROTOCOL
34
35typedef struct
36{
37 SDL_HIDAPI_Device *device;
38 SDL_Joystick *joystick;
39 int player_index;
40 bool player_lights;
41 Uint8 last_state[USB_PACKET_LENGTH];
42} SDL_DriverXbox360_Context;
43
44static void HIDAPI_DriverXbox360_RegisterHints(SDL_HintCallback callback, void *userdata)
45{
46 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
47 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
48}
49
50static void HIDAPI_DriverXbox360_UnregisterHints(SDL_HintCallback callback, void *userdata)
51{
52 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
53 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
54}
55
56static bool HIDAPI_DriverXbox360_IsEnabled(void)
57{
58 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,
59 SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)));
60}
61
62static bool HIDAPI_DriverXbox360_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
63{
64 const int XB360W_IFACE_PROTOCOL = 129; // Wireless
65
66 if (vendor_id == USB_VENDOR_ASTRO && product_id == USB_PRODUCT_ASTRO_C40_XBOX360) {
67 // This is the ASTRO C40 in Xbox 360 mode
68 return true;
69 }
70 if (vendor_id == USB_VENDOR_NVIDIA) {
71 // This is the NVIDIA Shield controller which doesn't talk Xbox controller protocol
72 return false;
73 }
74 if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER)) ||
75 (type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) {
76 // This is the wireless dongle, which talks a different protocol
77 return false;
78 }
79 if (interface_number > 0) {
80 // This is the chatpad or other input interface, not the Xbox 360 interface
81 return false;
82 }
83#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI)
84 if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) {
85 // GCController support doesn't work with the Steam Virtual Gamepad
86 return true;
87 } else {
88 // On macOS you can't write output reports to wired XBox controllers,
89 // so we'll just use the GCController support instead.
90 return false;
91 }
92#else
93 return (type == SDL_GAMEPAD_TYPE_XBOX360);
94#endif
95}
96
97static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on)
98{
99 const bool blink = false;
100 Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;
101 Uint8 led_packet[] = { 0x01, 0x03, 0x00 };
102
103 led_packet[2] = mode;
104 if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {
105 return false;
106 }
107 return true;
108}
109
110static void UpdateSlotLED(SDL_DriverXbox360_Context *ctx)
111{
112 if (ctx->player_lights && ctx->player_index >= 0) {
113 SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true);
114 } else {
115 SetSlotLED(ctx->device->dev, 0, false);
116 }
117}
118
119static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
120{
121 SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)userdata;
122 bool player_lights = SDL_GetStringBoolean(hint, true);
123
124 if (player_lights != ctx->player_lights) {
125 ctx->player_lights = player_lights;
126
127 UpdateSlotLED(ctx);
128 HIDAPI_UpdateDeviceProperties(ctx->device);
129 }
130}
131
132static bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device)
133{
134 SDL_DriverXbox360_Context *ctx;
135
136 ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx));
137 if (!ctx) {
138 return false;
139 }
140 ctx->device = device;
141
142 device->context = ctx;
143
144 device->type = SDL_GAMEPAD_TYPE_XBOX360;
145
146 if (SDL_IsJoystickSteamVirtualGamepad(device->vendor_id, device->product_id, device->version) &&
147 device->product_string && SDL_strncmp(device->product_string, "GamePad-", 8) == 0) {
148 int slot = 0;
149 SDL_sscanf(device->product_string, "GamePad-%d", &slot);
150 device->steam_virtual_gamepad_slot = (slot - 1);
151 }
152
153 return HIDAPI_JoystickConnected(device, NULL);
154}
155
156static int HIDAPI_DriverXbox360_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
157{
158 return -1;
159}
160
161static void HIDAPI_DriverXbox360_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
162{
163 SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
164
165 if (!ctx->joystick) {
166 return;
167 }
168
169 ctx->player_index = player_index;
170
171 UpdateSlotLED(ctx);
172}
173
174static bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
175{
176 SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
177
178 SDL_AssertJoysticksLocked();
179
180 ctx->joystick = joystick;
181 SDL_zeroa(ctx->last_state);
182
183 // Initialize player index (needed for setting LEDs)
184 ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
185 ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true);
186 UpdateSlotLED(ctx);
187
188 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
189 SDL_PlayerLEDHintChanged, ctx);
190
191 // Initialize the joystick capabilities
192 joystick->nbuttons = 11;
193 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
194 joystick->nhats = 1;
195
196 return true;
197}
198
199static bool HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
200{
201 Uint8 rumble_packet[] = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
202
203 rumble_packet[3] = (low_frequency_rumble >> 8);
204 rumble_packet[4] = (high_frequency_rumble >> 8);
205
206 if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
207 return SDL_SetError("Couldn't send rumble packet");
208 }
209 return true;
210}
211
212static bool HIDAPI_DriverXbox360_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
213{
214 return SDL_Unsupported();
215}
216
217static Uint32 HIDAPI_DriverXbox360_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
218{
219 SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
220 Uint32 result = SDL_JOYSTICK_CAP_RUMBLE;
221
222 if (ctx->player_lights) {
223 result |= SDL_JOYSTICK_CAP_PLAYER_LED;
224 }
225 return result;
226}
227
228static bool HIDAPI_DriverXbox360_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
229{
230 return SDL_Unsupported();
231}
232
233static bool HIDAPI_DriverXbox360_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
234{
235 return SDL_Unsupported();
236}
237
238static bool HIDAPI_DriverXbox360_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
239{
240 return SDL_Unsupported();
241}
242
243static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size)
244{
245 Sint16 axis;
246#ifdef SDL_PLATFORM_MACOS
247 const bool invert_y_axes = false;
248#else
249 const bool invert_y_axes = true;
250#endif
251 Uint64 timestamp = SDL_GetTicksNS();
252
253 if (ctx->last_state[2] != data[2]) {
254 Uint8 hat = 0;
255
256 if (data[2] & 0x01) {
257 hat |= SDL_HAT_UP;
258 }
259 if (data[2] & 0x02) {
260 hat |= SDL_HAT_DOWN;
261 }
262 if (data[2] & 0x04) {
263 hat |= SDL_HAT_LEFT;
264 }
265 if (data[2] & 0x08) {
266 hat |= SDL_HAT_RIGHT;
267 }
268 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
269
270 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0));
271 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0));
272 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0));
273 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));
274 }
275
276 if (ctx->last_state[3] != data[3]) {
277 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0));
278 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));
279 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0));
280 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0));
281 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));
282 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0));
283 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0));
284 }
285
286 axis = ((int)data[4] * 257) - 32768;
287 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
288 axis = ((int)data[5] * 257) - 32768;
289 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
290 axis = SDL_Swap16LE(*(Sint16 *)(&data[6]));
291 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
292 axis = SDL_Swap16LE(*(Sint16 *)(&data[8]));
293 if (invert_y_axes) {
294 axis = ~axis;
295 }
296 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
297 axis = SDL_Swap16LE(*(Sint16 *)(&data[10]));
298 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
299 axis = SDL_Swap16LE(*(Sint16 *)(&data[12]));
300 if (invert_y_axes) {
301 axis = ~axis;
302 }
303 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
304
305 SDL_memcpy(ctx->last_state, data, SDL_min((size_t)size, sizeof(ctx->last_state)));
306}
307
308static bool HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device)
309{
310 SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
311 SDL_Joystick *joystick = NULL;
312 Uint8 data[USB_PACKET_LENGTH];
313 int size = 0;
314
315 if (device->num_joysticks > 0) {
316 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
317 } else {
318 return false;
319 }
320
321 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
322#ifdef DEBUG_XBOX_PROTOCOL
323 HIDAPI_DumpPacket("Xbox 360 packet: size = %d", data, size);
324#endif
325 if (!joystick) {
326 continue;
327 }
328
329 if (data[0] == 0x00) {
330 HIDAPI_DriverXbox360_HandleStatePacket(joystick, ctx, data, size);
331 }
332 }
333
334 if (size < 0) {
335 // Read error, device is disconnected
336 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
337 }
338 return (size >= 0);
339}
340
341static void HIDAPI_DriverXbox360_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
342{
343 SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
344
345 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
346 SDL_PlayerLEDHintChanged, ctx);
347
348 ctx->joystick = NULL;
349}
350
351static void HIDAPI_DriverXbox360_FreeDevice(SDL_HIDAPI_Device *device)
352{
353}
354
355SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = {
356 SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,
357 true,
358 HIDAPI_DriverXbox360_RegisterHints,
359 HIDAPI_DriverXbox360_UnregisterHints,
360 HIDAPI_DriverXbox360_IsEnabled,
361 HIDAPI_DriverXbox360_IsSupportedDevice,
362 HIDAPI_DriverXbox360_InitDevice,
363 HIDAPI_DriverXbox360_GetDevicePlayerIndex,
364 HIDAPI_DriverXbox360_SetDevicePlayerIndex,
365 HIDAPI_DriverXbox360_UpdateDevice,
366 HIDAPI_DriverXbox360_OpenJoystick,
367 HIDAPI_DriverXbox360_RumbleJoystick,
368 HIDAPI_DriverXbox360_RumbleJoystickTriggers,
369 HIDAPI_DriverXbox360_GetJoystickCapabilities,
370 HIDAPI_DriverXbox360_SetJoystickLED,
371 HIDAPI_DriverXbox360_SendJoystickEffect,
372 HIDAPI_DriverXbox360_SetJoystickSensorsEnabled,
373 HIDAPI_DriverXbox360_CloseJoystick,
374 HIDAPI_DriverXbox360_FreeDevice,
375};
376
377#endif // SDL_JOYSTICK_HIDAPI_XBOX360
378
379#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360w.c
new file mode 100644
index 0000000..bf63707
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xbox360w.c
@@ -0,0 +1,388 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28#include "SDL_hidapi_rumble.h"
29
30#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
31
32// Define this if you want to log all packets from the controller
33// #define DEBUG_XBOX_PROTOCOL
34
35typedef struct
36{
37 SDL_HIDAPI_Device *device;
38 bool connected;
39 int player_index;
40 bool player_lights;
41 Uint8 last_state[USB_PACKET_LENGTH];
42} SDL_DriverXbox360W_Context;
43
44static void HIDAPI_DriverXbox360W_RegisterHints(SDL_HintCallback callback, void *userdata)
45{
46 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
47 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
48 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata);
49}
50
51static void HIDAPI_DriverXbox360W_UnregisterHints(SDL_HintCallback callback, void *userdata)
52{
53 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
54 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
55 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata);
56}
57
58static bool HIDAPI_DriverXbox360W_IsEnabled(void)
59{
60 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS,
61 SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))));
62}
63
64static bool HIDAPI_DriverXbox360W_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
65{
66 const int XB360W_IFACE_PROTOCOL = 129; // Wireless
67
68 if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY1 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER) && interface_protocol == 0) ||
69 (type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) {
70 return true;
71 }
72 return false;
73}
74
75static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on)
76{
77 const bool blink = false;
78 Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;
79 Uint8 led_packet[] = { 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
80
81 led_packet[3] = 0x40 + (mode % 0x0e);
82 if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {
83 return false;
84 }
85 return true;
86}
87
88static void UpdateSlotLED(SDL_DriverXbox360W_Context *ctx)
89{
90 if (ctx->player_lights && ctx->player_index >= 0) {
91 SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true);
92 } else {
93 SetSlotLED(ctx->device->dev, 0, false);
94 }
95}
96
97static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
98{
99 SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)userdata;
100 bool player_lights = SDL_GetStringBoolean(hint, true);
101
102 if (player_lights != ctx->player_lights) {
103 ctx->player_lights = player_lights;
104
105 UpdateSlotLED(ctx);
106 HIDAPI_UpdateDeviceProperties(ctx->device);
107 }
108}
109
110static void UpdatePowerLevel(SDL_Joystick *joystick, Uint8 level)
111{
112 int percent = (int)SDL_roundf((level / 255.0f) * 100.0f);
113 SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent);
114}
115
116static bool HIDAPI_DriverXbox360W_InitDevice(SDL_HIDAPI_Device *device)
117{
118 SDL_DriverXbox360W_Context *ctx;
119
120 // Requests controller presence information from the wireless dongle
121 const Uint8 init_packet[] = { 0x08, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
122
123 HIDAPI_SetDeviceName(device, "Xbox 360 Wireless Controller");
124
125 ctx = (SDL_DriverXbox360W_Context *)SDL_calloc(1, sizeof(*ctx));
126 if (!ctx) {
127 return false;
128 }
129 ctx->device = device;
130
131 device->context = ctx;
132
133 if (SDL_hid_write(device->dev, init_packet, sizeof(init_packet)) != sizeof(init_packet)) {
134 SDL_SetError("Couldn't write init packet");
135 return false;
136 }
137
138 device->type = SDL_GAMEPAD_TYPE_XBOX360;
139
140 return true;
141}
142
143static int HIDAPI_DriverXbox360W_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
144{
145 return -1;
146}
147
148static void HIDAPI_DriverXbox360W_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
149{
150 SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
151
152 if (!ctx) {
153 return;
154 }
155
156 ctx->player_index = player_index;
157
158 UpdateSlotLED(ctx);
159}
160
161static bool HIDAPI_DriverXbox360W_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
162{
163 SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
164
165 SDL_AssertJoysticksLocked();
166
167 SDL_zeroa(ctx->last_state);
168
169 // Initialize player index (needed for setting LEDs)
170 ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
171 ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true);
172 UpdateSlotLED(ctx);
173
174 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
175 SDL_PlayerLEDHintChanged, ctx);
176
177 // Initialize the joystick capabilities
178 joystick->nbuttons = 11;
179 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
180 joystick->nhats = 1;
181 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
182
183 return true;
184}
185
186static bool HIDAPI_DriverXbox360W_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
187{
188 Uint8 rumble_packet[] = { 0x00, 0x01, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
189
190 rumble_packet[5] = (low_frequency_rumble >> 8);
191 rumble_packet[6] = (high_frequency_rumble >> 8);
192
193 if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
194 return SDL_SetError("Couldn't send rumble packet");
195 }
196 return true;
197}
198
199static bool HIDAPI_DriverXbox360W_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
200{
201 return SDL_Unsupported();
202}
203
204static Uint32 HIDAPI_DriverXbox360W_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
205{
206 SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
207 Uint32 result = SDL_JOYSTICK_CAP_RUMBLE;
208
209 if (ctx->player_lights) {
210 result |= SDL_JOYSTICK_CAP_PLAYER_LED;
211 }
212 return result;
213}
214
215static bool HIDAPI_DriverXbox360W_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
216{
217 return SDL_Unsupported();
218}
219
220static bool HIDAPI_DriverXbox360W_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
221{
222 return SDL_Unsupported();
223}
224
225static bool HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
226{
227 return SDL_Unsupported();
228}
229
230static void HIDAPI_DriverXbox360W_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverXbox360W_Context *ctx, Uint8 *data, int size)
231{
232 Sint16 axis;
233 const bool invert_y_axes = true;
234 Uint64 timestamp = SDL_GetTicksNS();
235
236 if (ctx->last_state[2] != data[2]) {
237 Uint8 hat = 0;
238
239 if (data[2] & 0x01) {
240 hat |= SDL_HAT_UP;
241 }
242 if (data[2] & 0x02) {
243 hat |= SDL_HAT_DOWN;
244 }
245 if (data[2] & 0x04) {
246 hat |= SDL_HAT_LEFT;
247 }
248 if (data[2] & 0x08) {
249 hat |= SDL_HAT_RIGHT;
250 }
251 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
252
253 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0));
254 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0));
255 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0));
256 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));
257 }
258
259 if (ctx->last_state[3] != data[3]) {
260 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0));
261 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));
262 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0));
263 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0));
264 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));
265 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0));
266 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0));
267 }
268
269 axis = ((int)data[4] * 257) - 32768;
270 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
271 axis = ((int)data[5] * 257) - 32768;
272 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
273 axis = SDL_Swap16LE(*(Sint16 *)(&data[6]));
274 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
275 axis = SDL_Swap16LE(*(Sint16 *)(&data[8]));
276 if (invert_y_axes) {
277 axis = ~axis;
278 }
279 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
280 axis = SDL_Swap16LE(*(Sint16 *)(&data[10]));
281 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
282 axis = SDL_Swap16LE(*(Sint16 *)(&data[12]));
283 if (invert_y_axes) {
284 axis = ~axis;
285 }
286 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
287
288 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
289}
290
291static bool HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device)
292{
293 SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
294 SDL_Joystick *joystick = NULL;
295 Uint8 data[USB_PACKET_LENGTH];
296 int size;
297
298 if (device->num_joysticks > 0) {
299 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
300 }
301
302 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
303#ifdef DEBUG_XBOX_PROTOCOL
304 HIDAPI_DumpPacket("Xbox 360 wireless packet: size = %d", data, size);
305#endif
306 if (size == 2 && data[0] == 0x08) {
307 bool connected = (data[1] & 0x80) ? true : false;
308#ifdef DEBUG_JOYSTICK
309 SDL_Log("Connected = %s", connected ? "TRUE" : "FALSE");
310#endif
311 if (connected != ctx->connected) {
312 ctx->connected = connected;
313
314 if (connected) {
315 SDL_JoystickID joystickID;
316
317 HIDAPI_JoystickConnected(device, &joystickID);
318
319 } else if (device->num_joysticks > 0) {
320 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
321 }
322 }
323 } else if (size == 29 && data[0] == 0x00 && data[1] == 0x0f && data[2] == 0x00 && data[3] == 0xf0) {
324 // Serial number is data[7-13]
325#ifdef DEBUG_JOYSTICK
326 SDL_Log("Battery status (initial): %d", data[17]);
327#endif
328 if (joystick) {
329 UpdatePowerLevel(joystick, data[17]);
330 }
331 } else if (size == 29 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x13) {
332#ifdef DEBUG_JOYSTICK
333 SDL_Log("Battery status: %d", data[4]);
334#endif
335 if (joystick) {
336 UpdatePowerLevel(joystick, data[4]);
337 }
338 } else if (size == 29 && data[0] == 0x00 && (data[1] & 0x01) == 0x01) {
339 if (joystick) {
340 HIDAPI_DriverXbox360W_HandleStatePacket(joystick, device->dev, ctx, data + 4, size - 4);
341 }
342 }
343 }
344
345 if (size < 0 && device->num_joysticks > 0) {
346 // Read error, device is disconnected
347 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
348 }
349 return (size >= 0);
350}
351
352static void HIDAPI_DriverXbox360W_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
353{
354 SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
355
356 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
357 SDL_PlayerLEDHintChanged, ctx);
358}
359
360static void HIDAPI_DriverXbox360W_FreeDevice(SDL_HIDAPI_Device *device)
361{
362}
363
364SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W = {
365 SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS,
366 true,
367 HIDAPI_DriverXbox360W_RegisterHints,
368 HIDAPI_DriverXbox360W_UnregisterHints,
369 HIDAPI_DriverXbox360W_IsEnabled,
370 HIDAPI_DriverXbox360W_IsSupportedDevice,
371 HIDAPI_DriverXbox360W_InitDevice,
372 HIDAPI_DriverXbox360W_GetDevicePlayerIndex,
373 HIDAPI_DriverXbox360W_SetDevicePlayerIndex,
374 HIDAPI_DriverXbox360W_UpdateDevice,
375 HIDAPI_DriverXbox360W_OpenJoystick,
376 HIDAPI_DriverXbox360W_RumbleJoystick,
377 HIDAPI_DriverXbox360W_RumbleJoystickTriggers,
378 HIDAPI_DriverXbox360W_GetJoystickCapabilities,
379 HIDAPI_DriverXbox360W_SetJoystickLED,
380 HIDAPI_DriverXbox360W_SendJoystickEffect,
381 HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled,
382 HIDAPI_DriverXbox360W_CloseJoystick,
383 HIDAPI_DriverXbox360W_FreeDevice,
384};
385
386#endif // SDL_JOYSTICK_HIDAPI_XBOX360
387
388#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xboxone.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xboxone.c
new file mode 100644
index 0000000..342eabd
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapi_xboxone.c
@@ -0,0 +1,1675 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28#include "SDL_hidapi_rumble.h"
29
30#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE
31
32// Define this if you want verbose logging of the init sequence
33// #define DEBUG_JOYSTICK
34
35// Define this if you want to log all packets from the controller
36// #define DEBUG_XBOX_PROTOCOL
37
38#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)
39#define XBOX_ONE_DRIVER_ACTIVE 1
40#else
41#define XBOX_ONE_DRIVER_ACTIVE 0
42#endif
43
44#define CONTROLLER_IDENTIFY_TIMEOUT_MS 100
45#define CONTROLLER_PREPARE_INPUT_TIMEOUT_MS 50
46
47// Deadzone thresholds
48#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
49#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
50#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD -25058 // Uint8 30 scaled to Sint16 full range
51
52enum
53{
54 SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON = 11
55};
56
57// Power on
58static const Uint8 xbox_init_power_on[] = {
59 0x05, 0x20, 0x00, 0x01, 0x00
60};
61// Enable LED
62static const Uint8 xbox_init_enable_led[] = {
63 0x0A, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14
64};
65// This controller passed security check
66static const Uint8 xbox_init_security_passed[] = {
67 0x06, 0x20, 0x00, 0x02, 0x01, 0x00
68};
69// Some PowerA controllers need to actually start the rumble motors
70static const Uint8 xbox_init_powera_rumble[] = {
71 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,
72 0x1D, 0x1D, 0xFF, 0x00, 0x00
73};
74// Setup rumble (not needed for Microsoft controllers, but it doesn't hurt)
75static const Uint8 xbox_init_rumble[] = {
76 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,
77 0x00, 0x00, 0xFF, 0x00, 0xEB
78};
79
80/*
81 * This specifies the selection of init packets that a gamepad
82 * will be sent on init *and* the order in which they will be
83 * sent. The correct sequence number will be added when the
84 * packet is going to be sent.
85 */
86typedef struct
87{
88 Uint16 vendor_id;
89 Uint16 product_id;
90 const Uint8 *data;
91 int size;
92} SDL_DriverXboxOne_InitPacket;
93
94static const SDL_DriverXboxOne_InitPacket xboxone_init_packets[] = {
95 { 0x0000, 0x0000, xbox_init_power_on, sizeof(xbox_init_power_on) },
96 { 0x0000, 0x0000, xbox_init_enable_led, sizeof(xbox_init_enable_led) },
97 { 0x0000, 0x0000, xbox_init_security_passed, sizeof(xbox_init_security_passed) },
98 { 0x24c6, 0x541a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) },
99 { 0x24c6, 0x542a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) },
100 { 0x24c6, 0x543a, xbox_init_powera_rumble, sizeof(xbox_init_powera_rumble) },
101 { 0x0000, 0x0000, xbox_init_rumble, sizeof(xbox_init_rumble) },
102};
103
104typedef enum
105{
106 XBOX_ONE_INIT_STATE_ANNOUNCED,
107 XBOX_ONE_INIT_STATE_IDENTIFYING,
108 XBOX_ONE_INIT_STATE_STARTUP,
109 XBOX_ONE_INIT_STATE_PREPARE_INPUT,
110 XBOX_ONE_INIT_STATE_COMPLETE,
111} SDL_XboxOneInitState;
112
113typedef enum
114{
115 XBOX_ONE_RUMBLE_STATE_IDLE,
116 XBOX_ONE_RUMBLE_STATE_QUEUED,
117 XBOX_ONE_RUMBLE_STATE_BUSY
118} SDL_XboxOneRumbleState;
119
120typedef struct
121{
122 SDL_HIDAPI_Device *device;
123 Uint16 vendor_id;
124 Uint16 product_id;
125 SDL_XboxOneInitState init_state;
126 Uint64 start_time;
127 Uint8 sequence;
128 Uint64 send_time;
129 bool has_guide_packet;
130 bool has_color_led;
131 bool has_paddles;
132 bool has_unmapped_state;
133 bool has_trigger_rumble;
134 bool has_share_button;
135 Uint8 last_paddle_state;
136 Uint8 low_frequency_rumble;
137 Uint8 high_frequency_rumble;
138 Uint8 left_trigger_rumble;
139 Uint8 right_trigger_rumble;
140 SDL_XboxOneRumbleState rumble_state;
141 Uint64 rumble_time;
142 bool rumble_pending;
143 Uint8 last_state[USB_PACKET_LENGTH];
144 Uint8 *chunk_buffer;
145 Uint32 chunk_length;
146} SDL_DriverXboxOne_Context;
147
148static bool ControllerHasColorLED(Uint16 vendor_id, Uint16 product_id)
149{
150 return vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2;
151}
152
153static bool ControllerHasPaddles(Uint16 vendor_id, Uint16 product_id)
154{
155 return SDL_IsJoystickXboxOneElite(vendor_id, product_id);
156}
157
158static bool ControllerHasTriggerRumble(Uint16 vendor_id, Uint16 product_id)
159{
160 // All the Microsoft Xbox One controllers have trigger rumble
161 if (vendor_id == USB_VENDOR_MICROSOFT) {
162 return true;
163 }
164
165 /* It turns out other controllers a mixed bag as to whether they support
166 trigger rumble or not, and when they do it's often a buzz rather than
167 the vibration of the Microsoft trigger rumble, so for now just pretend
168 that it is not available.
169 */
170 return false;
171}
172
173static bool ControllerHasShareButton(Uint16 vendor_id, Uint16 product_id)
174{
175 return SDL_IsJoystickXboxSeriesX(vendor_id, product_id);
176}
177
178static int GetHomeLEDBrightness(const char *hint)
179{
180 const int MAX_VALUE = 50;
181 int value = 20;
182
183 if (hint && *hint) {
184 if (SDL_strchr(hint, '.') != NULL) {
185 value = (int)(MAX_VALUE * SDL_atof(hint));
186 } else if (!SDL_GetStringBoolean(hint, true)) {
187 value = 0;
188 }
189 }
190 return value;
191}
192
193static void SetHomeLED(SDL_DriverXboxOne_Context *ctx, int value)
194{
195 Uint8 led_packet[] = { 0x0A, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00 };
196
197 if (value > 0) {
198 led_packet[5] = 0x01;
199 led_packet[6] = (Uint8)value;
200 }
201 SDL_HIDAPI_SendRumble(ctx->device, led_packet, sizeof(led_packet));
202}
203
204static void SDLCALL SDL_HomeLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
205{
206 SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)userdata;
207
208 if (hint && *hint) {
209 SetHomeLED(ctx, GetHomeLEDBrightness(hint));
210 }
211}
212
213static void SetInitState(SDL_DriverXboxOne_Context *ctx, SDL_XboxOneInitState state)
214{
215#ifdef DEBUG_JOYSTICK
216 SDL_Log("Setting init state %d", state);
217#endif
218 ctx->init_state = state;
219}
220
221static Uint8 GetNextPacketSequence(SDL_DriverXboxOne_Context *ctx)
222{
223 ++ctx->sequence;
224 if (!ctx->sequence) {
225 ctx->sequence = 1;
226 }
227 return ctx->sequence;
228}
229
230static bool SendProtocolPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
231{
232#ifdef DEBUG_XBOX_PROTOCOL
233 HIDAPI_DumpPacket("Xbox One sending packet: size = %d", data, size);
234#endif
235
236 ctx->send_time = SDL_GetTicks();
237
238 if (!SDL_HIDAPI_LockRumble()) {
239 return false;
240 }
241 if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, size) != size) {
242 return false;
243 }
244 return true;
245}
246
247#if 0
248static bool SendSerialRequest(SDL_DriverXboxOne_Context *ctx)
249{
250 Uint8 packet[] = { 0x1E, 0x20, 0x00, 0x01, 0x04 };
251
252 packet[2] = GetNextPacketSequence(ctx);
253
254 /* Request the serial number
255 * Sending this should be done only after startup is complete.
256 * It will cancel the announce packet if sent before that, and will be
257 * ignored if sent during the startup sequence.
258 */
259 if (!SendProtocolPacket(ctx, packet, sizeof(packet))) {
260 SDL_SetError("Couldn't send serial request packet");
261 return false;
262 }
263 return true;
264}
265#endif
266
267static bool ControllerSendsAnnouncement(Uint16 vendor_id, Uint16 product_id)
268{
269 if (vendor_id == USB_VENDOR_PDP && product_id == 0x0246) {
270 // The PDP Rock Candy (PID 0x0246) doesn't send the announce packet on Linux for some reason
271 return false;
272 }
273 return true;
274}
275
276static bool SendIdentificationRequest(SDL_DriverXboxOne_Context *ctx)
277{
278 // Request identification, sent in response to announce packet
279 Uint8 packet[] = {
280 0x04, 0x20, 0x00, 0x00
281 };
282
283 packet[2] = GetNextPacketSequence(ctx);
284
285 if (!SendProtocolPacket(ctx, packet, sizeof(packet))) {
286 SDL_SetError("Couldn't send identification request packet");
287 return false;
288 }
289 return true;
290}
291
292static bool SendControllerStartup(SDL_DriverXboxOne_Context *ctx)
293{
294 Uint16 vendor_id = ctx->vendor_id;
295 Uint16 product_id = ctx->product_id;
296 Uint8 init_packet[USB_PACKET_LENGTH];
297 size_t i;
298
299 for (i = 0; i < SDL_arraysize(xboxone_init_packets); ++i) {
300 const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[i];
301
302 if (packet->vendor_id && (vendor_id != packet->vendor_id)) {
303 continue;
304 }
305
306 if (packet->product_id && (product_id != packet->product_id)) {
307 continue;
308 }
309
310 SDL_memcpy(init_packet, packet->data, packet->size);
311 init_packet[2] = GetNextPacketSequence(ctx);
312
313 if (init_packet[0] == 0x0A) {
314 // Get the initial brightness value
315 int brightness = GetHomeLEDBrightness(SDL_GetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED));
316 init_packet[5] = (brightness > 0) ? 0x01 : 0x00;
317 init_packet[6] = (Uint8)brightness;
318 }
319
320 if (!SendProtocolPacket(ctx, init_packet, packet->size)) {
321 SDL_SetError("Couldn't send initialization packet");
322 return false;
323 }
324
325 // Wait to process the rumble packet
326 if (packet->data == xbox_init_powera_rumble) {
327 SDL_Delay(10);
328 }
329 }
330 return true;
331}
332
333static void HIDAPI_DriverXboxOne_RegisterHints(SDL_HintCallback callback, void *userdata)
334{
335 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
336 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, callback, userdata);
337}
338
339static void HIDAPI_DriverXboxOne_UnregisterHints(SDL_HintCallback callback, void *userdata)
340{
341 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
342 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE, callback, userdata);
343}
344
345static bool HIDAPI_DriverXboxOne_IsEnabled(void)
346{
347 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE,
348 SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)));
349}
350
351static bool HIDAPI_DriverXboxOne_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
352{
353#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI)
354 if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) {
355 // On macOS we get a shortened version of the real report and
356 // you can't write output reports for wired controllers, so
357 // we'll just use the GCController support instead.
358 return false;
359 }
360#endif
361 return (type == SDL_GAMEPAD_TYPE_XBOXONE);
362}
363
364static bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device)
365{
366 SDL_DriverXboxOne_Context *ctx;
367
368 ctx = (SDL_DriverXboxOne_Context *)SDL_calloc(1, sizeof(*ctx));
369 if (!ctx) {
370 return false;
371 }
372 ctx->device = device;
373
374 device->context = ctx;
375
376 ctx->vendor_id = device->vendor_id;
377 ctx->product_id = device->product_id;
378 ctx->start_time = SDL_GetTicks();
379 ctx->sequence = 0;
380 ctx->has_color_led = ControllerHasColorLED(ctx->vendor_id, ctx->product_id);
381 ctx->has_paddles = ControllerHasPaddles(ctx->vendor_id, ctx->product_id);
382 ctx->has_trigger_rumble = ControllerHasTriggerRumble(ctx->vendor_id, ctx->product_id);
383 ctx->has_share_button = ControllerHasShareButton(ctx->vendor_id, ctx->product_id);
384
385 // Assume that the controller is correctly initialized when we start
386 if (!ControllerSendsAnnouncement(device->vendor_id, device->product_id)) {
387 // Jump into the startup sequence for this controller
388 ctx->init_state = XBOX_ONE_INIT_STATE_STARTUP;
389 } else {
390 ctx->init_state = XBOX_ONE_INIT_STATE_COMPLETE;
391 }
392
393#ifdef DEBUG_JOYSTICK
394 SDL_Log("Controller version: %d (0x%.4x)", device->version, device->version);
395#endif
396
397 device->type = SDL_GAMEPAD_TYPE_XBOXONE;
398
399 return HIDAPI_JoystickConnected(device, NULL);
400}
401
402static int HIDAPI_DriverXboxOne_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
403{
404 return -1;
405}
406
407static void HIDAPI_DriverXboxOne_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
408{
409}
410
411static bool HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
412{
413 SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
414
415 SDL_AssertJoysticksLocked();
416
417 ctx->low_frequency_rumble = 0;
418 ctx->high_frequency_rumble = 0;
419 ctx->left_trigger_rumble = 0;
420 ctx->right_trigger_rumble = 0;
421 ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE;
422 ctx->rumble_time = 0;
423 ctx->rumble_pending = false;
424 SDL_zeroa(ctx->last_state);
425
426 // Initialize the joystick capabilities
427 joystick->nbuttons = 11;
428 if (ctx->has_share_button) {
429 joystick->nbuttons += 1;
430 }
431 if (ctx->has_paddles) {
432 joystick->nbuttons += 4;
433 }
434 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
435 joystick->nhats = 1;
436
437 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED,
438 SDL_HomeLEDHintChanged, ctx);
439 return true;
440}
441
442static void HIDAPI_DriverXboxOne_RumbleSent(void *userdata)
443{
444 SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)userdata;
445 ctx->rumble_time = SDL_GetTicks();
446}
447
448static bool HIDAPI_DriverXboxOne_UpdateRumble(SDL_DriverXboxOne_Context *ctx)
449{
450 if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_QUEUED) {
451 if (ctx->rumble_time) {
452 ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_BUSY;
453 }
454 }
455
456 if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_BUSY) {
457 const int RUMBLE_BUSY_TIME_MS = ctx->device->is_bluetooth ? 50 : 10;
458 if (SDL_GetTicks() >= (ctx->rumble_time + RUMBLE_BUSY_TIME_MS)) {
459 ctx->rumble_time = 0;
460 ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE;
461 }
462 }
463
464 if (!ctx->rumble_pending) {
465 return true;
466 }
467
468 if (ctx->rumble_state != XBOX_ONE_RUMBLE_STATE_IDLE) {
469 return true;
470 }
471
472 // We're no longer pending, even if we fail to send the rumble below
473 ctx->rumble_pending = false;
474
475 if (!SDL_HIDAPI_LockRumble()) {
476 return false;
477 }
478
479 if (ctx->device->is_bluetooth) {
480 Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };
481
482 rumble_packet[2] = ctx->left_trigger_rumble;
483 rumble_packet[3] = ctx->right_trigger_rumble;
484 rumble_packet[4] = ctx->low_frequency_rumble;
485 rumble_packet[5] = ctx->high_frequency_rumble;
486
487 if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(ctx->device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) {
488 return SDL_SetError("Couldn't send rumble packet");
489 }
490 } else {
491 Uint8 rumble_packet[] = { 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };
492
493 rumble_packet[6] = ctx->left_trigger_rumble;
494 rumble_packet[7] = ctx->right_trigger_rumble;
495 rumble_packet[8] = ctx->low_frequency_rumble;
496 rumble_packet[9] = ctx->high_frequency_rumble;
497
498 if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(ctx->device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) {
499 return SDL_SetError("Couldn't send rumble packet");
500 }
501 }
502
503 ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_QUEUED;
504
505 return true;
506}
507
508static bool HIDAPI_DriverXboxOne_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
509{
510 SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
511
512 // Magnitude is 1..100 so scale the 16-bit input here
513 ctx->low_frequency_rumble = (Uint8)(low_frequency_rumble / 655);
514 ctx->high_frequency_rumble = (Uint8)(high_frequency_rumble / 655);
515 ctx->rumble_pending = true;
516
517 return HIDAPI_DriverXboxOne_UpdateRumble(ctx);
518}
519
520static bool HIDAPI_DriverXboxOne_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
521{
522 SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
523
524 if (!ctx->has_trigger_rumble) {
525 return SDL_Unsupported();
526 }
527
528 // Magnitude is 1..100 so scale the 16-bit input here
529 ctx->left_trigger_rumble = (Uint8)(left_rumble / 655);
530 ctx->right_trigger_rumble = (Uint8)(right_rumble / 655);
531 ctx->rumble_pending = true;
532
533 return HIDAPI_DriverXboxOne_UpdateRumble(ctx);
534}
535
536static Uint32 HIDAPI_DriverXboxOne_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
537{
538 SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
539 Uint32 result = 0;
540
541 result |= SDL_JOYSTICK_CAP_RUMBLE;
542 if (ctx->has_trigger_rumble) {
543 result |= SDL_JOYSTICK_CAP_TRIGGER_RUMBLE;
544 }
545
546 if (ctx->has_color_led) {
547 result |= SDL_JOYSTICK_CAP_RGB_LED;
548 }
549
550 return result;
551}
552
553static bool HIDAPI_DriverXboxOne_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
554{
555 SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
556
557 if (ctx->has_color_led) {
558 Uint8 led_packet[] = { 0x0E, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 };
559
560 led_packet[5] = 0x00; // Whiteness? Sets white intensity when RGB is 0, seems additive
561 led_packet[6] = red;
562 led_packet[7] = green;
563 led_packet[8] = blue;
564
565 if (SDL_HIDAPI_SendRumble(device, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {
566 return SDL_SetError("Couldn't send LED packet");
567 }
568 return true;
569 } else {
570 return SDL_Unsupported();
571 }
572}
573
574static bool HIDAPI_DriverXboxOne_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
575{
576 return SDL_Unsupported();
577}
578
579static bool HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
580{
581 return SDL_Unsupported();
582}
583
584/*
585 * The Xbox One Elite controller with 5.13+ firmware sends the unmapped state in a separate packet.
586 * We can use this to send the paddle state when they aren't mapped
587 */
588static void HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)
589{
590 Uint8 profile;
591 int paddle_index;
592 int button1_bit;
593 int button2_bit;
594 int button3_bit;
595 int button4_bit;
596 bool paddles_mapped;
597 Uint64 timestamp = SDL_GetTicksNS();
598
599 if (size == 17) {
600 // XBox One Elite Series 2
601 paddle_index = 14;
602 button1_bit = 0x01;
603 button2_bit = 0x02;
604 button3_bit = 0x04;
605 button4_bit = 0x08;
606 profile = data[15];
607
608 if (profile == 0) {
609 paddles_mapped = false;
610 } else if (SDL_memcmp(&data[0], &ctx->last_state[0], 14) == 0) {
611 // We're using a profile, but paddles aren't mapped
612 paddles_mapped = false;
613 } else {
614 // Something is mapped, we can't use the paddles
615 paddles_mapped = true;
616 }
617
618 } else {
619 // Unknown format
620 return;
621 }
622#ifdef DEBUG_XBOX_PROTOCOL
623 SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s",
624 (data[paddle_index] & button1_bit) ? 1 : 0,
625 (data[paddle_index] & button2_bit) ? 1 : 0,
626 (data[paddle_index] & button3_bit) ? 1 : 0,
627 (data[paddle_index] & button4_bit) ? 1 : 0,
628 paddles_mapped ? "TRUE" : "FALSE");
629#endif
630
631 if (paddles_mapped) {
632 // Respect that the paddles are being used for other controls and don't pass them on to the app
633 data[paddle_index] = 0;
634 }
635
636 if (ctx->last_paddle_state != data[paddle_index]) {
637 Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button
638 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0));
639 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0));
640 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0));
641 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0));
642 ctx->last_paddle_state = data[paddle_index];
643 }
644 ctx->has_unmapped_state = true;
645}
646
647static void HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)
648{
649 Sint16 axis;
650 Uint64 timestamp = SDL_GetTicksNS();
651
652 // Enable paddles on the Xbox Elite controller when connected over USB
653 if (ctx->has_paddles && !ctx->has_unmapped_state && size == 46) {
654 Uint8 packet[] = { 0x4d, 0x00, 0x00, 0x02, 0x07, 0x00 };
655
656#ifdef DEBUG_JOYSTICK
657 SDL_Log("Enabling paddles on XBox Elite 2");
658#endif
659 SDL_HIDAPI_SendRumble(ctx->device, packet, sizeof(packet));
660 }
661
662 if (ctx->last_state[0] != data[0]) {
663 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[0] & 0x04) != 0));
664 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[0] & 0x08) != 0));
665 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x10) != 0));
666 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x20) != 0));
667 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x40) != 0));
668 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x80) != 0));
669 }
670
671 if (ctx->last_state[1] != data[1]) {
672 Uint8 hat = 0;
673
674 if (data[1] & 0x01) {
675 hat |= SDL_HAT_UP;
676 }
677 if (data[1] & 0x02) {
678 hat |= SDL_HAT_DOWN;
679 }
680 if (data[1] & 0x04) {
681 hat |= SDL_HAT_LEFT;
682 }
683 if (data[1] & 0x08) {
684 hat |= SDL_HAT_RIGHT;
685 }
686 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
687
688 if (ctx->vendor_id == USB_VENDOR_RAZER && ctx->product_id == USB_PRODUCT_RAZER_ATROX) {
689 // The Razer Atrox has the right and left shoulder bits reversed
690 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x20) != 0));
691 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x10) != 0));
692 } else {
693 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0));
694 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0));
695 }
696 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x40) != 0));
697 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x80) != 0));
698 }
699
700 if (ctx->has_share_button) {
701 /* Xbox Series X firmware version 5.0, report is 32 bytes, share button is in byte 14
702 * Xbox Series X firmware version 5.1, report is 40 bytes, share button is in byte 14
703 * Xbox Series X firmware version 5.5, report is 44 bytes, share button is in byte 18
704 * Victrix Gambit Tournament Controller, report is 46 bytes, share button is in byte 28
705 * ThrustMaster eSwap PRO Controller Xbox, report is 60 bytes, share button is in byte 42
706 */
707 if (size < 44) {
708 if (ctx->last_state[14] != data[14]) {
709 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[14] & 0x01) != 0));
710 }
711 } else if (size == 44) {
712 if (ctx->last_state[18] != data[18]) {
713 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[18] & 0x01) != 0));
714 }
715 } else if (size == 46) {
716 if (ctx->last_state[28] != data[28]) {
717 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[28] & 0x01) != 0));
718 }
719 } else if (size == 60) {
720 if (ctx->last_state[42] != data[42]) {
721 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[42] & 0x01) != 0));
722 }
723 }
724 }
725
726 /* Xbox One S report is 14 bytes
727 Xbox One Elite Series 1 report is 29 bytes, paddles in data[28], mode in data[28] & 0x10, both modes have mapped paddles by default
728 Paddle bits:
729 P3: 0x01 (A) P1: 0x02 (B)
730 P4: 0x04 (X) P2: 0x08 (Y)
731 Xbox One Elite Series 2 4.x firmware report is 34 bytes, paddles in data[14], mode in data[15], mode 0 has no mapped paddles by default
732 Paddle bits:
733 P3: 0x04 (A) P1: 0x01 (B)
734 P4: 0x08 (X) P2: 0x02 (Y)
735 Xbox One Elite Series 2 5.x firmware report is 46 bytes, paddles in data[18], mode in data[19], mode 0 has no mapped paddles by default
736 Paddle bits:
737 P3: 0x04 (A) P1: 0x01 (B)
738 P4: 0x08 (X) P2: 0x02 (Y)
739 Xbox One Elite Series 2 5.17+ firmware report is 47 bytes, paddles in data[14], mode in data[20], mode 0 has no mapped paddles by default
740 Paddle bits:
741 P3: 0x04 (A) P1: 0x01 (B)
742 P4: 0x08 (X) P2: 0x02 (Y)
743 */
744 if (ctx->has_paddles && !ctx->has_unmapped_state && (size == 29 || size == 34 || size == 46 || size == 47)) {
745 int paddle_index;
746 int button1_bit;
747 int button2_bit;
748 int button3_bit;
749 int button4_bit;
750 bool paddles_mapped;
751
752 if (size == 29) {
753 // XBox One Elite Series 1
754 paddle_index = 28;
755 button1_bit = 0x02;
756 button2_bit = 0x08;
757 button3_bit = 0x01;
758 button4_bit = 0x04;
759
760 // The mapped controller state is at offset 0, the raw state is at offset 14, compare them to see if the paddles are mapped
761 paddles_mapped = (SDL_memcmp(&data[0], &data[14], 2) != 0);
762
763 } else if (size == 34) {
764 // XBox One Elite Series 2
765 paddle_index = 14;
766 button1_bit = 0x01;
767 button2_bit = 0x02;
768 button3_bit = 0x04;
769 button4_bit = 0x08;
770 paddles_mapped = (data[15] != 0);
771
772 } else if (size == 46) {
773 // XBox One Elite Series 2
774 paddle_index = 18;
775 button1_bit = 0x01;
776 button2_bit = 0x02;
777 button3_bit = 0x04;
778 button4_bit = 0x08;
779 paddles_mapped = (data[19] != 0);
780 } else /* if (size == 47) */ {
781 // XBox One Elite Series 2
782 paddle_index = 14;
783 button1_bit = 0x01;
784 button2_bit = 0x02;
785 button3_bit = 0x04;
786 button4_bit = 0x08;
787 paddles_mapped = (data[20] != 0);
788 }
789#ifdef DEBUG_XBOX_PROTOCOL
790 SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s",
791 (data[paddle_index] & button1_bit) ? 1 : 0,
792 (data[paddle_index] & button2_bit) ? 1 : 0,
793 (data[paddle_index] & button3_bit) ? 1 : 0,
794 (data[paddle_index] & button4_bit) ? 1 : 0,
795 paddles_mapped ? "TRUE" : "FALSE");
796#endif
797
798 if (paddles_mapped) {
799 // Respect that the paddles are being used for other controls and don't pass them on to the app
800 data[paddle_index] = 0;
801 }
802
803 if (ctx->last_paddle_state != data[paddle_index]) {
804 Uint8 nButton = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button
805 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0));
806 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0));
807 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0));
808 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0));
809 ctx->last_paddle_state = data[paddle_index];
810 }
811 }
812
813 axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[2])) * 64) - 32768;
814 if (axis == 32704) {
815 axis = 32767;
816 }
817 if (axis == -32768 && size == 26 && (data[18] & 0x80)) {
818 axis = 32767;
819 }
820 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
821
822 axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[4])) * 64) - 32768;
823 if (axis == -32768 && size == 26 && (data[18] & 0x40)) {
824 axis = 32767;
825 }
826 if (axis == 32704) {
827 axis = 32767;
828 }
829 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
830
831 axis = SDL_Swap16LE(*(Sint16 *)(&data[6]));
832 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
833 axis = SDL_Swap16LE(*(Sint16 *)(&data[8]));
834 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);
835 axis = SDL_Swap16LE(*(Sint16 *)(&data[10]));
836 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
837 axis = SDL_Swap16LE(*(Sint16 *)(&data[12]));
838 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis);
839
840 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
841
842 // We don't have the unmapped state for this packet
843 ctx->has_unmapped_state = false;
844}
845
846static void HIDAPI_DriverXboxOne_HandleStatusPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
847{
848 if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) {
849 SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE);
850 }
851}
852
853static void HIDAPI_DriverXboxOne_HandleModePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
854{
855 Uint64 timestamp = SDL_GetTicksNS();
856
857 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[0] & 0x01) != 0));
858}
859
860/*
861 * Xbox One S with firmware 3.1.1221 uses a 16 byte packet and the GUIDE button in a separate packet
862 */
863static void HIDAPI_DriverXboxOneBluetooth_HandleButtons16(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
864{
865 if (ctx->last_state[14] != data[14]) {
866 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0));
867 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0));
868 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x04) != 0));
869 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x08) != 0));
870 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x10) != 0));
871 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x20) != 0));
872 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[14] & 0x40) != 0));
873 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[14] & 0x80) != 0));
874 }
875
876 if (ctx->last_state[15] != data[15]) {
877 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x01) != 0));
878 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x02) != 0));
879 }
880}
881
882/*
883 * Xbox One S with firmware 4.8.1923 uses a 17 byte packet with BACK button in byte 16 and the GUIDE button in a separate packet (on Windows), or in byte 15 (on Linux)
884 * Xbox One S with firmware 5.x uses a 17 byte packet with BACK and GUIDE buttons in byte 15
885 * Xbox One Elite Series 2 with firmware 4.7.1872 uses a 55 byte packet with BACK button in byte 16, paddles starting at byte 33, and the GUIDE button in a separate packet
886 * Xbox One Elite Series 2 with firmware 4.8.1908 uses a 33 byte packet with BACK button in byte 16, paddles starting at byte 17, and the GUIDE button in a separate packet
887 * Xbox One Elite Series 2 with firmware 5.11.3112 uses a 19 byte packet with BACK and GUIDE buttons in byte 15
888 * Xbox Series X with firmware 5.5.2641 uses a 17 byte packet with BACK and GUIDE buttons in byte 15, and SHARE button in byte 17
889 */
890static void HIDAPI_DriverXboxOneBluetooth_HandleButtons(Uint64 timestamp, SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)
891{
892 if (ctx->last_state[14] != data[14]) {
893 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0));
894 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0));
895 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0));
896 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0));
897 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0));
898 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0));
899 }
900
901 if (ctx->last_state[15] != data[15]) {
902 if (!ctx->has_guide_packet) {
903 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[15] & 0x10) != 0));
904 }
905 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0));
906 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0));
907 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0));
908 }
909
910 if (ctx->has_share_button) {
911 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[15] & 0x04) != 0));
912 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, ((data[16] & 0x01) != 0));
913 } else {
914 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[15] & 0x04) || ((data[16] & 0x01)) != 0));
915 }
916
917 /*
918 Paddle bits:
919 P3: 0x04 (A) P1: 0x01 (B)
920 P4: 0x08 (X) P2: 0x02 (Y)
921 */
922 if (ctx->has_paddles && (size == 20 || size == 39 || size == 55)) {
923 int paddle_index;
924 int button1_bit;
925 int button2_bit;
926 int button3_bit;
927 int button4_bit;
928 bool paddles_mapped;
929
930 if (size == 55) {
931 // Initial firmware for the Xbox Elite Series 2 controller
932 paddle_index = 33;
933 button1_bit = 0x01;
934 button2_bit = 0x02;
935 button3_bit = 0x04;
936 button4_bit = 0x08;
937 paddles_mapped = (data[35] != 0);
938 } else if (size == 39) {
939 // Updated firmware for the Xbox Elite Series 2 controller
940 paddle_index = 17;
941 button1_bit = 0x01;
942 button2_bit = 0x02;
943 button3_bit = 0x04;
944 button4_bit = 0x08;
945 paddles_mapped = (data[19] != 0);
946 } else /* if (size == 20) */ {
947 // Updated firmware for the Xbox Elite Series 2 controller (5.13+)
948 paddle_index = 19;
949 button1_bit = 0x01;
950 button2_bit = 0x02;
951 button3_bit = 0x04;
952 button4_bit = 0x08;
953 paddles_mapped = (data[17] != 0);
954 }
955
956#ifdef DEBUG_XBOX_PROTOCOL
957 SDL_Log(">>> Paddles: %d,%d,%d,%d mapped = %s",
958 (data[paddle_index] & button1_bit) ? 1 : 0,
959 (data[paddle_index] & button2_bit) ? 1 : 0,
960 (data[paddle_index] & button3_bit) ? 1 : 0,
961 (data[paddle_index] & button4_bit) ? 1 : 0,
962 paddles_mapped ? "TRUE" : "FALSE");
963#endif
964
965 if (paddles_mapped) {
966 // Respect that the paddles are being used for other controls and don't pass them on to the app
967 data[paddle_index] = 0;
968 }
969
970 if (ctx->last_paddle_state != data[paddle_index]) {
971 Uint8 nButton = SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON; // Next available button
972 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button1_bit) != 0));
973 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button2_bit) != 0));
974 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button3_bit) != 0));
975 SDL_SendJoystickButton(timestamp, joystick, nButton++, ((data[paddle_index] & button4_bit) != 0));
976 ctx->last_paddle_state = data[paddle_index];
977 }
978 }
979}
980
981static void HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)
982{
983 Sint16 axis;
984 Uint64 timestamp = SDL_GetTicksNS();
985
986 if (size == 16) {
987 // Original Xbox One S, with separate report for guide button
988 HIDAPI_DriverXboxOneBluetooth_HandleButtons16(timestamp, joystick, ctx, data, size);
989 } else if (size > 16) {
990 HIDAPI_DriverXboxOneBluetooth_HandleButtons(timestamp, joystick, ctx, data, size);
991 } else {
992#ifdef DEBUG_XBOX_PROTOCOL
993 SDL_Log("Unknown Bluetooth state packet format");
994#endif
995 return;
996 }
997
998 if (ctx->last_state[13] != data[13]) {
999 Uint8 hat;
1000
1001 switch (data[13]) {
1002 case 1:
1003 hat = SDL_HAT_UP;
1004 break;
1005 case 2:
1006 hat = SDL_HAT_RIGHTUP;
1007 break;
1008 case 3:
1009 hat = SDL_HAT_RIGHT;
1010 break;
1011 case 4:
1012 hat = SDL_HAT_RIGHTDOWN;
1013 break;
1014 case 5:
1015 hat = SDL_HAT_DOWN;
1016 break;
1017 case 6:
1018 hat = SDL_HAT_LEFTDOWN;
1019 break;
1020 case 7:
1021 hat = SDL_HAT_LEFT;
1022 break;
1023 case 8:
1024 hat = SDL_HAT_LEFTUP;
1025 break;
1026 default:
1027 hat = SDL_HAT_CENTERED;
1028 break;
1029 }
1030 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1031 }
1032
1033 axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[9])) * 64) - 32768;
1034 if (axis == 32704) {
1035 axis = 32767;
1036 }
1037 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
1038
1039 axis = ((int)SDL_Swap16LE(*(Sint16 *)(&data[11])) * 64) - 32768;
1040 if (axis == 32704) {
1041 axis = 32767;
1042 }
1043 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
1044
1045 axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[1])) - 0x8000;
1046 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
1047 axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[3])) - 0x8000;
1048 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
1049 axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[5])) - 0x8000;
1050 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
1051 axis = (int)SDL_Swap16LE(*(Uint16 *)(&data[7])) - 0x8000;
1052 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
1053
1054 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
1055}
1056
1057static void HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
1058{
1059 Uint64 timestamp = SDL_GetTicksNS();
1060
1061 ctx->has_guide_packet = true;
1062 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x01) != 0));
1063}
1064
1065static void HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
1066{
1067 Uint8 flags = data[1];
1068 bool on_usb = (((flags & 0x0C) >> 2) == 0);
1069 SDL_PowerState state;
1070 int percent = 0;
1071
1072 // Mapped percentage value from:
1073 // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate
1074 switch (flags & 0x03) {
1075 case 0:
1076 percent = 10;
1077 break;
1078 case 1:
1079 percent = 40;
1080 break;
1081 case 2:
1082 percent = 70;
1083 break;
1084 case 3:
1085 percent = 100;
1086 break;
1087 }
1088 if (on_usb) {
1089 state = SDL_POWERSTATE_CHARGING;
1090 } else {
1091 state = SDL_POWERSTATE_ON_BATTERY;
1092 }
1093 SDL_SendJoystickPowerInfo(joystick, state, percent);
1094}
1095
1096static void HIDAPI_DriverXboxOne_HandleSerialIDPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
1097{
1098 char serial[29];
1099 int i;
1100
1101 for (i = 0; i < 14; ++i) {
1102 SDL_uitoa(data[2 + i], &serial[i * 2], 16);
1103 }
1104 serial[i * 2] = '\0';
1105
1106#ifdef DEBUG_JOYSTICK
1107 SDL_Log("Setting serial number to %s", serial);
1108#endif
1109 HIDAPI_SetDeviceSerial(ctx->device, serial);
1110}
1111
1112static bool HIDAPI_DriverXboxOne_UpdateInitState(SDL_DriverXboxOne_Context *ctx)
1113{
1114 SDL_XboxOneInitState prev_state;
1115 do {
1116 prev_state = ctx->init_state;
1117
1118 switch (ctx->init_state) {
1119 case XBOX_ONE_INIT_STATE_ANNOUNCED:
1120 if (XBOX_ONE_DRIVER_ACTIVE) {
1121 // The driver is taking care of identification
1122 SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE);
1123 } else {
1124 SendIdentificationRequest(ctx);
1125 SetInitState(ctx, XBOX_ONE_INIT_STATE_IDENTIFYING);
1126 }
1127 break;
1128 case XBOX_ONE_INIT_STATE_IDENTIFYING:
1129 if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_IDENTIFY_TIMEOUT_MS)) {
1130 // We haven't heard anything, let's move on
1131#ifdef DEBUG_JOYSTICK
1132 SDL_Log("Identification request timed out after %llu ms", (SDL_GetTicks() - ctx->send_time));
1133#endif
1134 SetInitState(ctx, XBOX_ONE_INIT_STATE_STARTUP);
1135 }
1136 break;
1137 case XBOX_ONE_INIT_STATE_STARTUP:
1138 if (XBOX_ONE_DRIVER_ACTIVE) {
1139 // The driver is taking care of startup
1140 SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE);
1141 } else {
1142 SendControllerStartup(ctx);
1143 SetInitState(ctx, XBOX_ONE_INIT_STATE_PREPARE_INPUT);
1144 }
1145 break;
1146 case XBOX_ONE_INIT_STATE_PREPARE_INPUT:
1147 if (SDL_GetTicks() >= (ctx->send_time + CONTROLLER_PREPARE_INPUT_TIMEOUT_MS)) {
1148#ifdef DEBUG_JOYSTICK
1149 SDL_Log("Prepare input complete after %llu ms", (SDL_GetTicks() - ctx->send_time));
1150#endif
1151 SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE);
1152 }
1153 break;
1154 case XBOX_ONE_INIT_STATE_COMPLETE:
1155 break;
1156 }
1157
1158 } while (ctx->init_state != prev_state);
1159
1160 return true;
1161}
1162
1163/* GIP protocol handling adapted under the Zlib license with permission from @medusalix:
1164 * https://github.com/medusalix/xone/blob/master/bus/protocol.h
1165 * https://github.com/medusalix/xone/blob/master/bus/protocol.c
1166 */
1167#define GIP_HEADER_MIN_LENGTH 3
1168
1169// Internal commands
1170#define GIP_CMD_ACKNOWLEDGE 0x01
1171#define GIP_CMD_ANNOUNCE 0x02
1172#define GIP_CMD_STATUS 0x03
1173#define GIP_CMD_IDENTIFY 0x04
1174#define GIP_CMD_POWER 0x05
1175#define GIP_CMD_AUTHENTICATE 0x06
1176#define GIP_CMD_VIRTUAL_KEY 0x07
1177#define GIP_CMD_AUDIO_CONTROL 0x08
1178#define GIP_CMD_LED 0x0A
1179#define GIP_CMD_HID_REPORT 0x0B
1180#define GIP_CMD_FIRMWARE 0x0C
1181#define GIP_CMD_SERIAL_NUMBER 0x1E
1182#define GIP_CMD_AUDIO_SAMPLES 0x60
1183
1184// External commands
1185#define GIP_CMD_RUMBLE 0x09
1186#define GIP_CMD_UNMAPPED_STATE 0x0C
1187#define GIP_CMD_INPUT 0x20
1188
1189// Header option flags
1190#define GIP_OPT_ACKNOWLEDGE 0x10
1191#define GIP_OPT_INTERNAL 0x20
1192#define GIP_OPT_CHUNK_START 0x40
1193#define GIP_OPT_CHUNK 0x80
1194
1195#pragma pack(push, 1)
1196
1197struct gip_header {
1198 Uint8 command;
1199 Uint8 options;
1200 Uint8 sequence;
1201 Uint32 packet_length;
1202 Uint32 chunk_offset;
1203};
1204
1205struct gip_pkt_acknowledge {
1206 Uint8 unknown;
1207 Uint8 command;
1208 Uint8 options;
1209 Uint16 length;
1210 Uint8 padding[2];
1211 Uint16 remaining;
1212};
1213
1214#pragma pack(pop)
1215
1216static int EncodeVariableInt(Uint8 *buf, Uint32 val)
1217{
1218 int i;
1219
1220 for (i = 0; i < sizeof(val); i++) {
1221 buf[i] = (Uint8)val;
1222 if (val > 0x7F) {
1223 buf[i] |= 0x80;
1224 }
1225
1226 val >>= 7;
1227 if (!val) {
1228 break;
1229 }
1230 }
1231 return i + 1;
1232}
1233
1234static int DecodeVariableInt(const Uint8 *data, int len, void *out)
1235{
1236 int i;
1237 Uint32 val = 0;
1238
1239 for (i = 0; i < sizeof(val) && i < len; i++) {
1240 val |= (data[i] & 0x7F) << (i * 7);
1241
1242 if (!(data[i] & 0x80)) {
1243 break;
1244 }
1245 }
1246 SDL_memcpy(out, &val, sizeof(val));
1247 return i + 1;
1248}
1249
1250static int HIDAPI_GIP_GetActualHeaderLength(struct gip_header *hdr)
1251{
1252 Uint32 pkt_len = hdr->packet_length;
1253 Uint32 chunk_offset = hdr->chunk_offset;
1254 int len = GIP_HEADER_MIN_LENGTH;
1255
1256 do {
1257 len++;
1258 pkt_len >>= 7;
1259 } while (pkt_len);
1260
1261 if (hdr->options & GIP_OPT_CHUNK) {
1262 while (chunk_offset) {
1263 len++;
1264 chunk_offset >>= 7;
1265 }
1266 }
1267
1268 return len;
1269}
1270
1271static int HIDAPI_GIP_GetHeaderLength(struct gip_header *hdr)
1272{
1273 int len = HIDAPI_GIP_GetActualHeaderLength(hdr);
1274
1275 // Header length must be even
1276 return len + (len % 2);
1277}
1278
1279static void HIDAPI_GIP_EncodeHeader(struct gip_header *hdr, Uint8 *buf)
1280{
1281 int hdr_len = 0;
1282
1283 buf[hdr_len++] = hdr->command;
1284 buf[hdr_len++] = hdr->options;
1285 buf[hdr_len++] = hdr->sequence;
1286
1287 hdr_len += EncodeVariableInt(buf + hdr_len, hdr->packet_length);
1288
1289 // Header length must be even
1290 if (HIDAPI_GIP_GetActualHeaderLength(hdr) % 2) {
1291 buf[hdr_len - 1] |= 0x80;
1292 buf[hdr_len++] = 0;
1293 }
1294
1295 if (hdr->options & GIP_OPT_CHUNK) {
1296 EncodeVariableInt(buf + hdr_len, hdr->chunk_offset);
1297 }
1298}
1299
1300static int HIDAPI_GIP_DecodeHeader(struct gip_header *hdr, const Uint8 *data, int len)
1301{
1302 int hdr_len = 0;
1303
1304 hdr->command = data[hdr_len++];
1305 hdr->options = data[hdr_len++];
1306 hdr->sequence = data[hdr_len++];
1307 hdr->packet_length = 0;
1308 hdr->chunk_offset = 0;
1309
1310 hdr_len += DecodeVariableInt(data + hdr_len, len - hdr_len, &hdr->packet_length);
1311
1312 if (hdr->options & GIP_OPT_CHUNK) {
1313 hdr_len += DecodeVariableInt(data + hdr_len, len - hdr_len, &hdr->chunk_offset);
1314 }
1315 return hdr_len;
1316}
1317
1318static bool HIDAPI_GIP_SendPacket(SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, const void *data)
1319{
1320 Uint8 packet[USB_PACKET_LENGTH];
1321 int hdr_len, size;
1322
1323 hdr_len = HIDAPI_GIP_GetHeaderLength(hdr);
1324 size = (hdr_len + hdr->packet_length);
1325 if (size > sizeof(packet)) {
1326 SDL_SetError("Couldn't send GIP packet, size (%d) too large", size);
1327 return false;
1328 }
1329
1330 if (!hdr->sequence) {
1331 hdr->sequence = GetNextPacketSequence(ctx);
1332 }
1333
1334 HIDAPI_GIP_EncodeHeader(hdr, packet);
1335 if (data) {
1336 SDL_memcpy(&packet[hdr_len], data, hdr->packet_length);
1337 }
1338
1339 if (!SendProtocolPacket(ctx, packet, size)) {
1340 SDL_SetError("Couldn't send protocol packet");
1341 return false;
1342 }
1343 return true;
1344}
1345
1346static bool HIDAPI_GIP_AcknowledgePacket(SDL_DriverXboxOne_Context *ctx, struct gip_header *ack)
1347{
1348 if (XBOX_ONE_DRIVER_ACTIVE) {
1349 // The driver is taking care of acks
1350 return true;
1351 } else {
1352 struct gip_header hdr;
1353 struct gip_pkt_acknowledge pkt;
1354
1355 SDL_zero(hdr);
1356 hdr.command = GIP_CMD_ACKNOWLEDGE;
1357 hdr.options = GIP_OPT_INTERNAL;
1358 hdr.sequence = ack->sequence;
1359 hdr.packet_length = sizeof(pkt);
1360
1361 SDL_zero(pkt);
1362 pkt.command = ack->command;
1363 pkt.options = GIP_OPT_INTERNAL;
1364 pkt.length = SDL_Swap16LE((Uint16)(ack->chunk_offset + ack->packet_length));
1365
1366 if ((ack->options & GIP_OPT_CHUNK) && ctx->chunk_buffer) {
1367 pkt.remaining = SDL_Swap16LE((Uint16)(ctx->chunk_length - pkt.length));
1368 }
1369
1370 return HIDAPI_GIP_SendPacket(ctx, &hdr, &pkt);
1371 }
1372}
1373
1374static bool HIDAPI_GIP_DispatchPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data, Uint32 size)
1375{
1376 if ((hdr->options & 0x0F) != 0) {
1377 // This is a packet for a device plugged into the controller, skip it
1378 return true;
1379 }
1380
1381 if (hdr->options & GIP_OPT_INTERNAL) {
1382 switch (hdr->command) {
1383 case GIP_CMD_ACKNOWLEDGE:
1384 // Ignore this packet
1385 break;
1386 case GIP_CMD_ANNOUNCE:
1387 // Controller is connected and waiting for initialization
1388 /* The data bytes are:
1389 0x02 0x20 NN 0x1c, where NN is the packet sequence
1390 then 6 bytes of wireless MAC address
1391 then 2 bytes padding
1392 then 16-bit VID
1393 then 16-bit PID
1394 then 16-bit firmware version quartet AA.BB.CC.DD
1395 e.g. 0x05 0x00 0x05 0x00 0x51 0x0a 0x00 0x00
1396 is firmware version 5.5.2641.0, and product version 0x0505 = 1285
1397 then 8 bytes of unknown data
1398 */
1399#ifdef DEBUG_JOYSTICK
1400 SDL_Log("Controller announce after %llu ms", (SDL_GetTicks() - ctx->start_time));
1401#endif
1402 SetInitState(ctx, XBOX_ONE_INIT_STATE_ANNOUNCED);
1403 break;
1404 case GIP_CMD_STATUS:
1405 // Controller status update
1406 HIDAPI_DriverXboxOne_HandleStatusPacket(ctx, data, size);
1407 break;
1408 case GIP_CMD_IDENTIFY:
1409#ifdef DEBUG_JOYSTICK
1410 SDL_Log("Identification request completed after %llu ms", (SDL_GetTicks() - ctx->send_time));
1411#endif
1412#ifdef DEBUG_XBOX_PROTOCOL
1413 HIDAPI_DumpPacket("Xbox One identification data: size = %d", data, size);
1414#endif
1415 SetInitState(ctx, XBOX_ONE_INIT_STATE_STARTUP);
1416 break;
1417 case GIP_CMD_POWER:
1418 // Ignore this packet
1419 break;
1420 case GIP_CMD_AUTHENTICATE:
1421 // Ignore this packet
1422 break;
1423 case GIP_CMD_VIRTUAL_KEY:
1424 if (!joystick) {
1425 break;
1426 }
1427 HIDAPI_DriverXboxOne_HandleModePacket(joystick, ctx, data, size);
1428 break;
1429 case GIP_CMD_SERIAL_NUMBER:
1430 /* If the packet starts with this:
1431 0x1E 0x30 0x00 0x10 0x04 0x00
1432 then the next 14 bytes are the controller serial number
1433 e.g. 0x30 0x39 0x37 0x31 0x32 0x33 0x33 0x32 0x33 0x35 0x34 0x30 0x33 0x36
1434 is serial number "3039373132333332333534303336"
1435
1436 The controller sends that in response to this request:
1437 0x1E 0x20 0x00 0x01 0x04
1438 */
1439 HIDAPI_DriverXboxOne_HandleSerialIDPacket(ctx, data, size);
1440 break;
1441 default:
1442#ifdef DEBUG_JOYSTICK
1443 SDL_Log("Unknown Xbox One packet: 0x%.2x", hdr->command);
1444#endif
1445 break;
1446 }
1447 } else {
1448 switch (hdr->command) {
1449 case GIP_CMD_INPUT:
1450 if (ctx->init_state < XBOX_ONE_INIT_STATE_COMPLETE) {
1451 SetInitState(ctx, XBOX_ONE_INIT_STATE_COMPLETE);
1452
1453 // Ignore the first input, it may be spurious
1454#ifdef DEBUG_JOYSTICK
1455 SDL_Log("Controller ignoring spurious input");
1456#endif
1457 break;
1458 }
1459 if (!joystick) {
1460 break;
1461 }
1462 HIDAPI_DriverXboxOne_HandleStatePacket(joystick, ctx, data, size);
1463 break;
1464 case GIP_CMD_UNMAPPED_STATE:
1465 if (!joystick) {
1466 break;
1467 }
1468 HIDAPI_DriverXboxOne_HandleUnmappedStatePacket(joystick, ctx, data, size);
1469 break;
1470 default:
1471#ifdef DEBUG_JOYSTICK
1472 SDL_Log("Unknown Xbox One packet: 0x%.2x", hdr->command);
1473#endif
1474 break;
1475 }
1476 }
1477 return true;
1478}
1479
1480static void HIDAPI_GIP_DestroyChunkBuffer(SDL_DriverXboxOne_Context *ctx)
1481{
1482 if (ctx->chunk_buffer) {
1483 SDL_free(ctx->chunk_buffer);
1484 ctx->chunk_buffer = NULL;
1485 ctx->chunk_length = 0;
1486 }
1487}
1488
1489static bool HIDAPI_GIP_CreateChunkBuffer(SDL_DriverXboxOne_Context *ctx, Uint32 size)
1490{
1491 HIDAPI_GIP_DestroyChunkBuffer(ctx);
1492
1493 ctx->chunk_buffer = (Uint8 *)SDL_malloc(size);
1494 if (ctx->chunk_buffer) {
1495 ctx->chunk_length = size;
1496 return true;
1497 } else {
1498 return false;
1499 }
1500}
1501
1502static bool HIDAPI_GIP_ProcessPacketChunked(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data)
1503{
1504 bool result;
1505
1506 if (!ctx->chunk_buffer) {
1507 return false;
1508 }
1509
1510 if ((hdr->chunk_offset + hdr->packet_length) > ctx->chunk_length) {
1511 return false;
1512 }
1513
1514 if (hdr->packet_length) {
1515 SDL_memcpy(ctx->chunk_buffer + hdr->chunk_offset, data, hdr->packet_length);
1516 return true;
1517 }
1518
1519 result = HIDAPI_GIP_DispatchPacket(joystick, ctx, hdr, ctx->chunk_buffer, ctx->chunk_length);
1520
1521 HIDAPI_GIP_DestroyChunkBuffer(ctx);
1522
1523 return result;
1524}
1525
1526static bool HIDAPI_GIP_ProcessPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, struct gip_header *hdr, Uint8 *data)
1527{
1528 if (hdr->options & GIP_OPT_CHUNK_START) {
1529 if (!HIDAPI_GIP_CreateChunkBuffer(ctx, hdr->chunk_offset)) {
1530 return false;
1531 }
1532 ctx->chunk_length = hdr->chunk_offset;
1533
1534 hdr->chunk_offset = 0;
1535 }
1536
1537 if (hdr->options & GIP_OPT_ACKNOWLEDGE) {
1538 if (!HIDAPI_GIP_AcknowledgePacket(ctx, hdr)) {
1539 return false;
1540 }
1541 }
1542
1543 if (hdr->options & GIP_OPT_CHUNK) {
1544 return HIDAPI_GIP_ProcessPacketChunked(joystick, ctx, hdr, data);
1545 } else {
1546 return HIDAPI_GIP_DispatchPacket(joystick, ctx, hdr, data, hdr->packet_length);
1547 }
1548}
1549
1550static bool HIDAPI_GIP_ProcessData(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)
1551{
1552 struct gip_header hdr;
1553 int hdr_len;
1554
1555 while (size > GIP_HEADER_MIN_LENGTH) {
1556 hdr_len = HIDAPI_GIP_DecodeHeader(&hdr, data, size);
1557 if ((hdr_len + hdr.packet_length) > (Uint32)size) {
1558 // On macOS we get a shortened version of the real report
1559 hdr.packet_length = (Uint32)(size - hdr_len);
1560 }
1561
1562 if (!HIDAPI_GIP_ProcessPacket(joystick, ctx, &hdr, data + hdr_len)) {
1563 return false;
1564 }
1565
1566 data += hdr_len + hdr.packet_length;
1567 size -= hdr_len + hdr.packet_length;
1568 }
1569 return true;
1570}
1571
1572static bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device)
1573{
1574 SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
1575 SDL_Joystick *joystick = NULL;
1576 Uint8 data[USB_PACKET_LENGTH];
1577 int size;
1578
1579 if (device->num_joysticks > 0) {
1580 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1581 } else {
1582 return false;
1583 }
1584
1585 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
1586#ifdef DEBUG_XBOX_PROTOCOL
1587 HIDAPI_DumpPacket("Xbox One packet: size = %d", data, size);
1588#endif
1589 if (device->is_bluetooth) {
1590 switch (data[0]) {
1591 case 0x01:
1592 if (!joystick) {
1593 break;
1594 }
1595 if (size >= 16) {
1596 HIDAPI_DriverXboxOneBluetooth_HandleStatePacket(joystick, ctx, data, size);
1597 } else {
1598#ifdef DEBUG_JOYSTICK
1599 SDL_Log("Unknown Xbox One Bluetooth packet size: %d", size);
1600#endif
1601 }
1602 break;
1603 case 0x02:
1604 if (!joystick) {
1605 break;
1606 }
1607 HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(joystick, ctx, data, size);
1608 break;
1609 case 0x04:
1610 if (!joystick) {
1611 break;
1612 }
1613 HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(joystick, ctx, data, size);
1614 break;
1615 default:
1616#ifdef DEBUG_JOYSTICK
1617 SDL_Log("Unknown Xbox One packet: 0x%.2x", data[0]);
1618#endif
1619 break;
1620 }
1621 } else {
1622 HIDAPI_GIP_ProcessData(joystick, ctx, data, size);
1623 }
1624 }
1625
1626 HIDAPI_DriverXboxOne_UpdateInitState(ctx);
1627 HIDAPI_DriverXboxOne_UpdateRumble(ctx);
1628
1629 if (size < 0) {
1630 // Read error, device is disconnected
1631 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1632 }
1633 return (size >= 0);
1634}
1635
1636static void HIDAPI_DriverXboxOne_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1637{
1638 SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
1639
1640 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED,
1641 SDL_HomeLEDHintChanged, ctx);
1642}
1643
1644static void HIDAPI_DriverXboxOne_FreeDevice(SDL_HIDAPI_Device *device)
1645{
1646 SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
1647
1648 HIDAPI_GIP_DestroyChunkBuffer(ctx);
1649}
1650
1651SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne = {
1652 SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE,
1653 true,
1654 HIDAPI_DriverXboxOne_RegisterHints,
1655 HIDAPI_DriverXboxOne_UnregisterHints,
1656 HIDAPI_DriverXboxOne_IsEnabled,
1657 HIDAPI_DriverXboxOne_IsSupportedDevice,
1658 HIDAPI_DriverXboxOne_InitDevice,
1659 HIDAPI_DriverXboxOne_GetDevicePlayerIndex,
1660 HIDAPI_DriverXboxOne_SetDevicePlayerIndex,
1661 HIDAPI_DriverXboxOne_UpdateDevice,
1662 HIDAPI_DriverXboxOne_OpenJoystick,
1663 HIDAPI_DriverXboxOne_RumbleJoystick,
1664 HIDAPI_DriverXboxOne_RumbleJoystickTriggers,
1665 HIDAPI_DriverXboxOne_GetJoystickCapabilities,
1666 HIDAPI_DriverXboxOne_SetJoystickLED,
1667 HIDAPI_DriverXboxOne_SendJoystickEffect,
1668 HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled,
1669 HIDAPI_DriverXboxOne_CloseJoystick,
1670 HIDAPI_DriverXboxOne_FreeDevice,
1671};
1672
1673#endif // SDL_JOYSTICK_HIDAPI_XBOXONE
1674
1675#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick.c b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick.c
new file mode 100644
index 0000000..aec6463
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -0,0 +1,1730 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../SDL_sysjoystick.h"
26#include "SDL_hidapijoystick_c.h"
27#include "SDL_hidapi_rumble.h"
28#include "../../SDL_hints_c.h"
29
30#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)
31#include "../windows/SDL_rawinputjoystick_c.h"
32#endif
33
34
35struct joystick_hwdata
36{
37 SDL_HIDAPI_Device *device;
38};
39
40static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = {
41#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
42 &SDL_HIDAPI_DriverGameCube,
43#endif
44#ifdef SDL_JOYSTICK_HIDAPI_LUNA
45 &SDL_HIDAPI_DriverLuna,
46#endif
47#ifdef SDL_JOYSTICK_HIDAPI_SHIELD
48 &SDL_HIDAPI_DriverShield,
49#endif
50#ifdef SDL_JOYSTICK_HIDAPI_PS3
51 &SDL_HIDAPI_DriverPS3,
52 &SDL_HIDAPI_DriverPS3ThirdParty,
53 &SDL_HIDAPI_DriverPS3SonySixaxis,
54#endif
55#ifdef SDL_JOYSTICK_HIDAPI_PS4
56 &SDL_HIDAPI_DriverPS4,
57#endif
58#ifdef SDL_JOYSTICK_HIDAPI_PS5
59 &SDL_HIDAPI_DriverPS5,
60#endif
61#ifdef SDL_JOYSTICK_HIDAPI_STADIA
62 &SDL_HIDAPI_DriverStadia,
63#endif
64#ifdef SDL_JOYSTICK_HIDAPI_STEAM
65 &SDL_HIDAPI_DriverSteam,
66#endif
67#ifdef SDL_JOYSTICK_HIDAPI_STEAM_HORI
68 &SDL_HIDAPI_DriverSteamHori,
69#endif
70#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK
71 &SDL_HIDAPI_DriverSteamDeck,
72#endif
73#ifdef SDL_JOYSTICK_HIDAPI_SWITCH
74 &SDL_HIDAPI_DriverNintendoClassic,
75 &SDL_HIDAPI_DriverJoyCons,
76 &SDL_HIDAPI_DriverSwitch,
77#endif
78#ifdef SDL_JOYSTICK_HIDAPI_WII
79 &SDL_HIDAPI_DriverWii,
80#endif
81#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
82 &SDL_HIDAPI_DriverXbox360,
83 &SDL_HIDAPI_DriverXbox360W,
84#endif
85#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE
86 &SDL_HIDAPI_DriverXboxOne,
87#endif
88};
89static int SDL_HIDAPI_numdrivers = 0;
90static SDL_AtomicInt SDL_HIDAPI_updating_devices;
91static bool SDL_HIDAPI_hints_changed = false;
92static Uint32 SDL_HIDAPI_change_count = 0;
93static SDL_HIDAPI_Device *SDL_HIDAPI_devices SDL_GUARDED_BY(SDL_joystick_lock);
94static int SDL_HIDAPI_numjoysticks = 0;
95static bool SDL_HIDAPI_combine_joycons = true;
96static bool initialized = false;
97static bool shutting_down = false;
98
99static char *HIDAPI_ConvertString(const wchar_t *wide_string)
100{
101 char *string = NULL;
102
103 if (wide_string) {
104 string = SDL_iconv_string("UTF-8", "WCHAR_T", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t));
105 if (!string) {
106 switch (sizeof(wchar_t)) {
107 case 2:
108 string = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t));
109 break;
110 case 4:
111 string = SDL_iconv_string("UTF-8", "UCS-4-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t));
112 break;
113 }
114 }
115 }
116 return string;
117}
118
119void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size)
120{
121 int i;
122 char *buffer;
123 size_t length = SDL_strlen(prefix) + 11 * (size / 8) + (5 * size * 2) + 1 + 1;
124 int start = 0, amount = size;
125 size_t current_len;
126
127 buffer = (char *)SDL_malloc(length);
128 current_len = SDL_snprintf(buffer, length, prefix, size);
129 for (i = start; i < start + amount; ++i) {
130 if ((i % 8) == 0) {
131 current_len += SDL_snprintf(&buffer[current_len], length - current_len, "\n%.2d: ", i);
132 }
133 current_len += SDL_snprintf(&buffer[current_len], length - current_len, " 0x%.2x", data[i]);
134 }
135 SDL_strlcat(buffer, "\n", length);
136 SDL_Log("%s", buffer);
137 SDL_free(buffer);
138}
139
140bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product)
141{
142 /* If we already know the controller is a different type, don't try to detect it.
143 * This fixes a hang with the HORIPAD for Nintendo Switch (0x0f0d/0x00c1)
144 */
145 if (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, false) != SDL_GAMEPAD_TYPE_STANDARD) {
146 return false;
147 }
148
149 switch (vendor) {
150 case USB_VENDOR_DRAGONRISE:
151 return true;
152 case USB_VENDOR_HORI:
153 return true;
154 case USB_VENDOR_LOGITECH:
155 /* Most Logitech devices are not PlayStation controllers, and some of them
156 * lock up or reset when we send them the Sony third-party query feature
157 * report, so don't include that vendor here. Instead add devices as
158 * appropriate to controller_list.h
159 */
160 return false;
161 case USB_VENDOR_MADCATZ:
162 if (product == USB_PRODUCT_MADCATZ_SAITEK_SIDE_PANEL_CONTROL_DECK) {
163 // This is not a Playstation compatible device
164 return false;
165 }
166 return true;
167 case USB_VENDOR_MAYFLASH:
168 return true;
169 case USB_VENDOR_NACON:
170 case USB_VENDOR_NACON_ALT:
171 return true;
172 case USB_VENDOR_PDP:
173 return true;
174 case USB_VENDOR_POWERA:
175 return true;
176 case USB_VENDOR_POWERA_ALT:
177 return true;
178 case USB_VENDOR_QANBA:
179 return true;
180 case USB_VENDOR_RAZER:
181 /* Most Razer devices are not PlayStation controllers, and some of them
182 * lock up or reset when we send them the Sony third-party query feature
183 * report, so don't include that vendor here. Instead add devices as
184 * appropriate to controller_list.h
185 *
186 * Reference: https://github.com/libsdl-org/SDL/issues/6733
187 * https://github.com/libsdl-org/SDL/issues/6799
188 */
189 return false;
190 case USB_VENDOR_SHANWAN:
191 return true;
192 case USB_VENDOR_SHANWAN_ALT:
193 return true;
194 case USB_VENDOR_THRUSTMASTER:
195 /* Most of these are wheels, don't have the full set of effects, and
196 * at least in the case of the T248 and T300 RS, the hid-tmff2 driver
197 * puts them in a non-standard report mode and they can't be read.
198 *
199 * If these should use the HIDAPI driver, add them to controller_list.h
200 */
201 return false;
202 case USB_VENDOR_ZEROPLUS:
203 return true;
204 case 0x7545 /* SZ-MYPOWER */:
205 return true;
206 default:
207 return false;
208 }
209}
210
211float HIDAPI_RemapVal(float val, float val_min, float val_max, float output_min, float output_max)
212{
213 return output_min + (output_max - output_min) * (val - val_min) / (val_max - val_min);
214}
215
216static void HIDAPI_UpdateDeviceList(void);
217static void HIDAPI_JoystickClose(SDL_Joystick *joystick);
218
219static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, Uint16 vendor, Uint16 product, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
220{
221 static const int LIBUSB_CLASS_VENDOR_SPEC = 0xFF;
222 static const int XB360_IFACE_SUBCLASS = 93;
223 static const int XB360_IFACE_PROTOCOL = 1; // Wired
224 static const int XB360W_IFACE_PROTOCOL = 129; // Wireless
225 static const int XBONE_IFACE_SUBCLASS = 71;
226 static const int XBONE_IFACE_PROTOCOL = 208;
227
228 SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD;
229
230 // This code should match the checks in libusb/hid.c and HIDDeviceManager.java
231 if (interface_class == LIBUSB_CLASS_VENDOR_SPEC &&
232 interface_subclass == XB360_IFACE_SUBCLASS &&
233 (interface_protocol == XB360_IFACE_PROTOCOL ||
234 interface_protocol == XB360W_IFACE_PROTOCOL)) {
235
236 static const int SUPPORTED_VENDORS[] = {
237 0x0079, // GPD Win 2
238 0x044f, // Thrustmaster
239 0x045e, // Microsoft
240 0x046d, // Logitech
241 0x056e, // Elecom
242 0x06a3, // Saitek
243 0x0738, // Mad Catz
244 0x07ff, // Mad Catz
245 0x0e6f, // PDP
246 0x0f0d, // Hori
247 0x1038, // SteelSeries
248 0x11c9, // Nacon
249 0x12ab, // Unknown
250 0x1430, // RedOctane
251 0x146b, // BigBen
252 0x1532, // Razer
253 0x15e4, // Numark
254 0x162e, // Joytech
255 0x1689, // Razer Onza
256 0x1949, // Lab126, Inc.
257 0x1bad, // Harmonix
258 0x20d6, // PowerA
259 0x24c6, // PowerA
260 0x2c22, // Qanba
261 0x2dc8, // 8BitDo
262 0x9886, // ASTRO Gaming
263 };
264
265 int i;
266 for (i = 0; i < SDL_arraysize(SUPPORTED_VENDORS); ++i) {
267 if (vendor == SUPPORTED_VENDORS[i]) {
268 type = SDL_GAMEPAD_TYPE_XBOX360;
269 break;
270 }
271 }
272 }
273
274 if (interface_number == 0 &&
275 interface_class == LIBUSB_CLASS_VENDOR_SPEC &&
276 interface_subclass == XBONE_IFACE_SUBCLASS &&
277 interface_protocol == XBONE_IFACE_PROTOCOL) {
278
279 static const int SUPPORTED_VENDORS[] = {
280 0x03f0, // HP
281 0x044f, // Thrustmaster
282 0x045e, // Microsoft
283 0x0738, // Mad Catz
284 0x0b05, // ASUS
285 0x0e6f, // PDP
286 0x0f0d, // Hori
287 0x10f5, // Turtle Beach
288 0x1532, // Razer
289 0x20d6, // PowerA
290 0x24c6, // PowerA
291 0x2dc8, // 8BitDo
292 0x2e24, // Hyperkin
293 0x3537, // GameSir
294 };
295
296 int i;
297 for (i = 0; i < SDL_arraysize(SUPPORTED_VENDORS); ++i) {
298 if (vendor == SUPPORTED_VENDORS[i]) {
299 type = SDL_GAMEPAD_TYPE_XBOXONE;
300 break;
301 }
302 }
303 }
304
305 if (type == SDL_GAMEPAD_TYPE_STANDARD) {
306 type = SDL_GetGamepadTypeFromVIDPID(vendor, product, name, false);
307 }
308 return type;
309}
310
311static bool HIDAPI_IsDeviceSupported(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
312{
313 int i;
314 SDL_GamepadType type = SDL_GetJoystickGameControllerProtocol(name, vendor_id, product_id, -1, 0, 0, 0);
315
316 for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) {
317 SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i];
318 if (driver->enabled && driver->IsSupportedDevice(NULL, name, type, vendor_id, product_id, version, -1, 0, 0, 0)) {
319 return true;
320 }
321 }
322 return false;
323}
324
325static SDL_HIDAPI_DeviceDriver *HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device)
326{
327 const Uint16 USAGE_PAGE_GENERIC_DESKTOP = 0x0001;
328 const Uint16 USAGE_JOYSTICK = 0x0004;
329 const Uint16 USAGE_GAMEPAD = 0x0005;
330 const Uint16 USAGE_MULTIAXISCONTROLLER = 0x0008;
331 int i;
332
333 if (device->num_children > 0) {
334 return &SDL_HIDAPI_DriverCombined;
335 }
336
337 if (SDL_ShouldIgnoreJoystick(device->vendor_id, device->product_id, device->version, device->name)) {
338 return NULL;
339 }
340
341 if (device->vendor_id != USB_VENDOR_VALVE) {
342 if (device->usage_page && device->usage_page != USAGE_PAGE_GENERIC_DESKTOP) {
343 return NULL;
344 }
345 if (device->usage && device->usage != USAGE_JOYSTICK && device->usage != USAGE_GAMEPAD && device->usage != USAGE_MULTIAXISCONTROLLER) {
346 return NULL;
347 }
348 }
349
350 for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) {
351 SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i];
352 if (driver->enabled && driver->IsSupportedDevice(device, device->name, device->type, device->vendor_id, device->product_id, device->version, device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol)) {
353 return driver;
354 }
355 }
356 return NULL;
357}
358
359static SDL_HIDAPI_Device *HIDAPI_GetDeviceByIndex(int device_index, SDL_JoystickID *pJoystickID)
360{
361 SDL_HIDAPI_Device *device;
362
363 SDL_AssertJoysticksLocked();
364
365 for (device = SDL_HIDAPI_devices; device; device = device->next) {
366 if (device->parent || device->broken) {
367 continue;
368 }
369 if (device->driver) {
370 if (device_index < device->num_joysticks) {
371 if (pJoystickID) {
372 *pJoystickID = device->joysticks[device_index];
373 }
374 return device;
375 }
376 device_index -= device->num_joysticks;
377 }
378 }
379 return NULL;
380}
381
382static SDL_HIDAPI_Device *HIDAPI_GetJoystickByInfo(const char *path, Uint16 vendor_id, Uint16 product_id)
383{
384 SDL_HIDAPI_Device *device;
385
386 SDL_AssertJoysticksLocked();
387
388 for (device = SDL_HIDAPI_devices; device; device = device->next) {
389 if (device->vendor_id == vendor_id && device->product_id == product_id &&
390 SDL_strcmp(device->path, path) == 0) {
391 break;
392 }
393 }
394 return device;
395}
396
397static void HIDAPI_CleanupDeviceDriver(SDL_HIDAPI_Device *device)
398{
399 if (!device->driver) {
400 return; // Already cleaned up
401 }
402
403 // Disconnect any joysticks
404 while (device->num_joysticks && device->joysticks) {
405 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
406 }
407
408 device->driver->FreeDevice(device);
409 device->driver = NULL;
410
411 SDL_LockMutex(device->dev_lock);
412 {
413 if (device->dev) {
414 SDL_hid_close(device->dev);
415 device->dev = NULL;
416 }
417
418 if (device->context) {
419 SDL_free(device->context);
420 device->context = NULL;
421 }
422 }
423 SDL_UnlockMutex(device->dev_lock);
424}
425
426static void HIDAPI_SetupDeviceDriver(SDL_HIDAPI_Device *device, bool *removed) SDL_NO_THREAD_SAFETY_ANALYSIS // We unlock the joystick lock to be able to open the HID device on Android
427{
428 *removed = false;
429
430 if (device->driver) {
431 bool enabled;
432
433 if (device->vendor_id == USB_VENDOR_NINTENDO && device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) {
434 enabled = SDL_HIDAPI_combine_joycons;
435 } else {
436 enabled = device->driver->enabled;
437 }
438 if (device->children) {
439 int i;
440
441 for (i = 0; i < device->num_children; ++i) {
442 SDL_HIDAPI_Device *child = device->children[i];
443 if (!child->driver || !child->driver->enabled) {
444 enabled = false;
445 break;
446 }
447 }
448 }
449 if (!enabled) {
450 HIDAPI_CleanupDeviceDriver(device);
451 }
452 return; // Already setup
453 }
454
455 if (HIDAPI_GetDeviceDriver(device)) {
456 // We might have a device driver for this device, try opening it and see
457 if (device->num_children == 0) {
458 SDL_hid_device *dev;
459
460 // Wait a little bit for the device to initialize
461 SDL_Delay(10);
462
463 dev = SDL_hid_open_path(device->path);
464
465 if (dev == NULL) {
466 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
467 "HIDAPI_SetupDeviceDriver() couldn't open %s: %s",
468 device->path, SDL_GetError());
469 return;
470 }
471 SDL_hid_set_nonblocking(dev, 1);
472
473 device->dev = dev;
474 }
475
476 device->driver = HIDAPI_GetDeviceDriver(device);
477
478 // Initialize the device, which may cause a connected event
479 if (device->driver && !device->driver->InitDevice(device)) {
480 HIDAPI_CleanupDeviceDriver(device);
481 }
482
483 if (!device->driver && device->dev) {
484 // No driver claimed this device, go ahead and close it
485 SDL_hid_close(device->dev);
486 device->dev = NULL;
487 }
488 }
489}
490
491static void SDL_HIDAPI_UpdateDrivers(void)
492{
493 int i;
494 SDL_HIDAPI_Device *device;
495 bool removed;
496
497 SDL_AssertJoysticksLocked();
498
499 SDL_HIDAPI_numdrivers = 0;
500 for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) {
501 SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i];
502 driver->enabled = driver->IsEnabled();
503 if (driver->enabled && driver != &SDL_HIDAPI_DriverCombined) {
504 ++SDL_HIDAPI_numdrivers;
505 }
506 }
507
508 removed = false;
509 do {
510 for (device = SDL_HIDAPI_devices; device; device = device->next) {
511 HIDAPI_SetupDeviceDriver(device, &removed);
512 if (removed) {
513 break;
514 }
515 }
516 } while (removed);
517}
518
519static void SDLCALL SDL_HIDAPIDriverHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
520{
521 if (SDL_strcmp(name, SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS) == 0) {
522 SDL_HIDAPI_combine_joycons = SDL_GetStringBoolean(hint, true);
523 }
524 SDL_HIDAPI_hints_changed = true;
525 SDL_HIDAPI_change_count = 0;
526}
527
528static bool HIDAPI_JoystickInit(void)
529{
530 int i;
531
532 if (initialized) {
533 return true;
534 }
535
536 if (SDL_hid_init() < 0) {
537 return SDL_SetError("Couldn't initialize hidapi");
538 }
539
540 for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) {
541 SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i];
542 driver->RegisterHints(SDL_HIDAPIDriverHintChanged, driver);
543 }
544 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS,
545 SDL_HIDAPIDriverHintChanged, NULL);
546 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI,
547 SDL_HIDAPIDriverHintChanged, NULL);
548
549 SDL_HIDAPI_change_count = SDL_hid_device_change_count();
550 HIDAPI_UpdateDeviceList();
551 HIDAPI_UpdateDevices();
552
553 initialized = true;
554
555 return true;
556}
557
558static bool HIDAPI_AddJoystickInstanceToDevice(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID)
559{
560 SDL_JoystickID *joysticks = (SDL_JoystickID *)SDL_realloc(device->joysticks, (device->num_joysticks + 1) * sizeof(*device->joysticks));
561 if (!joysticks) {
562 return false;
563 }
564
565 device->joysticks = joysticks;
566 device->joysticks[device->num_joysticks++] = joystickID;
567 return true;
568}
569
570static bool HIDAPI_DelJoystickInstanceFromDevice(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID)
571{
572 int i, size;
573
574 for (i = 0; i < device->num_joysticks; ++i) {
575 if (device->joysticks[i] == joystickID) {
576 size = (device->num_joysticks - i - 1) * sizeof(SDL_JoystickID);
577 SDL_memmove(&device->joysticks[i], &device->joysticks[i + 1], size);
578 --device->num_joysticks;
579 if (device->num_joysticks == 0) {
580 SDL_free(device->joysticks);
581 device->joysticks = NULL;
582 }
583 return true;
584 }
585 }
586 return false;
587}
588
589static bool HIDAPI_JoystickInstanceIsUnique(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID)
590{
591 if (device->parent && device->num_joysticks == 1 && device->parent->num_joysticks == 1 &&
592 device->joysticks[0] == device->parent->joysticks[0]) {
593 return false;
594 }
595 return true;
596}
597
598void HIDAPI_SetDeviceName(SDL_HIDAPI_Device *device, const char *name)
599{
600 if (name && *name && SDL_strcmp(name, device->name) != 0) {
601 SDL_free(device->name);
602 device->name = SDL_strdup(name);
603 SDL_SetJoystickGUIDCRC(&device->guid, SDL_crc16(0, name, SDL_strlen(name)));
604 }
605}
606
607void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 vendor_id, Uint16 product_id)
608{
609 // Don't set the device product ID directly, or we'll constantly re-enumerate this device
610 device->guid = SDL_CreateJoystickGUID(device->guid.data[0], vendor_id, product_id, device->version, device->manufacturer_string, device->product_string, 'h', 0);
611}
612
613static void HIDAPI_UpdateJoystickSerial(SDL_HIDAPI_Device *device)
614{
615 int i;
616
617 SDL_AssertJoysticksLocked();
618
619 for (i = 0; i < device->num_joysticks; ++i) {
620 SDL_Joystick *joystick = SDL_GetJoystickFromID(device->joysticks[i]);
621 if (joystick && device->serial) {
622 SDL_free(joystick->serial);
623 joystick->serial = SDL_strdup(device->serial);
624 }
625 }
626}
627
628static bool HIDAPI_SerialIsEmpty(SDL_HIDAPI_Device *device)
629{
630 bool all_zeroes = true;
631
632 if (device->serial) {
633 const char *serial = device->serial;
634 for (serial = device->serial; *serial; ++serial) {
635 if (*serial != '0') {
636 all_zeroes = false;
637 break;
638 }
639 }
640 }
641 return all_zeroes;
642}
643
644void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial)
645{
646 if (serial && *serial && (!device->serial || SDL_strcmp(serial, device->serial) != 0)) {
647 SDL_free(device->serial);
648 device->serial = SDL_strdup(serial);
649 HIDAPI_UpdateJoystickSerial(device);
650 }
651}
652
653static int wcstrcmp(const wchar_t *str1, const char *str2)
654{
655 int result;
656
657 while (1) {
658 result = (*str1 - *str2);
659 if (result != 0 || *str1 == 0) {
660 break;
661 }
662 ++str1;
663 ++str2;
664 }
665 return result;
666}
667
668static void HIDAPI_SetDeviceSerialW(SDL_HIDAPI_Device *device, const wchar_t *serial)
669{
670 if (serial && *serial && (!device->serial || wcstrcmp(serial, device->serial) != 0)) {
671 SDL_free(device->serial);
672 device->serial = HIDAPI_ConvertString(serial);
673 HIDAPI_UpdateJoystickSerial(device);
674 }
675}
676
677bool HIDAPI_HasConnectedUSBDevice(const char *serial)
678{
679 SDL_HIDAPI_Device *device;
680
681 SDL_AssertJoysticksLocked();
682
683 if (!serial) {
684 return false;
685 }
686
687 for (device = SDL_HIDAPI_devices; device; device = device->next) {
688 if (!device->driver || device->broken) {
689 continue;
690 }
691
692 if (device->is_bluetooth) {
693 continue;
694 }
695
696 if (device->serial && SDL_strcmp(serial, device->serial) == 0) {
697 return true;
698 }
699 }
700 return false;
701}
702
703void HIDAPI_DisconnectBluetoothDevice(const char *serial)
704{
705 SDL_HIDAPI_Device *device;
706
707 SDL_AssertJoysticksLocked();
708
709 if (!serial) {
710 return;
711 }
712
713 for (device = SDL_HIDAPI_devices; device; device = device->next) {
714 if (!device->driver || device->broken) {
715 continue;
716 }
717
718 if (!device->is_bluetooth) {
719 continue;
720 }
721
722 if (device->serial && SDL_strcmp(serial, device->serial) == 0) {
723 while (device->num_joysticks && device->joysticks) {
724 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
725 }
726 }
727 }
728}
729
730bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID)
731{
732 int i, j;
733 SDL_JoystickID joystickID;
734
735 SDL_AssertJoysticksLocked();
736
737 for (i = 0; i < device->num_children; ++i) {
738 SDL_HIDAPI_Device *child = device->children[i];
739 for (j = child->num_joysticks; j--;) {
740 HIDAPI_JoystickDisconnected(child, child->joysticks[j]);
741 }
742 }
743
744 joystickID = SDL_GetNextObjectID();
745 HIDAPI_AddJoystickInstanceToDevice(device, joystickID);
746
747 for (i = 0; i < device->num_children; ++i) {
748 SDL_HIDAPI_Device *child = device->children[i];
749 HIDAPI_AddJoystickInstanceToDevice(child, joystickID);
750 }
751
752 ++SDL_HIDAPI_numjoysticks;
753
754 SDL_PrivateJoystickAdded(joystickID);
755
756 if (pJoystickID) {
757 *pJoystickID = joystickID;
758 }
759 return true;
760}
761
762void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID)
763{
764 int i, j;
765
766 SDL_LockJoysticks();
767
768 if (!HIDAPI_JoystickInstanceIsUnique(device, joystickID)) {
769 // Disconnecting a child always disconnects the parent
770 device = device->parent;
771 }
772
773 for (i = 0; i < device->num_joysticks; ++i) {
774 if (device->joysticks[i] == joystickID) {
775 SDL_Joystick *joystick = SDL_GetJoystickFromID(joystickID);
776 if (joystick) {
777 HIDAPI_JoystickClose(joystick);
778 }
779
780 HIDAPI_DelJoystickInstanceFromDevice(device, joystickID);
781
782 for (j = 0; j < device->num_children; ++j) {
783 SDL_HIDAPI_Device *child = device->children[j];
784 HIDAPI_DelJoystickInstanceFromDevice(child, joystickID);
785 }
786
787 --SDL_HIDAPI_numjoysticks;
788
789 if (!shutting_down) {
790 SDL_PrivateJoystickRemoved(joystickID);
791 }
792 }
793 }
794
795 // Rescan the device list in case device state has changed
796 SDL_HIDAPI_change_count = 0;
797
798 SDL_UnlockJoysticks();
799}
800
801static void HIDAPI_UpdateJoystickProperties(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
802{
803 SDL_PropertiesID props = SDL_GetJoystickProperties(joystick);
804 Uint32 caps = device->driver->GetJoystickCapabilities(device, joystick);
805
806 if (caps & SDL_JOYSTICK_CAP_MONO_LED) {
807 SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN, true);
808 } else {
809 SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN, false);
810 }
811 if (caps & SDL_JOYSTICK_CAP_RGB_LED) {
812 SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true);
813 } else {
814 SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, false);
815 }
816 if (caps & SDL_JOYSTICK_CAP_PLAYER_LED) {
817 SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_PLAYER_LED_BOOLEAN, true);
818 } else {
819 SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_PLAYER_LED_BOOLEAN, false);
820 }
821 if (caps & SDL_JOYSTICK_CAP_RUMBLE) {
822 SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
823 } else {
824 SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, false);
825 }
826 if (caps & SDL_JOYSTICK_CAP_TRIGGER_RUMBLE) {
827 SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
828 } else {
829 SDL_SetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, false);
830 }
831}
832
833void HIDAPI_UpdateDeviceProperties(SDL_HIDAPI_Device *device)
834{
835 int i;
836
837 SDL_LockJoysticks();
838
839 for (i = 0; i < device->num_joysticks; ++i) {
840 SDL_Joystick *joystick = SDL_GetJoystickFromID(device->joysticks[i]);
841 if (joystick) {
842 HIDAPI_UpdateJoystickProperties(device, joystick);
843 }
844 }
845
846 SDL_UnlockJoysticks();
847}
848
849static int HIDAPI_JoystickGetCount(void)
850{
851 return SDL_HIDAPI_numjoysticks;
852}
853
854static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *info, int num_children, SDL_HIDAPI_Device **children)
855{
856 SDL_HIDAPI_Device *device;
857 SDL_HIDAPI_Device *curr, *last = NULL;
858 bool removed;
859 Uint16 bus;
860
861 SDL_AssertJoysticksLocked();
862
863 for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) {
864 }
865
866 device = (SDL_HIDAPI_Device *)SDL_calloc(1, sizeof(*device));
867 if (!device) {
868 return NULL;
869 }
870 SDL_SetObjectValid(device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK, true);
871 device->path = SDL_strdup(info->path);
872 if (!device->path) {
873 SDL_free(device);
874 return NULL;
875 }
876 device->seen = true;
877 device->vendor_id = info->vendor_id;
878 device->product_id = info->product_id;
879 device->version = info->release_number;
880 device->interface_number = info->interface_number;
881 device->interface_class = info->interface_class;
882 device->interface_subclass = info->interface_subclass;
883 device->interface_protocol = info->interface_protocol;
884 device->usage_page = info->usage_page;
885 device->usage = info->usage;
886 device->is_bluetooth = (info->bus_type == SDL_HID_API_BUS_BLUETOOTH);
887 device->dev_lock = SDL_CreateMutex();
888
889 // Need the device name before getting the driver to know whether to ignore this device
890 {
891 char *serial_number = HIDAPI_ConvertString(info->serial_number);
892
893 device->manufacturer_string = HIDAPI_ConvertString(info->manufacturer_string);
894 device->product_string = HIDAPI_ConvertString(info->product_string);
895 device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, device->manufacturer_string, device->product_string);
896
897 if (serial_number && *serial_number) {
898 device->serial = serial_number;
899 } else {
900 SDL_free(serial_number);
901 }
902
903 if (!device->name) {
904 SDL_free(device->manufacturer_string);
905 SDL_free(device->product_string);
906 SDL_free(device->serial);
907 SDL_free(device->path);
908 SDL_free(device);
909 return NULL;
910 }
911 }
912
913 if (info->bus_type == SDL_HID_API_BUS_BLUETOOTH) {
914 bus = SDL_HARDWARE_BUS_BLUETOOTH;
915 } else {
916 bus = SDL_HARDWARE_BUS_USB;
917 }
918 device->guid = SDL_CreateJoystickGUID(bus, device->vendor_id, device->product_id, device->version, device->manufacturer_string, device->product_string, 'h', 0);
919 device->joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
920 device->type = SDL_GetJoystickGameControllerProtocol(device->name, device->vendor_id, device->product_id, device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol);
921 device->steam_virtual_gamepad_slot = -1;
922
923 if (num_children > 0) {
924 int i;
925
926 device->num_children = num_children;
927 device->children = children;
928 for (i = 0; i < num_children; ++i) {
929 children[i]->parent = device;
930 }
931 }
932
933 // Add it to the list
934 if (last) {
935 last->next = device;
936 } else {
937 SDL_HIDAPI_devices = device;
938 }
939
940 removed = false;
941 HIDAPI_SetupDeviceDriver(device, &removed);
942 if (removed) {
943 return NULL;
944 }
945
946 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Added HIDAPI device '%s' VID 0x%.4x, PID 0x%.4x, bluetooth %d, version %d, serial %s, interface %d, interface_class %d, interface_subclass %d, interface_protocol %d, usage page 0x%.4x, usage 0x%.4x, path = %s, driver = %s (%s)", device->name, device->vendor_id, device->product_id, device->is_bluetooth, device->version,
947 device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage,
948 device->path, device->driver ? device->driver->name : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED");
949
950 return device;
951}
952
953static void HIDAPI_DelDevice(SDL_HIDAPI_Device *device)
954{
955 SDL_HIDAPI_Device *curr, *last;
956 int i;
957
958 SDL_AssertJoysticksLocked();
959
960 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Removing HIDAPI device '%s' VID 0x%.4x, PID 0x%.4x, bluetooth %d, version %d, serial %s, interface %d, interface_class %d, interface_subclass %d, interface_protocol %d, usage page 0x%.4x, usage 0x%.4x, path = %s, driver = %s (%s)", device->name, device->vendor_id, device->product_id, device->is_bluetooth, device->version,
961 device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage,
962 device->path, device->driver ? device->driver->name : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED");
963
964 for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) {
965 if (curr == device) {
966 if (last) {
967 last->next = curr->next;
968 } else {
969 SDL_HIDAPI_devices = curr->next;
970 }
971
972 HIDAPI_CleanupDeviceDriver(device);
973
974 // Make sure the rumble thread is done with this device
975 while (SDL_GetAtomicInt(&device->rumble_pending) > 0) {
976 SDL_Delay(10);
977 }
978
979 for (i = 0; i < device->num_children; ++i) {
980 device->children[i]->parent = NULL;
981 }
982
983 SDL_SetObjectValid(device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK, false);
984 SDL_DestroyMutex(device->dev_lock);
985 SDL_free(device->manufacturer_string);
986 SDL_free(device->product_string);
987 SDL_free(device->serial);
988 SDL_free(device->name);
989 SDL_free(device->path);
990 SDL_free(device->children);
991 SDL_free(device);
992 return;
993 }
994 }
995}
996
997static bool HIDAPI_CreateCombinedJoyCons(void)
998{
999 SDL_HIDAPI_Device *device, *combined;
1000 SDL_HIDAPI_Device *joycons[2] = { NULL, NULL };
1001
1002 SDL_AssertJoysticksLocked();
1003
1004 if (!SDL_HIDAPI_combine_joycons) {
1005 return false;
1006 }
1007
1008 for (device = SDL_HIDAPI_devices; device; device = device->next) {
1009 Uint16 vendor, product;
1010
1011 if (!device->driver) {
1012 // Unsupported device
1013 continue;
1014 }
1015 if (device->parent) {
1016 // This device is already part of a combined device
1017 continue;
1018 }
1019 if (device->broken) {
1020 // This device can't be used
1021 continue;
1022 }
1023
1024 SDL_GetJoystickGUIDInfo(device->guid, &vendor, &product, NULL, NULL);
1025
1026 if (!joycons[0] &&
1027 (SDL_IsJoystickNintendoSwitchJoyConLeft(vendor, product) ||
1028 (SDL_IsJoystickNintendoSwitchJoyConGrip(vendor, product) &&
1029 SDL_strstr(device->name, "(L)") != NULL))) {
1030 joycons[0] = device;
1031 }
1032 if (!joycons[1] &&
1033 (SDL_IsJoystickNintendoSwitchJoyConRight(vendor, product) ||
1034 (SDL_IsJoystickNintendoSwitchJoyConGrip(vendor, product) &&
1035 SDL_strstr(device->name, "(R)") != NULL))) {
1036 joycons[1] = device;
1037 }
1038 if (joycons[0] && joycons[1]) {
1039 SDL_hid_device_info info;
1040 SDL_HIDAPI_Device **children = (SDL_HIDAPI_Device **)SDL_malloc(2 * sizeof(SDL_HIDAPI_Device *));
1041 if (!children) {
1042 return false;
1043 }
1044 children[0] = joycons[0];
1045 children[1] = joycons[1];
1046
1047 SDL_zero(info);
1048 info.path = "nintendo_joycons_combined";
1049 info.vendor_id = USB_VENDOR_NINTENDO;
1050 info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
1051 info.interface_number = -1;
1052 info.usage_page = USB_USAGEPAGE_GENERIC_DESKTOP;
1053 info.usage = USB_USAGE_GENERIC_GAMEPAD;
1054 info.manufacturer_string = L"Nintendo";
1055 info.product_string = L"Switch Joy-Con (L/R)";
1056
1057 combined = HIDAPI_AddDevice(&info, 2, children);
1058 if (combined && combined->driver) {
1059 return true;
1060 } else {
1061 if (combined) {
1062 HIDAPI_DelDevice(combined);
1063 } else {
1064 SDL_free(children);
1065 }
1066 return false;
1067 }
1068 }
1069 }
1070 return false;
1071}
1072
1073static void HIDAPI_UpdateDeviceList(void)
1074{
1075 SDL_HIDAPI_Device *device;
1076 struct SDL_hid_device_info *devs, *info;
1077
1078 SDL_LockJoysticks();
1079
1080 if (SDL_HIDAPI_hints_changed) {
1081 SDL_HIDAPI_UpdateDrivers();
1082 SDL_HIDAPI_hints_changed = false;
1083 }
1084
1085 // Prepare the existing device list
1086 for (device = SDL_HIDAPI_devices; device; device = device->next) {
1087 if (device->children) {
1088 continue;
1089 }
1090 device->seen = false;
1091 }
1092
1093 // Enumerate the devices
1094 if (SDL_HIDAPI_numdrivers > 0) {
1095 devs = SDL_hid_enumerate(0, 0);
1096 if (devs) {
1097 for (info = devs; info; info = info->next) {
1098 device = HIDAPI_GetJoystickByInfo(info->path, info->vendor_id, info->product_id);
1099 if (device) {
1100 device->seen = true;
1101
1102 // Check to see if the serial number is available now
1103 if(HIDAPI_SerialIsEmpty(device)) {
1104 HIDAPI_SetDeviceSerialW(device, info->serial_number);
1105 }
1106 } else {
1107 HIDAPI_AddDevice(info, 0, NULL);
1108 }
1109 }
1110 SDL_hid_free_enumeration(devs);
1111 }
1112 }
1113
1114 // Remove any devices that weren't seen or have been disconnected due to read errors
1115check_removed:
1116 device = SDL_HIDAPI_devices;
1117 while (device) {
1118 SDL_HIDAPI_Device *next = device->next;
1119
1120 if (!device->seen ||
1121 ((device->driver || device->children) && device->num_joysticks == 0 && !device->dev)) {
1122 if (device->parent) {
1123 // When a child device goes away, so does the parent
1124 int i;
1125 device = device->parent;
1126 for (i = 0; i < device->num_children; ++i) {
1127 HIDAPI_DelDevice(device->children[i]);
1128 }
1129 HIDAPI_DelDevice(device);
1130
1131 // Update the device list again to pick up any children left
1132 SDL_HIDAPI_change_count = 0;
1133
1134 // We deleted more than one device here, restart the loop
1135 goto check_removed;
1136 } else {
1137 HIDAPI_DelDevice(device);
1138 device = NULL;
1139
1140 // Update the device list again in case this device comes back
1141 SDL_HIDAPI_change_count = 0;
1142 }
1143 }
1144 if (device && device->broken && device->parent) {
1145 HIDAPI_DelDevice(device->parent);
1146
1147 // We deleted a different device here, restart the loop
1148 goto check_removed;
1149 }
1150 device = next;
1151 }
1152
1153 // See if we can create any combined Joy-Con controllers
1154 while (HIDAPI_CreateCombinedJoyCons()) {
1155 }
1156
1157 SDL_UnlockJoysticks();
1158}
1159
1160static bool HIDAPI_IsEquivalentToDevice(Uint16 vendor_id, Uint16 product_id, SDL_HIDAPI_Device *device)
1161{
1162 if (vendor_id == device->vendor_id && product_id == device->product_id) {
1163 return true;
1164 }
1165
1166 if (vendor_id == USB_VENDOR_MICROSOFT) {
1167 // If we're looking for the wireless XBox 360 controller, also look for the dongle
1168 if (product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER && device->product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER) {
1169 return true;
1170 }
1171
1172 // If we're looking for the raw input Xbox One controller, match it against any other Xbox One controller
1173 if (product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER &&
1174 device->type == SDL_GAMEPAD_TYPE_XBOXONE) {
1175 return true;
1176 }
1177
1178 // If we're looking for an XInput controller, match it against any other Xbox controller
1179 if (product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER) {
1180 if (device->type == SDL_GAMEPAD_TYPE_XBOX360 || device->type == SDL_GAMEPAD_TYPE_XBOXONE) {
1181 return true;
1182 }
1183 }
1184 }
1185
1186 if (vendor_id == USB_VENDOR_NVIDIA) {
1187 // If we're looking for the NVIDIA SHIELD controller Xbox interface, match it against any NVIDIA SHIELD controller
1188 if (product_id == 0xb400 &&
1189 SDL_IsJoystickNVIDIASHIELDController(vendor_id, product_id)) {
1190 return true;
1191 }
1192 }
1193 return false;
1194}
1195
1196static bool HIDAPI_StartUpdatingDevices(void)
1197{
1198 return SDL_CompareAndSwapAtomicInt(&SDL_HIDAPI_updating_devices, false, true);
1199}
1200
1201static void HIDAPI_FinishUpdatingDevices(void)
1202{
1203 SDL_SetAtomicInt(&SDL_HIDAPI_updating_devices, false);
1204}
1205
1206bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type)
1207{
1208 SDL_HIDAPI_Device *device;
1209 bool result = false;
1210
1211 // Make sure we're initialized, as this could be called from other drivers during startup
1212 if (!HIDAPI_JoystickInit()) {
1213 return false;
1214 }
1215
1216 if (HIDAPI_StartUpdatingDevices()) {
1217 HIDAPI_UpdateDeviceList();
1218 HIDAPI_FinishUpdatingDevices();
1219 }
1220
1221 SDL_LockJoysticks();
1222 for (device = SDL_HIDAPI_devices; device; device = device->next) {
1223 if (device->driver && device->type == type) {
1224 result = true;
1225 break;
1226 }
1227 }
1228 SDL_UnlockJoysticks();
1229
1230#ifdef DEBUG_HIDAPI
1231 SDL_Log("HIDAPI_IsDeviceTypePresent() returning %s for %d", result ? "true" : "false", type);
1232#endif
1233 return result;
1234}
1235
1236bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
1237{
1238 SDL_HIDAPI_Device *device;
1239 bool supported = false;
1240 bool result = false;
1241
1242 // Make sure we're initialized, as this could be called from other drivers during startup
1243 if (!HIDAPI_JoystickInit()) {
1244 return false;
1245 }
1246
1247 /* Only update the device list for devices we know might be supported.
1248 If we did this for every device, it would hit the USB driver too hard and potentially
1249 lock up the system. This won't catch devices that we support but can only detect using
1250 USB interface details, like Xbox controllers, but hopefully the device list update is
1251 responsive enough to catch those.
1252 */
1253 supported = HIDAPI_IsDeviceSupported(vendor_id, product_id, version, name);
1254#if defined(SDL_JOYSTICK_HIDAPI_XBOX360) || defined(SDL_JOYSTICK_HIDAPI_XBOXONE)
1255 if (!supported &&
1256 (SDL_strstr(name, "Xbox") || SDL_strstr(name, "X-Box") || SDL_strstr(name, "XBOX"))) {
1257 supported = true;
1258 }
1259#endif // SDL_JOYSTICK_HIDAPI_XBOX360 || SDL_JOYSTICK_HIDAPI_XBOXONE
1260 if (supported) {
1261 if (HIDAPI_StartUpdatingDevices()) {
1262 HIDAPI_UpdateDeviceList();
1263 HIDAPI_FinishUpdatingDevices();
1264 }
1265 }
1266
1267 /* Note that this isn't a perfect check - there may be multiple devices with 0 VID/PID,
1268 or a different name than we have it listed here, etc, but if we support the device
1269 and we have something similar in our device list, mark it as present.
1270 */
1271 SDL_LockJoysticks();
1272 for (device = SDL_HIDAPI_devices; device; device = device->next) {
1273 if (device->driver &&
1274 HIDAPI_IsEquivalentToDevice(vendor_id, product_id, device)) {
1275 result = true;
1276 break;
1277 }
1278 }
1279 SDL_UnlockJoysticks();
1280
1281#ifdef DEBUG_HIDAPI
1282 SDL_Log("HIDAPI_IsDevicePresent() returning %s for 0x%.4x / 0x%.4x", result ? "true" : "false", vendor_id, product_id);
1283#endif
1284 return result;
1285}
1286
1287char *HIDAPI_GetDeviceProductName(Uint16 vendor_id, Uint16 product_id)
1288{
1289 SDL_HIDAPI_Device *device;
1290 char *name = NULL;
1291
1292 SDL_LockJoysticks();
1293 for (device = SDL_HIDAPI_devices; device; device = device->next) {
1294 if (vendor_id == device->vendor_id && product_id == device->product_id) {
1295 if (device->product_string) {
1296 name = SDL_strdup(device->product_string);
1297 }
1298 break;
1299 }
1300 }
1301 SDL_UnlockJoysticks();
1302
1303 return name;
1304}
1305
1306char *HIDAPI_GetDeviceManufacturerName(Uint16 vendor_id, Uint16 product_id)
1307{
1308 SDL_HIDAPI_Device *device;
1309 char *name = NULL;
1310
1311 SDL_LockJoysticks();
1312 for (device = SDL_HIDAPI_devices; device; device = device->next) {
1313 if (vendor_id == device->vendor_id && product_id == device->product_id) {
1314 if (device->manufacturer_string) {
1315 name = SDL_strdup(device->manufacturer_string);
1316 }
1317 break;
1318 }
1319 }
1320 SDL_UnlockJoysticks();
1321
1322 return name;
1323}
1324
1325SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_GUID guid)
1326{
1327 SDL_HIDAPI_Device *device;
1328 SDL_JoystickType type = SDL_JOYSTICK_TYPE_UNKNOWN;
1329
1330 SDL_LockJoysticks();
1331 for (device = SDL_HIDAPI_devices; device; device = device->next) {
1332 if (SDL_memcmp(&guid, &device->guid, sizeof(guid)) == 0) {
1333 type = device->joystick_type;
1334 break;
1335 }
1336 }
1337 SDL_UnlockJoysticks();
1338
1339 return type;
1340}
1341
1342SDL_GamepadType HIDAPI_GetGamepadTypeFromGUID(SDL_GUID guid)
1343{
1344 SDL_HIDAPI_Device *device;
1345 SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD;
1346
1347 SDL_LockJoysticks();
1348 for (device = SDL_HIDAPI_devices; device; device = device->next) {
1349 if (SDL_memcmp(&guid, &device->guid, sizeof(guid)) == 0) {
1350 type = device->type;
1351 break;
1352 }
1353 }
1354 SDL_UnlockJoysticks();
1355
1356 return type;
1357}
1358
1359static void HIDAPI_JoystickDetect(void)
1360{
1361 if (HIDAPI_StartUpdatingDevices()) {
1362 Uint32 count = SDL_hid_device_change_count();
1363 if (SDL_HIDAPI_change_count != count) {
1364 SDL_HIDAPI_change_count = count;
1365 HIDAPI_UpdateDeviceList();
1366 }
1367 HIDAPI_FinishUpdatingDevices();
1368 }
1369}
1370
1371void HIDAPI_UpdateDevices(void)
1372{
1373 SDL_HIDAPI_Device *device;
1374
1375 SDL_AssertJoysticksLocked();
1376
1377 // Update the devices, which may change connected joysticks and send events
1378
1379 // Prepare the existing device list
1380 if (HIDAPI_StartUpdatingDevices()) {
1381 for (device = SDL_HIDAPI_devices; device; device = device->next) {
1382 if (device->parent) {
1383 continue;
1384 }
1385 if (device->driver) {
1386 if (SDL_TryLockMutex(device->dev_lock)) {
1387 device->updating = true;
1388 device->driver->UpdateDevice(device);
1389 device->updating = false;
1390 SDL_UnlockMutex(device->dev_lock);
1391 }
1392 }
1393 }
1394 HIDAPI_FinishUpdatingDevices();
1395 }
1396}
1397
1398static const char *HIDAPI_JoystickGetDeviceName(int device_index)
1399{
1400 SDL_HIDAPI_Device *device;
1401 const char *name = NULL;
1402
1403 device = HIDAPI_GetDeviceByIndex(device_index, NULL);
1404 if (device) {
1405 // FIXME: The device could be freed after this name is returned...
1406 name = device->name;
1407 }
1408
1409 return name;
1410}
1411
1412static const char *HIDAPI_JoystickGetDevicePath(int device_index)
1413{
1414 SDL_HIDAPI_Device *device;
1415 const char *path = NULL;
1416
1417 device = HIDAPI_GetDeviceByIndex(device_index, NULL);
1418 if (device) {
1419 // FIXME: The device could be freed after this path is returned...
1420 path = device->path;
1421 }
1422
1423 return path;
1424}
1425
1426static int HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
1427{
1428 SDL_HIDAPI_Device *device;
1429
1430 device = HIDAPI_GetDeviceByIndex(device_index, NULL);
1431 if (device) {
1432 return device->steam_virtual_gamepad_slot;
1433 }
1434 return -1;
1435}
1436
1437static int HIDAPI_JoystickGetDevicePlayerIndex(int device_index)
1438{
1439 SDL_HIDAPI_Device *device;
1440 SDL_JoystickID instance_id;
1441 int player_index = -1;
1442
1443 device = HIDAPI_GetDeviceByIndex(device_index, &instance_id);
1444 if (device) {
1445 player_index = device->driver->GetDevicePlayerIndex(device, instance_id);
1446 }
1447
1448 return player_index;
1449}
1450
1451static void HIDAPI_JoystickSetDevicePlayerIndex(int device_index, int player_index)
1452{
1453 SDL_HIDAPI_Device *device;
1454 SDL_JoystickID instance_id;
1455
1456 device = HIDAPI_GetDeviceByIndex(device_index, &instance_id);
1457 if (device) {
1458 device->driver->SetDevicePlayerIndex(device, instance_id, player_index);
1459 }
1460}
1461
1462static SDL_GUID HIDAPI_JoystickGetDeviceGUID(int device_index)
1463{
1464 SDL_HIDAPI_Device *device;
1465 SDL_GUID guid;
1466
1467 device = HIDAPI_GetDeviceByIndex(device_index, NULL);
1468 if (device) {
1469 SDL_memcpy(&guid, &device->guid, sizeof(guid));
1470 } else {
1471 SDL_zero(guid);
1472 }
1473
1474 return guid;
1475}
1476
1477static SDL_JoystickID HIDAPI_JoystickGetDeviceInstanceID(int device_index)
1478{
1479 SDL_JoystickID joystickID = 0;
1480 HIDAPI_GetDeviceByIndex(device_index, &joystickID);
1481 return joystickID;
1482}
1483
1484static bool HIDAPI_JoystickOpen(SDL_Joystick *joystick, int device_index)
1485{
1486 SDL_JoystickID joystickID = 0;
1487 SDL_HIDAPI_Device *device = HIDAPI_GetDeviceByIndex(device_index, &joystickID);
1488 struct joystick_hwdata *hwdata;
1489
1490 SDL_AssertJoysticksLocked();
1491
1492 if (!device || !device->driver || device->broken) {
1493 // This should never happen - validated before being called
1494 return SDL_SetError("Couldn't find HIDAPI device at index %d", device_index);
1495 }
1496
1497 hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata));
1498 if (!hwdata) {
1499 return false;
1500 }
1501 hwdata->device = device;
1502
1503 // Process any pending reports before opening the device
1504 SDL_LockMutex(device->dev_lock);
1505 device->updating = true;
1506 device->driver->UpdateDevice(device);
1507 device->updating = false;
1508 SDL_UnlockMutex(device->dev_lock);
1509
1510 // UpdateDevice() may have called HIDAPI_JoystickDisconnected() if the device went away
1511 if (device->num_joysticks == 0) {
1512 SDL_free(hwdata);
1513 return SDL_SetError("HIDAPI device disconnected while opening");
1514 }
1515
1516 // Set the default connection state, can be overridden below
1517 if (device->is_bluetooth) {
1518 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
1519 } else {
1520 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
1521 }
1522
1523 if (!device->driver->OpenJoystick(device, joystick)) {
1524 // The open failed, mark this device as disconnected and update devices
1525 HIDAPI_JoystickDisconnected(device, joystickID);
1526 SDL_free(hwdata);
1527 return false;
1528 }
1529
1530 HIDAPI_UpdateJoystickProperties(device, joystick);
1531
1532 if (device->serial) {
1533 joystick->serial = SDL_strdup(device->serial);
1534 }
1535
1536 joystick->hwdata = hwdata;
1537 return true;
1538}
1539
1540static bool HIDAPI_GetJoystickDevice(SDL_Joystick *joystick, SDL_HIDAPI_Device **device)
1541{
1542 SDL_AssertJoysticksLocked();
1543
1544 if (joystick && joystick->hwdata) {
1545 *device = joystick->hwdata->device;
1546 if (SDL_ObjectValid(*device, SDL_OBJECT_TYPE_HIDAPI_JOYSTICK) && (*device)->driver != NULL) {
1547 return true;
1548 }
1549 }
1550 return false;
1551}
1552
1553static bool HIDAPI_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1554{
1555 bool result;
1556 SDL_HIDAPI_Device *device = NULL;
1557
1558 if (HIDAPI_GetJoystickDevice(joystick, &device)) {
1559 result = device->driver->RumbleJoystick(device, joystick, low_frequency_rumble, high_frequency_rumble);
1560 } else {
1561 result = SDL_SetError("Rumble failed, device disconnected");
1562 }
1563
1564 return result;
1565}
1566
1567static bool HIDAPI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
1568{
1569 bool result;
1570 SDL_HIDAPI_Device *device = NULL;
1571
1572 if (HIDAPI_GetJoystickDevice(joystick, &device)) {
1573 result = device->driver->RumbleJoystickTriggers(device, joystick, left_rumble, right_rumble);
1574 } else {
1575 result = SDL_SetError("Rumble failed, device disconnected");
1576 }
1577
1578 return result;
1579}
1580
1581static bool HIDAPI_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1582{
1583 bool result;
1584 SDL_HIDAPI_Device *device = NULL;
1585
1586 if (HIDAPI_GetJoystickDevice(joystick, &device)) {
1587 result = device->driver->SetJoystickLED(device, joystick, red, green, blue);
1588 } else {
1589 result = SDL_SetError("SetLED failed, device disconnected");
1590 }
1591
1592 return result;
1593}
1594
1595static bool HIDAPI_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
1596{
1597 bool result;
1598 SDL_HIDAPI_Device *device = NULL;
1599
1600 if (HIDAPI_GetJoystickDevice(joystick, &device)) {
1601 result = device->driver->SendJoystickEffect(device, joystick, data, size);
1602 } else {
1603 result = SDL_SetError("SendEffect failed, device disconnected");
1604 }
1605
1606 return result;
1607}
1608
1609static bool HIDAPI_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
1610{
1611 bool result;
1612 SDL_HIDAPI_Device *device = NULL;
1613
1614 if (HIDAPI_GetJoystickDevice(joystick, &device)) {
1615 result = device->driver->SetJoystickSensorsEnabled(device, joystick, enabled);
1616 } else {
1617 result = SDL_SetError("SetSensorsEnabled failed, device disconnected");
1618 }
1619
1620 return result;
1621}
1622
1623static void HIDAPI_JoystickUpdate(SDL_Joystick *joystick)
1624{
1625 // This is handled in SDL_HIDAPI_UpdateDevices()
1626}
1627
1628static void HIDAPI_JoystickClose(SDL_Joystick *joystick) SDL_NO_THREAD_SAFETY_ANALYSIS // We unlock the device lock so rumble can complete
1629{
1630 SDL_AssertJoysticksLocked();
1631
1632 if (joystick->hwdata) {
1633 SDL_HIDAPI_Device *device = joystick->hwdata->device;
1634 int i;
1635
1636 // Wait up to 30 ms for pending rumble to complete
1637 if (device->updating) {
1638 // Unlock the device so rumble can complete
1639 SDL_UnlockMutex(device->dev_lock);
1640 }
1641 for (i = 0; i < 3; ++i) {
1642 if (SDL_GetAtomicInt(&device->rumble_pending) > 0) {
1643 SDL_Delay(10);
1644 }
1645 }
1646 if (device->updating) {
1647 // Relock the device
1648 SDL_LockMutex(device->dev_lock);
1649 }
1650
1651 device->driver->CloseJoystick(device, joystick);
1652
1653 SDL_free(joystick->hwdata);
1654 joystick->hwdata = NULL;
1655 }
1656}
1657
1658static void HIDAPI_JoystickQuit(void)
1659{
1660 int i;
1661
1662 SDL_AssertJoysticksLocked();
1663
1664 shutting_down = true;
1665
1666 SDL_HIDAPI_QuitRumble();
1667
1668 while (SDL_HIDAPI_devices) {
1669 SDL_HIDAPI_Device *device = SDL_HIDAPI_devices;
1670 if (device->parent) {
1671 // When a child device goes away, so does the parent
1672 device = device->parent;
1673 for (i = 0; i < device->num_children; ++i) {
1674 HIDAPI_DelDevice(device->children[i]);
1675 }
1676 HIDAPI_DelDevice(device);
1677 } else {
1678 HIDAPI_DelDevice(device);
1679 }
1680 }
1681
1682 // Make sure the drivers cleaned up properly
1683 SDL_assert(SDL_HIDAPI_numjoysticks == 0);
1684
1685 for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) {
1686 SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i];
1687 driver->UnregisterHints(SDL_HIDAPIDriverHintChanged, driver);
1688 }
1689 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS,
1690 SDL_HIDAPIDriverHintChanged, NULL);
1691 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI,
1692 SDL_HIDAPIDriverHintChanged, NULL);
1693
1694 SDL_hid_exit();
1695
1696 SDL_HIDAPI_change_count = 0;
1697 shutting_down = false;
1698 initialized = false;
1699}
1700
1701static bool HIDAPI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
1702{
1703 return false;
1704}
1705
1706SDL_JoystickDriver SDL_HIDAPI_JoystickDriver = {
1707 HIDAPI_JoystickInit,
1708 HIDAPI_JoystickGetCount,
1709 HIDAPI_JoystickDetect,
1710 HIDAPI_IsDevicePresent,
1711 HIDAPI_JoystickGetDeviceName,
1712 HIDAPI_JoystickGetDevicePath,
1713 HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot,
1714 HIDAPI_JoystickGetDevicePlayerIndex,
1715 HIDAPI_JoystickSetDevicePlayerIndex,
1716 HIDAPI_JoystickGetDeviceGUID,
1717 HIDAPI_JoystickGetDeviceInstanceID,
1718 HIDAPI_JoystickOpen,
1719 HIDAPI_JoystickRumble,
1720 HIDAPI_JoystickRumbleTriggers,
1721 HIDAPI_JoystickSetLED,
1722 HIDAPI_JoystickSendEffect,
1723 HIDAPI_JoystickSetSensorsEnabled,
1724 HIDAPI_JoystickUpdate,
1725 HIDAPI_JoystickClose,
1726 HIDAPI_JoystickQuit,
1727 HIDAPI_JoystickGetGamepadMapping
1728};
1729
1730#endif // SDL_JOYSTICK_HIDAPI
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick_c.h b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick_c.h
new file mode 100644
index 0000000..9cd9f40
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/SDL_hidapijoystick_c.h
@@ -0,0 +1,195 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_JOYSTICK_HIDAPI_H
24#define SDL_JOYSTICK_HIDAPI_H
25
26#include "../usb_ids.h"
27
28// This is the full set of HIDAPI drivers available
29#define SDL_JOYSTICK_HIDAPI_GAMECUBE
30#define SDL_JOYSTICK_HIDAPI_LUNA
31#define SDL_JOYSTICK_HIDAPI_PS3
32#define SDL_JOYSTICK_HIDAPI_PS4
33#define SDL_JOYSTICK_HIDAPI_PS5
34#define SDL_JOYSTICK_HIDAPI_STADIA
35#define SDL_JOYSTICK_HIDAPI_STEAM
36#define SDL_JOYSTICK_HIDAPI_STEAMDECK
37#define SDL_JOYSTICK_HIDAPI_SWITCH
38#define SDL_JOYSTICK_HIDAPI_WII
39#define SDL_JOYSTICK_HIDAPI_XBOX360
40#define SDL_JOYSTICK_HIDAPI_XBOXONE
41#define SDL_JOYSTICK_HIDAPI_SHIELD
42#define SDL_JOYSTICK_HIDAPI_STEAM_HORI
43
44// Joystick capability definitions
45#define SDL_JOYSTICK_CAP_MONO_LED 0x00000001
46#define SDL_JOYSTICK_CAP_RGB_LED 0x00000002
47#define SDL_JOYSTICK_CAP_PLAYER_LED 0x00000004
48#define SDL_JOYSTICK_CAP_RUMBLE 0x00000010
49#define SDL_JOYSTICK_CAP_TRIGGER_RUMBLE 0x00000020
50
51// Whether HIDAPI is enabled by default
52#if defined(SDL_PLATFORM_ANDROID) || \
53 defined(SDL_PLATFORM_IOS) || \
54 defined(SDL_PLATFORM_TVOS) || \
55 defined(SDL_PLATFORM_VISIONOS)
56// On Android, HIDAPI prompts for permissions and acquires exclusive access to the device, and on Apple mobile platforms it doesn't do anything except for handling Bluetooth Steam Controllers, so we'll leave it off by default.
57#define SDL_HIDAPI_DEFAULT false
58#else
59#define SDL_HIDAPI_DEFAULT true
60#endif
61
62// The maximum size of a USB packet for HID devices
63#define USB_PACKET_LENGTH 64
64
65// Forward declaration
66struct SDL_HIDAPI_DeviceDriver;
67
68typedef struct SDL_HIDAPI_Device
69{
70 char *name;
71 char *manufacturer_string;
72 char *product_string;
73 char *path;
74 Uint16 vendor_id;
75 Uint16 product_id;
76 Uint16 version;
77 char *serial;
78 SDL_GUID guid;
79 int interface_number; // Available on Windows and Linux
80 int interface_class;
81 int interface_subclass;
82 int interface_protocol;
83 Uint16 usage_page; // Available on Windows and macOS
84 Uint16 usage; // Available on Windows and macOS
85 bool is_bluetooth;
86 SDL_JoystickType joystick_type;
87 SDL_GamepadType type;
88 int steam_virtual_gamepad_slot;
89
90 struct SDL_HIDAPI_DeviceDriver *driver;
91 void *context;
92 SDL_Mutex *dev_lock;
93 SDL_hid_device *dev;
94 SDL_AtomicInt rumble_pending;
95 int num_joysticks;
96 SDL_JoystickID *joysticks;
97
98 // Used during scanning for device changes
99 bool seen;
100
101 // Used to flag that the device is being updated
102 bool updating;
103
104 // Used to flag devices that failed open
105 // This can happen on Windows with Bluetooth devices that have turned off
106 bool broken;
107
108 struct SDL_HIDAPI_Device *parent;
109 int num_children;
110 struct SDL_HIDAPI_Device **children;
111
112 struct SDL_HIDAPI_Device *next;
113} SDL_HIDAPI_Device;
114
115typedef struct SDL_HIDAPI_DeviceDriver
116{
117 const char *name;
118 bool enabled;
119 void (*RegisterHints)(SDL_HintCallback callback, void *userdata);
120 void (*UnregisterHints)(SDL_HintCallback callback, void *userdata);
121 bool (*IsEnabled)(void);
122 bool (*IsSupportedDevice)(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
123 bool (*InitDevice)(SDL_HIDAPI_Device *device);
124 int (*GetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id);
125 void (*SetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index);
126 bool (*UpdateDevice)(SDL_HIDAPI_Device *device);
127 bool (*OpenJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
128 bool (*RumbleJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble);
129 bool (*RumbleJoystickTriggers)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble);
130 Uint32 (*GetJoystickCapabilities)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
131 bool (*SetJoystickLED)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue);
132 bool (*SendJoystickEffect)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size);
133 bool (*SetJoystickSensorsEnabled)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled);
134 void (*CloseJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
135 void (*FreeDevice)(SDL_HIDAPI_Device *device);
136
137} SDL_HIDAPI_DeviceDriver;
138
139// HIDAPI device support
140extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined;
141extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube;
142extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons;
143extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna;
144extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic;
145extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3;
146extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3ThirdParty;
147extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3SonySixaxis;
148extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4;
149extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5;
150extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield;
151extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia;
152extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam;
153extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamDeck;
154extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch;
155extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverWii;
156extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360;
157extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W;
158extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne;
159extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori;
160
161// Return true if a HID device is present and supported as a joystick of the given type
162extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type);
163
164// Return true if a HID device is present and supported as a joystick
165extern bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name);
166
167// Return the name of a connected device, which should be freed with SDL_free(), or NULL if it's not available
168extern char *HIDAPI_GetDeviceProductName(Uint16 vendor_id, Uint16 product_id);
169
170// Return the manufacturer of a connected device, which should be freed with SDL_free(), or NULL if it's not available
171extern char *HIDAPI_GetDeviceManufacturerName(Uint16 vendor_id, Uint16 product_id);
172
173// Return the type of a joystick if it's present and supported
174extern SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_GUID guid);
175
176// Return the type of a game controller if it's present and supported
177extern SDL_GamepadType HIDAPI_GetGamepadTypeFromGUID(SDL_GUID guid);
178
179extern void HIDAPI_UpdateDevices(void);
180extern void HIDAPI_SetDeviceName(SDL_HIDAPI_Device *device, const char *name);
181extern void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 vendor_id, Uint16 product_id);
182extern void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial);
183extern bool HIDAPI_HasConnectedUSBDevice(const char *serial);
184extern void HIDAPI_DisconnectBluetoothDevice(const char *serial);
185extern bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID);
186extern void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID);
187extern void HIDAPI_UpdateDeviceProperties(SDL_HIDAPI_Device *device);
188
189extern void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size);
190
191extern bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product);
192
193extern float HIDAPI_RemapVal(float val, float val_min, float val_max, float output_min, float output_max);
194
195#endif // SDL_JOYSTICK_HIDAPI_H
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_constants.h b/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_constants.h
new file mode 100644
index 0000000..78af016
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_constants.h
@@ -0,0 +1,582 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 2021 Valve Corporation
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef _CONTROLLER_CONSTANTS_
23#define _CONTROLLER_CONSTANTS_
24
25#include "controller_structs.h"
26
27#ifdef __cplusplus
28extern "C" {
29#endif
30
31#define FEATURE_REPORT_SIZE 64
32
33#define VALVE_USB_VID 0x28DE
34
35// Frame update rate (in ms).
36#define FAST_SCAN_INTERVAL 6
37#define SLOW_SCAN_INTERVAL 9
38
39// Contains each of the USB PIDs for Valve controllers (only add to this enum and never change the order)
40enum ValveControllerPID
41{
42 BASTILLE_PID = 0x2202,
43 CHELL_PID = 0x1101,
44 D0G_PID = 0x1102,
45 ELI_PID = 0x1103,
46 FREEMAN_PID = 0x1104,
47 D0G_BLE_PID = 0x1105,
48 D0G_BLE2_PID = 0x1106,
49 D0GGLE_PID = 0x1142,
50
51 JUPITER_PID = 0x1205,
52};
53
54// This enum contains all of the messages exchanged between the host and the target (only add to this enum and never change the order)
55enum FeatureReportMessageIDs
56{
57 ID_SET_DIGITAL_MAPPINGS = 0x80,
58 ID_CLEAR_DIGITAL_MAPPINGS = 0x81,
59 ID_GET_DIGITAL_MAPPINGS = 0x82,
60 ID_GET_ATTRIBUTES_VALUES = 0x83,
61 ID_GET_ATTRIBUTE_LABEL = 0x84,
62 ID_SET_DEFAULT_DIGITAL_MAPPINGS = 0x85,
63 ID_FACTORY_RESET = 0x86,
64 ID_SET_SETTINGS_VALUES = 0x87,
65 ID_CLEAR_SETTINGS_VALUES = 0x88,
66 ID_GET_SETTINGS_VALUES = 0x89,
67 ID_GET_SETTING_LABEL = 0x8A,
68 ID_GET_SETTINGS_MAXS = 0x8B,
69 ID_GET_SETTINGS_DEFAULTS = 0x8C,
70 ID_SET_CONTROLLER_MODE = 0x8D,
71 ID_LOAD_DEFAULT_SETTINGS = 0x8E,
72 ID_TRIGGER_HAPTIC_PULSE = 0x8F,
73
74 ID_TURN_OFF_CONTROLLER = 0x9F,
75
76 ID_GET_DEVICE_INFO = 0xA1,
77
78 ID_CALIBRATE_TRACKPADS = 0xA7,
79 ID_RESERVED_0 = 0xA8,
80 ID_SET_SERIAL_NUMBER = 0xA9,
81 ID_GET_TRACKPAD_CALIBRATION = 0xAA,
82 ID_GET_TRACKPAD_FACTORY_CALIBRATION = 0xAB,
83 ID_GET_TRACKPAD_RAW_DATA = 0xAC,
84 ID_ENABLE_PAIRING = 0xAD,
85 ID_GET_STRING_ATTRIBUTE = 0xAE,
86 ID_RADIO_ERASE_RECORDS = 0xAF,
87 ID_RADIO_WRITE_RECORD = 0xB0,
88 ID_SET_DONGLE_SETTING = 0xB1,
89 ID_DONGLE_DISCONNECT_DEVICE = 0xB2,
90 ID_DONGLE_COMMIT_DEVICE = 0xB3,
91 ID_DONGLE_GET_WIRELESS_STATE = 0xB4,
92 ID_CALIBRATE_GYRO = 0xB5,
93 ID_PLAY_AUDIO = 0xB6,
94 ID_AUDIO_UPDATE_START = 0xB7,
95 ID_AUDIO_UPDATE_DATA = 0xB8,
96 ID_AUDIO_UPDATE_COMPLETE = 0xB9,
97 ID_GET_CHIPID = 0xBA,
98
99 ID_CALIBRATE_JOYSTICK = 0xBF,
100 ID_CALIBRATE_ANALOG_TRIGGERS = 0xC0,
101 ID_SET_AUDIO_MAPPING = 0xC1,
102 ID_CHECK_GYRO_FW_LOAD = 0xC2,
103 ID_CALIBRATE_ANALOG = 0xC3,
104 ID_DONGLE_GET_CONNECTED_SLOTS = 0xC4,
105
106 ID_RESET_IMU = 0xCE,
107
108 // Deck only
109 ID_TRIGGER_HAPTIC_CMD = 0xEA,
110 ID_TRIGGER_RUMBLE_CMD = 0xEB,
111};
112
113
114// Enumeration of all wireless dongle events
115typedef enum WirelessEventTypes
116{
117 WIRELESS_EVENT_DISCONNECT = 1,
118 WIRELESS_EVENT_CONNECT = 2,
119 WIRELESS_EVENT_PAIR = 3,
120} EWirelessEventType;
121
122
123// Enumeration of generic digital inputs - not all of these will be supported on all controllers (only add to this enum and never change the order)
124typedef enum
125{
126 IO_DIGITAL_BUTTON_NONE = -1,
127 IO_DIGITAL_BUTTON_RIGHT_TRIGGER,
128 IO_DIGITAL_BUTTON_LEFT_TRIGGER,
129 IO_DIGITAL_BUTTON_1,
130 IO_DIGITAL_BUTTON_Y=IO_DIGITAL_BUTTON_1,
131 IO_DIGITAL_BUTTON_2,
132 IO_DIGITAL_BUTTON_B=IO_DIGITAL_BUTTON_2,
133 IO_DIGITAL_BUTTON_3,
134 IO_DIGITAL_BUTTON_X=IO_DIGITAL_BUTTON_3,
135 IO_DIGITAL_BUTTON_4,
136 IO_DIGITAL_BUTTON_A=IO_DIGITAL_BUTTON_4,
137 IO_DIGITAL_BUTTON_RIGHT_BUMPER,
138 IO_DIGITAL_BUTTON_LEFT_BUMPER,
139 IO_DIGITAL_BUTTON_LEFT_JOYSTICK_CLICK,
140 IO_DIGITAL_BUTTON_ESCAPE,
141 IO_DIGITAL_BUTTON_STEAM,
142 IO_DIGITAL_BUTTON_MENU,
143 IO_DIGITAL_STICK_UP,
144 IO_DIGITAL_STICK_DOWN,
145 IO_DIGITAL_STICK_LEFT,
146 IO_DIGITAL_STICK_RIGHT,
147 IO_DIGITAL_TOUCH_1,
148 IO_DIGITAL_BUTTON_UP=IO_DIGITAL_TOUCH_1,
149 IO_DIGITAL_TOUCH_2,
150 IO_DIGITAL_BUTTON_RIGHT=IO_DIGITAL_TOUCH_2,
151 IO_DIGITAL_TOUCH_3,
152 IO_DIGITAL_BUTTON_LEFT=IO_DIGITAL_TOUCH_3,
153 IO_DIGITAL_TOUCH_4,
154 IO_DIGITAL_BUTTON_DOWN=IO_DIGITAL_TOUCH_4,
155 IO_DIGITAL_BUTTON_BACK_LEFT,
156 IO_DIGITAL_BUTTON_BACK_RIGHT,
157 IO_DIGITAL_LEFT_TRACKPAD_N,
158 IO_DIGITAL_LEFT_TRACKPAD_NE,
159 IO_DIGITAL_LEFT_TRACKPAD_E,
160 IO_DIGITAL_LEFT_TRACKPAD_SE,
161 IO_DIGITAL_LEFT_TRACKPAD_S,
162 IO_DIGITAL_LEFT_TRACKPAD_SW,
163 IO_DIGITAL_LEFT_TRACKPAD_W,
164 IO_DIGITAL_LEFT_TRACKPAD_NW,
165 IO_DIGITAL_RIGHT_TRACKPAD_N,
166 IO_DIGITAL_RIGHT_TRACKPAD_NE,
167 IO_DIGITAL_RIGHT_TRACKPAD_E,
168 IO_DIGITAL_RIGHT_TRACKPAD_SE,
169 IO_DIGITAL_RIGHT_TRACKPAD_S,
170 IO_DIGITAL_RIGHT_TRACKPAD_SW,
171 IO_DIGITAL_RIGHT_TRACKPAD_W,
172 IO_DIGITAL_RIGHT_TRACKPAD_NW,
173 IO_DIGITAL_LEFT_TRACKPAD_DOUBLE_TAP,
174 IO_DIGITAL_RIGHT_TRACKPAD_DOUBLE_TAP,
175 IO_DIGITAL_LEFT_TRACKPAD_OUTER_RADIUS,
176 IO_DIGITAL_RIGHT_TRACKPAD_OUTER_RADIUS,
177 IO_DIGITAL_LEFT_TRACKPAD_CLICK,
178 IO_DIGITAL_RIGHT_TRACKPAD_CLICK,
179 IO_DIGITAL_BATTERY_LOW,
180 IO_DIGITAL_LEFT_TRIGGER_THRESHOLD,
181 IO_DIGITAL_RIGHT_TRIGGER_THRESHOLD,
182 IO_DIGITAL_BUTTON_BACK_LEFT2,
183 IO_DIGITAL_BUTTON_BACK_RIGHT2,
184 IO_DIGITAL_BUTTON_ALWAYS_ON,
185 IO_DIGITAL_BUTTON_ANCILLARY_1,
186 IO_DIGITAL_BUTTON_MACRO_0,
187 IO_DIGITAL_BUTTON_MACRO_1,
188 IO_DIGITAL_BUTTON_MACRO_2,
189 IO_DIGITAL_BUTTON_MACRO_3,
190 IO_DIGITAL_BUTTON_MACRO_4,
191 IO_DIGITAL_BUTTON_MACRO_5,
192 IO_DIGITAL_BUTTON_MACRO_6,
193 IO_DIGITAL_BUTTON_MACRO_7,
194 IO_DIGITAL_BUTTON_MACRO_1FINGER,
195 IO_DIGITAL_BUTTON_MACRO_2FINGER,
196 IO_DIGITAL_COUNT
197} DigitalIO ;
198
199// Enumeration of generic analog inputs - not all of these will be supported on all controllers (only add to this enum and never change the order)
200typedef enum
201{
202 IO_ANALOG_LEFT_STICK_X,
203 IO_ANALOG_LEFT_STICK_Y,
204 IO_ANALOG_RIGHT_STICK_X,
205 IO_ANALOG_RIGHT_STICK_Y,
206 IO_ANALOG_LEFT_TRIGGER,
207 IO_ANALOG_RIGHT_TRIGGER,
208 IO_MOUSE1_X,
209 IO_MOUSE1_Y,
210 IO_MOUSE1_Z,
211 IO_ACCEL_X,
212 IO_ACCEL_Y,
213 IO_ACCEL_Z,
214 IO_GYRO_X,
215 IO_GYRO_Y,
216 IO_GYRO_Z,
217 IO_GYRO_QUAT_W,
218 IO_GYRO_QUAT_X,
219 IO_GYRO_QUAT_Y,
220 IO_GYRO_QUAT_Z,
221 IO_GYRO_STEERING_VEC,
222 IO_RAW_TRIGGER_LEFT,
223 IO_RAW_TRIGGER_RIGHT,
224 IO_RAW_JOYSTICK_X,
225 IO_RAW_JOYSTICK_Y,
226 IO_GYRO_TILT_VEC,
227 IO_PRESSURE_LEFT_PAD,
228 IO_PRESSURE_RIGHT_PAD,
229 IO_PRESSURE_LEFT_BUMPER,
230 IO_PRESSURE_RIGHT_BUMPER,
231 IO_PRESSURE_LEFT_GRIP,
232 IO_PRESSURE_RIGHT_GRIP,
233 IO_ANALOG_LEFT_TRIGGER_THRESHOLD,
234 IO_ANALOG_RIGHT_TRIGGER_THRESHOLD,
235 IO_PRESSURE_RIGHT_PAD_THRESHOLD,
236 IO_PRESSURE_LEFT_PAD_THRESHOLD,
237 IO_PRESSURE_RIGHT_BUMPER_THRESHOLD,
238 IO_PRESSURE_LEFT_BUMPER_THRESHOLD,
239 IO_PRESSURE_RIGHT_GRIP_THRESHOLD,
240 IO_PRESSURE_LEFT_GRIP_THRESHOLD,
241 IO_PRESSURE_RIGHT_PAD_RAW,
242 IO_PRESSURE_LEFT_PAD_RAW,
243 IO_PRESSURE_RIGHT_BUMPER_RAW,
244 IO_PRESSURE_LEFT_BUMPER_RAW,
245 IO_PRESSURE_RIGHT_GRIP_RAW,
246 IO_PRESSURE_LEFT_GRIP_RAW,
247 IO_PRESSURE_RIGHT_GRIP2_THRESHOLD,
248 IO_PRESSURE_LEFT_GRIP2_THRESHOLD,
249 IO_PRESSURE_LEFT_GRIP2,
250 IO_PRESSURE_RIGHT_GRIP2,
251 IO_PRESSURE_RIGHT_GRIP2_RAW,
252 IO_PRESSURE_LEFT_GRIP2_RAW,
253 IO_ANALOG_COUNT
254} AnalogIO;
255
256
257// Contains list of all types of devices that the controller emulates (only add to this enum and never change the order)
258enum DeviceTypes
259{
260 DEVICE_KEYBOARD,
261 DEVICE_MOUSE,
262 DEVICE_GAMEPAD,
263 DEVICE_MODE_ADJUST,
264 DEVICE_COUNT
265};
266
267// Scan codes for HID keyboards
268enum HIDKeyboardKeys
269{
270 KEY_INVALID,
271 KEY_FIRST = 0x04,
272 KEY_A = KEY_FIRST, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L,
273 KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_1, KEY_2,
274 KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_RETURN, KEY_ESCAPE, KEY_BACKSPACE, KEY_TAB, KEY_SPACE, KEY_DASH, KEY_EQUALS, KEY_LEFT_BRACKET,
275 KEY_RIGHT_BRACKET, KEY_BACKSLASH, KEY_UNUSED1, KEY_SEMICOLON, KEY_SINGLE_QUOTE, KEY_BACK_TICK, KEY_COMMA, KEY_PERIOD, KEY_FORWARD_SLASH, KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6,
276 KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_PRINT_SCREEN, KEY_SCROLL_LOCK, KEY_BREAK, KEY_INSERT, KEY_HOME, KEY_PAGE_UP, KEY_DELETE, KEY_END, KEY_PAGE_DOWN, KEY_RIGHT_ARROW,
277 KEY_LEFT_ARROW, KEY_DOWN_ARROW, KEY_UP_ARROW, KEY_NUM_LOCK, KEY_KEYPAD_FORWARD_SLASH, KEY_KEYPAD_ASTERISK, KEY_KEYPAD_DASH, KEY_KEYPAD_PLUS, KEY_KEYPAD_ENTER, KEY_KEYPAD_1, KEY_KEYPAD_2, KEY_KEYPAD_3, KEY_KEYPAD_4, KEY_KEYPAD_5, KEY_KEYPAD_6, KEY_KEYPAD_7,
278 KEY_KEYPAD_8, KEY_KEYPAD_9, KEY_KEYPAD_0, KEY_KEYPAD_PERIOD,
279 KEY_LALT,
280 KEY_LSHIFT,
281 KEY_LWIN,
282 KEY_LCONTROL,
283 KEY_RALT,
284 KEY_RSHIFT,
285 KEY_RWIN,
286 KEY_RCONTROL,
287 KEY_VOLUP,
288 KEY_VOLDOWN,
289 KEY_MUTE,
290 KEY_PLAY,
291 KEY_STOP,
292 KEY_NEXT,
293 KEY_PREV,
294 KEY_LAST = KEY_PREV
295};
296
297enum ModifierMasks
298{
299 KEY_LCONTROL_MASK = (1<<0),
300 KEY_LSHIFT_MASK = (1<<1),
301 KEY_LALT_MASK = (1<<2),
302 KEY_LWIN_MASK = (1<<3),
303 KEY_RCONTROL_MASK = (1<<4),
304 KEY_RSHIFT_MASK = (1<<5),
305 KEY_RALT_MASK = (1<<6),
306 KEY_RWIN_MASK = (1<<7)
307};
308
309// Standard mouse buttons as specified in the HID mouse spec
310enum MouseButtons
311{
312 MOUSE_BTN_LEFT,
313 MOUSE_BTN_RIGHT,
314 MOUSE_BTN_MIDDLE,
315 MOUSE_BTN_BACK,
316 MOUSE_BTN_FORWARD,
317 MOUSE_SCROLL_UP,
318 MOUSE_SCROLL_DOWN,
319 MOUSE_BTN_COUNT
320};
321
322// Gamepad buttons
323enum GamepadButtons
324{
325 GAMEPAD_BTN_TRIGGER_LEFT=1,
326 GAMEPAD_BTN_TRIGGER_RIGHT,
327 GAMEPAD_BTN_A,
328 GAMEPAD_BTN_B,
329 GAMEPAD_BTN_Y,
330 GAMEPAD_BTN_X,
331 GAMEPAD_BTN_SHOULDER_LEFT,
332 GAMEPAD_BTN_SHOULDER_RIGHT,
333 GAMEPAD_BTN_LEFT_JOYSTICK,
334 GAMEPAD_BTN_RIGHT_JOYSTICK,
335 GAMEPAD_BTN_START,
336 GAMEPAD_BTN_SELECT,
337 GAMEPAD_BTN_STEAM,
338 GAMEPAD_BTN_DPAD_UP,
339 GAMEPAD_BTN_DPAD_DOWN,
340 GAMEPAD_BTN_DPAD_LEFT,
341 GAMEPAD_BTN_DPAD_RIGHT,
342 GAMEPAD_BTN_LSTICK_UP,
343 GAMEPAD_BTN_LSTICK_DOWN,
344 GAMEPAD_BTN_LSTICK_LEFT,
345 GAMEPAD_BTN_LSTICK_RIGHT,
346 GAMEPAD_BTN_RSTICK_UP,
347 GAMEPAD_BTN_RSTICK_DOWN,
348 GAMEPAD_BTN_RSTICK_LEFT,
349 GAMEPAD_BTN_RSTICK_RIGHT,
350 GAMEPAD_BTN_COUNT
351};
352
353// Mode adjust
354enum ModeAdjustModes
355{
356 MODE_ADJUST_SENSITITY=1,
357 MODE_ADJUST_LEFT_PAD_SECONDARY_MODE,
358 MODE_ADJUST_RIGHT_PAD_SECONDARY_MODE,
359 MODE_ADJUST_COUNT
360};
361
362// Read-only attributes of controllers (only add to this enum and never change the order)
363typedef enum
364{
365 ATTRIB_UNIQUE_ID,
366 ATTRIB_PRODUCT_ID,
367 ATTRIB_PRODUCT_REVISON, // deprecated
368 ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISON, // intentional aliasing
369 ATTRIB_FIRMWARE_VERSION, // deprecated
370 ATTRIB_FIRMWARE_BUILD_TIME,
371 ATTRIB_RADIO_FIRMWARE_BUILD_TIME,
372 ATTRIB_RADIO_DEVICE_ID0,
373 ATTRIB_RADIO_DEVICE_ID1,
374 ATTRIB_DONGLE_FIRMWARE_BUILD_TIME,
375 ATTRIB_BOARD_REVISION,
376 ATTRIB_BOOTLOADER_BUILD_TIME,
377 ATTRIB_CONNECTION_INTERVAL_IN_US,
378 ATTRIB_COUNT
379} ControllerAttributes;
380
381// Read-only string attributes of controllers (only add to this enum and never change the order)
382typedef enum
383{
384 ATTRIB_STR_BOARD_SERIAL,
385 ATTRIB_STR_UNIT_SERIAL,
386 ATTRIB_STR_COUNT
387} ControllerStringAttributes;
388
389typedef enum
390{
391 STATUS_CODE_NORMAL,
392 STATUS_CODE_CRITICAL_BATTERY,
393 STATUS_CODE_GYRO_INIT_ERROR,
394} ControllerStatusEventCodes;
395
396typedef enum
397{
398 STATUS_STATE_LOW_BATTERY=0,
399} ControllerStatusStateFlags;
400
401typedef enum {
402 TRACKPAD_ABSOLUTE_MOUSE,
403 TRACKPAD_RELATIVE_MOUSE,
404 TRACKPAD_DPAD_FOUR_WAY_DISCRETE,
405 TRACKPAD_DPAD_FOUR_WAY_OVERLAP,
406 TRACKPAD_DPAD_EIGHT_WAY,
407 TRACKPAD_RADIAL_MODE,
408 TRACKPAD_ABSOLUTE_DPAD,
409 TRACKPAD_NONE,
410 TRACKPAD_GESTURE_KEYBOARD,
411 TRACKPAD_NUM_MODES
412} TrackpadDPadMode;
413
414// Read-write controller settings (only add to this enum and never change the order)
415typedef enum
416{
417 SETTING_MOUSE_SENSITIVITY,
418 SETTING_MOUSE_ACCELERATION,
419 SETTING_TRACKBALL_ROTATION_ANGLE,
420 SETTING_HAPTIC_INTENSITY_UNUSED,
421 SETTING_LEFT_GAMEPAD_STICK_ENABLED,
422 SETTING_RIGHT_GAMEPAD_STICK_ENABLED,
423 SETTING_USB_DEBUG_MODE,
424 SETTING_LEFT_TRACKPAD_MODE,
425 SETTING_RIGHT_TRACKPAD_MODE,
426 SETTING_MOUSE_POINTER_ENABLED,
427
428 // 10
429 SETTING_DPAD_DEADZONE,
430 SETTING_MINIMUM_MOMENTUM_VEL,
431 SETTING_MOMENTUM_DECAY_AMOUNT,
432 SETTING_TRACKPAD_RELATIVE_MODE_TICKS_PER_PIXEL,
433 SETTING_HAPTIC_INCREMENT,
434 SETTING_DPAD_ANGLE_SIN,
435 SETTING_DPAD_ANGLE_COS,
436 SETTING_MOMENTUM_VERTICAL_DIVISOR,
437 SETTING_MOMENTUM_MAXIMUM_VELOCITY,
438 SETTING_TRACKPAD_Z_ON,
439
440 // 20
441 SETTING_TRACKPAD_Z_OFF,
442 SETTING_SENSITIVITY_SCALE_AMOUNT,
443 SETTING_LEFT_TRACKPAD_SECONDARY_MODE,
444 SETTING_RIGHT_TRACKPAD_SECONDARY_MODE,
445 SETTING_SMOOTH_ABSOLUTE_MOUSE,
446 SETTING_STEAMBUTTON_POWEROFF_TIME,
447 SETTING_UNUSED_1,
448 SETTING_TRACKPAD_OUTER_RADIUS,
449 SETTING_TRACKPAD_Z_ON_LEFT,
450 SETTING_TRACKPAD_Z_OFF_LEFT,
451
452 // 30
453 SETTING_TRACKPAD_OUTER_SPIN_VEL,
454 SETTING_TRACKPAD_OUTER_SPIN_RADIUS,
455 SETTING_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY,
456 SETTING_TRACKPAD_RELATIVE_MODE_DEADZONE,
457 SETTING_TRACKPAD_RELATIVE_MODE_MAX_VEL,
458 SETTING_TRACKPAD_RELATIVE_MODE_INVERT_Y,
459 SETTING_TRACKPAD_DOUBLE_TAP_BEEP_ENABLED,
460 SETTING_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD,
461 SETTING_TRACKPAD_DOUBLE_TAP_BEEP_COUNT,
462 SETTING_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION,
463
464 // 40
465 SETTING_RADIAL_MODE_ANGLE,
466 SETTING_HAPTIC_INTENSITY_MOUSE_MODE,
467 SETTING_LEFT_DPAD_REQUIRES_CLICK,
468 SETTING_RIGHT_DPAD_REQUIRES_CLICK,
469 SETTING_LED_BASELINE_BRIGHTNESS,
470 SETTING_LED_USER_BRIGHTNESS,
471 SETTING_ENABLE_RAW_JOYSTICK,
472 SETTING_ENABLE_FAST_SCAN,
473 SETTING_IMU_MODE,
474 SETTING_WIRELESS_PACKET_VERSION,
475
476 // 50
477 SETTING_SLEEP_INACTIVITY_TIMEOUT,
478 SETTING_TRACKPAD_NOISE_THRESHOLD,
479 SETTING_LEFT_TRACKPAD_CLICK_PRESSURE,
480 SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE,
481 SETTING_LEFT_BUMPER_CLICK_PRESSURE,
482 SETTING_RIGHT_BUMPER_CLICK_PRESSURE,
483 SETTING_LEFT_GRIP_CLICK_PRESSURE,
484 SETTING_RIGHT_GRIP_CLICK_PRESSURE,
485 SETTING_LEFT_GRIP2_CLICK_PRESSURE,
486 SETTING_RIGHT_GRIP2_CLICK_PRESSURE,
487
488 // 60
489 SETTING_PRESSURE_MODE,
490 SETTING_CONTROLLER_TEST_MODE,
491 SETTING_TRIGGER_MODE,
492 SETTING_TRACKPAD_Z_THRESHOLD,
493 SETTING_FRAME_RATE,
494 SETTING_TRACKPAD_FILT_CTRL,
495 SETTING_TRACKPAD_CLIP,
496 SETTING_DEBUG_OUTPUT_SELECT,
497 SETTING_TRIGGER_THRESHOLD_PERCENT,
498 SETTING_TRACKPAD_FREQUENCY_HOPPING,
499
500 // 70
501 SETTING_HAPTICS_ENABLED,
502 SETTING_STEAM_WATCHDOG_ENABLE,
503 SETTING_TIMP_TOUCH_THRESHOLD_ON,
504 SETTING_TIMP_TOUCH_THRESHOLD_OFF,
505 SETTING_FREQ_HOPPING,
506 SETTING_TEST_CONTROL,
507 SETTING_HAPTIC_MASTER_GAIN_DB,
508 SETTING_THUMB_TOUCH_THRESH,
509 SETTING_DEVICE_POWER_STATUS,
510 SETTING_HAPTIC_INTENSITY,
511
512 // 80
513 SETTING_STABILIZER_ENABLED,
514 SETTING_TIMP_MODE_MTE,
515 SETTING_COUNT,
516
517 // This is a special setting value use for callbacks and should not be set/get explicitly.
518 SETTING_ALL=0xFF
519} ControllerSettings;
520
521typedef enum
522{
523 SETTING_DEFAULT,
524 SETTING_MIN,
525 SETTING_MAX,
526 SETTING_DEFAULTMINMAXCOUNT
527} SettingDefaultMinMax;
528
529// Bitmask that define which IMU features to enable.
530typedef enum
531{
532 SETTING_GYRO_MODE_OFF = 0x0000,
533 SETTING_GYRO_MODE_STEERING = 0x0001,
534 SETTING_GYRO_MODE_TILT = 0x0002,
535 SETTING_GYRO_MODE_SEND_ORIENTATION = 0x0004,
536 SETTING_GYRO_MODE_SEND_RAW_ACCEL = 0x0008,
537 SETTING_GYRO_MODE_SEND_RAW_GYRO = 0x0010,
538} SettingGyroMode;
539
540// Bitmask for haptic pulse flags
541typedef enum
542{
543 HAPTIC_PULSE_NORMAL = 0x0000,
544 HAPTIC_PULSE_HIGH_PRIORITY = 0x0001,
545 HAPTIC_PULSE_VERY_HIGH_PRIORITY = 0x0002,
546 HAPTIC_PULSE_IGNORE_USER_PREFS = 0x0003,
547} SettingHapticPulseFlags;
548
549typedef struct
550{
551 // default,min,max in this array in that order
552 short defaultminmax[SETTING_DEFAULTMINMAXCOUNT];
553} SettingValueRange_t;
554
555// below is from controller_constants.c which should be compiled into any code that uses this
556extern const SettingValueRange_t g_DefaultSettingValues[SETTING_COUNT];
557
558// Read-write settings for dongle (only add to this enum and never change the order)
559typedef enum
560{
561 DONGLE_SETTING_MOUSE_KEYBOARD_ENABLED,
562 DONGLE_SETTING_COUNT,
563} DongleSettings;
564
565typedef enum
566{
567 AUDIO_STARTUP = 0,
568 AUDIO_SHUTDOWN = 1,
569 AUDIO_PAIR = 2,
570 AUDIO_PAIR_SUCCESS = 3,
571 AUDIO_IDENTIFY = 4,
572 AUDIO_LIZARDMODE = 5,
573 AUDIO_NORMALMODE = 6,
574
575 AUDIO_MAX_SLOT = 15
576} ControllerAudio;
577
578#ifdef __cplusplus
579}
580#endif
581
582#endif // _CONTROLLER_CONSTANTS_H
diff --git a/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_structs.h b/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_structs.h
new file mode 100644
index 0000000..ea2a352
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/hidapi/steam/controller_structs.h
@@ -0,0 +1,463 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 2020 Valve Corporation
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#ifndef _CONTROLLER_STRUCTS_
22#define _CONTROLLER_STRUCTS_
23
24#pragma pack(1)
25
26#define HID_FEATURE_REPORT_BYTES 64
27
28// Header for all host <==> target messages
29typedef struct
30{
31 unsigned char type;
32 unsigned char length;
33} FeatureReportHeader;
34
35// Generic controller settings structure
36typedef struct
37{
38 unsigned char settingNum;
39 unsigned short settingValue;
40} ControllerSetting;
41
42// Generic controller attribute structure
43typedef struct
44{
45 unsigned char attributeTag;
46 uint32_t attributeValue;
47} ControllerAttribute;
48
49// Generic controller settings structure
50typedef struct
51{
52 ControllerSetting settings[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerSetting ) ];
53} MsgSetSettingsValues, MsgGetSettingsValues, MsgGetSettingsDefaults, MsgGetSettingsMaxs;
54
55// Generic controller settings structure
56typedef struct
57{
58 ControllerAttribute attributes[ ( HID_FEATURE_REPORT_BYTES - sizeof( FeatureReportHeader ) ) / sizeof( ControllerAttribute ) ];
59} MsgGetAttributes;
60
61typedef struct
62{
63 unsigned char attributeTag;
64 char attributeValue[20];
65} MsgGetStringAttribute;
66
67typedef struct
68{
69 unsigned char mode;
70} MsgSetControllerMode;
71
72// Trigger a haptic pulse
73typedef struct {
74 unsigned char which_pad;
75 unsigned short pulse_duration;
76 unsigned short pulse_interval;
77 unsigned short pulse_count;
78 short dBgain;
79 unsigned char priority;
80} MsgFireHapticPulse;
81
82typedef struct {
83 uint8_t mode;
84} MsgHapticSetMode;
85
86typedef enum {
87 HAPTIC_TYPE_OFF,
88 HAPTIC_TYPE_TICK,
89 HAPTIC_TYPE_CLICK,
90 HAPTIC_TYPE_TONE,
91 HAPTIC_TYPE_RUMBLE,
92 HAPTIC_TYPE_NOISE,
93 HAPTIC_TYPE_SCRIPT,
94 HAPTIC_TYPE_LOG_SWEEP,
95} haptic_type_t;
96
97typedef enum {
98 HAPTIC_INTENSITY_SYSTEM,
99 HAPTIC_INTENSITY_SHORT,
100 HAPTIC_INTENSITY_MEDIUM,
101 HAPTIC_INTENSITY_LONG,
102 HAPTIC_INTENSITY_INSANE,
103} haptic_intensity_t;
104
105typedef struct {
106 uint8_t side; // 0x01 = L, 0x02 = R, 0x03 = Both
107 uint8_t cmd; // 0 = Off, 1 = tick, 2 = click, 3 = tone, 4 = rumble, 5 =
108 // rumble_noise, 6 = script, 7 = sweep,
109 uint8_t ui_intensity; // 0-4 (0 = default)
110 int8_t dBgain; // dB Can be positive (reasonable clipping / limiting will apply)
111 uint16_t freq; // Frequency of tone (if applicable)
112 int16_t dur_ms; // Duration of tone / rumble (if applicable) (neg = infinite)
113
114 uint16_t noise_intensity;
115 uint16_t lfo_freq; // Drives both tone and rumble geneators
116 uint8_t lfo_depth; // percentage, typically 100
117 uint8_t rand_tone_gain; // Randomize each LFO cycle's gain
118 uint8_t script_id; // Used w/ dBgain for scripted haptics
119
120 uint16_t lss_start_freq; // Used w/ Log Sine Sweep
121 uint16_t lss_end_freq; // Ditto
122} MsgTriggerHaptic;
123
124typedef struct {
125 uint8_t unRumbleType;
126 uint16_t unIntensity;
127 uint16_t unLeftMotorSpeed;
128 uint16_t unRightMotorSpeed;
129 int8_t nLeftGain;
130 int8_t nRightGain;
131} MsgSimpleRumbleCmd;
132
133// This is the only message struct that application code should use to interact with feature request messages. Any new
134// messages should be added to the union. The structures defined here should correspond to the ones defined in
135// ValveDeviceCore.cpp.
136//
137typedef struct
138{
139 FeatureReportHeader header;
140 union
141 {
142 MsgSetSettingsValues setSettingsValues;
143 MsgGetSettingsValues getSettingsValues;
144 MsgGetSettingsMaxs getSettingsMaxs;
145 MsgGetSettingsDefaults getSettingsDefaults;
146 MsgGetAttributes getAttributes;
147 MsgSetControllerMode controllerMode;
148 MsgFireHapticPulse fireHapticPulse;
149 MsgGetStringAttribute getStringAttribute;
150 MsgHapticSetMode hapticMode;
151 MsgTriggerHaptic triggerHaptic;
152 MsgSimpleRumbleCmd simpleRumble;
153 } payload;
154
155} FeatureReportMsg;
156
157// Roll this version forward anytime that you are breaking compatibility of existing
158// message types within ValveInReport_t or the header itself. Hopefully this should
159// be super rare and instead you should just add new message payloads to the union,
160// or just add fields to the end of existing payload structs which is expected to be
161// safe in all code consuming these as they should just consume/copy up to the prior size
162// they were aware of when processing.
163#define k_ValveInReportMsgVersion 0x01
164
165typedef enum
166{
167 ID_CONTROLLER_STATE = 1,
168 ID_CONTROLLER_DEBUG = 2,
169 ID_CONTROLLER_WIRELESS = 3,
170 ID_CONTROLLER_STATUS = 4,
171 ID_CONTROLLER_DEBUG2 = 5,
172 ID_CONTROLLER_SECONDARY_STATE = 6,
173 ID_CONTROLLER_BLE_STATE = 7,
174 ID_CONTROLLER_DECK_STATE = 9,
175 ID_CONTROLLER_MSG_COUNT
176} ValveInReportMessageIDs;
177
178typedef struct
179{
180 unsigned short unReportVersion;
181
182 unsigned char ucType;
183 unsigned char ucLength;
184
185} ValveInReportHeader_t;
186
187// State payload
188typedef struct
189{
190 // If packet num matches that on your prior call, then the controller state hasn't been changed since
191 // your last call and there is no need to process it
192 Uint32 unPacketNum;
193
194 // Button bitmask and trigger data.
195 union
196 {
197 Uint64 ulButtons;
198 struct
199 {
200 unsigned char _pad0[3];
201 unsigned char nLeft;
202 unsigned char nRight;
203 unsigned char _pad1[3];
204 } Triggers;
205 } ButtonTriggerData;
206
207 // Left pad coordinates
208 short sLeftPadX;
209 short sLeftPadY;
210
211 // Right pad coordinates
212 short sRightPadX;
213 short sRightPadY;
214
215 // This is redundant, packed above, but still sent over wired
216 unsigned short sTriggerL;
217 unsigned short sTriggerR;
218
219 // FIXME figure out a way to grab this stuff over wireless
220 short sAccelX;
221 short sAccelY;
222 short sAccelZ;
223
224 short sGyroX;
225 short sGyroY;
226 short sGyroZ;
227
228 short sGyroQuatW;
229 short sGyroQuatX;
230 short sGyroQuatY;
231 short sGyroQuatZ;
232
233} ValveControllerStatePacket_t;
234
235// BLE State payload this has to be re-formatted from the normal state because BLE controller shows up as
236//a HID device and we don't want to send all the optional parts of the message. Keep in sync with struct above.
237typedef struct
238{
239 // If packet num matches that on your prior call, then the controller state hasn't been changed since
240 // your last call and there is no need to process it
241 Uint32 unPacketNum;
242
243 // Button bitmask and trigger data.
244 union
245 {
246 Uint64 ulButtons;
247 struct
248 {
249 unsigned char _pad0[3];
250 unsigned char nLeft;
251 unsigned char nRight;
252 unsigned char _pad1[3];
253 } Triggers;
254 } ButtonTriggerData;
255
256 // Left pad coordinates
257 short sLeftPadX;
258 short sLeftPadY;
259
260 // Right pad coordinates
261 short sRightPadX;
262 short sRightPadY;
263
264 //This mimcs how the dongle reconstitutes HID packets, there will be 0-4 shorts depending on gyro mode
265 unsigned char ucGyroDataType; //TODO could maybe find some unused bits in the button field for this info (is only 2bits)
266 short sGyro[4];
267
268} ValveControllerBLEStatePacket_t;
269
270// Define a payload for reporting debug information
271typedef struct
272{
273 // Left pad coordinates
274 short sLeftPadX;
275 short sLeftPadY;
276
277 // Right pad coordinates
278 short sRightPadX;
279 short sRightPadY;
280
281 // Left mouse deltas
282 short sLeftPadMouseDX;
283 short sLeftPadMouseDY;
284
285 // Right mouse deltas
286 short sRightPadMouseDX;
287 short sRightPadMouseDY;
288
289 // Left mouse filtered deltas
290 short sLeftPadMouseFilteredDX;
291 short sLeftPadMouseFilteredDY;
292
293 // Right mouse filtered deltas
294 short sRightPadMouseFilteredDX;
295 short sRightPadMouseFilteredDY;
296
297 // Pad Z values
298 unsigned char ucLeftZ;
299 unsigned char ucRightZ;
300
301 // FingerPresent
302 unsigned char ucLeftFingerPresent;
303 unsigned char ucRightFingerPresent;
304
305 // Timestamps
306 unsigned char ucLeftTimestamp;
307 unsigned char ucRightTimestamp;
308
309 // Double tap state
310 unsigned char ucLeftTapState;
311 unsigned char ucRightTapState;
312
313 unsigned int unDigitalIOStates0;
314 unsigned int unDigitalIOStates1;
315
316} ValveControllerDebugPacket_t;
317
318typedef struct
319{
320 unsigned char ucPadNum;
321 unsigned char ucPad[3]; // need Data to be word aligned
322 short Data[20];
323 unsigned short unNoise;
324} ValveControllerTrackpadImage_t;
325
326typedef struct
327{
328 unsigned char ucPadNum;
329 unsigned char ucOffset;
330 unsigned char ucPad[2]; // need Data to be word aligned
331 short rgData[28];
332} ValveControllerRawTrackpadImage_t;
333
334// Payload for wireless metadata
335typedef struct
336{
337 unsigned char ucEventType;
338} SteamControllerWirelessEvent_t;
339
340typedef struct
341{
342 // Current packet number.
343 unsigned int unPacketNum;
344
345 // Event codes and state information.
346 unsigned short sEventCode;
347 unsigned short unStateFlags;
348
349 // Current battery voltage (mV).
350 unsigned short sBatteryVoltage;
351
352 // Current battery level (0-100).
353 unsigned char ucBatteryLevel;
354} SteamControllerStatusEvent_t;
355
356// Deck State payload
357typedef struct
358{
359 // If packet num matches that on your prior call, then the controller
360 // state hasn't been changed since your last call and there is no need to
361 // process it
362 Uint32 unPacketNum;
363
364 // Button bitmask and trigger data.
365 union
366 {
367 Uint64 ulButtons;
368 struct
369 {
370 Uint32 ulButtonsL;
371 Uint32 ulButtonsH;
372 };
373 };
374
375 // Left pad coordinates
376 short sLeftPadX;
377 short sLeftPadY;
378
379 // Right pad coordinates
380 short sRightPadX;
381 short sRightPadY;
382
383 // Accelerometer values
384 short sAccelX;
385 short sAccelY;
386 short sAccelZ;
387
388 // Gyroscope values
389 short sGyroX;
390 short sGyroY;
391 short sGyroZ;
392
393 // Gyro quaternions
394 short sGyroQuatW;
395 short sGyroQuatX;
396 short sGyroQuatY;
397 short sGyroQuatZ;
398
399 // Uncalibrated trigger values
400 unsigned short sTriggerRawL;
401 unsigned short sTriggerRawR;
402
403 // Left stick values
404 short sLeftStickX;
405 short sLeftStickY;
406
407 // Right stick values
408 short sRightStickX;
409 short sRightStickY;
410
411 // Touchpad pressures
412 unsigned short sPressurePadLeft;
413 unsigned short sPressurePadRight;
414} SteamDeckStatePacket_t;
415
416typedef struct
417{
418 ValveInReportHeader_t header;
419
420 union
421 {
422 ValveControllerStatePacket_t controllerState;
423 ValveControllerBLEStatePacket_t controllerBLEState;
424 ValveControllerDebugPacket_t debugState;
425 ValveControllerTrackpadImage_t padImage;
426 ValveControllerRawTrackpadImage_t rawPadImage;
427 SteamControllerWirelessEvent_t wirelessEvent;
428 SteamControllerStatusEvent_t statusEvent;
429 SteamDeckStatePacket_t deckState;
430 } payload;
431
432} ValveInReport_t;
433
434
435// Enumeration for BLE packet protocol
436enum EBLEPacketReportNums
437{
438 // Skipping past 2-3 because they are escape characters in Uart protocol
439 k_EBLEReportState = 4,
440 k_EBLEReportStatus = 5,
441};
442
443
444// Enumeration of data chunks in BLE state packets
445enum EBLEOptionDataChunksBitmask
446{
447 // First byte upper nibble
448 k_EBLEButtonChunk1 = 0x10,
449 k_EBLEButtonChunk2 = 0x20,
450 k_EBLEButtonChunk3 = 0x40,
451 k_EBLELeftJoystickChunk = 0x80,
452
453 // Second full byte
454 k_EBLELeftTrackpadChunk = 0x100,
455 k_EBLERightTrackpadChunk = 0x200,
456 k_EBLEIMUAccelChunk = 0x400,
457 k_EBLEIMUGyroChunk = 0x800,
458 k_EBLEIMUQuatChunk = 0x1000,
459};
460
461#pragma pack()
462
463#endif // _CONTROLLER_STRUCTS
diff --git a/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick.c
new file mode 100644
index 0000000..ea73821
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick.c
@@ -0,0 +1,2730 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_LINUX
24
25#ifndef SDL_INPUT_LINUXEV
26#error SDL now requires a Linux 2.4+ kernel with /dev/input/event support.
27#endif
28
29// This is the Linux implementation of the SDL joystick API
30
31#include <sys/stat.h>
32#include <errno.h> // errno, strerror
33#include <fcntl.h>
34#include <limits.h> // For the definition of PATH_MAX
35#ifdef HAVE_INOTIFY
36#include <sys/inotify.h>
37#include <string.h> // strerror
38#endif
39#include <sys/ioctl.h>
40#include <unistd.h>
41#include <dirent.h>
42#include <linux/joystick.h>
43
44#include "../../events/SDL_events_c.h"
45#include "../../core/linux/SDL_evdev.h"
46#include "../SDL_sysjoystick.h"
47#include "../SDL_joystick_c.h"
48#include "../usb_ids.h"
49#include "SDL_sysjoystick_c.h"
50#include "../hidapi/SDL_hidapijoystick_c.h"
51
52// This isn't defined in older Linux kernel headers
53#ifndef MSC_TIMESTAMP
54#define MSC_TIMESTAMP 0x05
55#endif
56
57#ifndef SYN_DROPPED
58#define SYN_DROPPED 3
59#endif
60#ifndef BTN_NORTH
61#define BTN_NORTH 0x133
62#endif
63#ifndef BTN_WEST
64#define BTN_WEST 0x134
65#endif
66#ifndef BTN_DPAD_UP
67#define BTN_DPAD_UP 0x220
68#endif
69#ifndef BTN_DPAD_DOWN
70#define BTN_DPAD_DOWN 0x221
71#endif
72#ifndef BTN_DPAD_LEFT
73#define BTN_DPAD_LEFT 0x222
74#endif
75#ifndef BTN_DPAD_RIGHT
76#define BTN_DPAD_RIGHT 0x223
77#endif
78
79#ifndef BTN_TRIGGER_HAPPY
80#define BTN_TRIGGER_HAPPY 0x2c0
81#define BTN_TRIGGER_HAPPY1 0x2c0
82#define BTN_TRIGGER_HAPPY2 0x2c1
83#define BTN_TRIGGER_HAPPY3 0x2c2
84#define BTN_TRIGGER_HAPPY4 0x2c3
85#define BTN_TRIGGER_HAPPY5 0x2c4
86#define BTN_TRIGGER_HAPPY6 0x2c5
87#define BTN_TRIGGER_HAPPY7 0x2c6
88#define BTN_TRIGGER_HAPPY8 0x2c7
89#define BTN_TRIGGER_HAPPY9 0x2c8
90#define BTN_TRIGGER_HAPPY10 0x2c9
91#define BTN_TRIGGER_HAPPY11 0x2ca
92#define BTN_TRIGGER_HAPPY12 0x2cb
93#define BTN_TRIGGER_HAPPY13 0x2cc
94#define BTN_TRIGGER_HAPPY14 0x2cd
95#define BTN_TRIGGER_HAPPY15 0x2ce
96#define BTN_TRIGGER_HAPPY16 0x2cf
97#define BTN_TRIGGER_HAPPY17 0x2d0
98#define BTN_TRIGGER_HAPPY18 0x2d1
99#define BTN_TRIGGER_HAPPY19 0x2d2
100#define BTN_TRIGGER_HAPPY20 0x2d3
101#define BTN_TRIGGER_HAPPY21 0x2d4
102#define BTN_TRIGGER_HAPPY22 0x2d5
103#define BTN_TRIGGER_HAPPY23 0x2d6
104#define BTN_TRIGGER_HAPPY24 0x2d7
105#define BTN_TRIGGER_HAPPY25 0x2d8
106#define BTN_TRIGGER_HAPPY26 0x2d9
107#define BTN_TRIGGER_HAPPY27 0x2da
108#define BTN_TRIGGER_HAPPY28 0x2db
109#define BTN_TRIGGER_HAPPY29 0x2dc
110#define BTN_TRIGGER_HAPPY30 0x2dd
111#define BTN_TRIGGER_HAPPY31 0x2de
112#define BTN_TRIGGER_HAPPY32 0x2df
113#define BTN_TRIGGER_HAPPY33 0x2e0
114#define BTN_TRIGGER_HAPPY34 0x2e1
115#define BTN_TRIGGER_HAPPY35 0x2e2
116#define BTN_TRIGGER_HAPPY36 0x2e3
117#define BTN_TRIGGER_HAPPY37 0x2e4
118#define BTN_TRIGGER_HAPPY38 0x2e5
119#define BTN_TRIGGER_HAPPY39 0x2e6
120#define BTN_TRIGGER_HAPPY40 0x2e7
121#endif
122
123
124#include "../../core/linux/SDL_evdev_capabilities.h"
125#include "../../core/linux/SDL_udev.h"
126
127#if 0
128#define DEBUG_INPUT_EVENTS 1
129#endif
130
131#if 0
132#define DEBUG_GAMEPAD_MAPPING 1
133#endif
134
135typedef enum
136{
137 ENUMERATION_UNSET,
138 ENUMERATION_LIBUDEV,
139 ENUMERATION_FALLBACK
140} EnumerationMethod;
141
142static EnumerationMethod enumeration_method = ENUMERATION_UNSET;
143
144static bool IsJoystickJSNode(const char *node);
145static void MaybeAddDevice(const char *path);
146static void MaybeRemoveDevice(const char *path);
147
148// A linked list of available joysticks
149typedef struct SDL_joylist_item
150{
151 SDL_JoystickID device_instance;
152 char *path; // "/dev/input/event2" or whatever
153 char *name; // "SideWinder 3D Pro" or whatever
154 SDL_GUID guid;
155 dev_t devnum;
156 int steam_virtual_gamepad_slot;
157 struct joystick_hwdata *hwdata;
158 struct SDL_joylist_item *next;
159
160 bool checked_mapping;
161 SDL_GamepadMapping *mapping;
162} SDL_joylist_item;
163
164// A linked list of available gamepad sensors
165typedef struct SDL_sensorlist_item
166{
167 char *path; // "/dev/input/event2" or whatever
168 dev_t devnum;
169 struct joystick_hwdata *hwdata;
170 struct SDL_sensorlist_item *next;
171} SDL_sensorlist_item;
172
173static bool SDL_classic_joysticks = false;
174static SDL_joylist_item *SDL_joylist SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
175static SDL_joylist_item *SDL_joylist_tail SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
176static int numjoysticks SDL_GUARDED_BY(SDL_joystick_lock) = 0;
177static SDL_sensorlist_item *SDL_sensorlist SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
178static int inotify_fd = -1;
179
180static Uint64 last_joy_detect_time;
181static time_t last_input_dir_mtime;
182
183static void FixupDeviceInfoForMapping(int fd, struct input_id *inpid)
184{
185 if (inpid->vendor == 0x045e && inpid->product == 0x0b05 && inpid->version == 0x0903) {
186 // This is a Microsoft Xbox One Elite Series 2 controller
187 unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
188
189 // The first version of the firmware duplicated all the inputs
190 if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) >= 0) &&
191 test_bit(0x2c0, keybit)) {
192 // Change the version to 0x0902, so we can map it differently
193 inpid->version = 0x0902;
194 }
195 }
196
197 /* For Atari vcs modern and classic controllers have the version reflecting
198 * firmware version, but the mapping stays stable so ignore
199 * version information */
200 if (inpid->vendor == 0x3250 && (inpid->product == 0x1001 || inpid->product == 0x1002)) {
201 inpid->version = 0;
202 }
203}
204
205#ifdef SDL_JOYSTICK_HIDAPI
206static bool IsVirtualJoystick(Uint16 vendor, Uint16 product, Uint16 version, const char *name)
207{
208 if (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX_ONE_S && version == 0 &&
209 SDL_strcmp(name, "Xbox One S Controller") == 0) {
210 // This is the virtual device created by the xow driver
211 return true;
212 }
213 return false;
214}
215#else
216static bool IsVirtualJoystick(Uint16 vendor, Uint16 product, Uint16 version, const char *name)
217{
218 return false;
219}
220#endif // SDL_JOYSTICK_HIDAPI
221
222static bool GetSteamVirtualGamepadSlot(int fd, int *slot)
223{
224 char name[128];
225
226 if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) > 0) {
227 const char *digits = SDL_strstr(name, "pad ");
228 if (digits) {
229 digits += 4;
230 if (SDL_isdigit(*digits)) {
231 *slot = SDL_atoi(digits);
232 return true;
233 }
234 }
235 }
236 return false;
237}
238
239static int GuessDeviceClass(int fd)
240{
241 unsigned long propbit[NBITS(INPUT_PROP_MAX)] = { 0 };
242 unsigned long evbit[NBITS(EV_MAX)] = { 0 };
243 unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
244 unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
245 unsigned long relbit[NBITS(REL_MAX)] = { 0 };
246
247 if ((ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit) < 0) ||
248 (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) < 0) ||
249 (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) < 0) ||
250 (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) < 0)) {
251 return 0;
252 }
253
254 /* This is a newer feature, so it's allowed to fail - if so, then the
255 * device just doesn't have any properties. */
256 (void) ioctl(fd, EVIOCGPROP(sizeof(propbit)), propbit);
257
258 return SDL_EVDEV_GuessDeviceClass(propbit, evbit, absbit, keybit, relbit);
259}
260
261static bool GuessIsJoystick(int fd)
262{
263 if (GuessDeviceClass(fd) & SDL_UDEV_DEVICE_JOYSTICK) {
264 return true;
265 }
266 return false;
267}
268
269static bool GuessIsSensor(int fd)
270{
271 if (GuessDeviceClass(fd) & SDL_UDEV_DEVICE_ACCELEROMETER) {
272 return true;
273 }
274 return false;
275}
276
277static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid)
278{
279 struct input_id inpid;
280 char *name;
281 char product_string[128];
282 int class = 0;
283
284 SDL_zero(inpid);
285#ifdef SDL_USE_LIBUDEV
286 // Opening input devices can generate synchronous device I/O, so avoid it if we can
287 if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
288 !(class & SDL_UDEV_DEVICE_JOYSTICK)) {
289 return false;
290 }
291#endif
292
293 if (fd && *fd < 0) {
294 *fd = open(path, O_RDONLY | O_CLOEXEC, 0);
295 }
296 if (!fd || *fd < 0) {
297 return false;
298 }
299
300 if (ioctl(*fd, JSIOCGNAME(sizeof(product_string)), product_string) <= 0) {
301 // When udev enumeration or classification, we only got joysticks here, so no need to test
302 if (enumeration_method != ENUMERATION_LIBUDEV && !class && !GuessIsJoystick(*fd)) {
303 return false;
304 }
305
306 // Could have vendor and product already from udev, but should agree with evdev
307 if (ioctl(*fd, EVIOCGID, &inpid) < 0) {
308 return false;
309 }
310
311 if (ioctl(*fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) {
312 return false;
313 }
314 }
315
316 name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string);
317 if (!name) {
318 return false;
319 }
320
321 if (!IsVirtualJoystick(inpid.vendor, inpid.product, inpid.version, name) &&
322 SDL_JoystickHandledByAnotherDriver(&SDL_LINUX_JoystickDriver, inpid.vendor, inpid.product, inpid.version, name)) {
323 SDL_free(name);
324 return false;
325 }
326
327 FixupDeviceInfoForMapping(*fd, &inpid);
328
329#ifdef DEBUG_JOYSTICK
330 SDL_Log("Joystick: %s, bustype = %d, vendor = 0x%.4x, product = 0x%.4x, version = %d", name, inpid.bustype, inpid.vendor, inpid.product, inpid.version);
331#endif
332
333 if (SDL_ShouldIgnoreJoystick(inpid.vendor, inpid.product, inpid.version, name)) {
334 SDL_free(name);
335 return false;
336 }
337 *name_return = name;
338 *vendor_return = inpid.vendor;
339 *product_return = inpid.product;
340 *guid = SDL_CreateJoystickGUID(inpid.bustype, inpid.vendor, inpid.product, inpid.version, NULL, product_string, 0, 0);
341 return true;
342}
343
344static bool IsSensor(const char *path, int *fd)
345{
346 struct input_id inpid;
347 int class = 0;
348
349 SDL_zero(inpid);
350#ifdef SDL_USE_LIBUDEV
351 // Opening input devices can generate synchronous device I/O, so avoid it if we can
352 if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
353 !(class & SDL_UDEV_DEVICE_ACCELEROMETER)) {
354 return false;
355 }
356#endif
357
358 if (fd && *fd < 0) {
359 *fd = open(path, O_RDONLY | O_CLOEXEC, 0);
360 }
361 if (!fd || *fd < 0) {
362 return false;
363 }
364
365 if (!class && !GuessIsSensor(*fd)) {
366 return false;
367 }
368
369 if (ioctl(*fd, EVIOCGID, &inpid) < 0) {
370 return false;
371 }
372
373 if (inpid.vendor == USB_VENDOR_NINTENDO && inpid.product == USB_PRODUCT_NINTENDO_WII_REMOTE) {
374 // Wii extension controls
375 // These may create 3 sensor devices but we only support reading from 1: ignore them
376 return false;
377 }
378
379 return true;
380}
381
382#ifdef SDL_USE_LIBUDEV
383static void joystick_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
384{
385 if (!devpath) {
386 return;
387 }
388
389 switch (udev_type) {
390 case SDL_UDEV_DEVICEADDED:
391 if (!(udev_class & (SDL_UDEV_DEVICE_JOYSTICK | SDL_UDEV_DEVICE_ACCELEROMETER))) {
392 return;
393 }
394 if (SDL_classic_joysticks) {
395 if (!IsJoystickJSNode(devpath)) {
396 return;
397 }
398 } else {
399 if (IsJoystickJSNode(devpath)) {
400 return;
401 }
402 }
403
404 // Wait a bit for the hidraw udev node to initialize
405 SDL_Delay(10);
406
407 MaybeAddDevice(devpath);
408 break;
409
410 case SDL_UDEV_DEVICEREMOVED:
411 MaybeRemoveDevice(devpath);
412 break;
413
414 default:
415 break;
416 }
417}
418#endif // SDL_USE_LIBUDEV
419
420static void FreeJoylistItem(SDL_joylist_item *item)
421{
422 SDL_free(item->mapping);
423 SDL_free(item->path);
424 SDL_free(item->name);
425 SDL_free(item);
426}
427
428static void FreeSensorlistItem(SDL_sensorlist_item *item)
429{
430 SDL_free(item->path);
431 SDL_free(item);
432}
433
434static void MaybeAddDevice(const char *path)
435{
436 struct stat sb;
437 int fd = -1;
438 char *name = NULL;
439 Uint16 vendor, product;
440 SDL_GUID guid;
441 SDL_joylist_item *item;
442 SDL_sensorlist_item *item_sensor;
443
444 if (!path) {
445 return;
446 }
447
448 fd = open(path, O_RDONLY | O_CLOEXEC, 0);
449 if (fd < 0) {
450 return;
451 }
452
453 if (fstat(fd, &sb) == -1) {
454 close(fd);
455 return;
456 }
457
458 SDL_LockJoysticks();
459
460 // Check to make sure it's not already in list.
461 for (item = SDL_joylist; item; item = item->next) {
462 if (sb.st_rdev == item->devnum) {
463 goto done; // already have this one
464 }
465 }
466 for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = item_sensor->next) {
467 if (sb.st_rdev == item_sensor->devnum) {
468 goto done; // already have this one
469 }
470 }
471
472#ifdef DEBUG_INPUT_EVENTS
473 SDL_Log("Checking %s", path);
474#endif
475
476 if (IsJoystick(path, &fd, &name, &vendor, &product, &guid)) {
477#ifdef DEBUG_INPUT_EVENTS
478 SDL_Log("found joystick: %s", path);
479#endif
480 item = (SDL_joylist_item *)SDL_calloc(1, sizeof(SDL_joylist_item));
481 if (!item) {
482 SDL_free(name);
483 goto done;
484 }
485
486 item->devnum = sb.st_rdev;
487 item->steam_virtual_gamepad_slot = -1;
488 item->path = SDL_strdup(path);
489 item->name = name;
490 item->guid = guid;
491
492 if (vendor == USB_VENDOR_VALVE &&
493 product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
494 GetSteamVirtualGamepadSlot(fd, &item->steam_virtual_gamepad_slot);
495 }
496
497 if ((!item->path) || (!item->name)) {
498 FreeJoylistItem(item);
499 goto done;
500 }
501
502 item->device_instance = SDL_GetNextObjectID();
503 if (!SDL_joylist_tail) {
504 SDL_joylist = SDL_joylist_tail = item;
505 } else {
506 SDL_joylist_tail->next = item;
507 SDL_joylist_tail = item;
508 }
509
510 // Need to increment the joystick count before we post the event
511 ++numjoysticks;
512
513 SDL_PrivateJoystickAdded(item->device_instance);
514 goto done;
515 }
516
517 if (IsSensor(path, &fd)) {
518#ifdef DEBUG_INPUT_EVENTS
519 SDL_Log("found sensor: %s", path);
520#endif
521 item_sensor = (SDL_sensorlist_item *)SDL_calloc(1, sizeof(SDL_sensorlist_item));
522 if (!item_sensor) {
523 goto done;
524 }
525 item_sensor->devnum = sb.st_rdev;
526 item_sensor->path = SDL_strdup(path);
527
528 if (!item_sensor->path) {
529 FreeSensorlistItem(item_sensor);
530 goto done;
531 }
532
533 item_sensor->next = SDL_sensorlist;
534 SDL_sensorlist = item_sensor;
535 goto done;
536 }
537
538done:
539 close(fd);
540 SDL_UnlockJoysticks();
541}
542
543static void RemoveJoylistItem(SDL_joylist_item *item, SDL_joylist_item *prev)
544{
545 SDL_AssertJoysticksLocked();
546
547 if (item->hwdata) {
548 item->hwdata->item = NULL;
549 }
550
551 if (prev) {
552 prev->next = item->next;
553 } else {
554 SDL_assert(SDL_joylist == item);
555 SDL_joylist = item->next;
556 }
557
558 if (item == SDL_joylist_tail) {
559 SDL_joylist_tail = prev;
560 }
561
562 // Need to decrement the joystick count before we post the event
563 --numjoysticks;
564
565 SDL_PrivateJoystickRemoved(item->device_instance);
566 FreeJoylistItem(item);
567}
568
569static void RemoveSensorlistItem(SDL_sensorlist_item *item, SDL_sensorlist_item *prev)
570{
571 SDL_AssertJoysticksLocked();
572
573 if (item->hwdata) {
574 item->hwdata->item_sensor = NULL;
575 }
576
577 if (prev) {
578 prev->next = item->next;
579 } else {
580 SDL_assert(SDL_sensorlist == item);
581 SDL_sensorlist = item->next;
582 }
583
584 /* Do not call SDL_PrivateJoystickRemoved here as RemoveJoylistItem will do it,
585 * assuming both sensor and joy item are removed at the same time */
586 FreeSensorlistItem(item);
587}
588
589static void MaybeRemoveDevice(const char *path)
590{
591 SDL_joylist_item *item;
592 SDL_joylist_item *prev = NULL;
593 SDL_sensorlist_item *item_sensor;
594 SDL_sensorlist_item *prev_sensor = NULL;
595
596 if (!path) {
597 return;
598 }
599
600 SDL_LockJoysticks();
601 for (item = SDL_joylist; item; item = item->next) {
602 // found it, remove it.
603 if (SDL_strcmp(path, item->path) == 0) {
604 RemoveJoylistItem(item, prev);
605 goto done;
606 }
607 prev = item;
608 }
609 for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = item_sensor->next) {
610 // found it, remove it.
611 if (SDL_strcmp(path, item_sensor->path) == 0) {
612 RemoveSensorlistItem(item_sensor, prev_sensor);
613 goto done;
614 }
615 prev_sensor = item_sensor;
616 }
617done:
618 SDL_UnlockJoysticks();
619}
620
621static void HandlePendingRemovals(void)
622{
623 SDL_joylist_item *prev = NULL;
624 SDL_joylist_item *item = NULL;
625 SDL_sensorlist_item *prev_sensor = NULL;
626 SDL_sensorlist_item *item_sensor = NULL;
627
628 SDL_AssertJoysticksLocked();
629
630 item = SDL_joylist;
631 while (item) {
632 if (item->hwdata && item->hwdata->gone) {
633 RemoveJoylistItem(item, prev);
634
635 if (prev) {
636 item = prev->next;
637 } else {
638 item = SDL_joylist;
639 }
640 } else {
641 prev = item;
642 item = item->next;
643 }
644 }
645
646 item_sensor = SDL_sensorlist;
647 while (item_sensor) {
648 if (item_sensor->hwdata && item_sensor->hwdata->sensor_gone) {
649 RemoveSensorlistItem(item_sensor, prev_sensor);
650
651 if (prev_sensor) {
652 item_sensor = prev_sensor->next;
653 } else {
654 item_sensor = SDL_sensorlist;
655 }
656 } else {
657 prev_sensor = item_sensor;
658 item_sensor = item_sensor->next;
659 }
660 }
661}
662
663static bool StrIsInteger(const char *string)
664{
665 const char *p;
666
667 if (*string == '\0') {
668 return false;
669 }
670
671 for (p = string; *p != '\0'; p++) {
672 if (*p < '0' || *p > '9') {
673 return false;
674 }
675 }
676
677 return true;
678}
679
680static bool IsJoystickJSNode(const char *node)
681{
682 const char *last_slash = SDL_strrchr(node, '/');
683 if (last_slash) {
684 node = last_slash + 1;
685 }
686 return SDL_startswith(node, "js") && StrIsInteger(node + 2);
687}
688
689static bool IsJoystickEventNode(const char *node)
690{
691 const char *last_slash = SDL_strrchr(node, '/');
692 if (last_slash) {
693 node = last_slash + 1;
694 }
695 return SDL_startswith(node, "event") && StrIsInteger(node + 5);
696}
697
698static bool IsJoystickDeviceNode(const char *node)
699{
700 if (SDL_classic_joysticks) {
701 return IsJoystickJSNode(node);
702 } else {
703 return IsJoystickEventNode(node);
704 }
705}
706
707#ifdef HAVE_INOTIFY
708#ifdef HAVE_INOTIFY_INIT1
709static int SDL_inotify_init1(void)
710{
711 return inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
712}
713#else
714static int SDL_inotify_init1(void)
715{
716 int fd = inotify_init();
717 if (fd < 0) {
718 return -1;
719 }
720 fcntl(fd, F_SETFL, O_NONBLOCK);
721 fcntl(fd, F_SETFD, FD_CLOEXEC);
722 return fd;
723}
724#endif
725
726static void LINUX_InotifyJoystickDetect(void)
727{
728 union
729 {
730 struct inotify_event event;
731 char storage[4096];
732 char enough_for_inotify[sizeof(struct inotify_event) + NAME_MAX + 1];
733 } buf;
734 ssize_t bytes;
735 size_t remain = 0;
736 size_t len;
737 char path[PATH_MAX];
738
739 bytes = read(inotify_fd, &buf, sizeof(buf));
740
741 if (bytes > 0) {
742 remain = (size_t)bytes;
743 }
744
745 while (remain > 0) {
746 if (buf.event.len > 0) {
747 if (IsJoystickDeviceNode(buf.event.name)) {
748 (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", buf.event.name);
749
750 if (buf.event.mask & (IN_CREATE | IN_MOVED_TO | IN_ATTRIB)) {
751 MaybeAddDevice(path);
752 } else if (buf.event.mask & (IN_DELETE | IN_MOVED_FROM)) {
753 MaybeRemoveDevice(path);
754 }
755 }
756 }
757
758 len = sizeof(struct inotify_event) + buf.event.len;
759 remain -= len;
760
761 if (remain != 0) {
762 SDL_memmove(&buf.storage[0], &buf.storage[len], remain);
763 }
764 }
765}
766#endif // HAVE_INOTIFY
767
768static int get_event_joystick_index(int event)
769{
770 int joystick_index = -1;
771 int i, count;
772 struct dirent **entries = NULL;
773 char path[PATH_MAX];
774
775 (void)SDL_snprintf(path, SDL_arraysize(path), "/sys/class/input/event%d/device", event);
776 count = scandir(path, &entries, NULL, alphasort);
777 for (i = 0; i < count; ++i) {
778 if (SDL_strncmp(entries[i]->d_name, "js", 2) == 0) {
779 joystick_index = SDL_atoi(entries[i]->d_name + 2);
780 }
781 free(entries[i]); // This should NOT be SDL_free()
782 }
783 free(entries); // This should NOT be SDL_free()
784
785 return joystick_index;
786}
787
788/* Detect devices by reading /dev/input. In the inotify code path we
789 * have to do this the first time, to detect devices that already existed
790 * before we started; in the non-inotify code path we do this repeatedly
791 * (polling). */
792static int filter_entries(const struct dirent *entry)
793{
794 return IsJoystickDeviceNode(entry->d_name);
795}
796static int SDLCALL sort_entries(const void *_a, const void *_b)
797{
798 const struct dirent **a = (const struct dirent **)_a;
799 const struct dirent **b = (const struct dirent **)_b;
800 int numA, numB;
801 int offset;
802
803 if (SDL_classic_joysticks) {
804 offset = 2; // strlen("js")
805 numA = SDL_atoi((*a)->d_name + offset);
806 numB = SDL_atoi((*b)->d_name + offset);
807 } else {
808 offset = 5; // strlen("event")
809 numA = SDL_atoi((*a)->d_name + offset);
810 numB = SDL_atoi((*b)->d_name + offset);
811
812 // See if we can get the joystick ordering
813 {
814 int jsA = get_event_joystick_index(numA);
815 int jsB = get_event_joystick_index(numB);
816 if (jsA >= 0 && jsB >= 0) {
817 numA = jsA;
818 numB = jsB;
819 } else if (jsA >= 0) {
820 return -1;
821 } else if (jsB >= 0) {
822 return 1;
823 }
824 }
825 }
826 return numA - numB;
827}
828
829typedef struct
830{
831 char *path;
832 int slot;
833} VirtualGamepadEntry;
834
835static int SDLCALL sort_virtual_gamepads(const void *_a, const void *_b)
836{
837 const VirtualGamepadEntry *a = (const VirtualGamepadEntry *)_a;
838 const VirtualGamepadEntry *b = (const VirtualGamepadEntry *)_b;
839 return a->slot - b->slot;
840}
841
842static void LINUX_ScanSteamVirtualGamepads(void)
843{
844 int i, count;
845 int fd;
846 struct dirent **entries = NULL;
847 char path[PATH_MAX];
848 struct input_id inpid;
849 int num_virtual_gamepads = 0;
850 int virtual_gamepad_slot;
851 VirtualGamepadEntry *virtual_gamepads = NULL;
852#ifdef SDL_USE_LIBUDEV
853 int class;
854#endif
855
856 count = scandir("/dev/input", &entries, filter_entries, NULL);
857 for (i = 0; i < count; ++i) {
858 (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", entries[i]->d_name);
859
860#ifdef SDL_USE_LIBUDEV
861 // Opening input devices can generate synchronous device I/O, so avoid it if we can
862 class = 0;
863 SDL_zero(inpid);
864 if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
865 (inpid.vendor != USB_VENDOR_VALVE || inpid.product != USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) {
866 free(entries[i]); // This should NOT be SDL_free()
867 continue;
868 }
869#endif
870 fd = open(path, O_RDONLY | O_CLOEXEC, 0);
871 if (fd >= 0) {
872 if (ioctl(fd, EVIOCGID, &inpid) == 0 &&
873 inpid.vendor == USB_VENDOR_VALVE &&
874 inpid.product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD &&
875 GetSteamVirtualGamepadSlot(fd, &virtual_gamepad_slot)) {
876 VirtualGamepadEntry *new_virtual_gamepads = (VirtualGamepadEntry *)SDL_realloc(virtual_gamepads, (num_virtual_gamepads + 1) * sizeof(*virtual_gamepads));
877 if (new_virtual_gamepads) {
878 VirtualGamepadEntry *entry = &new_virtual_gamepads[num_virtual_gamepads];
879 entry->path = SDL_strdup(path);
880 entry->slot = virtual_gamepad_slot;
881 if (entry->path) {
882 virtual_gamepads = new_virtual_gamepads;
883 ++num_virtual_gamepads;
884 } else {
885 SDL_free(entry->path);
886 SDL_free(new_virtual_gamepads);
887 }
888 }
889 }
890 close(fd);
891 }
892 free(entries[i]); // This should NOT be SDL_free()
893 }
894 free(entries); // This should NOT be SDL_free()
895
896 if (num_virtual_gamepads > 1) {
897 SDL_qsort(virtual_gamepads, num_virtual_gamepads, sizeof(*virtual_gamepads), sort_virtual_gamepads);
898 }
899 for (i = 0; i < num_virtual_gamepads; ++i) {
900 MaybeAddDevice(virtual_gamepads[i].path);
901 SDL_free(virtual_gamepads[i].path);
902 }
903 SDL_free(virtual_gamepads);
904}
905
906static void LINUX_ScanInputDevices(void)
907{
908 int i, count;
909 struct dirent **entries = NULL;
910 char path[PATH_MAX];
911
912 count = scandir("/dev/input", &entries, filter_entries, NULL);
913 if (count > 1) {
914 SDL_qsort(entries, count, sizeof(*entries), sort_entries);
915 }
916 for (i = 0; i < count; ++i) {
917 (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", entries[i]->d_name);
918 MaybeAddDevice(path);
919
920 free(entries[i]); // This should NOT be SDL_free()
921 }
922 free(entries); // This should NOT be SDL_free()
923}
924
925static void LINUX_FallbackJoystickDetect(void)
926{
927 const Uint32 SDL_JOY_DETECT_INTERVAL_MS = 3000; // Update every 3 seconds
928 Uint64 now = SDL_GetTicks();
929
930 if (!last_joy_detect_time || now >= (last_joy_detect_time + SDL_JOY_DETECT_INTERVAL_MS)) {
931 struct stat sb;
932
933 // Opening input devices can generate synchronous device I/O, so avoid it if we can
934 if (stat("/dev/input", &sb) == 0 && sb.st_mtime != last_input_dir_mtime) {
935 // Look for Steam virtual gamepads first, and sort by Steam controller slot
936 LINUX_ScanSteamVirtualGamepads();
937
938 LINUX_ScanInputDevices();
939
940 last_input_dir_mtime = sb.st_mtime;
941 }
942
943 last_joy_detect_time = now;
944 }
945}
946
947static void LINUX_JoystickDetect(void)
948{
949#ifdef SDL_USE_LIBUDEV
950 if (enumeration_method == ENUMERATION_LIBUDEV) {
951 SDL_UDEV_Poll();
952 } else
953#endif
954#ifdef HAVE_INOTIFY
955 if (inotify_fd >= 0 && last_joy_detect_time != 0) {
956 LINUX_InotifyJoystickDetect();
957 } else
958#endif
959 {
960 LINUX_FallbackJoystickDetect();
961 }
962
963 HandlePendingRemovals();
964}
965
966static bool LINUX_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
967{
968 // We don't override any other drivers
969 return false;
970}
971
972static bool LINUX_JoystickInit(void)
973{
974 const char *devices = SDL_GetHint(SDL_HINT_JOYSTICK_DEVICE);
975#ifdef SDL_USE_LIBUDEV
976 bool udev_initialized = SDL_UDEV_Init();
977#endif
978
979 SDL_classic_joysticks = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_LINUX_CLASSIC, false);
980
981 enumeration_method = ENUMERATION_UNSET;
982
983 // First see if the user specified one or more joysticks to use
984 if (devices) {
985 char *envcopy, *envpath, *delim;
986 envcopy = SDL_strdup(devices);
987 envpath = envcopy;
988 while (envpath) {
989 delim = SDL_strchr(envpath, ':');
990 if (delim) {
991 *delim++ = '\0';
992 }
993 MaybeAddDevice(envpath);
994 envpath = delim;
995 }
996 SDL_free(envcopy);
997 }
998
999 // Force immediate joystick detection if using fallback
1000 last_joy_detect_time = 0;
1001 last_input_dir_mtime = 0;
1002
1003 // Manually scan first, since we sort by device number and udev doesn't
1004 LINUX_JoystickDetect();
1005
1006#ifdef SDL_USE_LIBUDEV
1007 if (enumeration_method == ENUMERATION_UNSET) {
1008 if (SDL_GetHintBoolean("SDL_JOYSTICK_DISABLE_UDEV", false)) {
1009 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
1010 "udev disabled by SDL_JOYSTICK_DISABLE_UDEV");
1011 enumeration_method = ENUMERATION_FALLBACK;
1012 } else if (SDL_GetSandbox() != SDL_SANDBOX_NONE) {
1013 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
1014 "Container detected, disabling udev integration");
1015 enumeration_method = ENUMERATION_FALLBACK;
1016
1017 } else {
1018 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
1019 "Using udev for joystick device discovery");
1020 enumeration_method = ENUMERATION_LIBUDEV;
1021 }
1022 }
1023
1024 if (enumeration_method == ENUMERATION_LIBUDEV) {
1025 if (udev_initialized) {
1026 // Set up the udev callback
1027 if (!SDL_UDEV_AddCallback(joystick_udev_callback)) {
1028 SDL_UDEV_Quit();
1029 return SDL_SetError("Could not set up joystick <-> udev callback");
1030 }
1031
1032 // Force a scan to build the initial device list
1033 SDL_UDEV_Scan();
1034 } else {
1035 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
1036 "udev init failed, disabling udev integration");
1037 enumeration_method = ENUMERATION_FALLBACK;
1038 }
1039 } else {
1040 if (udev_initialized) {
1041 SDL_UDEV_Quit();
1042 }
1043 }
1044#endif
1045
1046 if (enumeration_method != ENUMERATION_LIBUDEV) {
1047#ifdef HAVE_INOTIFY
1048 inotify_fd = SDL_inotify_init1();
1049
1050 if (inotify_fd < 0) {
1051 SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
1052 "Unable to initialize inotify, falling back to polling: %s",
1053 strerror(errno));
1054 } else {
1055 /* We need to watch for attribute changes in addition to
1056 * creation, because when a device is first created, it has
1057 * permissions that we can't read. When udev chmods it to
1058 * something that we maybe *can* read, we'll get an
1059 * IN_ATTRIB event to tell us. */
1060 if (inotify_add_watch(inotify_fd, "/dev/input",
1061 IN_CREATE | IN_DELETE | IN_MOVE | IN_ATTRIB) < 0) {
1062 close(inotify_fd);
1063 inotify_fd = -1;
1064 SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
1065 "Unable to add inotify watch, falling back to polling: %s",
1066 strerror(errno));
1067 }
1068 }
1069#endif // HAVE_INOTIFY
1070 }
1071
1072 return true;
1073}
1074
1075static int LINUX_JoystickGetCount(void)
1076{
1077 SDL_AssertJoysticksLocked();
1078
1079 return numjoysticks;
1080}
1081
1082static SDL_joylist_item *GetJoystickByDevIndex(int device_index)
1083{
1084 SDL_joylist_item *item;
1085
1086 SDL_AssertJoysticksLocked();
1087
1088 if ((device_index < 0) || (device_index >= numjoysticks)) {
1089 return NULL;
1090 }
1091
1092 item = SDL_joylist;
1093 while (device_index > 0) {
1094 SDL_assert(item != NULL);
1095 device_index--;
1096 item = item->next;
1097 }
1098
1099 return item;
1100}
1101
1102static const char *LINUX_JoystickGetDeviceName(int device_index)
1103{
1104 return GetJoystickByDevIndex(device_index)->name;
1105}
1106
1107static const char *LINUX_JoystickGetDevicePath(int device_index)
1108{
1109 return GetJoystickByDevIndex(device_index)->path;
1110}
1111
1112static int LINUX_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
1113{
1114 return GetJoystickByDevIndex(device_index)->steam_virtual_gamepad_slot;
1115}
1116
1117static int LINUX_JoystickGetDevicePlayerIndex(int device_index)
1118{
1119 return -1;
1120}
1121
1122static void LINUX_JoystickSetDevicePlayerIndex(int device_index, int player_index)
1123{
1124}
1125
1126static SDL_GUID LINUX_JoystickGetDeviceGUID(int device_index)
1127{
1128 return GetJoystickByDevIndex(device_index)->guid;
1129}
1130
1131// Function to perform the mapping from device index to the instance id for this index
1132static SDL_JoystickID LINUX_JoystickGetDeviceInstanceID(int device_index)
1133{
1134 return GetJoystickByDevIndex(device_index)->device_instance;
1135}
1136
1137static bool allocate_balldata(SDL_Joystick *joystick)
1138{
1139 joystick->hwdata->balls =
1140 (struct hwdata_ball *)SDL_calloc(joystick->nballs, sizeof(struct hwdata_ball));
1141 if (joystick->hwdata->balls == NULL) {
1142 return false;
1143 }
1144 return true;
1145}
1146
1147static bool allocate_hatdata(SDL_Joystick *joystick)
1148{
1149 int i;
1150
1151 SDL_AssertJoysticksLocked();
1152
1153 joystick->hwdata->hats =
1154 (struct hwdata_hat *)SDL_malloc(joystick->nhats *
1155 sizeof(struct hwdata_hat));
1156 if (!joystick->hwdata->hats) {
1157 return false;
1158 }
1159 for (i = 0; i < joystick->nhats; ++i) {
1160 joystick->hwdata->hats[i].axis[0] = 1;
1161 joystick->hwdata->hats[i].axis[1] = 1;
1162 }
1163 return true;
1164}
1165
1166static bool GuessIfAxesAreDigitalHat(struct input_absinfo *absinfo_x, struct input_absinfo *absinfo_y)
1167{
1168 /* A "hat" is assumed to be a digital input with at most 9 possible states
1169 * (3 per axis: negative/zero/positive), as opposed to a true "axis" which
1170 * can report a continuous range of possible values. Unfortunately the Linux
1171 * joystick interface makes no distinction between digital hat axes and any
1172 * other continuous analog axis, so we have to guess. */
1173
1174 // If both axes are missing, they're not anything.
1175 if (!absinfo_x && !absinfo_y) {
1176 return false;
1177 }
1178
1179 // If the hint says so, treat all hats as digital.
1180 if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_LINUX_DIGITAL_HATS, false)) {
1181 return true;
1182 }
1183
1184 // If both axes have ranges constrained between -1 and 1, they're definitely digital.
1185 if ((!absinfo_x || (absinfo_x->minimum == -1 && absinfo_x->maximum == 1)) && (!absinfo_y || (absinfo_y->minimum == -1 && absinfo_y->maximum == 1))) {
1186 return true;
1187 }
1188
1189 // If both axes lack fuzz, flat, and resolution values, they're probably digital.
1190 if ((!absinfo_x || (!absinfo_x->fuzz && !absinfo_x->flat && !absinfo_x->resolution)) && (!absinfo_y || (!absinfo_y->fuzz && !absinfo_y->flat && !absinfo_y->resolution))) {
1191 return true;
1192 }
1193
1194 // Otherwise, treat them as analog.
1195 return false;
1196}
1197
1198static void ConfigJoystick(SDL_Joystick *joystick, int fd, int fd_sensor)
1199{
1200 int i, t;
1201 unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
1202 unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
1203 unsigned long relbit[NBITS(REL_MAX)] = { 0 };
1204 unsigned long ffbit[NBITS(FF_MAX)] = { 0 };
1205 Uint8 key_pam_size, abs_pam_size;
1206 bool use_deadzones = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_LINUX_DEADZONES, false);
1207 bool use_hat_deadzones = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_LINUX_HAT_DEADZONES, true);
1208
1209 SDL_AssertJoysticksLocked();
1210
1211 // See if this device uses the new unified event API
1212 if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) >= 0) &&
1213 (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) >= 0) &&
1214 (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) >= 0)) {
1215
1216 // Get the number of buttons, axes, and other thingamajigs
1217 for (i = BTN_JOYSTICK; i < KEY_MAX; ++i) {
1218 if (test_bit(i, keybit)) {
1219#ifdef DEBUG_INPUT_EVENTS
1220 SDL_Log("Joystick has button: 0x%x", i);
1221#endif
1222 joystick->hwdata->key_map[i] = joystick->nbuttons;
1223 joystick->hwdata->has_key[i] = true;
1224 ++joystick->nbuttons;
1225 }
1226 }
1227 for (i = 0; i < BTN_JOYSTICK; ++i) {
1228 if (test_bit(i, keybit)) {
1229#ifdef DEBUG_INPUT_EVENTS
1230 SDL_Log("Joystick has button: 0x%x", i);
1231#endif
1232 joystick->hwdata->key_map[i] = joystick->nbuttons;
1233 joystick->hwdata->has_key[i] = true;
1234 ++joystick->nbuttons;
1235 }
1236 }
1237 for (i = ABS_HAT0X; i <= ABS_HAT3Y; i += 2) {
1238 int hat_x = -1;
1239 int hat_y = -1;
1240 struct input_absinfo absinfo_x;
1241 struct input_absinfo absinfo_y;
1242 if (test_bit(i, absbit)) {
1243 hat_x = ioctl(fd, EVIOCGABS(i), &absinfo_x);
1244 }
1245 if (test_bit(i + 1, absbit)) {
1246 hat_y = ioctl(fd, EVIOCGABS(i + 1), &absinfo_y);
1247 }
1248 if (GuessIfAxesAreDigitalHat((hat_x < 0 ? (void *)0 : &absinfo_x),
1249 (hat_y < 0 ? (void *)0 : &absinfo_y))) {
1250 const int hat_index = (i - ABS_HAT0X) / 2;
1251 struct hat_axis_correct *correct = &joystick->hwdata->hat_correct[hat_index];
1252#ifdef DEBUG_INPUT_EVENTS
1253 SDL_Log("Joystick has digital hat: #%d", hat_index);
1254 if (hat_x >= 0) {
1255 SDL_Log("X Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }",
1256 absinfo_x.value, absinfo_x.minimum, absinfo_x.maximum,
1257 absinfo_x.fuzz, absinfo_x.flat, absinfo_x.resolution);
1258 }
1259 if (hat_y >= 0) {
1260 SDL_Log("Y Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }",
1261 absinfo_y.value, absinfo_y.minimum, absinfo_y.maximum,
1262 absinfo_y.fuzz, absinfo_y.flat, absinfo_y.resolution);
1263 }
1264#endif // DEBUG_INPUT_EVENTS
1265 joystick->hwdata->hats_indices[hat_index] = joystick->nhats;
1266 joystick->hwdata->has_hat[hat_index] = true;
1267 correct->use_deadzones = use_hat_deadzones;
1268 correct->minimum[0] = (hat_x < 0) ? -1 : absinfo_x.minimum;
1269 correct->maximum[0] = (hat_x < 0) ? 1 : absinfo_x.maximum;
1270 correct->minimum[1] = (hat_y < 0) ? -1 : absinfo_y.minimum;
1271 correct->maximum[1] = (hat_y < 0) ? 1 : absinfo_y.maximum;
1272 ++joystick->nhats;
1273 }
1274 }
1275 for (i = 0; i < ABS_MAX; ++i) {
1276 // Skip digital hats
1277 if (i >= ABS_HAT0X && i <= ABS_HAT3Y && joystick->hwdata->has_hat[(i - ABS_HAT0X) / 2]) {
1278 continue;
1279 }
1280 if (test_bit(i, absbit)) {
1281 struct input_absinfo absinfo;
1282 struct axis_correct *correct = &joystick->hwdata->abs_correct[i];
1283
1284 if (ioctl(fd, EVIOCGABS(i), &absinfo) < 0) {
1285 continue;
1286 }
1287#ifdef DEBUG_INPUT_EVENTS
1288 SDL_Log("Joystick has absolute axis: 0x%.2x", i);
1289 SDL_Log("Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }",
1290 absinfo.value, absinfo.minimum, absinfo.maximum,
1291 absinfo.fuzz, absinfo.flat, absinfo.resolution);
1292#endif // DEBUG_INPUT_EVENTS
1293 joystick->hwdata->abs_map[i] = joystick->naxes;
1294 joystick->hwdata->has_abs[i] = true;
1295
1296 correct->minimum = absinfo.minimum;
1297 correct->maximum = absinfo.maximum;
1298 if (correct->minimum != correct->maximum) {
1299 if (use_deadzones) {
1300 correct->use_deadzones = true;
1301 correct->coef[0] = (absinfo.maximum + absinfo.minimum) - 2 * absinfo.flat;
1302 correct->coef[1] = (absinfo.maximum + absinfo.minimum) + 2 * absinfo.flat;
1303 t = ((absinfo.maximum - absinfo.minimum) - 4 * absinfo.flat);
1304 if (t != 0) {
1305 correct->coef[2] = (1 << 28) / t;
1306 } else {
1307 correct->coef[2] = 0;
1308 }
1309 } else {
1310 float value_range = (correct->maximum - correct->minimum);
1311 float output_range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN);
1312
1313 correct->scale = (output_range / value_range);
1314 }
1315 }
1316 ++joystick->naxes;
1317 }
1318 }
1319 if (test_bit(REL_X, relbit) || test_bit(REL_Y, relbit)) {
1320 ++joystick->nballs;
1321 }
1322
1323 } else if ((ioctl(fd, JSIOCGBUTTONS, &key_pam_size, sizeof(key_pam_size)) >= 0) &&
1324 (ioctl(fd, JSIOCGAXES, &abs_pam_size, sizeof(abs_pam_size)) >= 0)) {
1325 size_t len;
1326
1327 joystick->hwdata->classic = true;
1328
1329 len = (KEY_MAX - BTN_MISC + 1) * sizeof(*joystick->hwdata->key_pam);
1330 joystick->hwdata->key_pam = (Uint16 *)SDL_calloc(1, len);
1331 if (joystick->hwdata->key_pam) {
1332 if (ioctl(fd, JSIOCGBTNMAP, joystick->hwdata->key_pam, len) < 0) {
1333 SDL_free(joystick->hwdata->key_pam);
1334 joystick->hwdata->key_pam = NULL;
1335 key_pam_size = 0;
1336 }
1337 } else {
1338 key_pam_size = 0;
1339 }
1340 for (i = 0; i < key_pam_size; ++i) {
1341 Uint16 code = joystick->hwdata->key_pam[i];
1342#ifdef DEBUG_INPUT_EVENTS
1343 SDL_Log("Joystick has button: 0x%x", code);
1344#endif
1345 joystick->hwdata->key_map[code] = joystick->nbuttons;
1346 joystick->hwdata->has_key[code] = true;
1347 ++joystick->nbuttons;
1348 }
1349
1350 len = ABS_CNT * sizeof(*joystick->hwdata->abs_pam);
1351 joystick->hwdata->abs_pam = (Uint8 *)SDL_calloc(1, len);
1352 if (joystick->hwdata->abs_pam) {
1353 if (ioctl(fd, JSIOCGAXMAP, joystick->hwdata->abs_pam, len) < 0) {
1354 SDL_free(joystick->hwdata->abs_pam);
1355 joystick->hwdata->abs_pam = NULL;
1356 abs_pam_size = 0;
1357 }
1358 } else {
1359 abs_pam_size = 0;
1360 }
1361 for (i = 0; i < abs_pam_size; ++i) {
1362 Uint8 code = joystick->hwdata->abs_pam[i];
1363
1364 // TODO: is there any way to detect analog hats in advance via this API?
1365 if (code >= ABS_HAT0X && code <= ABS_HAT3Y) {
1366 int hat_index = (code - ABS_HAT0X) / 2;
1367 if (!joystick->hwdata->has_hat[hat_index]) {
1368#ifdef DEBUG_INPUT_EVENTS
1369 SDL_Log("Joystick has digital hat: #%d", hat_index);
1370#endif
1371 joystick->hwdata->hats_indices[hat_index] = joystick->nhats++;
1372 joystick->hwdata->has_hat[hat_index] = true;
1373 joystick->hwdata->hat_correct[hat_index].minimum[0] = -1;
1374 joystick->hwdata->hat_correct[hat_index].maximum[0] = 1;
1375 joystick->hwdata->hat_correct[hat_index].minimum[1] = -1;
1376 joystick->hwdata->hat_correct[hat_index].maximum[1] = 1;
1377 }
1378 } else {
1379#ifdef DEBUG_INPUT_EVENTS
1380 SDL_Log("Joystick has absolute axis: 0x%.2x", code);
1381#endif
1382 joystick->hwdata->abs_map[code] = joystick->naxes;
1383 joystick->hwdata->has_abs[code] = true;
1384 ++joystick->naxes;
1385 }
1386 }
1387 }
1388
1389 // Sensors are only available through the new unified event API
1390 if (fd_sensor >= 0 && (ioctl(fd_sensor, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) >= 0)) {
1391 if (test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit) && test_bit(ABS_Z, absbit)) {
1392 joystick->hwdata->has_accelerometer = true;
1393 for (i = 0; i < 3; ++i) {
1394 struct input_absinfo absinfo;
1395 if (ioctl(fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) < 0) {
1396 joystick->hwdata->has_accelerometer = false;
1397 break; // do not report an accelerometer if we can't read all axes
1398 }
1399 joystick->hwdata->accelerometer_scale[i] = absinfo.resolution;
1400#ifdef DEBUG_INPUT_EVENTS
1401 SDL_Log("Joystick has accelerometer axis: 0x%.2x", ABS_X + i);
1402 SDL_Log("Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }",
1403 absinfo.value, absinfo.minimum, absinfo.maximum,
1404 absinfo.fuzz, absinfo.flat, absinfo.resolution);
1405#endif // DEBUG_INPUT_EVENTS
1406 }
1407 }
1408
1409 if (test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit) && test_bit(ABS_RZ, absbit)) {
1410 joystick->hwdata->has_gyro = true;
1411 for (i = 0; i < 3; ++i) {
1412 struct input_absinfo absinfo;
1413 if (ioctl(fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) < 0) {
1414 joystick->hwdata->has_gyro = false;
1415 break; // do not report a gyro if we can't read all axes
1416 }
1417 joystick->hwdata->gyro_scale[i] = absinfo.resolution;
1418#ifdef DEBUG_INPUT_EVENTS
1419 SDL_Log("Joystick has gyro axis: 0x%.2x", ABS_RX + i);
1420 SDL_Log("Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }",
1421 absinfo.value, absinfo.minimum, absinfo.maximum,
1422 absinfo.fuzz, absinfo.flat, absinfo.resolution);
1423#endif // DEBUG_INPUT_EVENTS
1424 }
1425 }
1426 }
1427
1428 // Allocate data to keep track of these thingamajigs
1429 if (joystick->nballs > 0) {
1430 if (!allocate_balldata(joystick)) {
1431 joystick->nballs = 0;
1432 }
1433 }
1434 if (joystick->nhats > 0) {
1435 if (!allocate_hatdata(joystick)) {
1436 joystick->nhats = 0;
1437 }
1438 }
1439
1440 if (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(ffbit)), ffbit) >= 0) {
1441 if (test_bit(FF_RUMBLE, ffbit)) {
1442 joystick->hwdata->ff_rumble = true;
1443 }
1444 if (test_bit(FF_SINE, ffbit)) {
1445 joystick->hwdata->ff_sine = true;
1446 }
1447 }
1448}
1449
1450/* This is used to do the heavy lifting for LINUX_JoystickOpen and
1451 also LINUX_JoystickGetGamepadMapping, so we can query the hardware
1452 without adding an opened SDL_Joystick object to the system.
1453 This expects `joystick->hwdata` to be allocated and will not free it
1454 on error. Returns -1 on error, 0 on success. */
1455static bool PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item, SDL_sensorlist_item *item_sensor)
1456{
1457 SDL_AssertJoysticksLocked();
1458
1459 joystick->hwdata->item = item;
1460 joystick->hwdata->item_sensor = item_sensor;
1461 joystick->hwdata->guid = item->guid;
1462 joystick->hwdata->effect.id = -1;
1463 SDL_memset(joystick->hwdata->key_map, 0xFF, sizeof(joystick->hwdata->key_map));
1464 SDL_memset(joystick->hwdata->abs_map, 0xFF, sizeof(joystick->hwdata->abs_map));
1465
1466 int fd = -1, fd_sensor = -1;
1467 // Try read-write first, so we can do rumble
1468 fd = open(item->path, O_RDWR | O_CLOEXEC, 0);
1469 if (fd < 0) {
1470 // Try read-only again, at least we'll get events in this case
1471 fd = open(item->path, O_RDONLY | O_CLOEXEC, 0);
1472 }
1473 if (fd < 0) {
1474 return SDL_SetError("Unable to open %s", item->path);
1475 }
1476 // If opening sensor fail, continue with buttons and axes only
1477 if (item_sensor) {
1478 fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0);
1479 }
1480
1481 joystick->hwdata->fd = fd;
1482 joystick->hwdata->fd_sensor = fd_sensor;
1483 joystick->hwdata->fname = SDL_strdup(item->path);
1484 if (!joystick->hwdata->fname) {
1485 close(fd);
1486 if (fd_sensor >= 0) {
1487 close(fd_sensor);
1488 }
1489 return false;
1490 }
1491
1492 // Set the joystick to non-blocking read mode
1493 fcntl(fd, F_SETFL, O_NONBLOCK);
1494 if (fd_sensor >= 0) {
1495 fcntl(fd_sensor, F_SETFL, O_NONBLOCK);
1496 }
1497
1498 // Get the number of buttons and axes on the joystick
1499 ConfigJoystick(joystick, fd, fd_sensor);
1500 return true;
1501}
1502
1503static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item)
1504{
1505 SDL_sensorlist_item *item_sensor;
1506 char uniq_item[128];
1507 int fd_item = -1;
1508
1509 SDL_AssertJoysticksLocked();
1510
1511 if (!item || !SDL_sensorlist) {
1512 return NULL;
1513 }
1514
1515 SDL_memset(uniq_item, 0, sizeof(uniq_item));
1516 fd_item = open(item->path, O_RDONLY | O_CLOEXEC, 0);
1517 if (fd_item < 0) {
1518 return NULL;
1519 }
1520 if (ioctl(fd_item, EVIOCGUNIQ(sizeof(uniq_item) - 1), &uniq_item) < 0) {
1521 close(fd_item);
1522 return NULL;
1523 }
1524 close(fd_item);
1525#ifdef DEBUG_INPUT_EVENTS
1526 SDL_Log("Joystick UNIQ: %s", uniq_item);
1527#endif // DEBUG_INPUT_EVENTS
1528
1529 for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = item_sensor->next) {
1530 char uniq_sensor[128];
1531 int fd_sensor = -1;
1532 if (item_sensor->hwdata) {
1533 // already associated with another joystick
1534 continue;
1535 }
1536
1537 SDL_memset(uniq_sensor, 0, sizeof(uniq_sensor));
1538 fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0);
1539 if (fd_sensor < 0) {
1540 continue;
1541 }
1542 if (ioctl(fd_sensor, EVIOCGUNIQ(sizeof(uniq_sensor) - 1), &uniq_sensor) < 0) {
1543 close(fd_sensor);
1544 continue;
1545 }
1546 close(fd_sensor);
1547#ifdef DEBUG_INPUT_EVENTS
1548 SDL_Log("Sensor UNIQ: %s", uniq_sensor);
1549#endif // DEBUG_INPUT_EVENTS
1550
1551 if (SDL_strcmp(uniq_item, uniq_sensor) == 0) {
1552 return item_sensor;
1553 }
1554 }
1555 return NULL;
1556}
1557
1558static bool LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index)
1559{
1560 SDL_joylist_item *item;
1561 SDL_sensorlist_item *item_sensor;
1562
1563 SDL_AssertJoysticksLocked();
1564
1565 item = GetJoystickByDevIndex(device_index);
1566 if (!item) {
1567 return SDL_SetError("No such device");
1568 }
1569
1570 joystick->hwdata = (struct joystick_hwdata *)
1571 SDL_calloc(1, sizeof(*joystick->hwdata));
1572 if (!joystick->hwdata) {
1573 return false;
1574 }
1575
1576 item_sensor = GetSensor(item);
1577 if (!PrepareJoystickHwdata(joystick, item, item_sensor)) {
1578 SDL_free(joystick->hwdata);
1579 joystick->hwdata = NULL;
1580 return false; // SDL_SetError will already have been called
1581 }
1582
1583 SDL_assert(item->hwdata == NULL);
1584 SDL_assert(!item_sensor || item_sensor->hwdata == NULL);
1585 item->hwdata = joystick->hwdata;
1586 if (item_sensor) {
1587 item_sensor->hwdata = joystick->hwdata;
1588 }
1589
1590 // mark joystick as fresh and ready
1591 joystick->hwdata->fresh = true;
1592
1593 if (joystick->hwdata->has_gyro) {
1594 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f);
1595 }
1596 if (joystick->hwdata->has_accelerometer) {
1597 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f);
1598 }
1599 if (joystick->hwdata->fd_sensor >= 0) {
1600 // Don't keep fd_sensor opened while sensor is disabled
1601 close(joystick->hwdata->fd_sensor);
1602 joystick->hwdata->fd_sensor = -1;
1603 }
1604
1605 if (joystick->hwdata->ff_rumble || joystick->hwdata->ff_sine) {
1606 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
1607 }
1608 return true;
1609}
1610
1611static bool LINUX_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1612{
1613 struct input_event event;
1614
1615 SDL_AssertJoysticksLocked();
1616
1617 if (joystick->hwdata->ff_rumble) {
1618 struct ff_effect *effect = &joystick->hwdata->effect;
1619
1620 effect->type = FF_RUMBLE;
1621 effect->replay.length = SDL_MAX_RUMBLE_DURATION_MS;
1622 effect->u.rumble.strong_magnitude = low_frequency_rumble;
1623 effect->u.rumble.weak_magnitude = high_frequency_rumble;
1624 } else if (joystick->hwdata->ff_sine) {
1625 // Scale and average the two rumble strengths
1626 Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
1627 struct ff_effect *effect = &joystick->hwdata->effect;
1628
1629 effect->type = FF_PERIODIC;
1630 effect->replay.length = SDL_MAX_RUMBLE_DURATION_MS;
1631 effect->u.periodic.waveform = FF_SINE;
1632 effect->u.periodic.magnitude = magnitude;
1633 } else {
1634 return SDL_Unsupported();
1635 }
1636
1637 if (ioctl(joystick->hwdata->fd, EVIOCSFF, &joystick->hwdata->effect) < 0) {
1638 // The kernel may have lost this effect, try to allocate a new one
1639 joystick->hwdata->effect.id = -1;
1640 if (ioctl(joystick->hwdata->fd, EVIOCSFF, &joystick->hwdata->effect) < 0) {
1641 return SDL_SetError("Couldn't update rumble effect: %s", strerror(errno));
1642 }
1643 }
1644
1645 event.type = EV_FF;
1646 event.code = joystick->hwdata->effect.id;
1647 event.value = 1;
1648 if (write(joystick->hwdata->fd, &event, sizeof(event)) < 0) {
1649 return SDL_SetError("Couldn't start rumble effect: %s", strerror(errno));
1650 }
1651 return true;
1652}
1653
1654static bool LINUX_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
1655{
1656 return SDL_Unsupported();
1657}
1658
1659static bool LINUX_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1660{
1661 return SDL_Unsupported();
1662}
1663
1664static bool LINUX_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
1665{
1666 return SDL_Unsupported();
1667}
1668
1669static bool LINUX_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
1670{
1671 SDL_AssertJoysticksLocked();
1672
1673 if (!joystick->hwdata->has_accelerometer && !joystick->hwdata->has_gyro) {
1674 return SDL_Unsupported();
1675 }
1676 if (enabled == joystick->hwdata->report_sensor) {
1677 return true;
1678 }
1679
1680 if (enabled) {
1681 if (!joystick->hwdata->item_sensor) {
1682 return SDL_SetError("Sensors unplugged.");
1683 }
1684 joystick->hwdata->fd_sensor = open(joystick->hwdata->item_sensor->path, O_RDONLY | O_CLOEXEC, 0);
1685 if (joystick->hwdata->fd_sensor < 0) {
1686 return SDL_SetError("Couldn't open sensor file %s.", joystick->hwdata->item_sensor->path);
1687 }
1688 fcntl(joystick->hwdata->fd_sensor, F_SETFL, O_NONBLOCK);
1689 } else {
1690 SDL_assert(joystick->hwdata->fd_sensor >= 0);
1691 close(joystick->hwdata->fd_sensor);
1692 joystick->hwdata->fd_sensor = -1;
1693 }
1694
1695 joystick->hwdata->report_sensor = enabled;
1696 return true;
1697}
1698
1699static void HandleHat(Uint64 timestamp, SDL_Joystick *stick, int hatidx, int axis, int value)
1700{
1701 int hatnum;
1702 struct hwdata_hat *the_hat;
1703 struct hat_axis_correct *correct;
1704 const Uint8 position_map[3][3] = {
1705 { SDL_HAT_LEFTUP, SDL_HAT_UP, SDL_HAT_RIGHTUP },
1706 { SDL_HAT_LEFT, SDL_HAT_CENTERED, SDL_HAT_RIGHT },
1707 { SDL_HAT_LEFTDOWN, SDL_HAT_DOWN, SDL_HAT_RIGHTDOWN }
1708 };
1709
1710 SDL_AssertJoysticksLocked();
1711
1712 hatnum = stick->hwdata->hats_indices[hatidx];
1713 the_hat = &stick->hwdata->hats[hatnum];
1714 correct = &stick->hwdata->hat_correct[hatidx];
1715 /* Hopefully we detected any analog axes and left them as is rather than trying
1716 * to use them as digital hats, but just in case, the deadzones here will
1717 * prevent the slightest of twitches on an analog axis from registering as a hat
1718 * movement. If the axes really are digital, this won't hurt since they should
1719 * only ever be sending min, 0, or max anyway. */
1720 if (value < 0) {
1721 if (value <= correct->minimum[axis]) {
1722 correct->minimum[axis] = value;
1723 value = 0;
1724 } else if (!correct->use_deadzones || value < correct->minimum[axis] / 3) {
1725 value = 0;
1726 } else {
1727 value = 1;
1728 }
1729 } else if (value > 0) {
1730 if (value >= correct->maximum[axis]) {
1731 correct->maximum[axis] = value;
1732 value = 2;
1733 } else if (!correct->use_deadzones || value > correct->maximum[axis] / 3) {
1734 value = 2;
1735 } else {
1736 value = 1;
1737 }
1738 } else { // value == 0
1739 value = 1;
1740 }
1741 if (value != the_hat->axis[axis]) {
1742 the_hat->axis[axis] = value;
1743 SDL_SendJoystickHat(timestamp, stick, hatnum,
1744 position_map[the_hat->axis[1]][the_hat->axis[0]]);
1745 }
1746}
1747
1748static void HandleBall(SDL_Joystick *stick, Uint8 ball, int axis, int value)
1749{
1750 stick->hwdata->balls[ball].axis[axis] += value;
1751}
1752
1753static int AxisCorrect(SDL_Joystick *joystick, int which, int value)
1754{
1755 struct axis_correct *correct;
1756
1757 SDL_AssertJoysticksLocked();
1758
1759 correct = &joystick->hwdata->abs_correct[which];
1760 if (correct->minimum != correct->maximum) {
1761 if (correct->use_deadzones) {
1762 value *= 2;
1763 if (value > correct->coef[0]) {
1764 if (value < correct->coef[1]) {
1765 return 0;
1766 }
1767 value -= correct->coef[1];
1768 } else {
1769 value -= correct->coef[0];
1770 }
1771 value *= correct->coef[2];
1772 value >>= 13;
1773 } else {
1774 value = (int)SDL_floorf((value - correct->minimum) * correct->scale + SDL_JOYSTICK_AXIS_MIN + 0.5f);
1775 }
1776 }
1777
1778 // Clamp and return
1779 if (value < SDL_JOYSTICK_AXIS_MIN) {
1780 return SDL_JOYSTICK_AXIS_MIN;
1781 }
1782 if (value > SDL_JOYSTICK_AXIS_MAX) {
1783 return SDL_JOYSTICK_AXIS_MAX;
1784 }
1785 return value;
1786}
1787
1788static void PollAllValues(Uint64 timestamp, SDL_Joystick *joystick)
1789{
1790 struct input_absinfo absinfo;
1791 unsigned long keyinfo[NBITS(KEY_MAX)];
1792 int i;
1793
1794 SDL_AssertJoysticksLocked();
1795
1796 // Poll all axis
1797 for (i = ABS_X; i < ABS_MAX; i++) {
1798 // We don't need to test for digital hats here, they won't have has_abs[] set
1799 if (joystick->hwdata->has_abs[i]) {
1800 if (ioctl(joystick->hwdata->fd, EVIOCGABS(i), &absinfo) >= 0) {
1801 absinfo.value = AxisCorrect(joystick, i, absinfo.value);
1802
1803#ifdef DEBUG_INPUT_EVENTS
1804 SDL_Log("Joystick : Re-read Axis %d (%d) val= %d",
1805 joystick->hwdata->abs_map[i], i, absinfo.value);
1806#endif
1807 SDL_SendJoystickAxis(timestamp, joystick,
1808 joystick->hwdata->abs_map[i],
1809 absinfo.value);
1810 }
1811 }
1812 }
1813
1814 // Poll all digital hats
1815 for (i = ABS_HAT0X; i <= ABS_HAT3Y; i++) {
1816 const int baseaxis = i - ABS_HAT0X;
1817 const int hatidx = baseaxis / 2;
1818 SDL_assert(hatidx < SDL_arraysize(joystick->hwdata->has_hat));
1819 // We don't need to test for analog axes here, they won't have has_hat[] set
1820 if (joystick->hwdata->has_hat[hatidx]) {
1821 if (ioctl(joystick->hwdata->fd, EVIOCGABS(i), &absinfo) >= 0) {
1822 const int hataxis = baseaxis % 2;
1823 HandleHat(timestamp, joystick, hatidx, hataxis, absinfo.value);
1824 }
1825 }
1826 }
1827
1828 // Poll all buttons
1829 SDL_zeroa(keyinfo);
1830 if (ioctl(joystick->hwdata->fd, EVIOCGKEY(sizeof(keyinfo)), keyinfo) >= 0) {
1831 for (i = 0; i < KEY_MAX; i++) {
1832 if (joystick->hwdata->has_key[i]) {
1833 bool down = test_bit(i, keyinfo);
1834#ifdef DEBUG_INPUT_EVENTS
1835 SDL_Log("Joystick : Re-read Button %d (%d) val= %d",
1836 joystick->hwdata->key_map[i], i, down);
1837#endif
1838 SDL_SendJoystickButton(timestamp, joystick,
1839 joystick->hwdata->key_map[i], down);
1840 }
1841 }
1842 }
1843
1844 // Joyballs are relative input, so there's no poll state. Events only!
1845}
1846
1847static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick)
1848{
1849 struct input_absinfo absinfo;
1850 int i;
1851
1852 SDL_AssertJoysticksLocked();
1853
1854 SDL_assert(joystick->hwdata->fd_sensor >= 0);
1855
1856 if (joystick->hwdata->has_gyro) {
1857 float data[3] = {0.0f, 0.0f, 0.0f};
1858 for (i = 0; i < 3; i++) {
1859 if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) >= 0) {
1860 data[i] = absinfo.value * (SDL_PI_F / 180.f) / joystick->hwdata->gyro_scale[i];
1861#ifdef DEBUG_INPUT_EVENTS
1862 SDL_Log("Joystick : Re-read Gyro (axis %d) val= %f", i, data[i]);
1863#endif
1864 }
1865 }
1866 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3);
1867 }
1868 if (joystick->hwdata->has_accelerometer) {
1869 float data[3] = {0.0f, 0.0f, 0.0f};
1870 for (i = 0; i < 3; i++) {
1871 if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) >= 0) {
1872 data[i] = absinfo.value * SDL_STANDARD_GRAVITY / joystick->hwdata->accelerometer_scale[i];
1873#ifdef DEBUG_INPUT_EVENTS
1874 SDL_Log("Joystick : Re-read Accelerometer (axis %d) val= %f", i, data[i]);
1875#endif
1876 }
1877 }
1878 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3);
1879 }
1880}
1881
1882static void HandleInputEvents(SDL_Joystick *joystick)
1883{
1884 struct input_event events[32];
1885 int i, len, code, hat_index;
1886
1887 SDL_AssertJoysticksLocked();
1888
1889 if (joystick->hwdata->fresh) {
1890 Uint64 ticks = SDL_GetTicksNS();
1891 PollAllValues(ticks, joystick);
1892 if (joystick->hwdata->report_sensor) {
1893 PollAllSensors(ticks, joystick);
1894 }
1895 joystick->hwdata->fresh = false;
1896 }
1897
1898 errno = 0;
1899
1900 while ((len = read(joystick->hwdata->fd, events, sizeof(events))) > 0) {
1901 len /= sizeof(events[0]);
1902 for (i = 0; i < len; ++i) {
1903 struct input_event *event = &events[i];
1904
1905 code = event->code;
1906
1907 /* If the kernel sent a SYN_DROPPED, we are supposed to ignore the
1908 rest of the packet (the end of it signified by a SYN_REPORT) */
1909 if (joystick->hwdata->recovering_from_dropped &&
1910 ((event->type != EV_SYN) || (code != SYN_REPORT))) {
1911 continue;
1912 }
1913
1914 switch (event->type) {
1915 case EV_KEY:
1916#ifdef DEBUG_INPUT_EVENTS
1917 SDL_Log("Key 0x%.2x %s", code, event->value ? "PRESSED" : "RELEASED");
1918#endif
1919 SDL_SendJoystickButton(SDL_EVDEV_GetEventTimestamp(event), joystick,
1920 joystick->hwdata->key_map[code],
1921 (event->value != 0));
1922 break;
1923 case EV_ABS:
1924 switch (code) {
1925 case ABS_HAT0X:
1926 case ABS_HAT0Y:
1927 case ABS_HAT1X:
1928 case ABS_HAT1Y:
1929 case ABS_HAT2X:
1930 case ABS_HAT2Y:
1931 case ABS_HAT3X:
1932 case ABS_HAT3Y:
1933 hat_index = (code - ABS_HAT0X) / 2;
1934 if (joystick->hwdata->has_hat[hat_index]) {
1935#ifdef DEBUG_INPUT_EVENTS
1936 SDL_Log("Axis 0x%.2x = %d", code, event->value);
1937#endif
1938 HandleHat(SDL_EVDEV_GetEventTimestamp(event), joystick, hat_index, code % 2, event->value);
1939 break;
1940 }
1941 SDL_FALLTHROUGH;
1942 default:
1943#ifdef DEBUG_INPUT_EVENTS
1944 SDL_Log("Axis 0x%.2x = %d", code, event->value);
1945#endif
1946 event->value = AxisCorrect(joystick, code, event->value);
1947 SDL_SendJoystickAxis(SDL_EVDEV_GetEventTimestamp(event), joystick,
1948 joystick->hwdata->abs_map[code],
1949 event->value);
1950 break;
1951 }
1952 break;
1953 case EV_REL:
1954 switch (code) {
1955 case REL_X:
1956 case REL_Y:
1957 code -= REL_X;
1958 HandleBall(joystick, code / 2, code % 2, event->value);
1959 break;
1960 default:
1961 break;
1962 }
1963 break;
1964 case EV_SYN:
1965 switch (code) {
1966 case SYN_DROPPED:
1967#ifdef DEBUG_INPUT_EVENTS
1968 SDL_Log("Event SYN_DROPPED detected");
1969#endif
1970 joystick->hwdata->recovering_from_dropped = true;
1971 break;
1972 case SYN_REPORT:
1973 if (joystick->hwdata->recovering_from_dropped) {
1974 joystick->hwdata->recovering_from_dropped = false;
1975 PollAllValues(SDL_GetTicksNS(), joystick); // try to sync up to current state now
1976 }
1977 break;
1978 default:
1979 break;
1980 }
1981 break;
1982 default:
1983 break;
1984 }
1985 }
1986 }
1987
1988 if (errno == ENODEV) {
1989 // We have to wait until the JoystickDetect callback to remove this
1990 joystick->hwdata->gone = true;
1991 errno = 0;
1992 }
1993
1994 if (joystick->hwdata->report_sensor) {
1995 SDL_assert(joystick->hwdata->fd_sensor >= 0);
1996
1997 while ((len = read(joystick->hwdata->fd_sensor, events, sizeof(events))) > 0) {
1998 len /= sizeof(events[0]);
1999 for (i = 0; i < len; ++i) {
2000 unsigned int j;
2001 struct input_event *event = &events[i];
2002
2003 code = event->code;
2004
2005 /* If the kernel sent a SYN_DROPPED, we are supposed to ignore the
2006 rest of the packet (the end of it signified by a SYN_REPORT) */
2007 if (joystick->hwdata->recovering_from_dropped_sensor &&
2008 ((event->type != EV_SYN) || (code != SYN_REPORT))) {
2009 continue;
2010 }
2011
2012 switch (event->type) {
2013 case EV_KEY:
2014 SDL_assert(0);
2015 break;
2016 case EV_ABS:
2017 switch (code) {
2018 case ABS_X:
2019 case ABS_Y:
2020 case ABS_Z:
2021 j = code - ABS_X;
2022 joystick->hwdata->accel_data[j] = event->value * SDL_STANDARD_GRAVITY
2023 / joystick->hwdata->accelerometer_scale[j];
2024 break;
2025 case ABS_RX:
2026 case ABS_RY:
2027 case ABS_RZ:
2028 j = code - ABS_RX;
2029 joystick->hwdata->gyro_data[j] = event->value * (SDL_PI_F / 180.f)
2030 / joystick->hwdata->gyro_scale[j];
2031 break;
2032 }
2033 break;
2034 case EV_MSC:
2035 if (code == MSC_TIMESTAMP) {
2036 Sint32 tick = event->value;
2037 Sint32 delta;
2038 if (joystick->hwdata->last_tick < tick) {
2039 delta = (tick - joystick->hwdata->last_tick);
2040 } else {
2041 delta = (SDL_MAX_SINT32 - joystick->hwdata->last_tick + tick + 1);
2042 }
2043 joystick->hwdata->sensor_tick += delta;
2044 joystick->hwdata->last_tick = tick;
2045 }
2046 break;
2047 case EV_SYN:
2048 switch (code) {
2049 case SYN_DROPPED:
2050 #ifdef DEBUG_INPUT_EVENTS
2051 SDL_Log("Event SYN_DROPPED detected");
2052 #endif
2053 joystick->hwdata->recovering_from_dropped_sensor = true;
2054 break;
2055 case SYN_REPORT:
2056 if (joystick->hwdata->recovering_from_dropped_sensor) {
2057 joystick->hwdata->recovering_from_dropped_sensor = false;
2058 PollAllSensors(SDL_GetTicksNS(), joystick); // try to sync up to current state now
2059 } else {
2060 Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event);
2061 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO,
2062 SDL_US_TO_NS(joystick->hwdata->sensor_tick),
2063 joystick->hwdata->gyro_data, 3);
2064 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL,
2065 SDL_US_TO_NS(joystick->hwdata->sensor_tick),
2066 joystick->hwdata->accel_data, 3);
2067 }
2068 break;
2069 default:
2070 break;
2071 }
2072 break;
2073 default:
2074 break;
2075 }
2076 }
2077 }
2078 }
2079
2080 if (errno == ENODEV) {
2081 // We have to wait until the JoystickDetect callback to remove this
2082 joystick->hwdata->sensor_gone = true;
2083 }
2084}
2085
2086static void HandleClassicEvents(SDL_Joystick *joystick)
2087{
2088 struct js_event events[32];
2089 int i, len, code, hat_index;
2090 Uint64 timestamp = SDL_GetTicksNS();
2091
2092 SDL_AssertJoysticksLocked();
2093
2094 joystick->hwdata->fresh = false;
2095 while ((len = read(joystick->hwdata->fd, events, sizeof(events))) > 0) {
2096 len /= sizeof(events[0]);
2097 for (i = 0; i < len; ++i) {
2098 switch (events[i].type) {
2099 case JS_EVENT_BUTTON:
2100 code = joystick->hwdata->key_pam[events[i].number];
2101 SDL_SendJoystickButton(timestamp, joystick,
2102 joystick->hwdata->key_map[code],
2103 (events[i].value != 0));
2104 break;
2105 case JS_EVENT_AXIS:
2106 code = joystick->hwdata->abs_pam[events[i].number];
2107 switch (code) {
2108 case ABS_HAT0X:
2109 case ABS_HAT0Y:
2110 case ABS_HAT1X:
2111 case ABS_HAT1Y:
2112 case ABS_HAT2X:
2113 case ABS_HAT2Y:
2114 case ABS_HAT3X:
2115 case ABS_HAT3Y:
2116 hat_index = (code - ABS_HAT0X) / 2;
2117 if (joystick->hwdata->has_hat[hat_index]) {
2118 HandleHat(timestamp, joystick, hat_index, code % 2, events[i].value);
2119 break;
2120 }
2121 SDL_FALLTHROUGH;
2122 default:
2123 SDL_SendJoystickAxis(timestamp, joystick,
2124 joystick->hwdata->abs_map[code],
2125 events[i].value);
2126 break;
2127 }
2128 }
2129 }
2130 }
2131}
2132
2133static void LINUX_JoystickUpdate(SDL_Joystick *joystick)
2134{
2135 int i;
2136
2137 SDL_AssertJoysticksLocked();
2138
2139 if (joystick->hwdata->classic) {
2140 HandleClassicEvents(joystick);
2141 } else {
2142 HandleInputEvents(joystick);
2143 }
2144
2145 // Deliver ball motion updates
2146 for (i = 0; i < joystick->nballs; ++i) {
2147 int xrel, yrel;
2148
2149 xrel = joystick->hwdata->balls[i].axis[0];
2150 yrel = joystick->hwdata->balls[i].axis[1];
2151 if (xrel || yrel) {
2152 joystick->hwdata->balls[i].axis[0] = 0;
2153 joystick->hwdata->balls[i].axis[1] = 0;
2154 SDL_SendJoystickBall(0, joystick, (Uint8)i, xrel, yrel);
2155 }
2156 }
2157}
2158
2159// Function to close a joystick after use
2160static void LINUX_JoystickClose(SDL_Joystick *joystick)
2161{
2162 SDL_AssertJoysticksLocked();
2163
2164 if (joystick->hwdata) {
2165 if (joystick->hwdata->effect.id >= 0) {
2166 ioctl(joystick->hwdata->fd, EVIOCRMFF, joystick->hwdata->effect.id);
2167 joystick->hwdata->effect.id = -1;
2168 }
2169 if (joystick->hwdata->fd >= 0) {
2170 close(joystick->hwdata->fd);
2171 }
2172 if (joystick->hwdata->fd_sensor >= 0) {
2173 close(joystick->hwdata->fd_sensor);
2174 }
2175 if (joystick->hwdata->item) {
2176 joystick->hwdata->item->hwdata = NULL;
2177 }
2178 if (joystick->hwdata->item_sensor) {
2179 joystick->hwdata->item_sensor->hwdata = NULL;
2180 }
2181 SDL_free(joystick->hwdata->key_pam);
2182 SDL_free(joystick->hwdata->abs_pam);
2183 SDL_free(joystick->hwdata->hats);
2184 SDL_free(joystick->hwdata->balls);
2185 SDL_free(joystick->hwdata->fname);
2186 SDL_free(joystick->hwdata);
2187 }
2188}
2189
2190// Function to perform any system-specific joystick related cleanup
2191static void LINUX_JoystickQuit(void)
2192{
2193 SDL_joylist_item *item = NULL;
2194 SDL_joylist_item *next = NULL;
2195 SDL_sensorlist_item *item_sensor = NULL;
2196 SDL_sensorlist_item *next_sensor = NULL;
2197
2198 SDL_AssertJoysticksLocked();
2199
2200 if (inotify_fd >= 0) {
2201 close(inotify_fd);
2202 inotify_fd = -1;
2203 }
2204
2205 for (item = SDL_joylist; item; item = next) {
2206 next = item->next;
2207 FreeJoylistItem(item);
2208 }
2209 for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = next_sensor) {
2210 next_sensor = item_sensor->next;
2211 FreeSensorlistItem(item_sensor);
2212 }
2213
2214 SDL_joylist = SDL_joylist_tail = NULL;
2215 SDL_sensorlist = NULL;
2216
2217 numjoysticks = 0;
2218
2219#ifdef SDL_USE_LIBUDEV
2220 if (enumeration_method == ENUMERATION_LIBUDEV) {
2221 SDL_UDEV_DelCallback(joystick_udev_callback);
2222 SDL_UDEV_Quit();
2223 }
2224#endif
2225}
2226
2227/*
2228 This is based on the Linux Gamepad Specification
2229 available at: https://www.kernel.org/doc/html/v4.15/input/gamepad.html
2230 and the Android gamepad documentation,
2231 https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
2232 */
2233static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
2234{
2235 SDL_Joystick *joystick;
2236 SDL_joylist_item *item = GetJoystickByDevIndex(device_index);
2237 enum {
2238 MAPPED_TRIGGER_LEFT = 0x1,
2239 MAPPED_TRIGGER_RIGHT = 0x2,
2240 MAPPED_TRIGGER_BOTH = 0x3,
2241
2242 MAPPED_DPAD_UP = 0x1,
2243 MAPPED_DPAD_DOWN = 0x2,
2244 MAPPED_DPAD_LEFT = 0x4,
2245 MAPPED_DPAD_RIGHT = 0x8,
2246 MAPPED_DPAD_ALL = 0xF,
2247 };
2248 unsigned int mapped;
2249 bool result = false;
2250
2251 SDL_AssertJoysticksLocked();
2252
2253 if (item->checked_mapping) {
2254 if (item->mapping) {
2255 SDL_memcpy(out, item->mapping, sizeof(*out));
2256#ifdef DEBUG_GAMEPAD_MAPPING
2257 SDL_Log("Prior mapping for device %d", device_index);
2258#endif
2259 return true;
2260 } else {
2261 return false;
2262 }
2263 }
2264
2265 /* We temporarily open the device to check how it's configured. Make
2266 a fake SDL_Joystick object to do so. */
2267 joystick = (SDL_Joystick *)SDL_calloc(1, sizeof(*joystick));
2268 if (!joystick) {
2269 return false;
2270 }
2271 SDL_memcpy(&joystick->guid, &item->guid, sizeof(item->guid));
2272
2273 joystick->hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*joystick->hwdata));
2274 if (!joystick->hwdata) {
2275 SDL_free(joystick);
2276 return false;
2277 }
2278 SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, true);
2279
2280 item->checked_mapping = true;
2281
2282 if (!PrepareJoystickHwdata(joystick, item, NULL)) {
2283 goto done; // SDL_SetError will already have been called
2284 }
2285
2286 // don't assign `item->hwdata` so it's not in any global state.
2287
2288 // it is now safe to call LINUX_JoystickClose on this fake joystick.
2289
2290 if (!joystick->hwdata->has_key[BTN_GAMEPAD]) {
2291 // Not a gamepad according to the specs.
2292 goto done;
2293 }
2294
2295 // We have a gamepad, start filling out the mappings
2296
2297#ifdef DEBUG_GAMEPAD_MAPPING
2298 SDL_Log("Mapping %s (VID/PID 0x%.4x/0x%.4x)", item->name, SDL_GetJoystickVendor(joystick), SDL_GetJoystickProduct(joystick));
2299#endif
2300
2301 if (joystick->hwdata->has_key[BTN_A]) {
2302 out->a.kind = EMappingKind_Button;
2303 out->a.target = joystick->hwdata->key_map[BTN_A];
2304#ifdef DEBUG_GAMEPAD_MAPPING
2305 SDL_Log("Mapped A to button %d (BTN_A)", out->a.target);
2306#endif
2307 }
2308
2309 if (joystick->hwdata->has_key[BTN_B]) {
2310 out->b.kind = EMappingKind_Button;
2311 out->b.target = joystick->hwdata->key_map[BTN_B];
2312#ifdef DEBUG_GAMEPAD_MAPPING
2313 SDL_Log("Mapped B to button %d (BTN_B)", out->b.target);
2314#endif
2315 }
2316
2317 // Xbox controllers use BTN_X and BTN_Y, and PS4 controllers use BTN_WEST and BTN_NORTH
2318 if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_SONY) {
2319 if (joystick->hwdata->has_key[BTN_WEST]) {
2320 out->x.kind = EMappingKind_Button;
2321 out->x.target = joystick->hwdata->key_map[BTN_WEST];
2322#ifdef DEBUG_GAMEPAD_MAPPING
2323 SDL_Log("Mapped X to button %d (BTN_WEST)", out->x.target);
2324#endif
2325 }
2326
2327 if (joystick->hwdata->has_key[BTN_NORTH]) {
2328 out->y.kind = EMappingKind_Button;
2329 out->y.target = joystick->hwdata->key_map[BTN_NORTH];
2330#ifdef DEBUG_GAMEPAD_MAPPING
2331 SDL_Log("Mapped Y to button %d (BTN_NORTH)", out->y.target);
2332#endif
2333 }
2334 } else {
2335 if (joystick->hwdata->has_key[BTN_X]) {
2336 out->x.kind = EMappingKind_Button;
2337 out->x.target = joystick->hwdata->key_map[BTN_X];
2338#ifdef DEBUG_GAMEPAD_MAPPING
2339 SDL_Log("Mapped X to button %d (BTN_X)", out->x.target);
2340#endif
2341 }
2342
2343 if (joystick->hwdata->has_key[BTN_Y]) {
2344 out->y.kind = EMappingKind_Button;
2345 out->y.target = joystick->hwdata->key_map[BTN_Y];
2346#ifdef DEBUG_GAMEPAD_MAPPING
2347 SDL_Log("Mapped Y to button %d (BTN_Y)", out->y.target);
2348#endif
2349 }
2350 }
2351
2352 if (joystick->hwdata->has_key[BTN_SELECT]) {
2353 out->back.kind = EMappingKind_Button;
2354 out->back.target = joystick->hwdata->key_map[BTN_SELECT];
2355#ifdef DEBUG_GAMEPAD_MAPPING
2356 SDL_Log("Mapped BACK to button %d (BTN_SELECT)", out->back.target);
2357#endif
2358 }
2359
2360 if (joystick->hwdata->has_key[BTN_START]) {
2361 out->start.kind = EMappingKind_Button;
2362 out->start.target = joystick->hwdata->key_map[BTN_START];
2363#ifdef DEBUG_GAMEPAD_MAPPING
2364 SDL_Log("Mapped START to button %d (BTN_START)", out->start.target);
2365#endif
2366 }
2367
2368 if (joystick->hwdata->has_key[BTN_THUMBL]) {
2369 out->leftstick.kind = EMappingKind_Button;
2370 out->leftstick.target = joystick->hwdata->key_map[BTN_THUMBL];
2371#ifdef DEBUG_GAMEPAD_MAPPING
2372 SDL_Log("Mapped LEFTSTICK to button %d (BTN_THUMBL)", out->leftstick.target);
2373#endif
2374 }
2375
2376 if (joystick->hwdata->has_key[BTN_THUMBR]) {
2377 out->rightstick.kind = EMappingKind_Button;
2378 out->rightstick.target = joystick->hwdata->key_map[BTN_THUMBR];
2379#ifdef DEBUG_GAMEPAD_MAPPING
2380 SDL_Log("Mapped RIGHTSTICK to button %d (BTN_THUMBR)", out->rightstick.target);
2381#endif
2382 }
2383
2384 if (joystick->hwdata->has_key[BTN_MODE]) {
2385 out->guide.kind = EMappingKind_Button;
2386 out->guide.target = joystick->hwdata->key_map[BTN_MODE];
2387#ifdef DEBUG_GAMEPAD_MAPPING
2388 SDL_Log("Mapped GUIDE to button %d (BTN_MODE)", out->guide.target);
2389#endif
2390 }
2391
2392 /*
2393 According to the specs the D-Pad, the shoulder buttons and the triggers
2394 can be digital, or analog, or both at the same time.
2395 */
2396
2397 // Prefer digital shoulder buttons, but settle for digital or analog hat.
2398 mapped = 0;
2399
2400 if (joystick->hwdata->has_key[BTN_TL]) {
2401 out->leftshoulder.kind = EMappingKind_Button;
2402 out->leftshoulder.target = joystick->hwdata->key_map[BTN_TL];
2403 mapped |= 0x1;
2404#ifdef DEBUG_GAMEPAD_MAPPING
2405 SDL_Log("Mapped LEFTSHOULDER to button %d (BTN_TL)", out->leftshoulder.target);
2406#endif
2407 }
2408
2409 if (joystick->hwdata->has_key[BTN_TR]) {
2410 out->rightshoulder.kind = EMappingKind_Button;
2411 out->rightshoulder.target = joystick->hwdata->key_map[BTN_TR];
2412 mapped |= 0x2;
2413#ifdef DEBUG_GAMEPAD_MAPPING
2414 SDL_Log("Mapped RIGHTSHOULDER to button %d (BTN_TR)", out->rightshoulder.target);
2415#endif
2416 }
2417
2418 if (mapped != 0x3 && joystick->hwdata->has_hat[1]) {
2419 int hat = joystick->hwdata->hats_indices[1] << 4;
2420 out->leftshoulder.kind = EMappingKind_Hat;
2421 out->rightshoulder.kind = EMappingKind_Hat;
2422 out->leftshoulder.target = hat | 0x4;
2423 out->rightshoulder.target = hat | 0x2;
2424 mapped |= 0x3;
2425#ifdef DEBUG_GAMEPAD_MAPPING
2426 SDL_Log("Mapped LEFT+RIGHTSHOULDER to hat 1 (ABS_HAT1X, ABS_HAT1Y)");
2427#endif
2428 }
2429
2430 if (!(mapped & 0x1) && joystick->hwdata->has_abs[ABS_HAT1Y]) {
2431 out->leftshoulder.kind = EMappingKind_Axis;
2432 out->leftshoulder.target = joystick->hwdata->abs_map[ABS_HAT1Y];
2433 mapped |= 0x1;
2434#ifdef DEBUG_GAMEPAD_MAPPING
2435 SDL_Log("Mapped LEFTSHOULDER to axis %d (ABS_HAT1Y)", out->leftshoulder.target);
2436#endif
2437 }
2438
2439 if (!(mapped & 0x2) && joystick->hwdata->has_abs[ABS_HAT1X]) {
2440 out->rightshoulder.kind = EMappingKind_Axis;
2441 out->rightshoulder.target = joystick->hwdata->abs_map[ABS_HAT1X];
2442 mapped |= 0x2;
2443#ifdef DEBUG_GAMEPAD_MAPPING
2444 SDL_Log("Mapped RIGHTSHOULDER to axis %d (ABS_HAT1X)", out->rightshoulder.target);
2445#endif
2446 }
2447
2448 // Prefer analog triggers, but settle for digital hat or buttons.
2449 mapped = 0;
2450
2451 /* Unfortunately there are several conventions for how analog triggers
2452 * are represented as absolute axes:
2453 *
2454 * - Linux Gamepad Specification:
2455 * LT = ABS_HAT2Y, RT = ABS_HAT2X
2456 * - Android (and therefore many Bluetooth controllers):
2457 * LT = ABS_BRAKE, RT = ABS_GAS
2458 * - De facto standard for older Xbox and Playstation controllers:
2459 * LT = ABS_Z, RT = ABS_RZ
2460 *
2461 * We try each one in turn. */
2462 if (joystick->hwdata->has_abs[ABS_HAT2Y]) {
2463 // Linux Gamepad Specification
2464 out->lefttrigger.kind = EMappingKind_Axis;
2465 out->lefttrigger.target = joystick->hwdata->abs_map[ABS_HAT2Y];
2466 mapped |= MAPPED_TRIGGER_LEFT;
2467#ifdef DEBUG_GAMEPAD_MAPPING
2468 SDL_Log("Mapped LEFTTRIGGER to axis %d (ABS_HAT2Y)", out->lefttrigger.target);
2469#endif
2470 } else if (joystick->hwdata->has_abs[ABS_BRAKE]) {
2471 // Android convention
2472 out->lefttrigger.kind = EMappingKind_Axis;
2473 out->lefttrigger.target = joystick->hwdata->abs_map[ABS_BRAKE];
2474 mapped |= MAPPED_TRIGGER_LEFT;
2475#ifdef DEBUG_GAMEPAD_MAPPING
2476 SDL_Log("Mapped LEFTTRIGGER to axis %d (ABS_BRAKE)", out->lefttrigger.target);
2477#endif
2478 } else if (joystick->hwdata->has_abs[ABS_Z]) {
2479 // De facto standard for Xbox 360 and Playstation gamepads
2480 out->lefttrigger.kind = EMappingKind_Axis;
2481 out->lefttrigger.target = joystick->hwdata->abs_map[ABS_Z];
2482 mapped |= MAPPED_TRIGGER_LEFT;
2483#ifdef DEBUG_GAMEPAD_MAPPING
2484 SDL_Log("Mapped LEFTTRIGGER to axis %d (ABS_Z)", out->lefttrigger.target);
2485#endif
2486 }
2487
2488 if (joystick->hwdata->has_abs[ABS_HAT2X]) {
2489 // Linux Gamepad Specification
2490 out->righttrigger.kind = EMappingKind_Axis;
2491 out->righttrigger.target = joystick->hwdata->abs_map[ABS_HAT2X];
2492 mapped |= MAPPED_TRIGGER_RIGHT;
2493#ifdef DEBUG_GAMEPAD_MAPPING
2494 SDL_Log("Mapped RIGHTTRIGGER to axis %d (ABS_HAT2X)", out->righttrigger.target);
2495#endif
2496 } else if (joystick->hwdata->has_abs[ABS_GAS]) {
2497 // Android convention
2498 out->righttrigger.kind = EMappingKind_Axis;
2499 out->righttrigger.target = joystick->hwdata->abs_map[ABS_GAS];
2500 mapped |= MAPPED_TRIGGER_RIGHT;
2501#ifdef DEBUG_GAMEPAD_MAPPING
2502 SDL_Log("Mapped RIGHTTRIGGER to axis %d (ABS_GAS)", out->righttrigger.target);
2503#endif
2504 } else if (joystick->hwdata->has_abs[ABS_RZ]) {
2505 // De facto standard for Xbox 360 and Playstation gamepads
2506 out->righttrigger.kind = EMappingKind_Axis;
2507 out->righttrigger.target = joystick->hwdata->abs_map[ABS_RZ];
2508 mapped |= MAPPED_TRIGGER_RIGHT;
2509#ifdef DEBUG_GAMEPAD_MAPPING
2510 SDL_Log("Mapped RIGHTTRIGGER to axis %d (ABS_RZ)", out->righttrigger.target);
2511#endif
2512 }
2513
2514 if (mapped != MAPPED_TRIGGER_BOTH && joystick->hwdata->has_hat[2]) {
2515 int hat = joystick->hwdata->hats_indices[2] << 4;
2516 out->lefttrigger.kind = EMappingKind_Hat;
2517 out->righttrigger.kind = EMappingKind_Hat;
2518 out->lefttrigger.target = hat | 0x4;
2519 out->righttrigger.target = hat | 0x2;
2520 mapped |= MAPPED_TRIGGER_BOTH;
2521#ifdef DEBUG_GAMEPAD_MAPPING
2522 SDL_Log("Mapped LEFT+RIGHTTRIGGER to hat 2 (ABS_HAT2X, ABS_HAT2Y)");
2523#endif
2524 }
2525
2526 if (!(mapped & MAPPED_TRIGGER_LEFT) && joystick->hwdata->has_key[BTN_TL2]) {
2527 out->lefttrigger.kind = EMappingKind_Button;
2528 out->lefttrigger.target = joystick->hwdata->key_map[BTN_TL2];
2529 mapped |= MAPPED_TRIGGER_LEFT;
2530#ifdef DEBUG_GAMEPAD_MAPPING
2531 SDL_Log("Mapped LEFTTRIGGER to button %d (BTN_TL2)", out->lefttrigger.target);
2532#endif
2533 }
2534
2535 if (!(mapped & MAPPED_TRIGGER_RIGHT) && joystick->hwdata->has_key[BTN_TR2]) {
2536 out->righttrigger.kind = EMappingKind_Button;
2537 out->righttrigger.target = joystick->hwdata->key_map[BTN_TR2];
2538 mapped |= MAPPED_TRIGGER_RIGHT;
2539#ifdef DEBUG_GAMEPAD_MAPPING
2540 SDL_Log("Mapped RIGHTTRIGGER to button %d (BTN_TR2)", out->righttrigger.target);
2541#endif
2542 }
2543
2544 // Prefer digital D-Pad buttons, but settle for digital or analog hat.
2545 mapped = 0;
2546
2547 if (joystick->hwdata->has_key[BTN_DPAD_UP]) {
2548 out->dpup.kind = EMappingKind_Button;
2549 out->dpup.target = joystick->hwdata->key_map[BTN_DPAD_UP];
2550 mapped |= MAPPED_DPAD_UP;
2551#ifdef DEBUG_GAMEPAD_MAPPING
2552 SDL_Log("Mapped DPUP to button %d (BTN_DPAD_UP)", out->dpup.target);
2553#endif
2554 }
2555
2556 if (joystick->hwdata->has_key[BTN_DPAD_DOWN]) {
2557 out->dpdown.kind = EMappingKind_Button;
2558 out->dpdown.target = joystick->hwdata->key_map[BTN_DPAD_DOWN];
2559 mapped |= MAPPED_DPAD_DOWN;
2560#ifdef DEBUG_GAMEPAD_MAPPING
2561 SDL_Log("Mapped DPDOWN to button %d (BTN_DPAD_DOWN)", out->dpdown.target);
2562#endif
2563 }
2564
2565 if (joystick->hwdata->has_key[BTN_DPAD_LEFT]) {
2566 out->dpleft.kind = EMappingKind_Button;
2567 out->dpleft.target = joystick->hwdata->key_map[BTN_DPAD_LEFT];
2568 mapped |= MAPPED_DPAD_LEFT;
2569#ifdef DEBUG_GAMEPAD_MAPPING
2570 SDL_Log("Mapped DPLEFT to button %d (BTN_DPAD_LEFT)", out->dpleft.target);
2571#endif
2572 }
2573
2574 if (joystick->hwdata->has_key[BTN_DPAD_RIGHT]) {
2575 out->dpright.kind = EMappingKind_Button;
2576 out->dpright.target = joystick->hwdata->key_map[BTN_DPAD_RIGHT];
2577 mapped |= MAPPED_DPAD_RIGHT;
2578#ifdef DEBUG_GAMEPAD_MAPPING
2579 SDL_Log("Mapped DPRIGHT to button %d (BTN_DPAD_RIGHT)", out->dpright.target);
2580#endif
2581 }
2582
2583 if (mapped != MAPPED_DPAD_ALL) {
2584 if (joystick->hwdata->has_hat[0]) {
2585 int hat = joystick->hwdata->hats_indices[0] << 4;
2586 out->dpleft.kind = EMappingKind_Hat;
2587 out->dpright.kind = EMappingKind_Hat;
2588 out->dpup.kind = EMappingKind_Hat;
2589 out->dpdown.kind = EMappingKind_Hat;
2590 out->dpleft.target = hat | 0x8;
2591 out->dpright.target = hat | 0x2;
2592 out->dpup.target = hat | 0x1;
2593 out->dpdown.target = hat | 0x4;
2594 mapped |= MAPPED_DPAD_ALL;
2595#ifdef DEBUG_GAMEPAD_MAPPING
2596 SDL_Log("Mapped DPUP+DOWN+LEFT+RIGHT to hat 0 (ABS_HAT0X, ABS_HAT0Y)");
2597#endif
2598 } else if (joystick->hwdata->has_abs[ABS_HAT0X] && joystick->hwdata->has_abs[ABS_HAT0Y]) {
2599 out->dpleft.kind = EMappingKind_Axis;
2600 out->dpright.kind = EMappingKind_Axis;
2601 out->dpup.kind = EMappingKind_Axis;
2602 out->dpdown.kind = EMappingKind_Axis;
2603 out->dpleft.target = joystick->hwdata->abs_map[ABS_HAT0X];
2604 out->dpright.target = joystick->hwdata->abs_map[ABS_HAT0X];
2605 out->dpup.target = joystick->hwdata->abs_map[ABS_HAT0Y];
2606 out->dpdown.target = joystick->hwdata->abs_map[ABS_HAT0Y];
2607 mapped |= MAPPED_DPAD_ALL;
2608#ifdef DEBUG_GAMEPAD_MAPPING
2609 SDL_Log("Mapped DPUP+DOWN to axis %d (ABS_HAT0Y)", out->dpup.target);
2610 SDL_Log("Mapped DPLEFT+RIGHT to axis %d (ABS_HAT0X)", out->dpleft.target);
2611#endif
2612 }
2613 }
2614
2615 if (joystick->hwdata->has_abs[ABS_X] && joystick->hwdata->has_abs[ABS_Y]) {
2616 out->leftx.kind = EMappingKind_Axis;
2617 out->lefty.kind = EMappingKind_Axis;
2618 out->leftx.target = joystick->hwdata->abs_map[ABS_X];
2619 out->lefty.target = joystick->hwdata->abs_map[ABS_Y];
2620#ifdef DEBUG_GAMEPAD_MAPPING
2621 SDL_Log("Mapped LEFTX to axis %d (ABS_X)", out->leftx.target);
2622 SDL_Log("Mapped LEFTY to axis %d (ABS_Y)", out->lefty.target);
2623#endif
2624 }
2625
2626 /* The Linux Gamepad Specification uses the RX and RY axes,
2627 * originally intended to represent X and Y rotation, as a second
2628 * joystick. This is common for USB gamepads, and also many Bluetooth
2629 * gamepads, particularly older ones.
2630 *
2631 * The Android mapping convention used by many Bluetooth controllers
2632 * instead uses the Z axis as a secondary X axis, and the RZ axis as
2633 * a secondary Y axis. */
2634 if (joystick->hwdata->has_abs[ABS_RX] && joystick->hwdata->has_abs[ABS_RY]) {
2635 // Linux Gamepad Specification, Xbox 360, Playstation etc.
2636 out->rightx.kind = EMappingKind_Axis;
2637 out->righty.kind = EMappingKind_Axis;
2638 out->rightx.target = joystick->hwdata->abs_map[ABS_RX];
2639 out->righty.target = joystick->hwdata->abs_map[ABS_RY];
2640#ifdef DEBUG_GAMEPAD_MAPPING
2641 SDL_Log("Mapped RIGHTX to axis %d (ABS_RX)", out->rightx.target);
2642 SDL_Log("Mapped RIGHTY to axis %d (ABS_RY)", out->righty.target);
2643#endif
2644 } else if (joystick->hwdata->has_abs[ABS_Z] && joystick->hwdata->has_abs[ABS_RZ]) {
2645 // Android convention
2646 out->rightx.kind = EMappingKind_Axis;
2647 out->righty.kind = EMappingKind_Axis;
2648 out->rightx.target = joystick->hwdata->abs_map[ABS_Z];
2649 out->righty.target = joystick->hwdata->abs_map[ABS_RZ];
2650#ifdef DEBUG_GAMEPAD_MAPPING
2651 SDL_Log("Mapped RIGHTX to axis %d (ABS_Z)", out->rightx.target);
2652 SDL_Log("Mapped RIGHTY to axis %d (ABS_RZ)", out->righty.target);
2653#endif
2654 }
2655
2656 if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
2657 // The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY5 - BTN_TRIGGER_HAPPY8
2658 if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY5] &&
2659 joystick->hwdata->has_key[BTN_TRIGGER_HAPPY6] &&
2660 joystick->hwdata->has_key[BTN_TRIGGER_HAPPY7] &&
2661 joystick->hwdata->has_key[BTN_TRIGGER_HAPPY8]) {
2662 out->right_paddle1.kind = EMappingKind_Button;
2663 out->right_paddle1.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY5];
2664 out->left_paddle1.kind = EMappingKind_Button;
2665 out->left_paddle1.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY7];
2666 out->right_paddle2.kind = EMappingKind_Button;
2667 out->right_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY6];
2668 out->left_paddle2.kind = EMappingKind_Button;
2669 out->left_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY8];
2670#ifdef DEBUG_GAMEPAD_MAPPING
2671 SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY5)", out->right_paddle1.target);
2672 SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY7)", out->left_paddle1.target);
2673 SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY6)", out->right_paddle2.target);
2674 SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY8)", out->left_paddle2.target);
2675#endif
2676 }
2677
2678 // The Xbox Series X controllers have the Share button as KEY_RECORD
2679 if (joystick->hwdata->has_key[KEY_RECORD]) {
2680 out->misc1.kind = EMappingKind_Button;
2681 out->misc1.target = joystick->hwdata->key_map[KEY_RECORD];
2682#ifdef DEBUG_GAMEPAD_MAPPING
2683 SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
2684#endif
2685 }
2686 }
2687
2688 // Cache the mapping for later
2689 item->mapping = (SDL_GamepadMapping *)SDL_malloc(sizeof(*item->mapping));
2690 if (item->mapping) {
2691 SDL_memcpy(item->mapping, out, sizeof(*out));
2692 }
2693#ifdef DEBUG_GAMEPAD_MAPPING
2694 SDL_Log("Generated mapping for device %d", device_index);
2695#endif
2696 result = true;
2697
2698done:
2699 LINUX_JoystickClose(joystick);
2700 SDL_SetObjectValid(joystick, SDL_OBJECT_TYPE_JOYSTICK, false);
2701 SDL_free(joystick);
2702
2703 return result;
2704}
2705
2706SDL_JoystickDriver SDL_LINUX_JoystickDriver = {
2707 LINUX_JoystickInit,
2708 LINUX_JoystickGetCount,
2709 LINUX_JoystickDetect,
2710 LINUX_JoystickIsDevicePresent,
2711 LINUX_JoystickGetDeviceName,
2712 LINUX_JoystickGetDevicePath,
2713 LINUX_JoystickGetDeviceSteamVirtualGamepadSlot,
2714 LINUX_JoystickGetDevicePlayerIndex,
2715 LINUX_JoystickSetDevicePlayerIndex,
2716 LINUX_JoystickGetDeviceGUID,
2717 LINUX_JoystickGetDeviceInstanceID,
2718 LINUX_JoystickOpen,
2719 LINUX_JoystickRumble,
2720 LINUX_JoystickRumbleTriggers,
2721 LINUX_JoystickSetLED,
2722 LINUX_JoystickSendEffect,
2723 LINUX_JoystickSetSensorsEnabled,
2724 LINUX_JoystickUpdate,
2725 LINUX_JoystickClose,
2726 LINUX_JoystickQuit,
2727 LINUX_JoystickGetGamepadMapping
2728};
2729
2730#endif // SDL_JOYSTICK_LINUX
diff --git a/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick_c.h
new file mode 100644
index 0000000..ae5384f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/linux/SDL_sysjoystick_c.h
@@ -0,0 +1,117 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef SDL_sysjoystick_c_h_
23#define SDL_sysjoystick_c_h_
24
25#include <linux/input.h>
26
27struct SDL_joylist_item;
28struct SDL_sensorlist_item;
29
30// The private structure used to keep track of a joystick
31struct joystick_hwdata
32{
33 int fd;
34 // linux driver creates a separate device for gyro/accelerometer
35 int fd_sensor;
36 struct SDL_joylist_item *item;
37 struct SDL_sensorlist_item *item_sensor;
38 SDL_GUID guid;
39 char *fname; // Used in haptic subsystem
40
41 bool ff_rumble;
42 bool ff_sine;
43 struct ff_effect effect;
44 Uint32 effect_expiration;
45
46 // The current Linux joystick driver maps balls to two axes
47 struct hwdata_ball
48 {
49 int axis[2];
50 } *balls;
51
52 // The current Linux joystick driver maps hats to two axes
53 struct hwdata_hat
54 {
55 int axis[2];
56 } *hats;
57
58 // Support for the Linux 2.4 unified input interface
59 Uint8 key_map[KEY_MAX];
60 Uint8 abs_map[ABS_MAX];
61 bool has_key[KEY_MAX];
62 bool has_abs[ABS_MAX];
63 bool has_accelerometer;
64 bool has_gyro;
65
66 // Support for the classic joystick interface
67 bool classic;
68 Uint16 *key_pam;
69 Uint8 *abs_pam;
70
71 struct axis_correct
72 {
73 bool use_deadzones;
74
75 // Deadzone coefficients
76 int coef[3];
77
78 // Raw coordinate scale
79 int minimum;
80 int maximum;
81 float scale;
82 } abs_correct[ABS_MAX];
83
84 float accelerometer_scale[3];
85 float gyro_scale[3];
86
87 /* Each axis is read independently, if we don't get all axis this call to
88 * LINUX_JoystickUpdateupdate(), store them for the next one */
89 float gyro_data[3];
90 float accel_data[3];
91 Uint64 sensor_tick;
92 Sint32 last_tick;
93
94 bool report_sensor;
95 bool fresh;
96 bool recovering_from_dropped;
97 bool recovering_from_dropped_sensor;
98
99 // Steam Controller support
100 bool m_bSteamController;
101
102 // 4 = (ABS_HAT3X-ABS_HAT0X)/2 (see input-event-codes.h in kernel)
103 int hats_indices[4];
104 bool has_hat[4];
105 struct hat_axis_correct
106 {
107 bool use_deadzones;
108 int minimum[2];
109 int maximum[2];
110 } hat_correct[4];
111
112 // Set when gamepad is pending removal due to ENODEV read error
113 bool gone;
114 bool sensor_gone;
115};
116
117#endif // SDL_sysjoystick_c_h_
diff --git a/contrib/SDL-3.2.8/src/joystick/n3ds/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/n3ds/SDL_sysjoystick.c
new file mode 100644
index 0000000..8396ac5
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/n3ds/SDL_sysjoystick.c
@@ -0,0 +1,298 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_N3DS
24
25// This is the N3DS implementation of the SDL joystick API
26
27#include <3ds.h>
28
29#include "../SDL_sysjoystick.h"
30
31#define NB_BUTTONS 23
32
33/*
34 N3DS sticks values are roughly within +/-160
35 which is too small to pass the jitter tolerance.
36 This correction is applied to axis values
37 so they fit better in SDL's value range.
38*/
39static inline int Correct_Axis_X(int X) {
40 if (X > 160) {
41 return SDL_JOYSTICK_AXIS_MAX;
42 }
43 else if (X < -160) {
44 return -SDL_JOYSTICK_AXIS_MAX;
45 }
46 return (X * SDL_JOYSTICK_AXIS_MAX) / 160;
47}
48
49/*
50 The Y axis needs to be flipped because SDL's "up"
51 is reversed compared to libctru's "up"
52*/
53static inline int Correct_Axis_Y(int Y) {
54 return Correct_Axis_X(-Y);
55}
56
57static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick);
58static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick);
59static void UpdateN3DSCircle(Uint64 timestamp, SDL_Joystick *joystick);
60static void UpdateN3DSCStick(Uint64 timestamp, SDL_Joystick *joystick);
61
62static bool N3DS_JoystickInit(void)
63{
64 hidInit();
65 SDL_PrivateJoystickAdded(1);
66 return true;
67}
68
69static const char *N3DS_JoystickGetDeviceName(int device_index)
70{
71 return "Nintendo 3DS";
72}
73
74static int N3DS_JoystickGetCount(void)
75{
76 return 1;
77}
78
79static SDL_GUID N3DS_JoystickGetDeviceGUID(int device_index)
80{
81 SDL_GUID guid = SDL_CreateJoystickGUIDForName("Nintendo 3DS");
82 return guid;
83}
84
85static SDL_JoystickID N3DS_JoystickGetDeviceInstanceID(int device_index)
86{
87 return device_index + 1;
88}
89
90static bool N3DS_JoystickOpen(SDL_Joystick *joystick, int device_index)
91{
92 joystick->nbuttons = NB_BUTTONS;
93 joystick->naxes = 4;
94 joystick->nhats = 0;
95
96 return true;
97}
98
99static bool N3DS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
100{
101 return SDL_Unsupported();
102}
103
104static void N3DS_JoystickUpdate(SDL_Joystick *joystick)
105{
106 Uint64 timestamp = SDL_GetTicksNS();
107
108 UpdateN3DSPressedButtons(timestamp, joystick);
109 UpdateN3DSReleasedButtons(timestamp, joystick);
110 UpdateN3DSCircle(timestamp, joystick);
111 UpdateN3DSCStick(timestamp, joystick);
112}
113
114static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick)
115{
116 static u32 previous_state = 0;
117 u32 updated_down;
118 u32 current_state = hidKeysDown();
119 updated_down = previous_state ^ current_state;
120 if (updated_down) {
121 for (Uint8 i = 0; i < joystick->nbuttons; i++) {
122 if (current_state & BIT(i) & updated_down) {
123 SDL_SendJoystickButton(timestamp, joystick, i, true);
124 }
125 }
126 }
127 previous_state = current_state;
128}
129
130static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick)
131{
132 static u32 previous_state = 0;
133 u32 updated_up;
134 u32 current_state = hidKeysUp();
135 updated_up = previous_state ^ current_state;
136 if (updated_up) {
137 for (Uint8 i = 0; i < joystick->nbuttons; i++) {
138 if (current_state & BIT(i) & updated_up) {
139 SDL_SendJoystickButton(timestamp, joystick, i, false);
140 }
141 }
142 }
143 previous_state = current_state;
144}
145
146static void UpdateN3DSCircle(Uint64 timestamp, SDL_Joystick *joystick)
147{
148 static circlePosition previous_state = { 0, 0 };
149 circlePosition current_state;
150 hidCircleRead(&current_state);
151 if (previous_state.dx != current_state.dx) {
152 SDL_SendJoystickAxis(timestamp, joystick,
153 0,
154 Correct_Axis_X(current_state.dx));
155 }
156 if (previous_state.dy != current_state.dy) {
157 SDL_SendJoystickAxis(timestamp, joystick,
158 1,
159 Correct_Axis_Y(current_state.dy));
160 }
161 previous_state = current_state;
162}
163
164static void UpdateN3DSCStick(Uint64 timestamp, SDL_Joystick *joystick)
165{
166 static circlePosition previous_state = { 0, 0 };
167 circlePosition current_state;
168 hidCstickRead(&current_state);
169 if (previous_state.dx != current_state.dx) {
170 SDL_SendJoystickAxis(timestamp, joystick,
171 2,
172 Correct_Axis_X(current_state.dx));
173 }
174 if (previous_state.dy != current_state.dy) {
175 SDL_SendJoystickAxis(timestamp, joystick,
176 3,
177 Correct_Axis_Y(current_state.dy));
178 }
179 previous_state = current_state;
180}
181
182static void N3DS_JoystickClose(SDL_Joystick *joystick)
183{
184}
185
186static void N3DS_JoystickQuit(void)
187{
188 hidExit();
189}
190
191static bool N3DS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
192{
193 // There is only one possible mapping.
194 *out = (SDL_GamepadMapping){
195 .a = { EMappingKind_Button, 0 },
196 .b = { EMappingKind_Button, 1 },
197 .x = { EMappingKind_Button, 10 },
198 .y = { EMappingKind_Button, 11 },
199 .back = { EMappingKind_Button, 2 },
200 .guide = { EMappingKind_None, 255 },
201 .start = { EMappingKind_Button, 3 },
202 .leftstick = { EMappingKind_None, 255 },
203 .rightstick = { EMappingKind_None, 255 },
204 .leftshoulder = { EMappingKind_Button, 9 },
205 .rightshoulder = { EMappingKind_Button, 8 },
206 .dpup = { EMappingKind_Button, 6 },
207 .dpdown = { EMappingKind_Button, 7 },
208 .dpleft = { EMappingKind_Button, 5 },
209 .dpright = { EMappingKind_Button, 4 },
210 .misc1 = { EMappingKind_None, 255 },
211 .right_paddle1 = { EMappingKind_None, 255 },
212 .left_paddle1 = { EMappingKind_None, 255 },
213 .right_paddle2 = { EMappingKind_None, 255 },
214 .left_paddle2 = { EMappingKind_None, 255 },
215 .leftx = { EMappingKind_Axis, 0 },
216 .lefty = { EMappingKind_Axis, 1 },
217 .rightx = { EMappingKind_Axis, 2 },
218 .righty = { EMappingKind_Axis, 3 },
219 .lefttrigger = { EMappingKind_Button, 14 },
220 .righttrigger = { EMappingKind_Button, 15 },
221 };
222 return true;
223}
224
225static void N3DS_JoystickDetect(void)
226{
227}
228
229static bool N3DS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
230{
231 // We don't override any other drivers
232 return false;
233}
234
235static const char *N3DS_JoystickGetDevicePath(int device_index)
236{
237 return NULL;
238}
239
240static int N3DS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
241{
242 return -1;
243}
244
245static int N3DS_JoystickGetDevicePlayerIndex(int device_index)
246{
247 return -1;
248}
249
250static void N3DS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
251{
252}
253
254static bool N3DS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
255{
256 return SDL_Unsupported();
257}
258
259static bool N3DS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
260{
261 return SDL_Unsupported();
262}
263
264static bool N3DS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
265{
266 return SDL_Unsupported();
267}
268
269static bool N3DS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
270{
271 return SDL_Unsupported();
272}
273
274SDL_JoystickDriver SDL_N3DS_JoystickDriver = {
275 N3DS_JoystickInit,
276 N3DS_JoystickGetCount,
277 N3DS_JoystickDetect,
278 N3DS_JoystickIsDevicePresent,
279 N3DS_JoystickGetDeviceName,
280 N3DS_JoystickGetDevicePath,
281 N3DS_JoystickGetDeviceSteamVirtualGamepadSlot,
282 N3DS_JoystickGetDevicePlayerIndex,
283 N3DS_JoystickSetDevicePlayerIndex,
284 N3DS_JoystickGetDeviceGUID,
285 N3DS_JoystickGetDeviceInstanceID,
286 N3DS_JoystickOpen,
287 N3DS_JoystickRumble,
288 N3DS_JoystickRumbleTriggers,
289 N3DS_JoystickSetLED,
290 N3DS_JoystickSendEffect,
291 N3DS_JoystickSetSensorsEnabled,
292 N3DS_JoystickUpdate,
293 N3DS_JoystickClose,
294 N3DS_JoystickQuit,
295 N3DS_JoystickGetGamepadMapping
296};
297
298#endif // SDL_JOYSTICK_N3DS
diff --git a/contrib/SDL-3.2.8/src/joystick/ps2/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/ps2/SDL_sysjoystick.c
new file mode 100644
index 0000000..b938b1f
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/ps2/SDL_sysjoystick.c
@@ -0,0 +1,366 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_PS2
24
25// This is the PS2 implementation of the SDL joystick API
26#include <libmtap.h>
27#include <libpad.h>
28#include <ps2_joystick_driver.h>
29
30#include <stdio.h> // For the definition of NULL
31#include <stdlib.h>
32
33#include "../SDL_sysjoystick.h"
34#include "../SDL_joystick_c.h"
35
36#define PS2_MAX_PORT 2 // each ps2 has 2 ports
37#define PS2_MAX_SLOT 4 // maximum - 4 slots in one multitap
38#define MAX_CONTROLLERS (PS2_MAX_PORT * PS2_MAX_SLOT)
39#define PS2_ANALOG_STICKS 2
40#define PS2_ANALOG_AXIS 2
41#define PS2_BUTTONS 16
42#define PS2_TOTAL_AXIS (PS2_ANALOG_STICKS * PS2_ANALOG_AXIS)
43
44struct JoyInfo
45{
46 uint8_t padBuf[256];
47 uint16_t btns;
48 uint8_t analog_state[PS2_TOTAL_AXIS];
49 uint8_t port;
50 uint8_t slot;
51 int8_t rumble_ready;
52 int8_t opened;
53} __attribute__((aligned(64)));
54
55static uint8_t enabled_pads = 0;
56static struct JoyInfo joyInfo[MAX_CONTROLLERS];
57
58static inline int16_t convert_u8_to_s16(uint8_t val)
59{
60 if (val == 0) {
61 return -0x7fff;
62 }
63 return val * 0x0101 - 0x8000;
64}
65
66static inline uint8_t rumble_status(uint8_t index)
67{
68 char actAlign[6];
69 int res;
70 struct JoyInfo *info = &joyInfo[index];
71
72 if (info->rumble_ready == 0) {
73 actAlign[0] = 0;
74 actAlign[1] = 1;
75 actAlign[2] = 0xff;
76 actAlign[3] = 0xff;
77 actAlign[4] = 0xff;
78 actAlign[5] = 0xff;
79
80 res = padSetActAlign(info->port, info->slot, actAlign);
81 info->rumble_ready = res <= 0 ? -1 : 1;
82 }
83
84 return info->rumble_ready == 1;
85}
86
87// Function to scan the system for joysticks.
88static bool PS2_JoystickInit(void)
89{
90 uint32_t port = 0;
91 uint32_t slot = 0;
92
93 if (init_joystick_driver(true) < 0) {
94 return false;
95 }
96
97 for (port = 0; port < PS2_MAX_PORT; port++) {
98 mtapPortOpen(port);
99 }
100 // it can fail - we dont care, we will check it more strictly when padPortOpen
101
102 for (slot = 0; slot < PS2_MAX_SLOT; slot++) {
103 for (port = 0; port < PS2_MAX_PORT; port++) {
104 /* 2 main controller ports acts the same with and without multitap
105 Port 0,0 -> Connector 1 - the same as Port 0
106 Port 1,0 -> Connector 2 - the same as Port 1
107 Port 0,1 -> Connector 3
108 Port 1,1 -> Connector 4
109 Port 0,2 -> Connector 5
110 Port 1,2 -> Connector 6
111 Port 0,3 -> Connector 7
112 Port 1,3 -> Connector 8
113 */
114
115 struct JoyInfo *info = &joyInfo[enabled_pads];
116 if (padPortOpen(port, slot, (void *)info->padBuf) > 0) {
117 info->port = (uint8_t)port;
118 info->slot = (uint8_t)slot;
119 info->opened = 1;
120 enabled_pads++;
121 SDL_PrivateJoystickAdded(enabled_pads);
122 }
123 }
124 }
125
126 return (enabled_pads > 0);
127}
128
129// Function to return the number of joystick devices plugged in right now
130static int PS2_JoystickGetCount(void)
131{
132 return (int)enabled_pads;
133}
134
135// Function to cause any queued joystick insertions to be processed
136static void PS2_JoystickDetect(void)
137{
138}
139
140static bool PS2_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
141{
142 // We don't override any other drivers
143 return false;
144}
145
146// Function to get the device-dependent name of a joystick
147static const char *PS2_JoystickGetDeviceName(int index)
148{
149 if (index >= 0 && index < enabled_pads) {
150 return "PS2 Controller";
151 }
152
153 SDL_SetError("No joystick available with that index");
154 return NULL;
155}
156
157// Function to get the device-dependent path of a joystick
158static const char *PS2_JoystickGetDevicePath(int index)
159{
160 return NULL;
161}
162
163// Function to get the Steam virtual gamepad slot of a joystick
164static int PS2_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
165{
166 return -1;
167}
168
169// Function to get the player index of a joystick
170static int PS2_JoystickGetDevicePlayerIndex(int device_index)
171{
172 return -1;
173}
174
175// Function to set the player index of a joystick
176static void PS2_JoystickSetDevicePlayerIndex(int device_index, int player_index)
177{
178}
179
180// Function to return the stable GUID for a plugged in device
181static SDL_GUID PS2_JoystickGetDeviceGUID(int device_index)
182{
183 // the GUID is just the name for now
184 const char *name = PS2_JoystickGetDeviceName(device_index);
185 return SDL_CreateJoystickGUIDForName(name);
186}
187
188// Function to get the current instance id of the joystick located at device_index
189static SDL_JoystickID PS2_JoystickGetDeviceInstanceID(int device_index)
190{
191 return device_index + 1;
192}
193
194/* Function to open a joystick for use.
195 The joystick to open is specified by the device index.
196 This should fill the nbuttons and naxes fields of the joystick structure.
197 It returns 0, or -1 if there is an error.
198*/
199static bool PS2_JoystickOpen(SDL_Joystick *joystick, int device_index)
200{
201 int index = joystick->instance_id;
202 struct JoyInfo *info = &joyInfo[index];
203
204 if (!info->opened) {
205 if (padPortOpen(info->port, info->slot, (void *)info->padBuf) > 0) {
206 info->opened = 1;
207 } else {
208 return false;
209 }
210 }
211 joystick->nbuttons = PS2_BUTTONS;
212 joystick->naxes = PS2_TOTAL_AXIS;
213 joystick->nhats = 0;
214
215 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
216
217 return true;
218}
219
220// Rumble functionality
221static bool PS2_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
222{
223 char actAlign[6];
224 int res;
225 int index = joystick->instance_id;
226 struct JoyInfo *info = &joyInfo[index];
227
228 if (!rumble_status(index)) {
229 return false;
230 }
231
232 // Initial value
233 actAlign[0] = low_frequency_rumble >> 8; // Enable small engine
234 actAlign[1] = high_frequency_rumble >> 8; // Enable big engine
235 actAlign[2] = 0xff;
236 actAlign[3] = 0xff;
237 actAlign[4] = 0xff;
238 actAlign[5] = 0xff;
239
240 res = padSetActDirect(info->port, info->slot, actAlign);
241 return (res == 1);
242}
243
244// Rumble functionality
245static bool PS2_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left, Uint16 right)
246{
247 return SDL_Unsupported();
248}
249
250// LED functionality
251static bool PS2_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
252{
253 return SDL_Unsupported();
254}
255
256// General effects
257static bool PS2_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
258{
259 return SDL_Unsupported();
260}
261
262// Sensor functionality
263static bool PS2_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
264{
265 return SDL_Unsupported();
266}
267
268/* Function to update the state of a joystick - called as a device poll.
269 * This function shouldn't update the joystick structure directly,
270 * but instead should call SDL_PrivateJoystick*() to deliver events
271 * and update joystick device state.
272 */
273static void PS2_JoystickUpdate(SDL_Joystick *joystick)
274{
275 uint8_t i;
276 uint8_t previous_axis, current_axis;
277 uint16_t mask, previous, current;
278 struct padButtonStatus buttons;
279 uint8_t all_axis[PS2_TOTAL_AXIS];
280 int index = joystick->instance_id;
281 struct JoyInfo *info = &joyInfo[index];
282 int state = padGetState(info->port, info->slot);
283 Uint64 timestamp = SDL_GetTicksNS();
284
285 if (state != PAD_STATE_DISCONN && state != PAD_STATE_EXECCMD && state != PAD_STATE_ERROR) {
286 int ret = padRead(info->port, info->slot, &buttons); // port, slot, buttons
287 if (ret != 0) {
288 // Buttons
289 int32_t pressed_buttons = 0xffff ^ buttons.btns;
290 ;
291 if (info->btns != pressed_buttons) {
292 for (i = 0; i < PS2_BUTTONS; i++) {
293 mask = (1 << i);
294 previous = info->btns & mask;
295 current = pressed_buttons & mask;
296 if (previous != current) {
297 SDL_SendJoystickButton(timestamp, joystick, i, (current != 0));
298 }
299 }
300 }
301 info->btns = pressed_buttons;
302
303 // Analog
304 all_axis[0] = buttons.ljoy_h;
305 all_axis[1] = buttons.ljoy_v;
306 all_axis[2] = buttons.rjoy_h;
307 all_axis[3] = buttons.rjoy_v;
308
309 for (i = 0; i < PS2_TOTAL_AXIS; i++) {
310 previous_axis = info->analog_state[i];
311 current_axis = all_axis[i];
312 if (previous_axis != current_axis) {
313 SDL_SendJoystickAxis(timestamp, joystick, i, convert_u8_to_s16(current_axis));
314 }
315
316 info->analog_state[i] = current_axis;
317 }
318 }
319 }
320}
321
322// Function to close a joystick after use
323static void PS2_JoystickClose(SDL_Joystick *joystick)
324{
325 int index = joystick->instance_id;
326 struct JoyInfo *info = &joyInfo[index];
327 padPortClose(info->port, info->slot);
328 info->opened = 0;
329}
330
331// Function to perform any system-specific joystick related cleanup
332static void PS2_JoystickQuit(void)
333{
334 deinit_joystick_driver(true);
335}
336
337static bool PS2_GetGamepadMapping(int device_index, SDL_GamepadMapping *out)
338{
339 return false;
340}
341
342SDL_JoystickDriver SDL_PS2_JoystickDriver = {
343 PS2_JoystickInit,
344 PS2_JoystickGetCount,
345 PS2_JoystickDetect,
346 PS2_JoystickIsDevicePresent,
347 PS2_JoystickGetDeviceName,
348 PS2_JoystickGetDevicePath,
349 PS2_JoystickGetDeviceSteamVirtualGamepadSlot,
350 PS2_JoystickGetDevicePlayerIndex,
351 PS2_JoystickSetDevicePlayerIndex,
352 PS2_JoystickGetDeviceGUID,
353 PS2_JoystickGetDeviceInstanceID,
354 PS2_JoystickOpen,
355 PS2_JoystickRumble,
356 PS2_JoystickRumbleTriggers,
357 PS2_JoystickSetLED,
358 PS2_JoystickSendEffect,
359 PS2_JoystickSetSensorsEnabled,
360 PS2_JoystickUpdate,
361 PS2_JoystickClose,
362 PS2_JoystickQuit,
363 PS2_GetGamepadMapping,
364};
365
366#endif // SDL_JOYSTICK_PS2
diff --git a/contrib/SDL-3.2.8/src/joystick/psp/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/psp/SDL_sysjoystick.c
new file mode 100644
index 0000000..8a0154c
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/psp/SDL_sysjoystick.c
@@ -0,0 +1,277 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_PSP
24
25// This is the PSP implementation of the SDL joystick API
26#include <pspctrl.h>
27
28#include <stdio.h> // For the definition of NULL
29#include <stdlib.h>
30
31#include "../SDL_sysjoystick.h"
32#include "../SDL_joystick_c.h"
33
34// Current pad state
35static SceCtrlData pad = { .Lx = 0, .Ly = 0, .Buttons = 0 };
36static const enum PspCtrlButtons button_map[] = {
37 PSP_CTRL_TRIANGLE, PSP_CTRL_CIRCLE, PSP_CTRL_CROSS, PSP_CTRL_SQUARE,
38 PSP_CTRL_LTRIGGER, PSP_CTRL_RTRIGGER,
39 PSP_CTRL_DOWN, PSP_CTRL_LEFT, PSP_CTRL_UP, PSP_CTRL_RIGHT,
40 PSP_CTRL_SELECT, PSP_CTRL_START, PSP_CTRL_HOME, PSP_CTRL_HOLD
41};
42static int analog_map[256]; // Map analog inputs to -32768 -> 32767
43
44// 4 points define the bezier-curve.
45static SDL_Point a = { 0, 0 };
46static SDL_Point b = { 50, 0 };
47static SDL_Point c = { 78, 32767 };
48static SDL_Point d = { 128, 32767 };
49
50// simple linear interpolation between two points
51static SDL_INLINE void lerp(SDL_Point *dest, const SDL_Point *pt_a, const SDL_Point *pt_b, float t)
52{
53 dest->x = pt_a->x + (int)((pt_b->x - pt_a->x) * t);
54 dest->y = pt_a->y + (int)((pt_b->y - pt_a->y) * t);
55}
56
57// evaluate a point on a bezier-curve. t goes from 0 to 1.0
58static int calc_bezier_y(float t)
59{
60 SDL_Point ab, bc, cd, abbc, bccd, dest;
61 lerp(&ab, &a, &b, t); // point between a and b
62 lerp(&bc, &b, &c, t); // point between b and c
63 lerp(&cd, &c, &d, t); // point between c and d
64 lerp(&abbc, &ab, &bc, t); // point between ab and bc
65 lerp(&bccd, &bc, &cd, t); // point between bc and cd
66 lerp(&dest, &abbc, &bccd, t); // point on the bezier-curve
67 return dest.y;
68}
69
70/* Function to scan the system for joysticks.
71 * Joystick 0 should be the system default joystick.
72 * It should return number of joysticks, or -1 on an unrecoverable fatal error.
73 */
74static bool PSP_JoystickInit(void)
75{
76 int i;
77
78 // Setup input
79 sceCtrlSetSamplingCycle(0);
80 sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);
81
82 /* Create an accurate map from analog inputs (0 to 255)
83 to SDL joystick positions (-32768 to 32767) */
84 for (i = 0; i < 128; i++) {
85 float t = (float)i / 127.0f;
86 analog_map[i + 128] = calc_bezier_y(t);
87 analog_map[127 - i] = -1 * analog_map[i + 128];
88 }
89
90 SDL_PrivateJoystickAdded(1);
91
92 return 1;
93}
94
95static int PSP_JoystickGetCount(void)
96{
97 return 1;
98}
99
100static void PSP_JoystickDetect(void)
101{
102}
103
104static bool PSP_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
105{
106 // We don't override any other drivers
107 return false;
108}
109
110// Function to get the device-dependent name of a joystick
111static const char *PSP_JoystickGetDeviceName(int device_index)
112{
113 if (device_index == 0) {
114 return "PSP builtin joypad";
115 }
116
117 SDL_SetError("No joystick available with that index");
118 return NULL;
119}
120
121static const char *PSP_JoystickGetDevicePath(int index)
122{
123 return NULL;
124}
125
126static int PSP_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
127{
128 return -1;
129}
130
131static int PSP_JoystickGetDevicePlayerIndex(int device_index)
132{
133 return -1;
134}
135
136static void PSP_JoystickSetDevicePlayerIndex(int device_index, int player_index)
137{
138}
139
140static SDL_GUID PSP_JoystickGetDeviceGUID(int device_index)
141{
142 // the GUID is just the name for now
143 const char *name = PSP_JoystickGetDeviceName(device_index);
144 return SDL_CreateJoystickGUIDForName(name);
145}
146
147// Function to perform the mapping from device index to the instance id for this index
148static SDL_JoystickID PSP_JoystickGetDeviceInstanceID(int device_index)
149{
150 return device_index + 1;
151}
152
153/* Function to open a joystick for use.
154 The joystick to open is specified by the device index.
155 This should fill the nbuttons and naxes fields of the joystick structure.
156 It returns 0, or -1 if there is an error.
157 */
158static bool PSP_JoystickOpen(SDL_Joystick *joystick, int device_index)
159{
160 joystick->nbuttons = SDL_arraysize(button_map);
161 joystick->naxes = 2;
162 joystick->nhats = 0;
163
164 return true;
165}
166
167static bool PSP_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
168{
169 return SDL_Unsupported();
170}
171
172static bool PSP_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
173{
174 return SDL_Unsupported();
175}
176
177static bool PSP_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
178{
179 return SDL_Unsupported();
180}
181
182static bool PSP_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
183{
184 return SDL_Unsupported();
185}
186
187static bool PSP_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
188{
189 return SDL_Unsupported();
190}
191
192/* Function to update the state of a joystick - called as a device poll.
193 * This function shouldn't update the joystick structure directly,
194 * but instead should call SDL_PrivateJoystick*() to deliver events
195 * and update joystick device state.
196 */
197static void PSP_JoystickUpdate(SDL_Joystick *joystick)
198{
199 int i;
200 enum PspCtrlButtons buttons;
201 enum PspCtrlButtons changed;
202 unsigned char x, y;
203 static enum PspCtrlButtons old_buttons = 0;
204 static unsigned char old_x = 0, old_y = 0;
205 Uint64 timestamp = SDL_GetTicksNS();
206
207 if (sceCtrlPeekBufferPositive(&pad, 1) <= 0) {
208 return;
209 }
210 buttons = pad.Buttons;
211 x = pad.Lx;
212 y = pad.Ly;
213
214 // Axes
215 if (old_x != x) {
216 SDL_SendJoystickAxis(timestamp, joystick, 0, analog_map[x]);
217 old_x = x;
218 }
219 if (old_y != y) {
220 SDL_SendJoystickAxis(timestamp, joystick, 1, analog_map[y]);
221 old_y = y;
222 }
223
224 // Buttons
225 changed = old_buttons ^ buttons;
226 old_buttons = buttons;
227 if (changed) {
228 for (i = 0; i < SDL_arraysize(button_map); i++) {
229 if (changed & button_map[i]) {
230 bool down = ((buttons & button_map[i]) != 0);
231 SDL_SendJoystickButton(timestamp,
232 joystick, i, down);
233 }
234 }
235 }
236}
237
238// Function to close a joystick after use
239static void PSP_JoystickClose(SDL_Joystick *joystick)
240{
241}
242
243// Function to perform any system-specific joystick related cleanup
244static void PSP_JoystickQuit(void)
245{
246}
247
248static bool PSP_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
249{
250 return false;
251}
252
253SDL_JoystickDriver SDL_PSP_JoystickDriver = {
254 PSP_JoystickInit,
255 PSP_JoystickGetCount,
256 PSP_JoystickDetect,
257 PSP_JoystickIsDevicePresent,
258 PSP_JoystickGetDeviceName,
259 PSP_JoystickGetDevicePath,
260 PSP_JoystickGetDeviceSteamVirtualGamepadSlot,
261 PSP_JoystickGetDevicePlayerIndex,
262 PSP_JoystickSetDevicePlayerIndex,
263 PSP_JoystickGetDeviceGUID,
264 PSP_JoystickGetDeviceInstanceID,
265 PSP_JoystickOpen,
266 PSP_JoystickRumble,
267 PSP_JoystickRumbleTriggers,
268 PSP_JoystickSetLED,
269 PSP_JoystickSendEffect,
270 PSP_JoystickSetSensorsEnabled,
271 PSP_JoystickUpdate,
272 PSP_JoystickClose,
273 PSP_JoystickQuit,
274 PSP_JoystickGetGamepadMapping
275};
276
277#endif // SDL_JOYSTICK_PSP
diff --git a/contrib/SDL-3.2.8/src/joystick/sort_controllers.py b/contrib/SDL-3.2.8/src/joystick/sort_controllers.py
new file mode 100755
index 0000000..19aec89
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/sort_controllers.py
@@ -0,0 +1,164 @@
1#!/usr/bin/env python3
2#
3# Script to sort the game controller database entries in SDL_gamepad.c
4
5import re
6
7
8filename = "SDL_gamepad_db.h"
9input = open(filename)
10output = open(f"{filename}.new", "w")
11parsing_controllers = False
12controllers = []
13controller_guids = {}
14conditionals = []
15split_pattern = re.compile(r'([^"]*")([^,]*,)([^,]*,)([^"]*)(".*)')
16# BUS (1) CRC (3,2) VID (5,4) (6) PID (8,7) (9) VERSION (11,10) MISC (12)
17standard_guid_pattern = re.compile(r'^([0-9a-fA-F]{4})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})(0000)([0-9a-fA-F]{2})([0-9a-fA-F]{2})(0000)([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{4},)$')
18
19# These chipsets are used in multiple controllers with different mappings,
20# without enough unique information to differentiate them. e.g.
21# https://github.com/gabomdq/SDL_GameControllerDB/issues/202
22invalid_controllers = (
23 ('0079', '0006', '0000'), # DragonRise Inc. Generic USB Joystick
24 ('0079', '0006', '6120'), # DragonRise Inc. Generic USB Joystick
25 ('04b4', '2412', 'c529'), # Flydigi Vader 2, Vader 2 Pro, Apex 2, Apex 3, Apex 4
26 ('16c0', '05e1', '0000'), # Xinmotek Controller
27)
28
29def find_element(prefix, bindings):
30 i=0
31 for element in bindings:
32 if element.startswith(prefix):
33 return i
34 i=(i + 1)
35
36 return -1
37
38def get_crc_from_entry(entry):
39 crc = ""
40 line = "".join(entry)
41 bindings = line.split(",")
42 pos = find_element("crc:", bindings)
43 if pos >= 0:
44 crc = bindings[pos][4:]
45 return crc
46
47def save_controller(line):
48 global controllers
49 match = split_pattern.match(line)
50 entry = [ match.group(1), match.group(2), match.group(3) ]
51 bindings = sorted(match.group(4).split(","))
52 if (bindings[0] == ""):
53 bindings.pop(0)
54
55 name = entry[2].rstrip(',')
56
57 crc = ""
58 pos = find_element("crc:", bindings)
59 if pos >= 0:
60 crc = bindings[pos] + ","
61 bindings.pop(pos)
62
63 guid_match = standard_guid_pattern.match(entry[1])
64 if guid_match:
65 groups = guid_match.groups()
66 crc_value = groups[2] + groups[1]
67 vid_value = groups[4] + groups[3]
68 pid_value = groups[7] + groups[6]
69 version_value = groups[10] + groups[9]
70 #print("CRC: %s, VID: %s, PID: %s, VERSION: %s" % (crc_value, vid_value, pid_value, version_value))
71
72 if crc_value == "0000":
73 if crc != "":
74 crc_value = crc[4:-1]
75 else:
76 print("Extracting CRC from GUID of " + name)
77 entry[1] = groups[0] + "0000" + "".join(groups[3:])
78 crc = "crc:" + crc_value + ","
79
80 if (vid_value, pid_value, crc_value) in invalid_controllers:
81 print("Controller '%s' not unique, skipping" % name)
82 return
83
84 pos = find_element("type", bindings)
85 if pos >= 0:
86 bindings.insert(0, bindings.pop(pos))
87
88 pos = find_element("platform", bindings)
89 if pos >= 0:
90 bindings.insert(0, bindings.pop(pos))
91
92 pos = find_element("sdk", bindings)
93 if pos >= 0:
94 bindings.append(bindings.pop(pos))
95
96 pos = find_element("hint:", bindings)
97 if pos >= 0:
98 bindings.append(bindings.pop(pos))
99
100 entry.extend(crc)
101 entry.extend(",".join(bindings) + ",")
102 entry.append(match.group(5))
103 controllers.append(entry)
104
105 entry_id = entry[1] + get_crc_from_entry(entry)
106 if ',sdk' in line or ',hint:' in line:
107 conditionals.append(entry_id)
108
109def write_controllers():
110 global controllers
111 global controller_guids
112 # Check for duplicates
113 for entry in controllers:
114 entry_id = entry[1] + get_crc_from_entry(entry)
115 if (entry_id in controller_guids and entry_id not in conditionals):
116 current_name = entry[2]
117 existing_name = controller_guids[entry_id][2]
118 print("Warning: entry '%s' is duplicate of entry '%s'" % (current_name, existing_name))
119
120 if (not current_name.startswith("(DUPE)")):
121 entry[2] = f"(DUPE) {current_name}"
122
123 if (not existing_name.startswith("(DUPE)")):
124 controller_guids[entry_id][2] = f"(DUPE) {existing_name}"
125
126 controller_guids[entry_id] = entry
127
128 for entry in sorted(controllers, key=lambda entry: f"{entry[2]}-{entry[1]}"):
129 line = "".join(entry) + "\n"
130 line = line.replace("\t", " ")
131 if not line.endswith(",\n") and not line.endswith("*/\n") and not line.endswith(",\r\n") and not line.endswith("*/\r\n"):
132 print("Warning: '%s' is missing a comma at the end of the line" % (line))
133 output.write(line)
134
135 controllers = []
136 controller_guids = {}
137
138for line in input:
139 if parsing_controllers:
140 if (line.startswith("{")):
141 output.write(line)
142 elif (line.startswith(" NULL")):
143 parsing_controllers = False
144 write_controllers()
145 output.write(line)
146 elif (line.startswith("#if")):
147 print(f"Parsing {line.strip()}")
148 output.write(line)
149 elif ("SDL_PRIVATE_GAMEPAD_DEFINITIONS" in line):
150 write_controllers()
151 output.write(line)
152 elif (line.startswith("#endif")):
153 write_controllers()
154 output.write(line)
155 else:
156 save_controller(line)
157 else:
158 if (line.startswith("static const char *")):
159 parsing_controllers = True
160
161 output.write(line)
162
163output.close()
164print(f"Finished writing {filename}.new")
diff --git a/contrib/SDL-3.2.8/src/joystick/usb_ids.h b/contrib/SDL-3.2.8/src/joystick/usb_ids.h
new file mode 100644
index 0000000..794beb8
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/usb_ids.h
@@ -0,0 +1,194 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef usb_ids_h_
23#define usb_ids_h_
24
25// Definitions of useful USB VID/PID values
26
27#define USB_VENDOR_8BITDO 0x2dc8
28#define USB_VENDOR_AMAZON 0x1949
29#define USB_VENDOR_APPLE 0x05ac
30#define USB_VENDOR_ASTRO 0x9886
31#define USB_VENDOR_ASUS 0x0b05
32#define USB_VENDOR_BACKBONE 0x358a
33#define USB_VENDOR_GAMESIR 0x3537
34#define USB_VENDOR_DRAGONRISE 0x0079
35#define USB_VENDOR_GOOGLE 0x18d1
36#define USB_VENDOR_HORI 0x0f0d
37#define USB_VENDOR_HP 0x03f0
38#define USB_VENDOR_HYPERKIN 0x2e24
39#define USB_VENDOR_LOGITECH 0x046d
40#define USB_VENDOR_MADCATZ 0x0738
41#define USB_VENDOR_MAYFLASH 0x33df
42#define USB_VENDOR_MICROSOFT 0x045e
43#define USB_VENDOR_NACON 0x146b
44#define USB_VENDOR_NACON_ALT 0x3285
45#define USB_VENDOR_NINTENDO 0x057e
46#define USB_VENDOR_NVIDIA 0x0955
47#define USB_VENDOR_PDP 0x0e6f
48#define USB_VENDOR_POWERA 0x24c6
49#define USB_VENDOR_POWERA_ALT 0x20d6
50#define USB_VENDOR_QANBA 0x2c22
51#define USB_VENDOR_RAZER 0x1532
52#define USB_VENDOR_SAITEK 0x06a3
53#define USB_VENDOR_SHANWAN 0x2563
54#define USB_VENDOR_SHANWAN_ALT 0x20bc
55#define USB_VENDOR_SONY 0x054c
56#define USB_VENDOR_THRUSTMASTER 0x044f
57#define USB_VENDOR_TURTLE_BEACH 0x10f5
58#define USB_VENDOR_SWITCH 0x2563
59#define USB_VENDOR_VALVE 0x28de
60#define USB_VENDOR_ZEROPLUS 0x0c12
61
62#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 0x2002 // Ultimate Wired Controller for Xbox
63#define USB_PRODUCT_8BITDO_XBOX_CONTROLLER2 0x3106 // Ultimate Wireless / Pro 2 Wired Controller
64#define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419
65#define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024
66#define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103
67#define USB_PRODUCT_BACKBONE_ONE_IOS_PS5 0x0104
68#define USB_PRODUCT_GAMESIR_G7 0x1001
69#define USB_PRODUCT_GOOGLE_STADIA_CONTROLLER 0x9400
70#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 0x1843
71#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1846
72#define USB_PRODUCT_HORI_FIGHTING_COMMANDER_OCTA_SERIES_X 0x0150
73#define USB_PRODUCT_HORI_HORIPAD_PRO_SERIES_X 0x014f
74#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS4 0x011c
75#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184
76#define USB_PRODUCT_HORI_FIGHTING_STICK_ALPHA_PS5 0x0184
77#define USB_PRODUCT_HORI_STEAM_CONTROLLER 0x01AB
78#define USB_PRODUCT_HORI_STEAM_CONTROLLER_BT 0x0196
79#define USB_PRODUCT_LOGITECH_F310 0xc216
80#define USB_PRODUCT_LOGITECH_CHILLSTREAM 0xcad1
81#define USB_PRODUCT_MADCATZ_SAITEK_SIDE_PANEL_CONTROL_DECK 0x2218
82#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRELESS 0x0d16
83#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRED 0x0d17
84#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS 0x0d18
85#define USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED 0x0d19
86#define USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER 0x0337
87#define USB_PRODUCT_NINTENDO_N64_CONTROLLER 0x2019
88#define USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER 0x201e
89#define USB_PRODUCT_NINTENDO_SNES_CONTROLLER 0x2017
90#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP 0x200e
91#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT 0x2006
92#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR 0x2008 // Used by joycond
93#define USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT 0x2007
94#define USB_PRODUCT_NINTENDO_SWITCH_PRO 0x2009
95#define USB_PRODUCT_NINTENDO_WII_REMOTE 0x0306
96#define USB_PRODUCT_NINTENDO_WII_REMOTE2 0x0330
97#define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 0x7210
98#define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104 0x7214
99#define USB_PRODUCT_RAZER_ATROX 0x0a00
100#define USB_PRODUCT_RAZER_KITSUNE 0x1012
101#define USB_PRODUCT_RAZER_PANTHERA 0x0401
102#define USB_PRODUCT_RAZER_PANTHERA_EVO 0x1008
103#define USB_PRODUCT_RAZER_RAIJU 0x1000
104#define USB_PRODUCT_RAZER_TOURNAMENT_EDITION_USB 0x1007
105#define USB_PRODUCT_RAZER_TOURNAMENT_EDITION_BLUETOOTH 0x100a
106#define USB_PRODUCT_RAZER_ULTIMATE_EDITION_USB 0x1004
107#define USB_PRODUCT_RAZER_ULTIMATE_EDITION_BLUETOOTH 0x1009
108#define USB_PRODUCT_RAZER_WOLVERINE_V2 0x0a29
109#define USB_PRODUCT_RAZER_WOLVERINE_V2_CHROMA 0x0a2e
110#define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRED 0x100b
111#define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS 0x100c
112#define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRED 0x1010
113#define USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_XBOX_WIRELESS 0x1011
114#define USB_PRODUCT_RAZER_WOLVERINE_V3_PRO 0x0a3f
115#define USB_PRODUCT_ROG_RAIKIRI 0x1a38
116#define USB_PRODUCT_SAITEK_CYBORG_V3 0xf622
117#define USB_PRODUCT_SHANWAN_DS3 0x0523
118#define USB_PRODUCT_SONY_DS3 0x0268
119#define USB_PRODUCT_SONY_DS4 0x05c4
120#define USB_PRODUCT_SONY_DS4_DONGLE 0x0ba0
121#define USB_PRODUCT_SONY_DS4_SLIM 0x09cc
122#define USB_PRODUCT_SONY_DS4_STRIKEPAD 0x05c5
123#define USB_PRODUCT_SONY_DS5 0x0ce6
124#define USB_PRODUCT_SONY_DS5_EDGE 0x0df2
125#define USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER 0x0575
126#define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_PS4 0xd00e
127#define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_SERIES_X 0xd012
128#define USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R 0x7013
129#define USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON 0x7009
130#define USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE 0x1142
131#define USB_PRODUCT_VICTRIX_FS_PRO 0x0203
132#define USB_PRODUCT_VICTRIX_FS_PRO_V2 0x0207
133#define USB_PRODUCT_XBOX360_XUSB_CONTROLLER 0x02a1 // XUSB driver software PID
134#define USB_PRODUCT_XBOX360_WIRED_CONTROLLER 0x028e
135#define USB_PRODUCT_XBOX360_WIRELESS_RECEIVER 0x0719
136#define USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY1 0x02a9
137#define USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 0x0291
138#define USB_PRODUCT_XBOX_ONE_ADAPTIVE 0x0b0a
139#define USB_PRODUCT_XBOX_ONE_ADAPTIVE_BLUETOOTH 0x0b0c
140#define USB_PRODUCT_XBOX_ONE_ADAPTIVE_BLE 0x0b21
141#define USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1 0x02e3
142#define USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2 0x0b00
143#define USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH 0x0b05
144#define USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLE 0x0b22
145#define USB_PRODUCT_XBOX_ONE_S 0x02ea
146#define USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH 0x02e0
147#define USB_PRODUCT_XBOX_ONE_S_REV2_BLUETOOTH 0x02fd
148#define USB_PRODUCT_XBOX_ONE_S_REV2_BLE 0x0b20
149#define USB_PRODUCT_XBOX_SERIES_X 0x0b12
150#define USB_PRODUCT_XBOX_SERIES_X_BLE 0x0b13
151#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX 0x08b6
152#define USB_PRODUCT_XBOX_SERIES_X_HP_HYPERX_RGB 0x07a0
153#define USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW 0x02da
154#define USB_PRODUCT_XBOX_SERIES_X_PDP_BLUE 0x02d9
155#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 0x4001
156#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO4 0x400b
157#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_USB 0x4014
158#define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO_WIRELESS_DONGLE 0x4016
159#define USB_PRODUCT_XBOX_SERIES_X_POWERA_MOGA_XP_ULTRA 0x890b
160#define USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA 0x4002
161#define USB_PRODUCT_XBOX_SERIES_X_VICTRIX_GAMBIT 0x02d6
162#define USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER 0x02ff // XBOXGIP driver software PID
163#define USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD 0x11ff
164
165// USB usage pages
166#define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001
167#define USB_USAGEPAGE_BUTTON 0x0009
168
169// USB usages for USAGE_PAGE_GENERIC_DESKTOP
170#define USB_USAGE_GENERIC_POINTER 0x0001
171#define USB_USAGE_GENERIC_MOUSE 0x0002
172#define USB_USAGE_GENERIC_JOYSTICK 0x0004
173#define USB_USAGE_GENERIC_GAMEPAD 0x0005
174#define USB_USAGE_GENERIC_KEYBOARD 0x0006
175#define USB_USAGE_GENERIC_KEYPAD 0x0007
176#define USB_USAGE_GENERIC_MULTIAXISCONTROLLER 0x0008
177#define USB_USAGE_GENERIC_X 0x0030
178#define USB_USAGE_GENERIC_Y 0x0031
179#define USB_USAGE_GENERIC_Z 0x0032
180#define USB_USAGE_GENERIC_RX 0x0033
181#define USB_USAGE_GENERIC_RY 0x0034
182#define USB_USAGE_GENERIC_RZ 0x0035
183#define USB_USAGE_GENERIC_SLIDER 0x0036
184#define USB_USAGE_GENERIC_DIAL 0x0037
185#define USB_USAGE_GENERIC_WHEEL 0x0038
186#define USB_USAGE_GENERIC_HAT 0x0039
187
188/* Bluetooth SIG assigned Company Identifiers
189 https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/ */
190#define BLUETOOTH_VENDOR_AMAZON 0x0171
191
192#define BLUETOOTH_PRODUCT_LUNA_CONTROLLER 0x0419
193
194#endif // usb_ids_h_
diff --git a/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick.c b/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick.c
new file mode 100644
index 0000000..6925662
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick.c
@@ -0,0 +1,990 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_VIRTUAL
24
25// This is the virtual implementation of the SDL joystick API
26
27#include "SDL_virtualjoystick_c.h"
28#include "../SDL_sysjoystick.h"
29#include "../SDL_joystick_c.h"
30
31static joystick_hwdata *g_VJoys SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
32
33static joystick_hwdata *VIRTUAL_HWDataForInstance(SDL_JoystickID instance_id)
34{
35 joystick_hwdata *vjoy;
36
37 SDL_AssertJoysticksLocked();
38
39 for (vjoy = g_VJoys; vjoy; vjoy = vjoy->next) {
40 if (instance_id == vjoy->instance_id) {
41 return vjoy;
42 }
43 }
44 return NULL;
45}
46
47static joystick_hwdata *VIRTUAL_HWDataForIndex(int device_index)
48{
49 joystick_hwdata *vjoy;
50
51 SDL_AssertJoysticksLocked();
52
53 for (vjoy = g_VJoys; vjoy; vjoy = vjoy->next) {
54 if (device_index == 0) {
55 break;
56 }
57 --device_index;
58 }
59 return vjoy;
60}
61
62static void VIRTUAL_FreeHWData(joystick_hwdata *hwdata)
63{
64 joystick_hwdata *cur;
65 joystick_hwdata *prev = NULL;
66
67 SDL_AssertJoysticksLocked();
68
69 if (!hwdata) {
70 return;
71 }
72
73 if (hwdata->desc.Cleanup) {
74 hwdata->desc.Cleanup(hwdata->desc.userdata);
75 }
76
77 // Remove hwdata from SDL-global list
78 for (cur = g_VJoys; cur; prev = cur, cur = cur->next) {
79 if (hwdata == cur) {
80 if (prev) {
81 prev->next = cur->next;
82 } else {
83 g_VJoys = cur->next;
84 }
85 break;
86 }
87 }
88
89 if (hwdata->joystick) {
90 hwdata->joystick->hwdata = NULL;
91 hwdata->joystick = NULL;
92 }
93 if (hwdata->name) {
94 SDL_free(hwdata->name);
95 hwdata->name = NULL;
96 }
97 if (hwdata->axes) {
98 SDL_free((void *)hwdata->axes);
99 hwdata->axes = NULL;
100 }
101 if (hwdata->buttons) {
102 SDL_free(hwdata->buttons);
103 hwdata->buttons = NULL;
104 }
105 if (hwdata->hats) {
106 SDL_free(hwdata->hats);
107 hwdata->hats = NULL;
108 }
109 if (hwdata->balls) {
110 SDL_free(hwdata->balls);
111 hwdata->balls = NULL;
112 }
113 if (hwdata->touchpads) {
114 for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) {
115 SDL_free(hwdata->touchpads[i].fingers);
116 hwdata->touchpads[i].fingers = NULL;
117 }
118 SDL_free(hwdata->touchpads);
119 hwdata->touchpads = NULL;
120 }
121 if (hwdata->sensors) {
122 SDL_free(hwdata->sensors);
123 hwdata->sensors = NULL;
124 }
125 if (hwdata->sensor_events) {
126 SDL_free(hwdata->sensor_events);
127 hwdata->sensor_events = NULL;
128 }
129 SDL_free(hwdata);
130}
131
132SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *desc)
133{
134 joystick_hwdata *hwdata = NULL;
135 const char *name = NULL;
136 int axis_triggerleft = -1;
137 int axis_triggerright = -1;
138
139 SDL_AssertJoysticksLocked();
140
141 if (!desc) {
142 SDL_InvalidParamError("desc");
143 return 0;
144 }
145 if (desc->version < sizeof(*desc)) {
146 // Update this to handle older versions of this interface
147 SDL_SetError("Invalid desc, should be initialized with SDL_INIT_INTERFACE()");
148 return 0;
149 }
150
151 hwdata = (joystick_hwdata *)SDL_calloc(1, sizeof(joystick_hwdata));
152 if (!hwdata) {
153 VIRTUAL_FreeHWData(hwdata);
154 return 0;
155 }
156 SDL_copyp(&hwdata->desc, desc);
157 hwdata->desc.touchpads = NULL;
158 hwdata->desc.sensors = NULL;
159
160 if (hwdata->desc.name) {
161 name = hwdata->desc.name;
162 } else {
163 switch (hwdata->desc.type) {
164 case SDL_JOYSTICK_TYPE_GAMEPAD:
165 name = "Virtual Controller";
166 break;
167 case SDL_JOYSTICK_TYPE_WHEEL:
168 name = "Virtual Wheel";
169 break;
170 case SDL_JOYSTICK_TYPE_ARCADE_STICK:
171 name = "Virtual Arcade Stick";
172 break;
173 case SDL_JOYSTICK_TYPE_FLIGHT_STICK:
174 name = "Virtual Flight Stick";
175 break;
176 case SDL_JOYSTICK_TYPE_DANCE_PAD:
177 name = "Virtual Dance Pad";
178 break;
179 case SDL_JOYSTICK_TYPE_GUITAR:
180 name = "Virtual Guitar";
181 break;
182 case SDL_JOYSTICK_TYPE_DRUM_KIT:
183 name = "Virtual Drum Kit";
184 break;
185 case SDL_JOYSTICK_TYPE_ARCADE_PAD:
186 name = "Virtual Arcade Pad";
187 break;
188 case SDL_JOYSTICK_TYPE_THROTTLE:
189 name = "Virtual Throttle";
190 break;
191 default:
192 name = "Virtual Joystick";
193 break;
194 }
195 }
196 hwdata->name = SDL_strdup(name);
197
198 if (hwdata->desc.type == SDL_JOYSTICK_TYPE_GAMEPAD) {
199 int i, axis;
200
201 if (hwdata->desc.button_mask == 0) {
202 for (i = 0; i < hwdata->desc.nbuttons && i < sizeof(hwdata->desc.button_mask) * 8; ++i) {
203 hwdata->desc.button_mask |= (1 << i);
204 }
205 }
206
207 if (hwdata->desc.axis_mask == 0) {
208 if (hwdata->desc.naxes >= 2) {
209 hwdata->desc.axis_mask |= ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY));
210 }
211 if (hwdata->desc.naxes >= 4) {
212 hwdata->desc.axis_mask |= ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY));
213 }
214 if (hwdata->desc.naxes >= 6) {
215 hwdata->desc.axis_mask |= ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER));
216 }
217 }
218
219 // Find the trigger axes
220 axis = 0;
221 for (i = 0; axis < hwdata->desc.naxes && i < SDL_GAMEPAD_AXIS_COUNT; ++i) {
222 if (hwdata->desc.axis_mask & (1 << i)) {
223 if (i == SDL_GAMEPAD_AXIS_LEFT_TRIGGER) {
224 axis_triggerleft = axis;
225 }
226 if (i == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
227 axis_triggerright = axis;
228 }
229 ++axis;
230 }
231 }
232 }
233
234 hwdata->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_VIRTUAL, hwdata->desc.vendor_id, hwdata->desc.product_id, 0, NULL, name, 'v', (Uint8)hwdata->desc.type);
235
236 // Allocate fields for different control-types
237 if (hwdata->desc.naxes > 0) {
238 hwdata->axes = (Sint16 *)SDL_calloc(hwdata->desc.naxes, sizeof(*hwdata->axes));
239 if (!hwdata->axes) {
240 VIRTUAL_FreeHWData(hwdata);
241 return 0;
242 }
243
244 // Trigger axes are at minimum value at rest
245 if (axis_triggerleft >= 0) {
246 hwdata->axes[axis_triggerleft] = SDL_JOYSTICK_AXIS_MIN;
247 }
248 if (axis_triggerright >= 0) {
249 hwdata->axes[axis_triggerright] = SDL_JOYSTICK_AXIS_MIN;
250 }
251 }
252 if (hwdata->desc.nbuttons > 0) {
253 hwdata->buttons = (bool *)SDL_calloc(hwdata->desc.nbuttons, sizeof(*hwdata->buttons));
254 if (!hwdata->buttons) {
255 VIRTUAL_FreeHWData(hwdata);
256 return 0;
257 }
258 }
259 if (hwdata->desc.nhats > 0) {
260 hwdata->hats = (Uint8 *)SDL_calloc(hwdata->desc.nhats, sizeof(*hwdata->hats));
261 if (!hwdata->hats) {
262 VIRTUAL_FreeHWData(hwdata);
263 return 0;
264 }
265 }
266 if (hwdata->desc.nballs > 0) {
267 hwdata->balls = (SDL_JoystickBallData *)SDL_calloc(hwdata->desc.nballs, sizeof(*hwdata->balls));
268 if (!hwdata->balls) {
269 VIRTUAL_FreeHWData(hwdata);
270 return 0;
271 }
272 }
273 if (hwdata->desc.ntouchpads > 0) {
274 if (!desc->touchpads) {
275 VIRTUAL_FreeHWData(hwdata);
276 SDL_SetError("desc missing touchpad descriptions");
277 return 0;
278 }
279 hwdata->touchpads = (SDL_JoystickTouchpadInfo *)SDL_calloc(hwdata->desc.ntouchpads, sizeof(*hwdata->touchpads));
280 if (!hwdata->touchpads) {
281 VIRTUAL_FreeHWData(hwdata);
282 return 0;
283 }
284 for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) {
285 const SDL_VirtualJoystickTouchpadDesc *touchpad_desc = &desc->touchpads[i];
286 hwdata->touchpads[i].nfingers = touchpad_desc->nfingers;
287 hwdata->touchpads[i].fingers = (SDL_JoystickTouchpadFingerInfo *)SDL_calloc(touchpad_desc->nfingers, sizeof(*hwdata->touchpads[i].fingers));
288 if (!hwdata->touchpads[i].fingers) {
289 VIRTUAL_FreeHWData(hwdata);
290 return 0;
291 }
292 }
293 }
294 if (hwdata->desc.nsensors > 0) {
295 if (!desc->sensors) {
296 VIRTUAL_FreeHWData(hwdata);
297 SDL_SetError("desc missing sensor descriptions");
298 return 0;
299 }
300 hwdata->sensors = (SDL_JoystickSensorInfo *)SDL_calloc(hwdata->desc.nsensors, sizeof(*hwdata->sensors));
301 if (!hwdata->sensors) {
302 VIRTUAL_FreeHWData(hwdata);
303 return 0;
304 }
305 for (Uint16 i = 0; i < hwdata->desc.nsensors; ++i) {
306 const SDL_VirtualJoystickSensorDesc *sensor_desc = &desc->sensors[i];
307 hwdata->sensors[i].type = sensor_desc->type;
308 hwdata->sensors[i].rate = sensor_desc->rate;
309 }
310 }
311
312 // Allocate an instance ID for this device
313 hwdata->instance_id = SDL_GetNextObjectID();
314
315 // Add virtual joystick to SDL-global lists
316 if (g_VJoys) {
317 joystick_hwdata *last;
318
319 for (last = g_VJoys; last->next; last = last->next) {
320 }
321 last->next = hwdata;
322 } else {
323 g_VJoys = hwdata;
324 }
325 SDL_PrivateJoystickAdded(hwdata->instance_id);
326
327 return hwdata->instance_id;
328}
329
330bool SDL_JoystickDetachVirtualInner(SDL_JoystickID instance_id)
331{
332 joystick_hwdata *hwdata = VIRTUAL_HWDataForInstance(instance_id);
333 if (!hwdata) {
334 return SDL_SetError("Virtual joystick data not found");
335 }
336 VIRTUAL_FreeHWData(hwdata);
337 SDL_PrivateJoystickRemoved(instance_id);
338 return true;
339}
340
341bool SDL_SetJoystickVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 value)
342{
343 joystick_hwdata *hwdata;
344
345 SDL_AssertJoysticksLocked();
346
347 if (!joystick || !joystick->hwdata) {
348 return SDL_SetError("Invalid joystick");
349 }
350
351 hwdata = (joystick_hwdata *)joystick->hwdata;
352 if (axis < 0 || axis >= hwdata->desc.naxes) {
353 return SDL_SetError("Invalid axis index");
354 }
355
356 hwdata->axes[axis] = value;
357 hwdata->changes |= AXES_CHANGED;
358
359 return true;
360}
361
362bool SDL_SetJoystickVirtualBallInner(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel)
363{
364 joystick_hwdata *hwdata;
365
366 SDL_AssertJoysticksLocked();
367
368 if (!joystick || !joystick->hwdata) {
369 return SDL_SetError("Invalid joystick");
370 }
371
372 hwdata = (joystick_hwdata *)joystick->hwdata;
373 if (ball < 0 || ball >= hwdata->desc.nballs) {
374 return SDL_SetError("Invalid ball index");
375 }
376
377 hwdata->balls[ball].dx += xrel;
378 hwdata->balls[ball].dx = SDL_clamp(hwdata->balls[ball].dx, SDL_MIN_SINT16, SDL_MAX_SINT16);
379 hwdata->balls[ball].dy += yrel;
380 hwdata->balls[ball].dy = SDL_clamp(hwdata->balls[ball].dy, SDL_MIN_SINT16, SDL_MAX_SINT16);
381 hwdata->changes |= BALLS_CHANGED;
382
383 return true;
384}
385
386bool SDL_SetJoystickVirtualButtonInner(SDL_Joystick *joystick, int button, bool down)
387{
388 joystick_hwdata *hwdata;
389
390 SDL_AssertJoysticksLocked();
391
392 if (!joystick || !joystick->hwdata) {
393 return SDL_SetError("Invalid joystick");
394 }
395
396 hwdata = (joystick_hwdata *)joystick->hwdata;
397 if (button < 0 || button >= hwdata->desc.nbuttons) {
398 return SDL_SetError("Invalid button index");
399 }
400
401 hwdata->buttons[button] = down;
402 hwdata->changes |= BUTTONS_CHANGED;
403
404 return true;
405}
406
407bool SDL_SetJoystickVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value)
408{
409 joystick_hwdata *hwdata;
410
411 SDL_AssertJoysticksLocked();
412
413 if (!joystick || !joystick->hwdata) {
414 return SDL_SetError("Invalid joystick");
415 }
416
417 hwdata = (joystick_hwdata *)joystick->hwdata;
418 if (hat < 0 || hat >= hwdata->desc.nhats) {
419 return SDL_SetError("Invalid hat index");
420 }
421
422 hwdata->hats[hat] = value;
423 hwdata->changes |= HATS_CHANGED;
424
425 return true;
426}
427
428bool SDL_SetJoystickVirtualTouchpadInner(SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure)
429{
430 joystick_hwdata *hwdata;
431
432 SDL_AssertJoysticksLocked();
433
434 if (!joystick || !joystick->hwdata) {
435 return SDL_SetError("Invalid joystick");
436 }
437
438 hwdata = (joystick_hwdata *)joystick->hwdata;
439 if (touchpad < 0 || touchpad >= hwdata->desc.ntouchpads) {
440 return SDL_SetError("Invalid touchpad index");
441 }
442 if (finger < 0 || finger >= hwdata->touchpads[touchpad].nfingers) {
443 return SDL_SetError("Invalid finger index");
444 }
445
446 SDL_JoystickTouchpadFingerInfo *info = &hwdata->touchpads[touchpad].fingers[finger];
447 info->down = down;
448 info->x = x;
449 info->y = y;
450 info->pressure = pressure;
451 hwdata->changes |= TOUCHPADS_CHANGED;
452
453 return true;
454}
455
456bool SDL_SendJoystickVirtualSensorDataInner(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values)
457{
458 joystick_hwdata *hwdata;
459
460 SDL_AssertJoysticksLocked();
461
462 if (!joystick || !joystick->hwdata) {
463 return SDL_SetError("Invalid joystick");
464 }
465
466 hwdata = (joystick_hwdata *)joystick->hwdata;
467 if (hwdata->num_sensor_events == hwdata->max_sensor_events) {
468 int new_max_sensor_events = (hwdata->max_sensor_events + 1);
469 VirtualSensorEvent *sensor_events = (VirtualSensorEvent *)SDL_realloc(hwdata->sensor_events, new_max_sensor_events * sizeof(*sensor_events));
470 if (!sensor_events) {
471 return false;
472 }
473 hwdata->sensor_events = sensor_events;
474 hwdata->max_sensor_events = hwdata->max_sensor_events;
475 }
476
477 VirtualSensorEvent *event = &hwdata->sensor_events[hwdata->num_sensor_events++];
478 event->type = type;
479 event->sensor_timestamp = sensor_timestamp;
480 event->num_values = SDL_min(num_values, SDL_arraysize(event->data));
481 SDL_memcpy(event->data, data, (event->num_values * sizeof(*event->data)));
482
483 return true;
484}
485
486static bool VIRTUAL_JoystickInit(void)
487{
488 return true;
489}
490
491static int VIRTUAL_JoystickGetCount(void)
492{
493 joystick_hwdata *cur;
494 int count = 0;
495
496 SDL_AssertJoysticksLocked();
497
498 for (cur = g_VJoys; cur; cur = cur->next) {
499 ++count;
500 }
501 return count;
502}
503
504static void VIRTUAL_JoystickDetect(void)
505{
506}
507
508static bool VIRTUAL_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
509{
510 // We don't override any other drivers... or do we?
511 return false;
512}
513
514static const char *VIRTUAL_JoystickGetDeviceName(int device_index)
515{
516 joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index);
517 if (!hwdata) {
518 return NULL;
519 }
520 return hwdata->name;
521}
522
523static const char *VIRTUAL_JoystickGetDevicePath(int device_index)
524{
525 return NULL;
526}
527
528static int VIRTUAL_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
529{
530 return -1;
531}
532
533static int VIRTUAL_JoystickGetDevicePlayerIndex(int device_index)
534{
535 return -1;
536}
537
538static void VIRTUAL_JoystickSetDevicePlayerIndex(int device_index, int player_index)
539{
540 joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index);
541
542 if (hwdata && hwdata->desc.SetPlayerIndex) {
543 hwdata->desc.SetPlayerIndex(hwdata->desc.userdata, player_index);
544 }
545}
546
547static SDL_GUID VIRTUAL_JoystickGetDeviceGUID(int device_index)
548{
549 joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index);
550 if (!hwdata) {
551 SDL_GUID guid;
552 SDL_zero(guid);
553 return guid;
554 }
555 return hwdata->guid;
556}
557
558static SDL_JoystickID VIRTUAL_JoystickGetDeviceInstanceID(int device_index)
559{
560 joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index);
561 if (!hwdata) {
562 return true;
563 }
564 return hwdata->instance_id;
565}
566
567static bool VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index)
568{
569 joystick_hwdata *hwdata;
570
571 SDL_AssertJoysticksLocked();
572
573 hwdata = VIRTUAL_HWDataForIndex(device_index);
574 if (!hwdata) {
575 return SDL_SetError("No such device");
576 }
577 joystick->hwdata = hwdata;
578 joystick->naxes = hwdata->desc.naxes;
579 joystick->nbuttons = hwdata->desc.nbuttons;
580 joystick->nhats = hwdata->desc.nhats;
581 hwdata->joystick = joystick;
582
583 for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) {
584 const SDL_JoystickTouchpadInfo *touchpad = &hwdata->touchpads[i];
585 SDL_PrivateJoystickAddTouchpad(joystick, touchpad->nfingers);
586 }
587 for (Uint16 i = 0; i < hwdata->desc.nsensors; ++i) {
588 const SDL_JoystickSensorInfo *sensor = &hwdata->sensors[i];
589 SDL_PrivateJoystickAddSensor(joystick, sensor->type, sensor->rate);
590 }
591
592 if (hwdata->desc.SetLED) {
593 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true);
594 }
595 if (hwdata->desc.Rumble) {
596 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
597 }
598 if (hwdata->desc.RumbleTriggers) {
599 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
600 }
601 return true;
602}
603
604static bool VIRTUAL_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
605{
606 bool result;
607
608 SDL_AssertJoysticksLocked();
609
610 if (joystick->hwdata) {
611 joystick_hwdata *hwdata = joystick->hwdata;
612 if (hwdata->desc.Rumble) {
613 result = hwdata->desc.Rumble(hwdata->desc.userdata, low_frequency_rumble, high_frequency_rumble);
614 } else {
615 result = SDL_Unsupported();
616 }
617 } else {
618 result = SDL_SetError("Rumble failed, device disconnected");
619 }
620
621 return result;
622}
623
624static bool VIRTUAL_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
625{
626 bool result;
627
628 SDL_AssertJoysticksLocked();
629
630 if (joystick->hwdata) {
631 joystick_hwdata *hwdata = joystick->hwdata;
632 if (hwdata->desc.RumbleTriggers) {
633 result = hwdata->desc.RumbleTriggers(hwdata->desc.userdata, left_rumble, right_rumble);
634 } else {
635 result = SDL_Unsupported();
636 }
637 } else {
638 result = SDL_SetError("Rumble failed, device disconnected");
639 }
640
641 return result;
642}
643
644static bool VIRTUAL_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
645{
646 bool result;
647
648 SDL_AssertJoysticksLocked();
649
650 if (joystick->hwdata) {
651 joystick_hwdata *hwdata = joystick->hwdata;
652 if (hwdata->desc.SetLED) {
653 result = hwdata->desc.SetLED(hwdata->desc.userdata, red, green, blue);
654 } else {
655 result = SDL_Unsupported();
656 }
657 } else {
658 result = SDL_SetError("SetLED failed, device disconnected");
659 }
660
661 return result;
662}
663
664static bool VIRTUAL_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
665{
666 bool result;
667
668 SDL_AssertJoysticksLocked();
669
670 if (joystick->hwdata) {
671 joystick_hwdata *hwdata = joystick->hwdata;
672 if (hwdata->desc.SendEffect) {
673 result = hwdata->desc.SendEffect(hwdata->desc.userdata, data, size);
674 } else {
675 result = SDL_Unsupported();
676 }
677 } else {
678 result = SDL_SetError("SendEffect failed, device disconnected");
679 }
680
681 return result;
682}
683
684static bool VIRTUAL_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
685{
686 bool result;
687
688 SDL_AssertJoysticksLocked();
689
690 if (joystick->hwdata) {
691 joystick_hwdata *hwdata = joystick->hwdata;
692 if (hwdata->desc.SetSensorsEnabled) {
693 result = hwdata->desc.SetSensorsEnabled(hwdata->desc.userdata, enabled);
694 } else {
695 result = true;
696 }
697 if (result) {
698 hwdata->sensors_enabled = enabled;
699 }
700 } else {
701 result = SDL_SetError("SetSensorsEnabled failed, device disconnected");
702 }
703
704 return result;
705}
706
707static void VIRTUAL_JoystickUpdate(SDL_Joystick *joystick)
708{
709 joystick_hwdata *hwdata;
710 Uint64 timestamp = SDL_GetTicksNS();
711
712 SDL_AssertJoysticksLocked();
713
714 if (!joystick) {
715 return;
716 }
717 if (!joystick->hwdata) {
718 return;
719 }
720
721 hwdata = (joystick_hwdata *)joystick->hwdata;
722
723 if (hwdata->desc.Update) {
724 hwdata->desc.Update(hwdata->desc.userdata);
725 }
726
727 if (hwdata->changes & AXES_CHANGED) {
728 for (Uint8 i = 0; i < hwdata->desc.naxes; ++i) {
729 SDL_SendJoystickAxis(timestamp, joystick, i, hwdata->axes[i]);
730 }
731 }
732 if (hwdata->changes & BALLS_CHANGED) {
733 for (Uint8 i = 0; i < hwdata->desc.nballs; ++i) {
734 SDL_JoystickBallData *ball = &hwdata->balls[i];
735 if (ball->dx || ball->dy) {
736 SDL_SendJoystickBall(timestamp, joystick, i, (Sint16)ball->dx, (Sint16)ball->dy);
737 ball->dx = 0;
738 ball->dy = 0;
739 }
740 }
741 }
742 if (hwdata->changes & BUTTONS_CHANGED) {
743 for (Uint8 i = 0; i < hwdata->desc.nbuttons; ++i) {
744 SDL_SendJoystickButton(timestamp, joystick, i, hwdata->buttons[i]);
745 }
746 }
747 if (hwdata->changes & HATS_CHANGED) {
748 for (Uint8 i = 0; i < hwdata->desc.nhats; ++i) {
749 SDL_SendJoystickHat(timestamp, joystick, i, hwdata->hats[i]);
750 }
751 }
752 if (hwdata->changes & TOUCHPADS_CHANGED) {
753 for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) {
754 const SDL_JoystickTouchpadInfo *touchpad = &hwdata->touchpads[i];
755 for (int j = 0; j < touchpad->nfingers; ++j) {
756 const SDL_JoystickTouchpadFingerInfo *finger = &touchpad->fingers[j];
757 SDL_SendJoystickTouchpad(timestamp, joystick, i, j, finger->down, finger->x, finger->y, finger->pressure);
758 }
759 }
760 }
761 if (hwdata->num_sensor_events > 0) {
762 if (hwdata->sensors_enabled) {
763 for (int i = 0; i < hwdata->num_sensor_events; ++i) {
764 const VirtualSensorEvent *event = &hwdata->sensor_events[i];
765 SDL_SendJoystickSensor(timestamp, joystick, event->type, event->sensor_timestamp, event->data, event->num_values);
766 }
767 }
768 hwdata->num_sensor_events = 0;
769 }
770 hwdata->changes = 0;
771}
772
773static void VIRTUAL_JoystickClose(SDL_Joystick *joystick)
774{
775 SDL_AssertJoysticksLocked();
776
777 if (joystick->hwdata) {
778 joystick_hwdata *hwdata = joystick->hwdata;
779 hwdata->joystick = NULL;
780 joystick->hwdata = NULL;
781 }
782}
783
784static void VIRTUAL_JoystickQuit(void)
785{
786 SDL_AssertJoysticksLocked();
787
788 while (g_VJoys) {
789 VIRTUAL_FreeHWData(g_VJoys);
790 }
791}
792
793static bool VIRTUAL_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
794{
795 joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index);
796 Uint8 current_button = 0;
797 Uint8 current_axis = 0;
798
799 if (!hwdata || hwdata->desc.type != SDL_JOYSTICK_TYPE_GAMEPAD) {
800 return false;
801 }
802
803 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_SOUTH))) {
804 out->a.kind = EMappingKind_Button;
805 out->a.target = current_button++;
806 }
807
808 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_EAST))) {
809 out->b.kind = EMappingKind_Button;
810 out->b.target = current_button++;
811 }
812
813 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_WEST))) {
814 out->x.kind = EMappingKind_Button;
815 out->x.target = current_button++;
816 }
817
818 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_NORTH))) {
819 out->y.kind = EMappingKind_Button;
820 out->y.target = current_button++;
821 }
822
823 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK))) {
824 out->back.kind = EMappingKind_Button;
825 out->back.target = current_button++;
826 }
827
828 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_GUIDE))) {
829 out->guide.kind = EMappingKind_Button;
830 out->guide.target = current_button++;
831 }
832
833 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_START))) {
834 out->start.kind = EMappingKind_Button;
835 out->start.target = current_button++;
836 }
837
838 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK))) {
839 out->leftstick.kind = EMappingKind_Button;
840 out->leftstick.target = current_button++;
841 }
842
843 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK))) {
844 out->rightstick.kind = EMappingKind_Button;
845 out->rightstick.target = current_button++;
846 }
847
848 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER))) {
849 out->leftshoulder.kind = EMappingKind_Button;
850 out->leftshoulder.target = current_button++;
851 }
852
853 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER))) {
854 out->rightshoulder.kind = EMappingKind_Button;
855 out->rightshoulder.target = current_button++;
856 }
857
858 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_UP))) {
859 out->dpup.kind = EMappingKind_Button;
860 out->dpup.target = current_button++;
861 }
862
863 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN))) {
864 out->dpdown.kind = EMappingKind_Button;
865 out->dpdown.target = current_button++;
866 }
867
868 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT))) {
869 out->dpleft.kind = EMappingKind_Button;
870 out->dpleft.target = current_button++;
871 }
872
873 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT))) {
874 out->dpright.kind = EMappingKind_Button;
875 out->dpright.target = current_button++;
876 }
877
878 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC1))) {
879 out->misc1.kind = EMappingKind_Button;
880 out->misc1.target = current_button++;
881 }
882
883 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1))) {
884 out->right_paddle1.kind = EMappingKind_Button;
885 out->right_paddle1.target = current_button++;
886 }
887
888 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE1))) {
889 out->left_paddle1.kind = EMappingKind_Button;
890 out->left_paddle1.target = current_button++;
891 }
892
893 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2))) {
894 out->right_paddle2.kind = EMappingKind_Button;
895 out->right_paddle2.target = current_button++;
896 }
897
898 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE2))) {
899 out->left_paddle2.kind = EMappingKind_Button;
900 out->left_paddle2.target = current_button++;
901 }
902
903 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_TOUCHPAD))) {
904 out->touchpad.kind = EMappingKind_Button;
905 out->touchpad.target = current_button++;
906 }
907
908 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC2))) {
909 out->misc2.kind = EMappingKind_Button;
910 out->misc2.target = current_button++;
911 }
912
913 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC3))) {
914 out->misc3.kind = EMappingKind_Button;
915 out->misc3.target = current_button++;
916 }
917
918 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC4))) {
919 out->misc4.kind = EMappingKind_Button;
920 out->misc4.target = current_button++;
921 }
922
923 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC5))) {
924 out->misc5.kind = EMappingKind_Button;
925 out->misc5.target = current_button++;
926 }
927
928 if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC6))) {
929 out->misc6.kind = EMappingKind_Button;
930 out->misc6.target = current_button++;
931 }
932
933 if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTX))) {
934 out->leftx.kind = EMappingKind_Axis;
935 out->leftx.target = current_axis++;
936 }
937
938 if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTY))) {
939 out->lefty.kind = EMappingKind_Axis;
940 out->lefty.target = current_axis++;
941 }
942
943 if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTX))) {
944 out->rightx.kind = EMappingKind_Axis;
945 out->rightx.target = current_axis++;
946 }
947
948 if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTY))) {
949 out->righty.kind = EMappingKind_Axis;
950 out->righty.target = current_axis++;
951 }
952
953 if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER))) {
954 out->lefttrigger.kind = EMappingKind_Axis;
955 out->lefttrigger.target = current_axis++;
956 }
957
958 if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))) {
959 out->righttrigger.kind = EMappingKind_Axis;
960 out->righttrigger.target = current_axis++;
961 }
962
963 return true;
964}
965
966SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver = {
967 VIRTUAL_JoystickInit,
968 VIRTUAL_JoystickGetCount,
969 VIRTUAL_JoystickDetect,
970 VIRTUAL_JoystickIsDevicePresent,
971 VIRTUAL_JoystickGetDeviceName,
972 VIRTUAL_JoystickGetDevicePath,
973 VIRTUAL_JoystickGetDeviceSteamVirtualGamepadSlot,
974 VIRTUAL_JoystickGetDevicePlayerIndex,
975 VIRTUAL_JoystickSetDevicePlayerIndex,
976 VIRTUAL_JoystickGetDeviceGUID,
977 VIRTUAL_JoystickGetDeviceInstanceID,
978 VIRTUAL_JoystickOpen,
979 VIRTUAL_JoystickRumble,
980 VIRTUAL_JoystickRumbleTriggers,
981 VIRTUAL_JoystickSetLED,
982 VIRTUAL_JoystickSendEffect,
983 VIRTUAL_JoystickSetSensorsEnabled,
984 VIRTUAL_JoystickUpdate,
985 VIRTUAL_JoystickClose,
986 VIRTUAL_JoystickQuit,
987 VIRTUAL_JoystickGetGamepadMapping
988};
989
990#endif // SDL_JOYSTICK_VIRTUAL
diff --git a/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick_c.h
new file mode 100644
index 0000000..14fe59e
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/virtual/SDL_virtualjoystick_c.h
@@ -0,0 +1,84 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_VIRTUALJOYSTICK_C_H
24#define SDL_VIRTUALJOYSTICK_C_H
25
26#ifdef SDL_JOYSTICK_VIRTUAL
27
28#include "../SDL_sysjoystick.h"
29
30#define AXES_CHANGED 0x00000001
31#define BALLS_CHANGED 0x00000002
32#define BUTTONS_CHANGED 0x00000004
33#define HATS_CHANGED 0x00000008
34#define TOUCHPADS_CHANGED 0x00000010
35
36/**
37 * Data for a virtual, software-only joystick.
38 */
39typedef struct VirtualSensorEvent
40{
41 SDL_SensorType type;
42 Uint64 sensor_timestamp;
43 float data[3];
44 int num_values;
45} VirtualSensorEvent;
46
47typedef struct joystick_hwdata
48{
49 SDL_JoystickID instance_id;
50 bool attached;
51 char *name;
52 SDL_JoystickType type;
53 SDL_GUID guid;
54 SDL_VirtualJoystickDesc desc;
55 Uint32 changes;
56 Sint16 *axes;
57 bool *buttons;
58 Uint8 *hats;
59 SDL_JoystickBallData *balls;
60 SDL_JoystickTouchpadInfo *touchpads;
61 SDL_JoystickSensorInfo *sensors;
62 bool sensors_enabled;
63 int num_sensor_events;
64 int max_sensor_events;
65 VirtualSensorEvent *sensor_events;
66
67 SDL_Joystick *joystick;
68
69 struct joystick_hwdata *next;
70} joystick_hwdata;
71
72extern SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *desc);
73extern bool SDL_JoystickDetachVirtualInner(SDL_JoystickID instance_id);
74
75extern bool SDL_SetJoystickVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 value);
76extern bool SDL_SetJoystickVirtualBallInner(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel);
77extern bool SDL_SetJoystickVirtualButtonInner(SDL_Joystick *joystick, int button, bool down);
78extern bool SDL_SetJoystickVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value);
79extern bool SDL_SetJoystickVirtualTouchpadInner(SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure);
80extern bool SDL_SendJoystickVirtualSensorDataInner(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values);
81
82#endif // SDL_JOYSTICK_VIRTUAL
83
84#endif // SDL_VIRTUALJOYSTICK_C_H
diff --git a/contrib/SDL-3.2.8/src/joystick/vita/SDL_sysjoystick.c b/contrib/SDL-3.2.8/src/joystick/vita/SDL_sysjoystick.c
new file mode 100644
index 0000000..6a69411
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/vita/SDL_sysjoystick.c
@@ -0,0 +1,400 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_VITA
24
25// This is the PSVita implementation of the SDL joystick API
26#include <psp2/types.h>
27#include <psp2/ctrl.h>
28#include <psp2/kernel/threadmgr.h>
29
30#include <stdio.h> // For the definition of NULL
31#include <stdlib.h>
32
33#include "../SDL_sysjoystick.h"
34#include "../SDL_joystick_c.h"
35
36// Current pad state
37static SceCtrlData pad0 = { .lx = 0, .ly = 0, .rx = 0, .ry = 0, .lt = 0, .rt = 0, .buttons = 0 };
38static SceCtrlData pad1 = { .lx = 0, .ly = 0, .rx = 0, .ry = 0, .lt = 0, .rt = 0, .buttons = 0 };
39static SceCtrlData pad2 = { .lx = 0, .ly = 0, .rx = 0, .ry = 0, .lt = 0, .rt = 0, .buttons = 0 };
40static SceCtrlData pad3 = { .lx = 0, .ly = 0, .rx = 0, .ry = 0, .lt = 0, .rt = 0, .buttons = 0 };
41
42static int ext_port_map[4] = { 1, 2, 3, 4 }; // index: SDL joy number, entry: Vita port number. For external controllers
43
44static int SDL_numjoysticks = 1;
45
46static const unsigned int ext_button_map[] = {
47 SCE_CTRL_TRIANGLE,
48 SCE_CTRL_CIRCLE,
49 SCE_CTRL_CROSS,
50 SCE_CTRL_SQUARE,
51 SCE_CTRL_L1,
52 SCE_CTRL_R1,
53 SCE_CTRL_DOWN,
54 SCE_CTRL_LEFT,
55 SCE_CTRL_UP,
56 SCE_CTRL_RIGHT,
57 SCE_CTRL_SELECT,
58 SCE_CTRL_START,
59 SCE_CTRL_L2,
60 SCE_CTRL_R2,
61 SCE_CTRL_L3,
62 SCE_CTRL_R3
63};
64
65static int analog_map[256]; // Map analog inputs to -32768 -> 32767
66
67// 4 points define the bezier-curve.
68// The Vita has a good amount of analog travel, so use a linear curve
69static SDL_Point a = { 0, 0 };
70static SDL_Point b = { 0, 0 };
71static SDL_Point c = { 128, 32767 };
72static SDL_Point d = { 128, 32767 };
73
74// simple linear interpolation between two points
75static SDL_INLINE void lerp(SDL_Point *dest, const SDL_Point *first, const SDL_Point *second, float t)
76{
77 dest->x = first->x + (int)((second->x - first->x) * t);
78 dest->y = first->y + (int)((second->y - first->y) * t);
79}
80
81// evaluate a point on a bezier-curve. t goes from 0 to 1.0
82static int calc_bezier_y(float t)
83{
84 SDL_Point ab, bc, cd, abbc, bccd, dest;
85 lerp(&ab, &a, &b, t); // point between a and b
86 lerp(&bc, &b, &c, t); // point between b and c
87 lerp(&cd, &c, &d, t); // point between c and d
88 lerp(&abbc, &ab, &bc, t); // point between ab and bc
89 lerp(&bccd, &bc, &cd, t); // point between bc and cd
90 lerp(&dest, &abbc, &bccd, t); // point on the bezier-curve
91 return dest.y;
92}
93
94/* Function to scan the system for joysticks.
95 * Joystick 0 should be the system default joystick.
96 * It should return number of joysticks, or -1 on an unrecoverable fatal error.
97 */
98static bool VITA_JoystickInit(void)
99{
100 int i;
101 SceCtrlPortInfo myPortInfo;
102
103 // Setup input
104 sceCtrlSetSamplingMode(SCE_CTRL_MODE_ANALOG_WIDE);
105 sceCtrlSetSamplingModeExt(SCE_CTRL_MODE_ANALOG_WIDE);
106
107 /* Create an accurate map from analog inputs (0 to 255)
108 to SDL joystick positions (-32768 to 32767) */
109 for (i = 0; i < 128; i++) {
110 float t = (float)i / 127.0f;
111 analog_map[i + 128] = calc_bezier_y(t);
112 analog_map[127 - i] = -1 * analog_map[i + 128];
113 }
114
115 // Assume we have at least one controller, even when nothing is paired
116 // This way the user can jump in, pair a controller
117 // and control things immediately even if it is paired
118 // after the app has already started.
119
120 SDL_numjoysticks = 1;
121 SDL_PrivateJoystickAdded(SDL_numjoysticks);
122
123 // How many additional paired controllers are there?
124 sceCtrlGetControllerPortInfo(&myPortInfo);
125
126 // On Vita TV, port 0 and 1 are the same controller
127 // and that is the first one, so start at port 2
128 for (i = 2; i <= 4; i++) {
129 if (myPortInfo.port[i] != SCE_CTRL_TYPE_UNPAIRED) {
130 ++SDL_numjoysticks;
131 SDL_PrivateJoystickAdded(SDL_numjoysticks);
132 }
133 }
134 return SDL_numjoysticks;
135}
136
137static int VITA_JoystickGetCount(void)
138{
139 return SDL_numjoysticks;
140}
141
142static void VITA_JoystickDetect(void)
143{
144}
145
146static bool VITA_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
147{
148 // We don't override any other drivers
149 return false;
150}
151
152// Function to perform the mapping from device index to the instance id for this index
153static SDL_JoystickID VITA_JoystickGetDeviceInstanceID(int device_index)
154{
155 return device_index + 1;
156}
157
158static const char *VITA_JoystickGetDeviceName(int index)
159{
160 if (index == 0) {
161 return "PSVita Controller";
162 }
163
164 if (index == 1) {
165 return "PSVita Controller";
166 }
167
168 if (index == 2) {
169 return "PSVita Controller";
170 }
171
172 if (index == 3) {
173 return "PSVita Controller";
174 }
175
176 SDL_SetError("No joystick available with that index");
177 return NULL;
178}
179
180static const char *VITA_JoystickGetDevicePath(int index)
181{
182 return NULL;
183}
184
185static int VITA_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
186{
187 return -1;
188}
189
190static int VITA_JoystickGetDevicePlayerIndex(int device_index)
191{
192 return -1;
193}
194
195static void VITA_JoystickSetDevicePlayerIndex(int device_index, int player_index)
196{
197}
198
199/* Function to open a joystick for use.
200 The joystick to open is specified by the device index.
201 This should fill the nbuttons and naxes fields of the joystick structure.
202 It returns 0, or -1 if there is an error.
203 */
204static bool VITA_JoystickOpen(SDL_Joystick *joystick, int device_index)
205{
206 joystick->nbuttons = SDL_arraysize(ext_button_map);
207 joystick->naxes = 6;
208 joystick->nhats = 0;
209
210 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true);
211 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
212
213 return true;
214}
215
216/* Function to update the state of a joystick - called as a device poll.
217 * This function shouldn't update the joystick structure directly,
218 * but instead should call SDL_PrivateJoystick*() to deliver events
219 * and update joystick device state.
220 */
221static void VITA_JoystickUpdate(SDL_Joystick *joystick)
222{
223 int i;
224 unsigned int buttons;
225 unsigned int changed;
226 unsigned char lx, ly, rx, ry, lt, rt;
227 static unsigned int old_buttons[] = { 0, 0, 0, 0 };
228 static unsigned char old_lx[] = { 0, 0, 0, 0 };
229 static unsigned char old_ly[] = { 0, 0, 0, 0 };
230 static unsigned char old_rx[] = { 0, 0, 0, 0 };
231 static unsigned char old_ry[] = { 0, 0, 0, 0 };
232 static unsigned char old_lt[] = { 0, 0, 0, 0 };
233 static unsigned char old_rt[] = { 0, 0, 0, 0 };
234 SceCtrlData *pad = NULL;
235 Uint64 timestamp = SDL_GetTicksNS();
236
237 int index = (int)SDL_GetJoystickID(joystick) - 1;
238
239 if (index == 0)
240 pad = &pad0;
241 else if (index == 1)
242 pad = &pad1;
243 else if (index == 2)
244 pad = &pad2;
245 else if (index == 3)
246 pad = &pad3;
247 else
248 return;
249
250 if (index == 0) {
251 if (sceCtrlPeekBufferPositive2(ext_port_map[index], pad, 1) < 0) {
252 // on vita fallback to port 0
253 sceCtrlPeekBufferPositive2(0, pad, 1);
254 }
255 } else {
256 sceCtrlPeekBufferPositive2(ext_port_map[index], pad, 1);
257 }
258
259 buttons = pad->buttons;
260
261 lx = pad->lx;
262 ly = pad->ly;
263 rx = pad->rx;
264 ry = pad->ry;
265 lt = pad->lt;
266 rt = pad->rt;
267
268 // Axes
269
270 if (old_lx[index] != lx) {
271 SDL_SendJoystickAxis(timestamp, joystick, 0, analog_map[lx]);
272 old_lx[index] = lx;
273 }
274 if (old_ly[index] != ly) {
275 SDL_SendJoystickAxis(timestamp, joystick, 1, analog_map[ly]);
276 old_ly[index] = ly;
277 }
278 if (old_rx[index] != rx) {
279 SDL_SendJoystickAxis(timestamp, joystick, 2, analog_map[rx]);
280 old_rx[index] = rx;
281 }
282 if (old_ry[index] != ry) {
283 SDL_SendJoystickAxis(timestamp, joystick, 3, analog_map[ry]);
284 old_ry[index] = ry;
285 }
286
287 if (old_lt[index] != lt) {
288 SDL_SendJoystickAxis(timestamp, joystick, 4, analog_map[lt]);
289 old_lt[index] = lt;
290 }
291 if (old_rt[index] != rt) {
292 SDL_SendJoystickAxis(timestamp, joystick, 5, analog_map[rt]);
293 old_rt[index] = rt;
294 }
295
296 // Buttons
297 changed = old_buttons[index] ^ buttons;
298 old_buttons[index] = buttons;
299
300 if (changed) {
301 for (i = 0; i < SDL_arraysize(ext_button_map); i++) {
302 if (changed & ext_button_map[i]) {
303 bool down = ((buttons & ext_button_map[i]) != 0);
304 SDL_SendJoystickButton(timestamp, joystick, i, down);
305 }
306 }
307 }
308}
309
310// Function to close a joystick after use
311static void VITA_JoystickClose(SDL_Joystick *joystick)
312{
313}
314
315// Function to perform any system-specific joystick related cleanup
316static void VITA_JoystickQuit(void)
317{
318}
319
320static SDL_GUID VITA_JoystickGetDeviceGUID(int device_index)
321{
322 // the GUID is just the name for now
323 const char *name = VITA_JoystickGetDeviceName(device_index);
324 return SDL_CreateJoystickGUIDForName(name);
325}
326
327static bool VITA_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
328{
329 int index = (int)SDL_GetJoystickID(joystick) - 1;
330 SceCtrlActuator act;
331
332 if (index < 0 || index > 3) {
333 return false;
334 }
335 SDL_zero(act);
336 act.small = high_frequency_rumble / 256;
337 act.large = low_frequency_rumble / 256;
338 if (sceCtrlSetActuator(ext_port_map[index], &act) < 0) {
339 return SDL_Unsupported();
340 }
341 return true;
342}
343
344static bool VITA_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left, Uint16 right)
345{
346 return SDL_Unsupported();
347}
348
349static bool VITA_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
350{
351 int index = (int)SDL_GetJoystickID(joystick) - 1;
352 if (index < 0 || index > 3) {
353 return false;
354 }
355 if (sceCtrlSetLightBar(ext_port_map[index], red, green, blue) < 0) {
356 return SDL_Unsupported();
357 }
358 return true;
359}
360
361static bool VITA_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
362{
363 return SDL_Unsupported();
364}
365
366static bool VITA_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
367{
368 return SDL_Unsupported();
369}
370
371static bool VITA_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
372{
373 return false;
374}
375
376SDL_JoystickDriver SDL_VITA_JoystickDriver = {
377 VITA_JoystickInit,
378 VITA_JoystickGetCount,
379 VITA_JoystickDetect,
380 VITA_JoystickIsDevicePresent,
381 VITA_JoystickGetDeviceName,
382 VITA_JoystickGetDevicePath,
383 VITA_JoystickGetDeviceSteamVirtualGamepadSlot,
384 VITA_JoystickGetDevicePlayerIndex,
385 VITA_JoystickSetDevicePlayerIndex,
386 VITA_JoystickGetDeviceGUID,
387 VITA_JoystickGetDeviceInstanceID,
388 VITA_JoystickOpen,
389 VITA_JoystickRumble,
390 VITA_JoystickRumbleTriggers,
391 VITA_JoystickSetLED,
392 VITA_JoystickSendEffect,
393 VITA_JoystickSetSensorsEnabled,
394 VITA_JoystickUpdate,
395 VITA_JoystickClose,
396 VITA_JoystickQuit,
397 VITA_JoystickGetGamepadMapping,
398};
399
400#endif // SDL_JOYSTICK_VITA
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c
new file mode 100644
index 0000000..b00218d
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c
@@ -0,0 +1,1210 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_sysjoystick.h"
24
25#ifdef SDL_JOYSTICK_DINPUT
26
27#include "SDL_windowsjoystick_c.h"
28#include "SDL_dinputjoystick_c.h"
29#include "SDL_rawinputjoystick_c.h"
30#include "SDL_xinputjoystick_c.h"
31#include "../hidapi/SDL_hidapijoystick_c.h"
32
33#ifndef DIDFT_OPTIONAL
34#define DIDFT_OPTIONAL 0x80000000
35#endif
36
37#define INPUT_QSIZE 128 // Buffer up to 128 input messages
38#define JOY_AXIS_THRESHOLD (((SDL_JOYSTICK_AXIS_MAX) - (SDL_JOYSTICK_AXIS_MIN)) / 100) // 1% motion
39
40#define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF)
41
42// external variables referenced.
43#ifdef SDL_VIDEO_DRIVER_WINDOWS
44extern HWND SDL_HelperWindow;
45#else
46static const HWND SDL_HelperWindow = NULL;
47#endif
48
49// local variables
50static bool coinitialized = false;
51static LPDIRECTINPUT8 dinput = NULL;
52
53// Taken from Wine - Thanks!
54static DIOBJECTDATAFORMAT dfDIJoystick2[] = {
55 { &GUID_XAxis, DIJOFS_X, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
56 { &GUID_YAxis, DIJOFS_Y, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
57 { &GUID_ZAxis, DIJOFS_Z, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
58 { &GUID_RxAxis, DIJOFS_RX, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
59 { &GUID_RyAxis, DIJOFS_RY, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
60 { &GUID_RzAxis, DIJOFS_RZ, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
61 { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
62 { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
63 { &GUID_POV, DIJOFS_POV(0), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
64 { &GUID_POV, DIJOFS_POV(1), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
65 { &GUID_POV, DIJOFS_POV(2), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
66 { &GUID_POV, DIJOFS_POV(3), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
67 { NULL, DIJOFS_BUTTON(0), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
68 { NULL, DIJOFS_BUTTON(1), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
69 { NULL, DIJOFS_BUTTON(2), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
70 { NULL, DIJOFS_BUTTON(3), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
71 { NULL, DIJOFS_BUTTON(4), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
72 { NULL, DIJOFS_BUTTON(5), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
73 { NULL, DIJOFS_BUTTON(6), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
74 { NULL, DIJOFS_BUTTON(7), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
75 { NULL, DIJOFS_BUTTON(8), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
76 { NULL, DIJOFS_BUTTON(9), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
77 { NULL, DIJOFS_BUTTON(10), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
78 { NULL, DIJOFS_BUTTON(11), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
79 { NULL, DIJOFS_BUTTON(12), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
80 { NULL, DIJOFS_BUTTON(13), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
81 { NULL, DIJOFS_BUTTON(14), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
82 { NULL, DIJOFS_BUTTON(15), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
83 { NULL, DIJOFS_BUTTON(16), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
84 { NULL, DIJOFS_BUTTON(17), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
85 { NULL, DIJOFS_BUTTON(18), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
86 { NULL, DIJOFS_BUTTON(19), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
87 { NULL, DIJOFS_BUTTON(20), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
88 { NULL, DIJOFS_BUTTON(21), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
89 { NULL, DIJOFS_BUTTON(22), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
90 { NULL, DIJOFS_BUTTON(23), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
91 { NULL, DIJOFS_BUTTON(24), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
92 { NULL, DIJOFS_BUTTON(25), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
93 { NULL, DIJOFS_BUTTON(26), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
94 { NULL, DIJOFS_BUTTON(27), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
95 { NULL, DIJOFS_BUTTON(28), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
96 { NULL, DIJOFS_BUTTON(29), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
97 { NULL, DIJOFS_BUTTON(30), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
98 { NULL, DIJOFS_BUTTON(31), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
99 { NULL, DIJOFS_BUTTON(32), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
100 { NULL, DIJOFS_BUTTON(33), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
101 { NULL, DIJOFS_BUTTON(34), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
102 { NULL, DIJOFS_BUTTON(35), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
103 { NULL, DIJOFS_BUTTON(36), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
104 { NULL, DIJOFS_BUTTON(37), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
105 { NULL, DIJOFS_BUTTON(38), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
106 { NULL, DIJOFS_BUTTON(39), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
107 { NULL, DIJOFS_BUTTON(40), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
108 { NULL, DIJOFS_BUTTON(41), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
109 { NULL, DIJOFS_BUTTON(42), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
110 { NULL, DIJOFS_BUTTON(43), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
111 { NULL, DIJOFS_BUTTON(44), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
112 { NULL, DIJOFS_BUTTON(45), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
113 { NULL, DIJOFS_BUTTON(46), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
114 { NULL, DIJOFS_BUTTON(47), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
115 { NULL, DIJOFS_BUTTON(48), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
116 { NULL, DIJOFS_BUTTON(49), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
117 { NULL, DIJOFS_BUTTON(50), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
118 { NULL, DIJOFS_BUTTON(51), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
119 { NULL, DIJOFS_BUTTON(52), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
120 { NULL, DIJOFS_BUTTON(53), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
121 { NULL, DIJOFS_BUTTON(54), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
122 { NULL, DIJOFS_BUTTON(55), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
123 { NULL, DIJOFS_BUTTON(56), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
124 { NULL, DIJOFS_BUTTON(57), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
125 { NULL, DIJOFS_BUTTON(58), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
126 { NULL, DIJOFS_BUTTON(59), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
127 { NULL, DIJOFS_BUTTON(60), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
128 { NULL, DIJOFS_BUTTON(61), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
129 { NULL, DIJOFS_BUTTON(62), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
130 { NULL, DIJOFS_BUTTON(63), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
131 { NULL, DIJOFS_BUTTON(64), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
132 { NULL, DIJOFS_BUTTON(65), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
133 { NULL, DIJOFS_BUTTON(66), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
134 { NULL, DIJOFS_BUTTON(67), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
135 { NULL, DIJOFS_BUTTON(68), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
136 { NULL, DIJOFS_BUTTON(69), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
137 { NULL, DIJOFS_BUTTON(70), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
138 { NULL, DIJOFS_BUTTON(71), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
139 { NULL, DIJOFS_BUTTON(72), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
140 { NULL, DIJOFS_BUTTON(73), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
141 { NULL, DIJOFS_BUTTON(74), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
142 { NULL, DIJOFS_BUTTON(75), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
143 { NULL, DIJOFS_BUTTON(76), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
144 { NULL, DIJOFS_BUTTON(77), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
145 { NULL, DIJOFS_BUTTON(78), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
146 { NULL, DIJOFS_BUTTON(79), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
147 { NULL, DIJOFS_BUTTON(80), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
148 { NULL, DIJOFS_BUTTON(81), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
149 { NULL, DIJOFS_BUTTON(82), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
150 { NULL, DIJOFS_BUTTON(83), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
151 { NULL, DIJOFS_BUTTON(84), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
152 { NULL, DIJOFS_BUTTON(85), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
153 { NULL, DIJOFS_BUTTON(86), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
154 { NULL, DIJOFS_BUTTON(87), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
155 { NULL, DIJOFS_BUTTON(88), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
156 { NULL, DIJOFS_BUTTON(89), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
157 { NULL, DIJOFS_BUTTON(90), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
158 { NULL, DIJOFS_BUTTON(91), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
159 { NULL, DIJOFS_BUTTON(92), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
160 { NULL, DIJOFS_BUTTON(93), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
161 { NULL, DIJOFS_BUTTON(94), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
162 { NULL, DIJOFS_BUTTON(95), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
163 { NULL, DIJOFS_BUTTON(96), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
164 { NULL, DIJOFS_BUTTON(97), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
165 { NULL, DIJOFS_BUTTON(98), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
166 { NULL, DIJOFS_BUTTON(99), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
167 { NULL, DIJOFS_BUTTON(100), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
168 { NULL, DIJOFS_BUTTON(101), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
169 { NULL, DIJOFS_BUTTON(102), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
170 { NULL, DIJOFS_BUTTON(103), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
171 { NULL, DIJOFS_BUTTON(104), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
172 { NULL, DIJOFS_BUTTON(105), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
173 { NULL, DIJOFS_BUTTON(106), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
174 { NULL, DIJOFS_BUTTON(107), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
175 { NULL, DIJOFS_BUTTON(108), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
176 { NULL, DIJOFS_BUTTON(109), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
177 { NULL, DIJOFS_BUTTON(110), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
178 { NULL, DIJOFS_BUTTON(111), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
179 { NULL, DIJOFS_BUTTON(112), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
180 { NULL, DIJOFS_BUTTON(113), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
181 { NULL, DIJOFS_BUTTON(114), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
182 { NULL, DIJOFS_BUTTON(115), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
183 { NULL, DIJOFS_BUTTON(116), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
184 { NULL, DIJOFS_BUTTON(117), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
185 { NULL, DIJOFS_BUTTON(118), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
186 { NULL, DIJOFS_BUTTON(119), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
187 { NULL, DIJOFS_BUTTON(120), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
188 { NULL, DIJOFS_BUTTON(121), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
189 { NULL, DIJOFS_BUTTON(122), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
190 { NULL, DIJOFS_BUTTON(123), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
191 { NULL, DIJOFS_BUTTON(124), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
192 { NULL, DIJOFS_BUTTON(125), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
193 { NULL, DIJOFS_BUTTON(126), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
194 { NULL, DIJOFS_BUTTON(127), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
195 { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lVX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
196 { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lVY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
197 { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lVZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
198 { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lVRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
199 { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lVRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
200 { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lVRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
201 // note: dwOfs value matches Windows
202 { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
203 { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
204 { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lAX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
205 { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lAY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
206 { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lAZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
207 { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lARx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
208 { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lARy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
209 { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lARz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
210 // note: dwOfs value matches Windows
211 { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
212 { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
213 { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lFX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
214 { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lFY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
215 { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lFZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
216 { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lFRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
217 { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lFRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
218 { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lFRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
219 // note: dwOfs value matches Windows
220 { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
221 { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
222};
223
224const DIDATAFORMAT SDL_c_dfDIJoystick2 = {
225 sizeof(DIDATAFORMAT),
226 sizeof(DIOBJECTDATAFORMAT),
227 DIDF_ABSAXIS,
228 sizeof(DIJOYSTATE2),
229 SDL_arraysize(dfDIJoystick2),
230 dfDIJoystick2
231};
232
233// Convert a DirectInput return code to a text message
234static bool SetDIerror(const char *function, HRESULT code)
235{
236 return SDL_SetError("%s() DirectX error 0x%8.8lx", function, code);
237}
238
239static bool SDL_IsXInputDevice(Uint16 vendor_id, Uint16 product_id, const char *hidPath)
240{
241#if defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT)
242 SDL_GamepadType type;
243
244 // XInput and RawInput backends will pick up XInput-compatible devices
245 if (!SDL_XINPUT_Enabled()
246#ifdef SDL_JOYSTICK_RAWINPUT
247 && !RAWINPUT_IsEnabled()
248#endif
249 ) {
250 return false;
251 }
252
253 // If device path contains "IG_" then its an XInput device
254 // See: https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput
255 if (SDL_strstr(hidPath, "IG_") != NULL) {
256 return true;
257 }
258
259 type = SDL_GetGamepadTypeFromVIDPID(vendor_id, product_id, NULL, false);
260 if (type == SDL_GAMEPAD_TYPE_XBOX360 ||
261 type == SDL_GAMEPAD_TYPE_XBOXONE ||
262 (vendor_id == USB_VENDOR_VALVE && product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) {
263 return true;
264 }
265#endif // SDL_JOYSTICK_XINPUT || SDL_JOYSTICK_RAWINPUT
266
267 return false;
268}
269
270static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, Uint16 vendor_id, Uint16 product_id, char **manufacturer_string, char **product_string)
271{
272 DIPROPSTRING dipstr;
273
274 if (!device || !manufacturer_string || !product_string) {
275 return false;
276 }
277
278#ifdef SDL_JOYSTICK_HIDAPI
279 *manufacturer_string = HIDAPI_GetDeviceManufacturerName(vendor_id, product_id);
280 *product_string = HIDAPI_GetDeviceProductName(vendor_id, product_id);
281 if (*product_string) {
282 return true;
283 }
284#endif
285
286 dipstr.diph.dwSize = sizeof(dipstr);
287 dipstr.diph.dwHeaderSize = sizeof(dipstr.diph);
288 dipstr.diph.dwObj = 0;
289 dipstr.diph.dwHow = DIPH_DEVICE;
290
291 if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_PRODUCTNAME, &dipstr.diph))) {
292 return false;
293 }
294
295 *manufacturer_string = NULL;
296 *product_string = WIN_StringToUTF8(dipstr.wsz);
297
298 return true;
299}
300
301static bool QueryDevicePath(LPDIRECTINPUTDEVICE8 device, char **device_path)
302{
303 DIPROPGUIDANDPATH dippath;
304
305 if (!device || !device_path) {
306 return false;
307 }
308
309 dippath.diph.dwSize = sizeof(dippath);
310 dippath.diph.dwHeaderSize = sizeof(dippath.diph);
311 dippath.diph.dwObj = 0;
312 dippath.diph.dwHow = DIPH_DEVICE;
313
314 if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_GUIDANDPATH, &dippath.diph))) {
315 return false;
316 }
317
318 *device_path = WIN_StringToUTF8W(dippath.wszPath);
319
320 // Normalize path to upper case.
321 SDL_strupr(*device_path);
322
323 return true;
324}
325
326static bool QueryDeviceInfo(LPDIRECTINPUTDEVICE8 device, Uint16 *vendor_id, Uint16 *product_id)
327{
328 DIPROPDWORD dipdw;
329
330 if (!device || !vendor_id || !product_id) {
331 return false;
332 }
333
334 dipdw.diph.dwSize = sizeof(dipdw);
335 dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
336 dipdw.diph.dwObj = 0;
337 dipdw.diph.dwHow = DIPH_DEVICE;
338 dipdw.dwData = 0;
339
340 if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_VIDPID, &dipdw.diph))) {
341 return false;
342 }
343
344 *vendor_id = LOWORD(dipdw.dwData);
345 *product_id = HIWORD(dipdw.dwData);
346
347 return true;
348}
349
350void FreeRumbleEffectData(DIEFFECT *effect)
351{
352 if (!effect) {
353 return;
354 }
355 SDL_free(effect->rgdwAxes);
356 SDL_free(effect->rglDirection);
357 SDL_free(effect->lpvTypeSpecificParams);
358 SDL_free(effect);
359}
360
361DIEFFECT *CreateRumbleEffectData(Sint16 magnitude)
362{
363 DIEFFECT *effect;
364 DIPERIODIC *periodic;
365
366 // Create the effect
367 effect = (DIEFFECT *)SDL_calloc(1, sizeof(*effect));
368 if (!effect) {
369 return NULL;
370 }
371 effect->dwSize = sizeof(*effect);
372 effect->dwGain = 10000;
373 effect->dwFlags = DIEFF_OBJECTOFFSETS;
374 effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000; // In microseconds.
375 effect->dwTriggerButton = DIEB_NOTRIGGER;
376
377 effect->cAxes = 2;
378 effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD));
379 if (!effect->rgdwAxes) {
380 FreeRumbleEffectData(effect);
381 return NULL;
382 }
383
384 effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG));
385 if (!effect->rglDirection) {
386 FreeRumbleEffectData(effect);
387 return NULL;
388 }
389 effect->dwFlags |= DIEFF_CARTESIAN;
390
391 periodic = (DIPERIODIC *)SDL_calloc(1, sizeof(*periodic));
392 if (!periodic) {
393 FreeRumbleEffectData(effect);
394 return NULL;
395 }
396 periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
397 periodic->dwPeriod = 1000000;
398
399 effect->cbTypeSpecificParams = sizeof(*periodic);
400 effect->lpvTypeSpecificParams = periodic;
401
402 return effect;
403}
404
405bool SDL_DINPUT_JoystickInit(void)
406{
407 HRESULT result;
408 HINSTANCE instance;
409
410 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true)) {
411 // In some environments, IDirectInput8_Initialize / _EnumDevices can take a minute even with no controllers.
412 dinput = NULL;
413 return true;
414 }
415
416 result = WIN_CoInitialize();
417 if (FAILED(result)) {
418 return SetDIerror("CoInitialize", result);
419 }
420
421 coinitialized = true;
422
423 result = CoCreateInstance(&CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER,
424 &IID_IDirectInput8, (LPVOID *)&dinput);
425
426 if (FAILED(result)) {
427 return SetDIerror("CoCreateInstance", result);
428 }
429
430 // Because we used CoCreateInstance, we need to Initialize it, first.
431 instance = GetModuleHandle(NULL);
432 if (!instance) {
433 IDirectInput8_Release(dinput);
434 dinput = NULL;
435 return SDL_SetError("GetModuleHandle() failed with error code %lu.", GetLastError());
436 }
437 result = IDirectInput8_Initialize(dinput, instance, DIRECTINPUT_VERSION);
438
439 if (FAILED(result)) {
440 IDirectInput8_Release(dinput);
441 dinput = NULL;
442 return SetDIerror("IDirectInput::Initialize", result);
443 }
444 return true;
445}
446
447static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path)
448{
449 int slot = -1;
450
451 if (vendor_id == USB_VENDOR_VALVE &&
452 product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
453 (void)SDL_sscanf(device_path, "\\\\?\\HID#VID_28DE&PID_11FF&IG_0%d", &slot);
454 }
455 return slot;
456}
457
458// helper function for direct input, gets called for each connected joystick
459static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInstance, LPVOID pContext)
460{
461#define CHECK(expression) \
462 { \
463 if (!(expression)) \
464 goto err; \
465 }
466 JoyStick_DeviceData *pNewJoystick = NULL;
467 JoyStick_DeviceData *pPrevJoystick = NULL;
468 Uint16 vendor = 0;
469 Uint16 product = 0;
470 Uint16 version = 0;
471 char *hidPath = NULL;
472 char *manufacturer_string = NULL;
473 char *product_string = NULL;
474 LPDIRECTINPUTDEVICE8 device = NULL;
475
476 // We are only supporting HID devices.
477 CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID);
478
479 CHECK(SUCCEEDED(IDirectInput8_CreateDevice(dinput, &pDeviceInstance->guidInstance, &device, NULL)));
480 CHECK(QueryDevicePath(device, &hidPath));
481 CHECK(QueryDeviceInfo(device, &vendor, &product));
482 CHECK(QueryDeviceName(device, vendor, product, &manufacturer_string, &product_string));
483
484 CHECK(!SDL_IsXInputDevice(vendor, product, hidPath));
485 CHECK(!SDL_ShouldIgnoreJoystick(vendor, product, version, product_string));
486 CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, product_string));
487
488 pNewJoystick = *(JoyStick_DeviceData **)pContext;
489 while (pNewJoystick) {
490 // update GUIDs of joysticks with matching paths, in case they're not open yet
491 if (SDL_strcmp(pNewJoystick->path, hidPath) == 0) {
492 // if we are replacing the front of the list then update it
493 if (pNewJoystick == *(JoyStick_DeviceData **)pContext) {
494 *(JoyStick_DeviceData **)pContext = pNewJoystick->pNext;
495 } else if (pPrevJoystick) {
496 pPrevJoystick->pNext = pNewJoystick->pNext;
497 }
498
499 // Update with new guid/etc, if it has changed
500 SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE));
501
502 pNewJoystick->pNext = SYS_Joystick;
503 SYS_Joystick = pNewJoystick;
504
505 pNewJoystick = NULL;
506 CHECK(FALSE);
507 }
508
509 pPrevJoystick = pNewJoystick;
510 pNewJoystick = pNewJoystick->pNext;
511 }
512
513 pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData));
514 CHECK(pNewJoystick);
515
516 pNewJoystick->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(vendor, product, hidPath);
517 SDL_strlcpy(pNewJoystick->path, hidPath, SDL_arraysize(pNewJoystick->path));
518 SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE));
519
520 pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);
521 CHECK(pNewJoystick->joystickname);
522
523 if (vendor && product) {
524 pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, manufacturer_string, product_string, 0, 0);
525 } else {
526 pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, version, manufacturer_string, product_string, 0, 0);
527 }
528
529 WINDOWS_AddJoystickDevice(pNewJoystick);
530 pNewJoystick = NULL;
531
532err:
533 if (pNewJoystick) {
534 SDL_free(pNewJoystick->joystickname);
535 SDL_free(pNewJoystick);
536 }
537
538 SDL_free(hidPath);
539 SDL_free(manufacturer_string);
540 SDL_free(product_string);
541
542 if (device) {
543 IDirectInputDevice8_Release(device);
544 }
545
546 return DIENUM_CONTINUE; // get next device, please
547#undef CHECK
548}
549
550void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
551{
552 if (!dinput) {
553 return;
554 }
555
556 IDirectInput8_EnumDevices(dinput, DI8DEVCLASS_GAMECTRL, EnumJoystickDetectCallback, pContext, DIEDFL_ATTACHEDONLY);
557}
558
559// helper function for direct input, gets called for each connected joystick
560typedef struct
561{
562 Uint16 vendor;
563 Uint16 product;
564 bool present;
565} Joystick_PresentData;
566
567static BOOL CALLBACK EnumJoystickPresentCallback(LPCDIDEVICEINSTANCE pDeviceInstance, LPVOID pContext)
568{
569#define CHECK(expression) \
570 { \
571 if (!(expression)) \
572 goto err; \
573 }
574 Joystick_PresentData *pData = (Joystick_PresentData *)pContext;
575 Uint16 vendor = 0;
576 Uint16 product = 0;
577 LPDIRECTINPUTDEVICE8 device = NULL;
578 BOOL result = DIENUM_CONTINUE;
579
580 // We are only supporting HID devices.
581 CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID);
582
583 CHECK(SUCCEEDED(IDirectInput8_CreateDevice(dinput, &pDeviceInstance->guidInstance, &device, NULL)));
584 CHECK(QueryDeviceInfo(device, &vendor, &product));
585
586 if (vendor == pData->vendor && product == pData->product) {
587 pData->present = true;
588 result = DIENUM_STOP; // found it
589 }
590
591err:
592 if (device) {
593 IDirectInputDevice8_Release(device);
594 }
595
596 return result;
597#undef CHECK
598}
599
600bool SDL_DINPUT_JoystickPresent(Uint16 vendor_id, Uint16 product_id, Uint16 version_number)
601{
602 Joystick_PresentData data;
603
604 if (!dinput) {
605 return false;
606 }
607
608 data.vendor = vendor_id;
609 data.product = product_id;
610 data.present = false;
611 IDirectInput8_EnumDevices(dinput, DI8DEVCLASS_GAMECTRL, EnumJoystickPresentCallback, &data, DIEDFL_ATTACHEDONLY);
612 return data.present;
613}
614
615static BOOL CALLBACK EnumDevObjectsCallback(LPCDIDEVICEOBJECTINSTANCE pDeviceObject, LPVOID pContext)
616{
617 SDL_Joystick *joystick = (SDL_Joystick *)pContext;
618 HRESULT result;
619 input_t *in = &joystick->hwdata->Inputs[joystick->hwdata->NumInputs];
620
621 if (pDeviceObject->dwType & DIDFT_BUTTON) {
622 in->type = BUTTON;
623 in->num = (Uint8)joystick->nbuttons;
624 in->ofs = DIJOFS_BUTTON(in->num);
625 joystick->nbuttons++;
626 } else if (pDeviceObject->dwType & DIDFT_POV) {
627 in->type = HAT;
628 in->num = (Uint8)joystick->nhats;
629 in->ofs = DIJOFS_POV(in->num);
630 joystick->nhats++;
631 } else if (pDeviceObject->dwType & DIDFT_AXIS) {
632 DIPROPRANGE diprg;
633 DIPROPDWORD dilong;
634
635 in->type = AXIS;
636 in->num = (Uint8)joystick->naxes;
637 if (SDL_memcmp(&pDeviceObject->guidType, &GUID_XAxis, sizeof(pDeviceObject->guidType)) == 0) {
638 in->ofs = DIJOFS_X;
639 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_YAxis, sizeof(pDeviceObject->guidType)) == 0) {
640 in->ofs = DIJOFS_Y;
641 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_ZAxis, sizeof(pDeviceObject->guidType)) == 0) {
642 in->ofs = DIJOFS_Z;
643 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RxAxis, sizeof(pDeviceObject->guidType)) == 0) {
644 in->ofs = DIJOFS_RX;
645 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RyAxis, sizeof(pDeviceObject->guidType)) == 0) {
646 in->ofs = DIJOFS_RY;
647 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RzAxis, sizeof(pDeviceObject->guidType)) == 0) {
648 in->ofs = DIJOFS_RZ;
649 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_Slider, sizeof(pDeviceObject->guidType)) == 0) {
650 in->ofs = DIJOFS_SLIDER(joystick->hwdata->NumSliders);
651 ++joystick->hwdata->NumSliders;
652 } else {
653 return DIENUM_CONTINUE; // not an axis we can grok
654 }
655
656 diprg.diph.dwSize = sizeof(diprg);
657 diprg.diph.dwHeaderSize = sizeof(diprg.diph);
658 diprg.diph.dwObj = pDeviceObject->dwType;
659 diprg.diph.dwHow = DIPH_BYID;
660 diprg.lMin = SDL_JOYSTICK_AXIS_MIN;
661 diprg.lMax = SDL_JOYSTICK_AXIS_MAX;
662
663 result =
664 IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice,
665 DIPROP_RANGE, &diprg.diph);
666 if (FAILED(result)) {
667 return DIENUM_CONTINUE; // don't use this axis
668 }
669
670 // Set dead zone to 0.
671 dilong.diph.dwSize = sizeof(dilong);
672 dilong.diph.dwHeaderSize = sizeof(dilong.diph);
673 dilong.diph.dwObj = pDeviceObject->dwType;
674 dilong.diph.dwHow = DIPH_BYID;
675 dilong.dwData = 0;
676 result =
677 IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice,
678 DIPROP_DEADZONE, &dilong.diph);
679 if (FAILED(result)) {
680 return DIENUM_CONTINUE; // don't use this axis
681 }
682
683 joystick->naxes++;
684 } else {
685 // not supported at this time
686 return DIENUM_CONTINUE;
687 }
688
689 joystick->hwdata->NumInputs++;
690
691 if (joystick->hwdata->NumInputs == MAX_INPUTS) {
692 return DIENUM_STOP; // too many
693 }
694
695 return DIENUM_CONTINUE;
696}
697
698/* Sort using the data offset into the DInput struct.
699 * This gives a reasonable ordering for the inputs.
700 */
701static int SDLCALL SortDevFunc(const void *a, const void *b)
702{
703 const input_t *inputA = (const input_t *)a;
704 const input_t *inputB = (const input_t *)b;
705
706 if (inputA->ofs < inputB->ofs) {
707 return -1;
708 }
709 if (inputA->ofs > inputB->ofs) {
710 return 1;
711 }
712 return 0;
713}
714
715// Sort the input objects and recalculate the indices for each input.
716static void SortDevObjects(SDL_Joystick *joystick)
717{
718 input_t *inputs = joystick->hwdata->Inputs;
719 Uint8 nButtons = 0;
720 Uint8 nHats = 0;
721 Uint8 nAxis = 0;
722 int n;
723
724 SDL_qsort(inputs, joystick->hwdata->NumInputs, sizeof(input_t), SortDevFunc);
725
726 for (n = 0; n < joystick->hwdata->NumInputs; n++) {
727 switch (inputs[n].type) {
728 case BUTTON:
729 inputs[n].num = nButtons;
730 nButtons++;
731 break;
732
733 case HAT:
734 inputs[n].num = nHats;
735 nHats++;
736 break;
737
738 case AXIS:
739 inputs[n].num = nAxis;
740 nAxis++;
741 break;
742 }
743 }
744}
745
746bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
747{
748 HRESULT result;
749 DIPROPDWORD dipdw;
750
751 joystick->hwdata->buffered = true;
752 joystick->hwdata->Capabilities.dwSize = sizeof(DIDEVCAPS);
753
754 SDL_zero(dipdw);
755 dipdw.diph.dwSize = sizeof(DIPROPDWORD);
756 dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
757
758 result =
759 IDirectInput8_CreateDevice(dinput,
760 &joystickdevice->dxdevice.guidInstance,
761 &joystick->hwdata->InputDevice,
762 NULL);
763 if (FAILED(result)) {
764 return SetDIerror("IDirectInput::CreateDevice", result);
765 }
766
767 /* Acquire shared access. Exclusive access is required for forces,
768 * though. */
769 result =
770 IDirectInputDevice8_SetCooperativeLevel(joystick->hwdata->InputDevice, SDL_HelperWindow,
771 DISCL_EXCLUSIVE |
772 DISCL_BACKGROUND);
773 if (FAILED(result)) {
774 return SetDIerror("IDirectInputDevice8::SetCooperativeLevel", result);
775 }
776
777 // Use the extended data structure: DIJOYSTATE2.
778 result =
779 IDirectInputDevice8_SetDataFormat(joystick->hwdata->InputDevice,
780 &SDL_c_dfDIJoystick2);
781 if (FAILED(result)) {
782 return SetDIerror("IDirectInputDevice8::SetDataFormat", result);
783 }
784
785 // Get device capabilities
786 result =
787 IDirectInputDevice8_GetCapabilities(joystick->hwdata->InputDevice,
788 &joystick->hwdata->Capabilities);
789 if (FAILED(result)) {
790 return SetDIerror("IDirectInputDevice8::GetCapabilities", result);
791 }
792
793 // Force capable?
794 if (joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK) {
795 result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
796 if (FAILED(result)) {
797 return SetDIerror("IDirectInputDevice8::Acquire", result);
798 }
799
800 // reset all actuators.
801 result =
802 IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice,
803 DISFFC_RESET);
804
805 /* Not necessarily supported, ignore if not supported.
806 if (FAILED(result)) {
807 return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand", result);
808 }
809 */
810
811 result = IDirectInputDevice8_Unacquire(joystick->hwdata->InputDevice);
812
813 if (FAILED(result)) {
814 return SetDIerror("IDirectInputDevice8::Unacquire", result);
815 }
816
817 /* Turn on auto-centering for a ForceFeedback device (until told
818 * otherwise). */
819 dipdw.diph.dwObj = 0;
820 dipdw.diph.dwHow = DIPH_DEVICE;
821 dipdw.dwData = DIPROPAUTOCENTER_ON;
822
823 result =
824 IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice,
825 DIPROP_AUTOCENTER, &dipdw.diph);
826
827 /* Not necessarily supported, ignore if not supported.
828 if (FAILED(result)) {
829 return SetDIerror("IDirectInputDevice8::SetProperty", result);
830 }
831 */
832
833 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
834 }
835
836 // What buttons and axes does it have?
837 IDirectInputDevice8_EnumObjects(joystick->hwdata->InputDevice,
838 EnumDevObjectsCallback, joystick,
839 DIDFT_BUTTON | DIDFT_AXIS | DIDFT_POV);
840
841 /* Reorder the input objects. Some devices do not report the X axis as
842 * the first axis, for example. */
843 SortDevObjects(joystick);
844
845 dipdw.diph.dwObj = 0;
846 dipdw.diph.dwHow = DIPH_DEVICE;
847 dipdw.dwData = INPUT_QSIZE;
848
849 // Set the buffer size
850 result =
851 IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice,
852 DIPROP_BUFFERSIZE, &dipdw.diph);
853
854 if (result == DI_POLLEDDEVICE) {
855 /* This device doesn't support buffering, so we're forced
856 * to use less reliable polling. */
857 joystick->hwdata->buffered = false;
858 } else if (FAILED(result)) {
859 return SetDIerror("IDirectInputDevice8::SetProperty", result);
860 }
861 joystick->hwdata->first_update = true;
862
863 // Poll and wait for initial device state to be populated
864 result = IDirectInputDevice8_Poll(joystick->hwdata->InputDevice);
865 if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
866 IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
867 IDirectInputDevice8_Poll(joystick->hwdata->InputDevice);
868 }
869 SDL_Delay(50);
870
871 return true;
872}
873
874static bool SDL_DINPUT_JoystickInitRumble(SDL_Joystick *joystick, Sint16 magnitude)
875{
876 HRESULT result;
877
878 // Reset and then enable actuators
879 result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_RESET);
880 if (result == DIERR_INPUTLOST || result == DIERR_NOTEXCLUSIVEACQUIRED) {
881 result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
882 if (SUCCEEDED(result)) {
883 result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_RESET);
884 }
885 }
886 if (FAILED(result)) {
887 return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand(DISFFC_RESET)", result);
888 }
889
890 result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_SETACTUATORSON);
891 if (FAILED(result)) {
892 return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand(DISFFC_SETACTUATORSON)", result);
893 }
894
895 // Create the effect
896 joystick->hwdata->ffeffect = CreateRumbleEffectData(magnitude);
897 if (!joystick->hwdata->ffeffect) {
898 return false;
899 }
900
901 result = IDirectInputDevice8_CreateEffect(joystick->hwdata->InputDevice, &GUID_Sine,
902 joystick->hwdata->ffeffect, &joystick->hwdata->ffeffect_ref, NULL);
903 if (FAILED(result)) {
904 return SetDIerror("IDirectInputDevice8::CreateEffect", result);
905 }
906 return true;
907}
908
909bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
910{
911 HRESULT result;
912
913 // Scale and average the two rumble strengths
914 Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
915
916 if (!(joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK)) {
917 return SDL_Unsupported();
918 }
919
920 if (joystick->hwdata->ff_initialized) {
921 DIPERIODIC *periodic = ((DIPERIODIC *)joystick->hwdata->ffeffect->lpvTypeSpecificParams);
922 periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
923
924 result = IDirectInputEffect_SetParameters(joystick->hwdata->ffeffect_ref, joystick->hwdata->ffeffect, (DIEP_DURATION | DIEP_TYPESPECIFICPARAMS));
925 if (result == DIERR_INPUTLOST) {
926 result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
927 if (SUCCEEDED(result)) {
928 result = IDirectInputEffect_SetParameters(joystick->hwdata->ffeffect_ref, joystick->hwdata->ffeffect, (DIEP_DURATION | DIEP_TYPESPECIFICPARAMS));
929 }
930 }
931 if (FAILED(result)) {
932 return SetDIerror("IDirectInputDevice8::SetParameters", result);
933 }
934 } else {
935 if (!SDL_DINPUT_JoystickInitRumble(joystick, magnitude)) {
936 return false;
937 }
938 joystick->hwdata->ff_initialized = true;
939 }
940
941 result = IDirectInputEffect_Start(joystick->hwdata->ffeffect_ref, 1, 0);
942 if (result == DIERR_INPUTLOST || result == DIERR_NOTEXCLUSIVEACQUIRED) {
943 result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
944 if (SUCCEEDED(result)) {
945 result = IDirectInputEffect_Start(joystick->hwdata->ffeffect_ref, 1, 0);
946 }
947 }
948 if (FAILED(result)) {
949 return SetDIerror("IDirectInputDevice8::Start", result);
950 }
951 return true;
952}
953
954static Uint8 TranslatePOV(DWORD value)
955{
956 const Uint8 HAT_VALS[] = {
957 SDL_HAT_UP,
958 SDL_HAT_UP | SDL_HAT_RIGHT,
959 SDL_HAT_RIGHT,
960 SDL_HAT_DOWN | SDL_HAT_RIGHT,
961 SDL_HAT_DOWN,
962 SDL_HAT_DOWN | SDL_HAT_LEFT,
963 SDL_HAT_LEFT,
964 SDL_HAT_UP | SDL_HAT_LEFT
965 };
966
967 if (LOWORD(value) == 0xFFFF) {
968 return SDL_HAT_CENTERED;
969 }
970
971 // Round the value up:
972 value += 4500 / 2;
973 value %= 36000;
974 value /= 4500;
975
976 if (value >= 8) {
977 return SDL_HAT_CENTERED; // shouldn't happen
978 }
979
980 return HAT_VALS[value];
981}
982
983/* Function to update the state of a joystick - called as a device poll.
984 * This function shouldn't update the joystick structure directly,
985 * but instead should call SDL_PrivateJoystick*() to deliver events
986 * and update joystick device state.
987 */
988static void UpdateDINPUTJoystickState_Polled(SDL_Joystick *joystick)
989{
990 DIJOYSTATE2 state;
991 HRESULT result;
992 int i;
993 Uint64 timestamp = SDL_GetTicksNS();
994
995 result =
996 IDirectInputDevice8_GetDeviceState(joystick->hwdata->InputDevice,
997 sizeof(DIJOYSTATE2), &state);
998 if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
999 IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
1000 result =
1001 IDirectInputDevice8_GetDeviceState(joystick->hwdata->InputDevice,
1002 sizeof(DIJOYSTATE2), &state);
1003 }
1004
1005 if (result != DI_OK) {
1006 return;
1007 }
1008
1009 // Set each known axis, button and POV.
1010 for (i = 0; i < joystick->hwdata->NumInputs; ++i) {
1011 const input_t *in = &joystick->hwdata->Inputs[i];
1012
1013 switch (in->type) {
1014 case AXIS:
1015 switch (in->ofs) {
1016 case DIJOFS_X:
1017 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lX);
1018 break;
1019 case DIJOFS_Y:
1020 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lY);
1021 break;
1022 case DIJOFS_Z:
1023 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lZ);
1024 break;
1025 case DIJOFS_RX:
1026 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRx);
1027 break;
1028 case DIJOFS_RY:
1029 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRy);
1030 break;
1031 case DIJOFS_RZ:
1032 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRz);
1033 break;
1034 case DIJOFS_SLIDER(0):
1035 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.rglSlider[0]);
1036 break;
1037 case DIJOFS_SLIDER(1):
1038 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.rglSlider[1]);
1039 break;
1040 }
1041 break;
1042
1043 case BUTTON:
1044 SDL_SendJoystickButton(timestamp, joystick, in->num,
1045 (state.rgbButtons[in->ofs - DIJOFS_BUTTON0] != 0));
1046 break;
1047 case HAT:
1048 {
1049 Uint8 pos = TranslatePOV(state.rgdwPOV[in->ofs - DIJOFS_POV(0)]);
1050 SDL_SendJoystickHat(timestamp, joystick, in->num, pos);
1051 break;
1052 }
1053 }
1054 }
1055}
1056
1057static void UpdateDINPUTJoystickState_Buffered(SDL_Joystick *joystick)
1058{
1059 int i;
1060 HRESULT result;
1061 DWORD numevents;
1062 DIDEVICEOBJECTDATA evtbuf[INPUT_QSIZE];
1063 Uint64 timestamp = SDL_GetTicksNS();
1064
1065 numevents = INPUT_QSIZE;
1066 result =
1067 IDirectInputDevice8_GetDeviceData(joystick->hwdata->InputDevice,
1068 sizeof(DIDEVICEOBJECTDATA), evtbuf,
1069 &numevents, 0);
1070 if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
1071 IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
1072 result =
1073 IDirectInputDevice8_GetDeviceData(joystick->hwdata->InputDevice,
1074 sizeof(DIDEVICEOBJECTDATA),
1075 evtbuf, &numevents, 0);
1076 }
1077
1078 // Handle the events or punt
1079 if (FAILED(result)) {
1080 return;
1081 }
1082
1083 for (i = 0; i < (int)numevents; ++i) {
1084 int j;
1085
1086 for (j = 0; j < joystick->hwdata->NumInputs; ++j) {
1087 const input_t *in = &joystick->hwdata->Inputs[j];
1088
1089 if (evtbuf[i].dwOfs != in->ofs) {
1090 continue;
1091 }
1092
1093 switch (in->type) {
1094 case AXIS:
1095 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)evtbuf[i].dwData);
1096 break;
1097 case BUTTON:
1098 SDL_SendJoystickButton(timestamp, joystick, in->num,
1099 (evtbuf[i].dwData != 0));
1100 break;
1101 case HAT:
1102 {
1103 Uint8 pos = TranslatePOV(evtbuf[i].dwData);
1104 SDL_SendJoystickHat(timestamp, joystick, in->num, pos);
1105 } break;
1106 }
1107 }
1108 }
1109
1110 if (result == DI_BUFFEROVERFLOW) {
1111 /* Our buffer wasn't big enough to hold all the queued events,
1112 * so poll the device to make sure we have the complete state.
1113 */
1114 UpdateDINPUTJoystickState_Polled(joystick);
1115 }
1116}
1117
1118void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick)
1119{
1120 HRESULT result;
1121
1122 result = IDirectInputDevice8_Poll(joystick->hwdata->InputDevice);
1123 if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
1124 IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
1125 IDirectInputDevice8_Poll(joystick->hwdata->InputDevice);
1126 }
1127
1128 if (joystick->hwdata->first_update) {
1129 // Poll to get the initial state of the joystick
1130 UpdateDINPUTJoystickState_Polled(joystick);
1131 joystick->hwdata->first_update = false;
1132 return;
1133 }
1134
1135 if (joystick->hwdata->buffered ) {
1136 UpdateDINPUTJoystickState_Buffered(joystick);
1137 } else {
1138 UpdateDINPUTJoystickState_Polled(joystick);
1139 }
1140}
1141
1142void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick)
1143{
1144 if (joystick->hwdata->ffeffect_ref) {
1145 IDirectInputEffect_Unload(joystick->hwdata->ffeffect_ref);
1146 joystick->hwdata->ffeffect_ref = NULL;
1147 }
1148 if (joystick->hwdata->ffeffect) {
1149 FreeRumbleEffectData(joystick->hwdata->ffeffect);
1150 joystick->hwdata->ffeffect = NULL;
1151 }
1152 IDirectInputDevice8_Unacquire(joystick->hwdata->InputDevice);
1153 IDirectInputDevice8_Release(joystick->hwdata->InputDevice);
1154 joystick->hwdata->ff_initialized = false;
1155}
1156
1157void SDL_DINPUT_JoystickQuit(void)
1158{
1159 if (dinput != NULL) {
1160 IDirectInput8_Release(dinput);
1161 dinput = NULL;
1162 }
1163
1164 if (coinitialized) {
1165 WIN_CoUninitialize();
1166 coinitialized = false;
1167 }
1168}
1169
1170#else // !SDL_JOYSTICK_DINPUT
1171
1172typedef struct JoyStick_DeviceData JoyStick_DeviceData;
1173
1174bool SDL_DINPUT_JoystickInit(void)
1175{
1176 return true;
1177}
1178
1179void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
1180{
1181}
1182
1183bool SDL_DINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)
1184{
1185 return false;
1186}
1187
1188bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
1189{
1190 return SDL_Unsupported();
1191}
1192
1193bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1194{
1195 return SDL_Unsupported();
1196}
1197
1198void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick)
1199{
1200}
1201
1202void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick)
1203{
1204}
1205
1206void SDL_DINPUT_JoystickQuit(void)
1207{
1208}
1209
1210#endif // SDL_JOYSTICK_DINPUT
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h
new file mode 100644
index 0000000..0643ce1
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h
@@ -0,0 +1,40 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// Set up for C function definitions, even when using C++
24#ifdef __cplusplus
25extern "C" {
26#endif
27
28extern bool SDL_DINPUT_JoystickInit(void);
29extern void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext);
30extern bool SDL_DINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version);
31extern bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice);
32extern bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble);
33extern void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick);
34extern void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick);
35extern void SDL_DINPUT_JoystickQuit(void);
36
37// Ends C function definitions when using C++
38#ifdef __cplusplus
39}
40#endif
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c
new file mode 100644
index 0000000..d5166de
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c
@@ -0,0 +1,2238 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20
21*/
22/*
23 RAWINPUT Joystick API for better handling XInput-capable devices on Windows.
24
25 XInput is limited to 4 devices.
26 Windows.Gaming.Input does not get inputs from XBox One controllers when not in the foreground.
27 DirectInput does not get inputs from XBox One controllers when not in the foreground, nor rumble or accurate triggers.
28 RawInput does not get rumble or accurate triggers.
29
30 So, combine them as best we can!
31*/
32#include "SDL_internal.h"
33
34#ifdef SDL_JOYSTICK_RAWINPUT
35
36#include "../usb_ids.h"
37#include "../SDL_sysjoystick.h"
38#include "../../core/windows/SDL_windows.h"
39#include "../../core/windows/SDL_hid.h"
40#include "../hidapi/SDL_hidapijoystick_c.h"
41
42/* SDL_JOYSTICK_RAWINPUT_XINPUT is disabled because using XInput at the same time as
43 raw input will turn off the Xbox Series X controller when it is connected via the
44 Xbox One Wireless Adapter.
45 */
46#ifdef HAVE_XINPUT_H
47#define SDL_JOYSTICK_RAWINPUT_XINPUT
48#endif
49#ifdef HAVE_WINDOWS_GAMING_INPUT_H
50#define SDL_JOYSTICK_RAWINPUT_WGI
51#endif
52
53#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
54#include "../../core/windows/SDL_xinput.h"
55#endif
56
57#ifdef SDL_JOYSTICK_RAWINPUT_WGI
58#include "../../core/windows/SDL_windows.h"
59typedef struct WindowsGamingInputGamepadState WindowsGamingInputGamepadState;
60#define GamepadButtons_GUIDE 0x40000000
61#define COBJMACROS
62#include "windows.gaming.input.h"
63#include <roapi.h>
64#endif
65
66#if defined(SDL_JOYSTICK_RAWINPUT_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT_WGI)
67#define SDL_JOYSTICK_RAWINPUT_MATCHING
68#define SDL_JOYSTICK_RAWINPUT_MATCH_AXES
69#define SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
70#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
71#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 6 // stick + trigger axes
72#else
73#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 4 // stick axes
74#endif
75#endif
76
77#if 0
78#define DEBUG_RAWINPUT
79#endif
80
81#ifndef RIDEV_EXINPUTSINK
82#define RIDEV_EXINPUTSINK 0x00001000
83#define RIDEV_DEVNOTIFY 0x00002000
84#endif
85
86#ifndef WM_INPUT_DEVICE_CHANGE
87#define WM_INPUT_DEVICE_CHANGE 0x00FE
88#endif
89#ifndef WM_INPUT
90#define WM_INPUT 0x00FF
91#endif
92#ifndef GIDC_ARRIVAL
93#define GIDC_ARRIVAL 1
94#define GIDC_REMOVAL 2
95#endif
96
97extern void WINDOWS_RAWINPUTEnabledChanged(void);
98extern void WINDOWS_JoystickDetect(void);
99
100static bool SDL_RAWINPUT_inited = false;
101static bool SDL_RAWINPUT_remote_desktop = false;
102static int SDL_RAWINPUT_numjoysticks = 0;
103
104static void RAWINPUT_JoystickClose(SDL_Joystick *joystick);
105
106typedef struct SDL_RAWINPUT_Device
107{
108 SDL_AtomicInt refcount;
109 char *name;
110 char *path;
111 Uint16 vendor_id;
112 Uint16 product_id;
113 Uint16 version;
114 SDL_GUID guid;
115 bool is_xinput;
116 bool is_xboxone;
117 int steam_virtual_gamepad_slot;
118 PHIDP_PREPARSED_DATA preparsed_data;
119
120 HANDLE hDevice;
121 SDL_Joystick *joystick;
122 SDL_JoystickID joystick_id;
123
124 struct SDL_RAWINPUT_Device *next;
125} SDL_RAWINPUT_Device;
126
127struct joystick_hwdata
128{
129 bool is_xinput;
130 bool is_xboxone;
131 PHIDP_PREPARSED_DATA preparsed_data;
132 ULONG max_data_length;
133 HIDP_DATA *data;
134 USHORT *button_indices;
135 USHORT *axis_indices;
136 USHORT *hat_indices;
137 bool guide_hack;
138 bool trigger_hack;
139 USHORT trigger_hack_index;
140
141#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
142 Uint64 match_state; // Lowest 16 bits for button states, higher 24 for 6 4bit axes
143 Uint64 last_state_packet;
144#endif
145
146#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
147 bool xinput_enabled;
148 bool xinput_correlated;
149 Uint8 xinput_correlation_id;
150 Uint8 xinput_correlation_count;
151 Uint8 xinput_uncorrelate_count;
152 Uint8 xinput_slot;
153#endif
154
155#ifdef SDL_JOYSTICK_RAWINPUT_WGI
156 bool wgi_correlated;
157 Uint8 wgi_correlation_id;
158 Uint8 wgi_correlation_count;
159 Uint8 wgi_uncorrelate_count;
160 WindowsGamingInputGamepadState *wgi_slot;
161#endif
162
163 SDL_RAWINPUT_Device *device;
164};
165typedef struct joystick_hwdata RAWINPUT_DeviceContext;
166
167SDL_RAWINPUT_Device *SDL_RAWINPUT_devices;
168
169static const Uint16 subscribed_devices[] = {
170 USB_USAGE_GENERIC_GAMEPAD,
171 /* Don't need Joystick for any devices we're handling here (XInput-capable)
172 USB_USAGE_GENERIC_JOYSTICK,
173 USB_USAGE_GENERIC_MULTIAXISCONTROLLER,
174 */
175};
176
177#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
178
179static struct
180{
181 Uint64 last_state_packet;
182 SDL_Joystick *joystick;
183 SDL_Joystick *last_joystick;
184} guide_button_candidate;
185
186typedef struct WindowsMatchState
187{
188#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
189 SHORT match_axes[SDL_JOYSTICK_RAWINPUT_MATCH_COUNT];
190#endif
191#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
192 WORD xinput_buttons;
193#endif
194#ifdef SDL_JOYSTICK_RAWINPUT_WGI
195 Uint32 wgi_buttons;
196#endif
197 bool any_data;
198} WindowsMatchState;
199
200static void RAWINPUT_FillMatchState(WindowsMatchState *state, Uint64 match_state)
201{
202#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
203 int ii;
204#endif
205
206 bool any_axes_data = false;
207#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
208 /* SHORT state->match_axes[4] = {
209 (match_state & 0x000F0000) >> 4,
210 (match_state & 0x00F00000) >> 8,
211 (match_state & 0x0F000000) >> 12,
212 (match_state & 0xF0000000) >> 16,
213 }; */
214 for (ii = 0; ii < 4; ii++) {
215 state->match_axes[ii] = (SHORT)((match_state & (0x000F0000ull << (ii * 4))) >> (4 + ii * 4));
216 any_axes_data |= ((Uint32)(state->match_axes[ii] + 0x1000) > 0x2000); // match_state bit is not 0xF, 0x1, or 0x2
217 }
218#endif // SDL_JOYSTICK_RAWINPUT_MATCH_AXES
219#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
220 for (; ii < SDL_JOYSTICK_RAWINPUT_MATCH_COUNT; ii++) {
221 state->match_axes[ii] = (SHORT)((match_state & (0x000F0000ull << (ii * 4))) >> (4 + ii * 4));
222 any_axes_data |= (state->match_axes[ii] != SDL_MIN_SINT16);
223 }
224#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
225
226 state->any_data = any_axes_data;
227
228#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
229 // Match axes by checking if the distance between the high 4 bits of axis and the 4 bits from match_state is 1 or less
230#define XInputAxesMatch(gamepad) ( \
231 (Uint32)(gamepad.sThumbLX - state->match_axes[0] + 0x1000) <= 0x2fff && \
232 (Uint32)(~gamepad.sThumbLY - state->match_axes[1] + 0x1000) <= 0x2fff && \
233 (Uint32)(gamepad.sThumbRX - state->match_axes[2] + 0x1000) <= 0x2fff && \
234 (Uint32)(~gamepad.sThumbRY - state->match_axes[3] + 0x1000) <= 0x2fff)
235 /* Explicit
236#define XInputAxesMatch(gamepad) (\
237 SDL_abs((Sint8)((gamepad.sThumbLX & 0xF000) >> 8) - ((match_state & 0x000F0000) >> 12)) <= 0x10 && \
238 SDL_abs((Sint8)((~gamepad.sThumbLY & 0xF000) >> 8) - ((match_state & 0x00F00000) >> 16)) <= 0x10 && \
239 SDL_abs((Sint8)((gamepad.sThumbRX & 0xF000) >> 8) - ((match_state & 0x0F000000) >> 20)) <= 0x10 && \
240 SDL_abs((Sint8)((~gamepad.sThumbRY & 0xF000) >> 8) - ((match_state & 0xF0000000) >> 24)) <= 0x10) */
241
242 // Can only match trigger values if a single trigger has a value.
243#define XInputTriggersMatch(gamepad) ( \
244 ((state->match_axes[4] == SDL_MIN_SINT16) && (state->match_axes[5] == SDL_MIN_SINT16)) || \
245 ((gamepad.bLeftTrigger != 0) && (gamepad.bRightTrigger != 0)) || \
246 ((Uint32)((((int)gamepad.bLeftTrigger * 257) - 32768) - state->match_axes[4]) <= 0x2fff) || \
247 ((Uint32)((((int)gamepad.bRightTrigger * 257) - 32768) - state->match_axes[5]) <= 0x2fff))
248
249 state->xinput_buttons =
250 // Bitwise map .RLDUWVQTS.KYXBA -> YXBA..WVQTKSRLDU
251 (WORD)(match_state << 12 | (match_state & 0x0780) >> 1 | (match_state & 0x0010) << 1 | (match_state & 0x0040) >> 2 | (match_state & 0x7800) >> 11);
252 /* Explicit
253 ((match_state & (1<<SDL_GAMEPAD_BUTTON_SOUTH)) ? XINPUT_GAMEPAD_A : 0) |
254 ((match_state & (1<<SDL_GAMEPAD_BUTTON_EAST)) ? XINPUT_GAMEPAD_B : 0) |
255 ((match_state & (1<<SDL_GAMEPAD_BUTTON_WEST)) ? XINPUT_GAMEPAD_X : 0) |
256 ((match_state & (1<<SDL_GAMEPAD_BUTTON_NORTH)) ? XINPUT_GAMEPAD_Y : 0) |
257 ((match_state & (1<<SDL_GAMEPAD_BUTTON_BACK)) ? XINPUT_GAMEPAD_BACK : 0) |
258 ((match_state & (1<<SDL_GAMEPAD_BUTTON_START)) ? XINPUT_GAMEPAD_START : 0) |
259 ((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_STICK)) ? XINPUT_GAMEPAD_LEFT_THUMB : 0) |
260 ((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_STICK)) ? XINPUT_GAMEPAD_RIGHT_THUMB: 0) |
261 ((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) ? XINPUT_GAMEPAD_LEFT_SHOULDER : 0) |
262 ((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) ? XINPUT_GAMEPAD_RIGHT_SHOULDER : 0) |
263 ((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_UP)) ? XINPUT_GAMEPAD_DPAD_UP : 0) |
264 ((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_DOWN)) ? XINPUT_GAMEPAD_DPAD_DOWN : 0) |
265 ((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_LEFT)) ? XINPUT_GAMEPAD_DPAD_LEFT : 0) |
266 ((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) ? XINPUT_GAMEPAD_DPAD_RIGHT : 0);
267 */
268
269 if (state->xinput_buttons) {
270 state->any_data = true;
271 }
272#endif
273
274#ifdef SDL_JOYSTICK_RAWINPUT_WGI
275 // Match axes by checking if the distance between the high 4 bits of axis and the 4 bits from match_state is 1 or less
276#define WindowsGamingInputAxesMatch(gamepad) ( \
277 (Uint16)(((Sint16)(gamepad.LeftThumbstickX * SDL_MAX_SINT16) & 0xF000) - state->match_axes[0] + 0x1000) <= 0x2fff && \
278 (Uint16)((~(Sint16)(gamepad.LeftThumbstickY * SDL_MAX_SINT16) & 0xF000) - state->match_axes[1] + 0x1000) <= 0x2fff && \
279 (Uint16)(((Sint16)(gamepad.RightThumbstickX * SDL_MAX_SINT16) & 0xF000) - state->match_axes[2] + 0x1000) <= 0x2fff && \
280 (Uint16)((~(Sint16)(gamepad.RightThumbstickY * SDL_MAX_SINT16) & 0xF000) - state->match_axes[3] + 0x1000) <= 0x2fff)
281
282#define WindowsGamingInputTriggersMatch(gamepad) ( \
283 ((state->match_axes[4] == SDL_MIN_SINT16) && (state->match_axes[5] == SDL_MIN_SINT16)) || \
284 ((gamepad.LeftTrigger == 0.0f) && (gamepad.RightTrigger == 0.0f)) || \
285 ((Uint16)((((int)(gamepad.LeftTrigger * SDL_MAX_UINT16)) - 32768) - state->match_axes[4]) <= 0x2fff) || \
286 ((Uint16)((((int)(gamepad.RightTrigger * SDL_MAX_UINT16)) - 32768) - state->match_axes[5]) <= 0x2fff))
287
288 state->wgi_buttons =
289 // Bitwise map .RLD UWVQ TS.K YXBA -> ..QT WVRL DUYX BAKS
290 // RStick/LStick (QT) RShould/LShould (WV) DPad R/L/D/U YXBA bac(K) (S)tart
291 (match_state & 0x0180) << 5 | (match_state & 0x0600) << 1 | (match_state & 0x7800) >> 5 | (match_state & 0x000F) << 2 | (match_state & 0x0010) >> 3 | (match_state & 0x0040) >> 6;
292 /* Explicit
293 ((match_state & (1<<SDL_GAMEPAD_BUTTON_SOUTH)) ? GamepadButtons_A : 0) |
294 ((match_state & (1<<SDL_GAMEPAD_BUTTON_EAST)) ? GamepadButtons_B : 0) |
295 ((match_state & (1<<SDL_GAMEPAD_BUTTON_WEST)) ? GamepadButtons_X : 0) |
296 ((match_state & (1<<SDL_GAMEPAD_BUTTON_NORTH)) ? GamepadButtons_Y : 0) |
297 ((match_state & (1<<SDL_GAMEPAD_BUTTON_BACK)) ? GamepadButtons_View : 0) |
298 ((match_state & (1<<SDL_GAMEPAD_BUTTON_START)) ? GamepadButtons_Menu : 0) |
299 ((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_STICK)) ? GamepadButtons_LeftThumbstick : 0) |
300 ((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_STICK)) ? GamepadButtons_RightThumbstick: 0) |
301 ((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) ? GamepadButtons_LeftShoulder: 0) |
302 ((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) ? GamepadButtons_RightShoulder: 0) |
303 ((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_UP)) ? GamepadButtons_DPadUp : 0) |
304 ((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_DOWN)) ? GamepadButtons_DPadDown : 0) |
305 ((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_LEFT)) ? GamepadButtons_DPadLeft : 0) |
306 ((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) ? GamepadButtons_DPadRight : 0); */
307
308 if (state->wgi_buttons) {
309 state->any_data = true;
310 }
311#endif
312}
313
314#endif // SDL_JOYSTICK_RAWINPUT_MATCHING
315
316#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
317
318static struct
319{
320 XINPUT_STATE state;
321 XINPUT_BATTERY_INFORMATION_EX battery;
322 bool connected; // Currently has an active XInput device
323 bool used; // Is currently mapped to an SDL device
324 Uint8 correlation_id;
325} xinput_state[XUSER_MAX_COUNT];
326static bool xinput_device_change = true;
327static bool xinput_state_dirty = true;
328
329static void RAWINPUT_UpdateXInput(void)
330{
331 DWORD user_index;
332 if (xinput_device_change) {
333 for (user_index = 0; user_index < XUSER_MAX_COUNT; user_index++) {
334 XINPUT_CAPABILITIES capabilities;
335 xinput_state[user_index].connected = (XINPUTGETCAPABILITIES(user_index, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS);
336 }
337 xinput_device_change = false;
338 xinput_state_dirty = true;
339 }
340 if (xinput_state_dirty) {
341 xinput_state_dirty = false;
342 for (user_index = 0; user_index < SDL_arraysize(xinput_state); ++user_index) {
343 if (xinput_state[user_index].connected) {
344 if (XINPUTGETSTATE(user_index, &xinput_state[user_index].state) != ERROR_SUCCESS) {
345 xinput_state[user_index].connected = false;
346 }
347 xinput_state[user_index].battery.BatteryType = BATTERY_TYPE_UNKNOWN;
348 if (XINPUTGETBATTERYINFORMATION) {
349 XINPUTGETBATTERYINFORMATION(user_index, BATTERY_DEVTYPE_GAMEPAD, &xinput_state[user_index].battery);
350 }
351 }
352 }
353 }
354}
355
356static void RAWINPUT_MarkXInputSlotUsed(Uint8 xinput_slot)
357{
358 if (xinput_slot != XUSER_INDEX_ANY) {
359 xinput_state[xinput_slot].used = true;
360 }
361}
362
363static void RAWINPUT_MarkXInputSlotFree(Uint8 xinput_slot)
364{
365 if (xinput_slot != XUSER_INDEX_ANY) {
366 xinput_state[xinput_slot].used = false;
367 }
368}
369static bool RAWINPUT_MissingXInputSlot(void)
370{
371 int ii;
372 for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) {
373 if (xinput_state[ii].connected && !xinput_state[ii].used) {
374 return true;
375 }
376 }
377 return false;
378}
379
380static bool RAWINPUT_XInputSlotMatches(const WindowsMatchState *state, Uint8 slot_idx)
381{
382 if (xinput_state[slot_idx].connected) {
383 WORD xinput_buttons = xinput_state[slot_idx].state.Gamepad.wButtons;
384 if ((xinput_buttons & ~XINPUT_GAMEPAD_GUIDE) == state->xinput_buttons
385#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
386 && XInputAxesMatch(xinput_state[slot_idx].state.Gamepad)
387#endif
388#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
389 && XInputTriggersMatch(xinput_state[slot_idx].state.Gamepad)
390#endif
391 ) {
392 return true;
393 }
394 }
395 return false;
396}
397
398static bool RAWINPUT_GuessXInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, Uint8 *slot_idx)
399{
400 Uint8 user_index;
401 int match_count;
402
403 /* If there is only one available slot, let's use that
404 * That will be right most of the time, and uncorrelation will fix any bad guesses
405 */
406 match_count = 0;
407 for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) {
408 if (xinput_state[user_index].connected && !xinput_state[user_index].used) {
409 *slot_idx = user_index;
410 ++match_count;
411 }
412 }
413 if (match_count == 1) {
414 *correlation_id = ++xinput_state[*slot_idx].correlation_id;
415 return true;
416 }
417
418 *slot_idx = 0;
419
420 match_count = 0;
421 for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) {
422 if (!xinput_state[user_index].used && RAWINPUT_XInputSlotMatches(state, user_index)) {
423 ++match_count;
424 *slot_idx = user_index;
425 // Incrementing correlation_id for any match, as negative evidence for others being correlated
426 *correlation_id = ++xinput_state[user_index].correlation_id;
427 }
428 }
429 /* Only return a match if we match exactly one, and we have some non-zero data (buttons or axes) that matched.
430 Note that we're still invalidating *other* potential correlations if we have more than one match or we have no
431 data. */
432 if (match_count == 1 && state->any_data) {
433 return true;
434 }
435 return false;
436}
437
438#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
439
440#ifdef SDL_JOYSTICK_RAWINPUT_WGI
441
442typedef struct WindowsGamingInputGamepadState
443{
444 __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad;
445 struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading state;
446 RAWINPUT_DeviceContext *correlated_context;
447 bool used; // Is currently mapped to an SDL device
448 bool connected; // Just used during update to track disconnected
449 Uint8 correlation_id;
450 struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration;
451} WindowsGamingInputGamepadState;
452
453static struct
454{
455 WindowsGamingInputGamepadState **per_gamepad;
456 int per_gamepad_count;
457 bool initialized;
458 bool dirty;
459 bool need_device_list_update;
460 int ref_count;
461 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics;
462 EventRegistrationToken gamepad_added_token;
463 EventRegistrationToken gamepad_removed_token;
464} wgi_state;
465
466typedef struct GamepadDelegate
467{
468 __FIEventHandler_1_Windows__CGaming__CInput__CGamepad iface;
469 SDL_AtomicInt refcount;
470} GamepadDelegate;
471
472static const IID IID_IEventHandler_Gamepad = { 0x8a7639ee, 0x624a, 0x501a, { 0xbb, 0x53, 0x56, 0x2d, 0x1e, 0xc1, 0x1b, 0x52 } };
473
474static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_QueryInterface(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, REFIID riid, void **ppvObject)
475{
476 if (!ppvObject) {
477 return E_INVALIDARG;
478 }
479
480 *ppvObject = NULL;
481 if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &IID_IAgileObject) || WIN_IsEqualIID(riid, &IID_IEventHandler_Gamepad)) {
482 *ppvObject = This;
483 __FIEventHandler_1_Windows__CGaming__CInput__CGamepad_AddRef(This);
484 return S_OK;
485 } else if (WIN_IsEqualIID(riid, &IID_IMarshal)) {
486 // This seems complicated. Let's hope it doesn't happen.
487 return E_OUTOFMEMORY;
488 } else {
489 return E_NOINTERFACE;
490 }
491}
492
493static ULONG STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_AddRef(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This)
494{
495 GamepadDelegate *self = (GamepadDelegate *)This;
496 return SDL_AddAtomicInt(&self->refcount, 1) + 1UL;
497}
498
499static ULONG STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_Release(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This)
500{
501 GamepadDelegate *self = (GamepadDelegate *)This;
502 int rc = SDL_AddAtomicInt(&self->refcount, -1) - 1;
503 // Should never free the static delegate objects
504 SDL_assert(rc > 0);
505 return rc;
506}
507
508static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIGamepad *e)
509{
510 wgi_state.need_device_list_update = true;
511 return S_OK;
512}
513
514static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_InvokeRemoved(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIGamepad *e)
515{
516 wgi_state.need_device_list_update = true;
517 return S_OK;
518}
519
520#ifdef _MSC_VER
521#pragma warning(push)
522#pragma warning(disable : 4028) // formal parameter 3 different from declaration, when using older buggy WGI headers
523#pragma warning(disable : 4113) // X differs in parameter lists from Y, when using older buggy WGI headers
524#endif
525
526static __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl gamepad_added_vtbl = {
527 IEventHandler_CGamepadVtbl_QueryInterface,
528 IEventHandler_CGamepadVtbl_AddRef,
529 IEventHandler_CGamepadVtbl_Release,
530 IEventHandler_CGamepadVtbl_InvokeAdded
531};
532static GamepadDelegate gamepad_added = {
533 { &gamepad_added_vtbl },
534 { 1 }
535};
536
537static __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl gamepad_removed_vtbl = {
538 IEventHandler_CGamepadVtbl_QueryInterface,
539 IEventHandler_CGamepadVtbl_AddRef,
540 IEventHandler_CGamepadVtbl_Release,
541 IEventHandler_CGamepadVtbl_InvokeRemoved
542};
543static GamepadDelegate gamepad_removed = {
544 { &gamepad_removed_vtbl },
545 { 1 }
546};
547
548#ifdef _MSC_VER
549#pragma warning(pop)
550#endif
551
552static void RAWINPUT_MarkWindowsGamingInputSlotUsed(WindowsGamingInputGamepadState *wgi_slot, RAWINPUT_DeviceContext *ctx)
553{
554 wgi_slot->used = true;
555 wgi_slot->correlated_context = ctx;
556}
557
558static void RAWINPUT_MarkWindowsGamingInputSlotFree(WindowsGamingInputGamepadState *wgi_slot)
559{
560 wgi_slot->used = false;
561 wgi_slot->correlated_context = NULL;
562}
563
564static bool RAWINPUT_MissingWindowsGamingInputSlot(void)
565{
566 int ii;
567 for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {
568 if (!wgi_state.per_gamepad[ii]->used) {
569 return true;
570 }
571 }
572 return false;
573}
574
575static bool RAWINPUT_UpdateWindowsGamingInput(void)
576{
577 int ii;
578 if (!wgi_state.gamepad_statics) {
579 return true;
580 }
581
582 if (!wgi_state.dirty) {
583 return true;
584 }
585
586 wgi_state.dirty = false;
587
588 if (wgi_state.need_device_list_update) {
589 HRESULT hr;
590 __FIVectorView_1_Windows__CGaming__CInput__CGamepad *gamepads;
591 wgi_state.need_device_list_update = false;
592 for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {
593 wgi_state.per_gamepad[ii]->connected = false;
594 }
595
596 hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_get_Gamepads(wgi_state.gamepad_statics, &gamepads);
597 if (SUCCEEDED(hr)) {
598 unsigned int num_gamepads;
599
600 hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_get_Size(gamepads, &num_gamepads);
601 if (SUCCEEDED(hr)) {
602 unsigned int i;
603 for (i = 0; i < num_gamepads; ++i) {
604 __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad;
605
606 hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_GetAt(gamepads, i, &gamepad);
607 if (SUCCEEDED(hr)) {
608 bool found = false;
609 int jj;
610 for (jj = 0; jj < wgi_state.per_gamepad_count; jj++) {
611 if (wgi_state.per_gamepad[jj]->gamepad == gamepad) {
612 found = true;
613 wgi_state.per_gamepad[jj]->connected = true;
614 break;
615 }
616 }
617 if (!found) {
618 // New device, add it
619 WindowsGamingInputGamepadState *gamepad_state;
620 WindowsGamingInputGamepadState **new_per_gamepad;
621 gamepad_state = SDL_calloc(1, sizeof(*gamepad_state));
622 if (!gamepad_state) {
623 return false;
624 }
625 new_per_gamepad = SDL_realloc(wgi_state.per_gamepad, sizeof(wgi_state.per_gamepad[0]) * (wgi_state.per_gamepad_count + 1));
626 if (!new_per_gamepad) {
627 SDL_free(gamepad_state);
628 return false;
629 }
630 wgi_state.per_gamepad = new_per_gamepad;
631 wgi_state.per_gamepad_count++;
632 wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1] = gamepad_state;
633 gamepad_state->gamepad = gamepad;
634 gamepad_state->connected = true;
635 } else {
636 // Already tracked
637 __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad);
638 }
639 }
640 }
641 for (ii = wgi_state.per_gamepad_count - 1; ii >= 0; ii--) {
642 WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[ii];
643 if (!gamepad_state->connected) {
644 // Device missing, must be disconnected
645 if (gamepad_state->correlated_context) {
646 gamepad_state->correlated_context->wgi_correlated = false;
647 gamepad_state->correlated_context->wgi_slot = NULL;
648 }
649 __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad_state->gamepad);
650 SDL_free(gamepad_state);
651 wgi_state.per_gamepad[ii] = wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1];
652 --wgi_state.per_gamepad_count;
653 }
654 }
655 }
656 __FIVectorView_1_Windows__CGaming__CInput__CGamepad_Release(gamepads);
657 }
658 } // need_device_list_update
659
660 for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {
661 HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(wgi_state.per_gamepad[ii]->gamepad, &wgi_state.per_gamepad[ii]->state);
662 if (!SUCCEEDED(hr)) {
663 wgi_state.per_gamepad[ii]->connected = false; // Not used by anything, currently
664 }
665 }
666 return true;
667}
668static void RAWINPUT_InitWindowsGamingInput(RAWINPUT_DeviceContext *ctx)
669{
670 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) {
671 return;
672 }
673
674 wgi_state.ref_count++;
675 if (!wgi_state.initialized) {
676 static const IID SDL_IID_IGamepadStatics = { 0x8BBCE529, 0xD49C, 0x39E9, { 0x95, 0x60, 0xE4, 0x7D, 0xDE, 0x96, 0xB7, 0xC8 } };
677 HRESULT hr;
678
679 if (FAILED(WIN_RoInitialize())) {
680 return;
681 }
682 wgi_state.initialized = true;
683 wgi_state.dirty = true;
684
685 {
686 typedef HRESULT(WINAPI * WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER * hstringHeader, HSTRING * string);
687 typedef HRESULT(WINAPI * RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory);
688
689 WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference");
690 RoGetActivationFactory_t RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory");
691 if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc) {
692 PCWSTR pNamespace = L"Windows.Gaming.Input.Gamepad";
693 HSTRING_HEADER hNamespaceStringHeader;
694 HSTRING hNamespaceString;
695
696 hr = WindowsCreateStringReferenceFunc(pNamespace, (UINT32)SDL_wcslen(pNamespace), &hNamespaceStringHeader, &hNamespaceString);
697 if (SUCCEEDED(hr)) {
698 RoGetActivationFactoryFunc(hNamespaceString, &SDL_IID_IGamepadStatics, (void **)&wgi_state.gamepad_statics);
699 }
700
701 if (wgi_state.gamepad_statics) {
702 wgi_state.need_device_list_update = true;
703
704 hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadAdded(wgi_state.gamepad_statics, &gamepad_added.iface, &wgi_state.gamepad_added_token);
705 if (!SUCCEEDED(hr)) {
706 SDL_SetError("add_GamepadAdded() failed: 0x%lx", hr);
707 }
708
709 hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadRemoved(wgi_state.gamepad_statics, &gamepad_removed.iface, &wgi_state.gamepad_removed_token);
710 if (!SUCCEEDED(hr)) {
711 SDL_SetError("add_GamepadRemoved() failed: 0x%lx", hr);
712 }
713 }
714 }
715 }
716 }
717}
718
719static bool RAWINPUT_WindowsGamingInputSlotMatches(const WindowsMatchState *state, WindowsGamingInputGamepadState *slot, bool xinput_correlated)
720{
721 Uint32 wgi_buttons = slot->state.Buttons;
722 if ((wgi_buttons & 0x3FFF) == state->wgi_buttons
723#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
724 && WindowsGamingInputAxesMatch(slot->state)
725#endif
726#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
727 // Don't try to match WGI triggers if getting values from XInput
728 && (xinput_correlated || WindowsGamingInputTriggersMatch(slot->state))
729#endif
730 ) {
731 return true;
732 }
733 return false;
734}
735
736static bool RAWINPUT_GuessWindowsGamingInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, WindowsGamingInputGamepadState **slot, bool xinput_correlated)
737{
738 int match_count, user_index;
739 WindowsGamingInputGamepadState *gamepad_state = NULL;
740
741 /* If there is only one available slot, let's use that
742 * That will be right most of the time, and uncorrelation will fix any bad guesses
743 */
744 match_count = 0;
745 for (user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) {
746 gamepad_state = wgi_state.per_gamepad[user_index];
747 if (gamepad_state->connected && !gamepad_state->used) {
748 *slot = gamepad_state;
749 ++match_count;
750 }
751 }
752 if (match_count == 1) {
753 *correlation_id = ++gamepad_state->correlation_id;
754 return true;
755 }
756
757 match_count = 0;
758 for (user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) {
759 gamepad_state = wgi_state.per_gamepad[user_index];
760 if (RAWINPUT_WindowsGamingInputSlotMatches(state, gamepad_state, xinput_correlated)) {
761 ++match_count;
762 *slot = gamepad_state;
763 // Incrementing correlation_id for any match, as negative evidence for others being correlated
764 *correlation_id = ++gamepad_state->correlation_id;
765 }
766 }
767 /* Only return a match if we match exactly one, and we have some non-zero data (buttons or axes) that matched.
768 Note that we're still invalidating *other* potential correlations if we have more than one match or we have no
769 data. */
770 if (match_count == 1 && state->any_data) {
771 return true;
772 }
773 return false;
774}
775
776static void RAWINPUT_QuitWindowsGamingInput(RAWINPUT_DeviceContext *ctx)
777{
778 --wgi_state.ref_count;
779 if (!wgi_state.ref_count && wgi_state.initialized) {
780 int ii;
781 for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {
782 __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(wgi_state.per_gamepad[ii]->gamepad);
783 }
784 if (wgi_state.per_gamepad) {
785 SDL_free(wgi_state.per_gamepad);
786 wgi_state.per_gamepad = NULL;
787 }
788 wgi_state.per_gamepad_count = 0;
789 if (wgi_state.gamepad_statics) {
790 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_remove_GamepadAdded(wgi_state.gamepad_statics, wgi_state.gamepad_added_token);
791 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_remove_GamepadRemoved(wgi_state.gamepad_statics, wgi_state.gamepad_removed_token);
792 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(wgi_state.gamepad_statics);
793 wgi_state.gamepad_statics = NULL;
794 }
795 WIN_RoUninitialize();
796 wgi_state.initialized = false;
797 }
798}
799
800#endif // SDL_JOYSTICK_RAWINPUT_WGI
801
802static SDL_RAWINPUT_Device *RAWINPUT_AcquireDevice(SDL_RAWINPUT_Device *device)
803{
804 SDL_AtomicIncRef(&device->refcount);
805 return device;
806}
807
808static void RAWINPUT_ReleaseDevice(SDL_RAWINPUT_Device *device)
809{
810#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
811 if (device->joystick) {
812 RAWINPUT_DeviceContext *ctx = device->joystick->hwdata;
813
814 if (ctx->xinput_enabled && ctx->xinput_correlated) {
815 RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot);
816 ctx->xinput_correlated = false;
817 }
818 }
819#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
820
821 if (SDL_AtomicDecRef(&device->refcount)) {
822 SDL_free(device->preparsed_data);
823 SDL_free(device->name);
824 SDL_free(device->path);
825 SDL_free(device);
826 }
827}
828
829static SDL_RAWINPUT_Device *RAWINPUT_DeviceFromHandle(HANDLE hDevice)
830{
831 SDL_RAWINPUT_Device *curr;
832
833 for (curr = SDL_RAWINPUT_devices; curr; curr = curr->next) {
834 if (curr->hDevice == hDevice) {
835 return curr;
836 }
837 }
838 return NULL;
839}
840
841static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path)
842{
843 int slot = -1;
844
845 // The format for the raw input device path is documented here:
846 // https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices
847 if (vendor_id == USB_VENDOR_VALVE &&
848 product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
849 (void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot);
850 }
851 return slot;
852}
853
854static void RAWINPUT_AddDevice(HANDLE hDevice)
855{
856#define CHECK(expression) \
857 { \
858 if (!(expression)) \
859 goto err; \
860 }
861 SDL_RAWINPUT_Device *device = NULL;
862 SDL_RAWINPUT_Device *curr, *last;
863 RID_DEVICE_INFO rdi;
864 UINT size;
865 char dev_name[MAX_PATH] = { 0 };
866 HANDLE hFile = INVALID_HANDLE_VALUE;
867
868 // Make sure we're not trying to add the same device twice
869 if (RAWINPUT_DeviceFromHandle(hDevice)) {
870 return;
871 }
872
873 // Figure out what kind of device it is
874 size = sizeof(rdi);
875 SDL_zero(rdi);
876 CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICEINFO, &rdi, &size) != (UINT)-1);
877 CHECK(rdi.dwType == RIM_TYPEHID);
878
879 // Get the device "name" (HID Path)
880 size = SDL_arraysize(dev_name);
881 CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, dev_name, &size) != (UINT)-1);
882 // Only take XInput-capable devices
883 CHECK(SDL_strstr(dev_name, "IG_") != NULL);
884 CHECK(!SDL_ShouldIgnoreJoystick((Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber, ""));
885 CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_RAWINPUT_JoystickDriver, (Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber, ""));
886
887 device = (SDL_RAWINPUT_Device *)SDL_calloc(1, sizeof(SDL_RAWINPUT_Device));
888 CHECK(device);
889 device->hDevice = hDevice;
890 device->vendor_id = (Uint16)rdi.hid.dwVendorId;
891 device->product_id = (Uint16)rdi.hid.dwProductId;
892 device->version = (Uint16)rdi.hid.dwVersionNumber;
893 device->is_xinput = true;
894 device->is_xboxone = SDL_IsJoystickXboxOne(device->vendor_id, device->product_id);
895 device->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(device->vendor_id, device->product_id, dev_name);
896
897 // Get HID Top-Level Collection Preparsed Data
898 size = 0;
899 CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_PREPARSEDDATA, NULL, &size) != (UINT)-1);
900 device->preparsed_data = (PHIDP_PREPARSED_DATA)SDL_calloc(size, sizeof(BYTE));
901 CHECK(device->preparsed_data);
902 CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_PREPARSEDDATA, device->preparsed_data, &size) != (UINT)-1);
903
904 hFile = CreateFileA(dev_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
905 CHECK(hFile != INVALID_HANDLE_VALUE);
906
907 {
908 char *manufacturer_string = NULL;
909 char *product_string = NULL;
910 WCHAR string[128];
911
912 if (SDL_HidD_GetManufacturerString(hFile, string, sizeof(string))) {
913 manufacturer_string = WIN_StringToUTF8W(string);
914 }
915 if (SDL_HidD_GetProductString(hFile, string, sizeof(string))) {
916 product_string = WIN_StringToUTF8W(string);
917 }
918
919 device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, manufacturer_string, product_string);
920 device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, manufacturer_string, product_string, 'r', 0);
921
922 if (manufacturer_string) {
923 SDL_free(manufacturer_string);
924 }
925 if (product_string) {
926 SDL_free(product_string);
927 }
928 }
929
930 device->path = SDL_strdup(dev_name);
931
932 CloseHandle(hFile);
933 hFile = INVALID_HANDLE_VALUE;
934
935 device->joystick_id = SDL_GetNextObjectID();
936
937#ifdef DEBUG_RAWINPUT
938 SDL_Log("Adding RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle 0x%.8x", device->name, device->vendor_id, device->product_id, device->version, device->hDevice);
939#endif
940
941 // Add it to the list
942 RAWINPUT_AcquireDevice(device);
943 for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) {
944 }
945 if (last) {
946 last->next = device;
947 } else {
948 SDL_RAWINPUT_devices = device;
949 }
950
951 ++SDL_RAWINPUT_numjoysticks;
952
953 SDL_PrivateJoystickAdded(device->joystick_id);
954
955 return;
956
957err:
958 if (hFile != INVALID_HANDLE_VALUE) {
959 CloseHandle(hFile);
960 }
961 if (device) {
962 if (device->name) {
963 SDL_free(device->name);
964 }
965 if (device->path) {
966 SDL_free(device->path);
967 }
968 SDL_free(device);
969 }
970#undef CHECK
971}
972
973static void RAWINPUT_DelDevice(SDL_RAWINPUT_Device *device, bool send_event)
974{
975 SDL_RAWINPUT_Device *curr, *last;
976 for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) {
977 if (curr == device) {
978 if (last) {
979 last->next = curr->next;
980 } else {
981 SDL_RAWINPUT_devices = curr->next;
982 }
983 --SDL_RAWINPUT_numjoysticks;
984
985 SDL_PrivateJoystickRemoved(device->joystick_id);
986
987#ifdef DEBUG_RAWINPUT
988 SDL_Log("Removing RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle %p", device->name, device->vendor_id, device->product_id, device->version, device->hDevice);
989#endif
990 RAWINPUT_ReleaseDevice(device);
991 return;
992 }
993 }
994}
995
996static void RAWINPUT_DetectDevices(void)
997{
998 UINT device_count = 0;
999
1000 if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) != -1) && device_count > 0) {
1001 PRAWINPUTDEVICELIST devices = NULL;
1002 UINT i;
1003
1004 devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * device_count);
1005 if (devices) {
1006 device_count = GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST));
1007 if (device_count != (UINT)-1) {
1008 for (i = 0; i < device_count; ++i) {
1009 RAWINPUT_AddDevice(devices[i].hDevice);
1010 }
1011 }
1012 SDL_free(devices);
1013 }
1014 }
1015}
1016
1017static void RAWINPUT_RemoveDevices(void)
1018{
1019 while (SDL_RAWINPUT_devices) {
1020 RAWINPUT_DelDevice(SDL_RAWINPUT_devices, false);
1021 }
1022 SDL_assert(SDL_RAWINPUT_numjoysticks == 0);
1023}
1024
1025static bool RAWINPUT_JoystickInit(void)
1026{
1027 SDL_assert(!SDL_RAWINPUT_inited);
1028
1029 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, true)) {
1030 return true;
1031 }
1032
1033 if (!WIN_IsWindowsVistaOrGreater()) {
1034 // According to bug 6400, this doesn't work on Windows XP
1035 return false;
1036 }
1037
1038 if (!WIN_LoadHIDDLL()) {
1039 return false;
1040 }
1041
1042 SDL_RAWINPUT_inited = true;
1043
1044 RAWINPUT_DetectDevices();
1045
1046 return true;
1047}
1048
1049static int RAWINPUT_JoystickGetCount(void)
1050{
1051 return SDL_RAWINPUT_numjoysticks;
1052}
1053
1054bool RAWINPUT_IsEnabled(void)
1055{
1056 return SDL_RAWINPUT_inited && !SDL_RAWINPUT_remote_desktop;
1057}
1058
1059static void RAWINPUT_PostUpdate(void)
1060{
1061#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1062 bool unmapped_guide_pressed = false;
1063
1064#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1065 if (!wgi_state.dirty) {
1066 int ii;
1067 for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {
1068 WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[ii];
1069 if (!gamepad_state->used && (gamepad_state->state.Buttons & GamepadButtons_GUIDE)) {
1070 unmapped_guide_pressed = true;
1071 break;
1072 }
1073 }
1074 }
1075 wgi_state.dirty = true;
1076#endif
1077
1078#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1079 if (!xinput_state_dirty) {
1080 int ii;
1081 for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) {
1082 if (xinput_state[ii].connected && !xinput_state[ii].used && (xinput_state[ii].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE)) {
1083 unmapped_guide_pressed = true;
1084 break;
1085 }
1086 }
1087 }
1088 xinput_state_dirty = true;
1089#endif
1090
1091 if (unmapped_guide_pressed) {
1092 if (guide_button_candidate.joystick && !guide_button_candidate.last_joystick) {
1093 SDL_Joystick *joystick = guide_button_candidate.joystick;
1094 RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1095 if (ctx->guide_hack) {
1096 int guide_button = joystick->nbuttons - 1;
1097
1098 SDL_SendJoystickButton(SDL_GetTicksNS(), guide_button_candidate.joystick, (Uint8)guide_button, true);
1099 }
1100 guide_button_candidate.last_joystick = guide_button_candidate.joystick;
1101 }
1102 } else if (guide_button_candidate.last_joystick) {
1103 SDL_Joystick *joystick = guide_button_candidate.last_joystick;
1104 RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1105 if (ctx->guide_hack) {
1106 int guide_button = joystick->nbuttons - 1;
1107
1108 SDL_SendJoystickButton(SDL_GetTicksNS(), joystick, (Uint8)guide_button, false);
1109 }
1110 guide_button_candidate.last_joystick = NULL;
1111 }
1112 guide_button_candidate.joystick = NULL;
1113
1114#endif // SDL_JOYSTICK_RAWINPUT_MATCHING
1115}
1116
1117static void RAWINPUT_JoystickDetect(void)
1118{
1119 bool remote_desktop;
1120
1121 if (!SDL_RAWINPUT_inited) {
1122 return;
1123 }
1124
1125 remote_desktop = GetSystemMetrics(SM_REMOTESESSION) ? true : false;
1126 if (remote_desktop != SDL_RAWINPUT_remote_desktop) {
1127 SDL_RAWINPUT_remote_desktop = remote_desktop;
1128
1129 WINDOWS_RAWINPUTEnabledChanged();
1130
1131 if (remote_desktop) {
1132 RAWINPUT_RemoveDevices();
1133 WINDOWS_JoystickDetect();
1134 } else {
1135 WINDOWS_JoystickDetect();
1136 RAWINPUT_DetectDevices();
1137 }
1138 }
1139 RAWINPUT_PostUpdate();
1140}
1141
1142static bool RAWINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
1143{
1144 SDL_RAWINPUT_Device *device;
1145
1146 // If we're being asked about a device, that means another API just detected one, so rescan
1147#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1148 xinput_device_change = true;
1149#endif
1150
1151 device = SDL_RAWINPUT_devices;
1152 while (device) {
1153 if (vendor_id == device->vendor_id && product_id == device->product_id) {
1154 return true;
1155 }
1156
1157 /* The Xbox 360 wireless controller shows up as product 0 in WGI.
1158 Try to match it to a Raw Input device via name or known product ID. */
1159 if (vendor_id == device->vendor_id && product_id == 0 &&
1160 ((name && SDL_strstr(device->name, name) != NULL) ||
1161 (device->vendor_id == USB_VENDOR_MICROSOFT &&
1162 device->product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER))) {
1163 return true;
1164 }
1165
1166 // The Xbox One controller shows up as a hardcoded raw input VID/PID
1167 if (name && SDL_strcmp(name, "Xbox One Game Controller") == 0 &&
1168 device->vendor_id == USB_VENDOR_MICROSOFT &&
1169 device->product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) {
1170 return true;
1171 }
1172
1173 device = device->next;
1174 }
1175 return false;
1176}
1177
1178static SDL_RAWINPUT_Device *RAWINPUT_GetDeviceByIndex(int device_index)
1179{
1180 SDL_RAWINPUT_Device *device = SDL_RAWINPUT_devices;
1181 while (device) {
1182 if (device_index == 0) {
1183 break;
1184 }
1185 --device_index;
1186 device = device->next;
1187 }
1188 return device;
1189}
1190
1191static const char *RAWINPUT_JoystickGetDeviceName(int device_index)
1192{
1193 return RAWINPUT_GetDeviceByIndex(device_index)->name;
1194}
1195
1196static const char *RAWINPUT_JoystickGetDevicePath(int device_index)
1197{
1198 return RAWINPUT_GetDeviceByIndex(device_index)->path;
1199}
1200
1201static int RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
1202{
1203 return RAWINPUT_GetDeviceByIndex(device_index)->steam_virtual_gamepad_slot;
1204}
1205
1206static int RAWINPUT_JoystickGetDevicePlayerIndex(int device_index)
1207{
1208 return false;
1209}
1210
1211static void RAWINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index)
1212{
1213}
1214
1215static SDL_GUID RAWINPUT_JoystickGetDeviceGUID(int device_index)
1216{
1217 return RAWINPUT_GetDeviceByIndex(device_index)->guid;
1218}
1219
1220static SDL_JoystickID RAWINPUT_JoystickGetDeviceInstanceID(int device_index)
1221{
1222 return RAWINPUT_GetDeviceByIndex(device_index)->joystick_id;
1223}
1224
1225static int SDLCALL RAWINPUT_SortValueCaps(const void *A, const void *B)
1226{
1227 HIDP_VALUE_CAPS *capsA = (HIDP_VALUE_CAPS *)A;
1228 HIDP_VALUE_CAPS *capsB = (HIDP_VALUE_CAPS *)B;
1229
1230 // Sort by Usage for single values, or UsageMax for range of values
1231 return (int)capsA->NotRange.Usage - capsB->NotRange.Usage;
1232}
1233
1234static bool RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)
1235{
1236 SDL_RAWINPUT_Device *device = RAWINPUT_GetDeviceByIndex(device_index);
1237 RAWINPUT_DeviceContext *ctx;
1238 HIDP_CAPS caps;
1239 HIDP_BUTTON_CAPS *button_caps;
1240 HIDP_VALUE_CAPS *value_caps;
1241 ULONG i;
1242
1243 ctx = (RAWINPUT_DeviceContext *)SDL_calloc(1, sizeof(RAWINPUT_DeviceContext));
1244 if (!ctx) {
1245 return false;
1246 }
1247 joystick->hwdata = ctx;
1248
1249 ctx->device = RAWINPUT_AcquireDevice(device);
1250 device->joystick = joystick;
1251
1252 if (device->is_xinput) {
1253 // We'll try to get guide button and trigger axes from XInput
1254#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1255 xinput_device_change = true;
1256 ctx->xinput_enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, true);
1257 if (ctx->xinput_enabled && (!WIN_LoadXInputDLL() || !XINPUTGETSTATE)) {
1258 ctx->xinput_enabled = false;
1259 }
1260 ctx->xinput_slot = XUSER_INDEX_ANY;
1261#endif
1262#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1263 RAWINPUT_InitWindowsGamingInput(ctx);
1264#endif
1265 }
1266
1267 ctx->is_xinput = device->is_xinput;
1268 ctx->is_xboxone = device->is_xboxone;
1269#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1270 ctx->match_state = 0x0000008800000000ULL; // Trigger axes at rest
1271#endif
1272 ctx->preparsed_data = device->preparsed_data;
1273 ctx->max_data_length = SDL_HidP_MaxDataListLength(HidP_Input, ctx->preparsed_data);
1274 ctx->data = (HIDP_DATA *)SDL_malloc(ctx->max_data_length * sizeof(*ctx->data));
1275 if (!ctx->data) {
1276 RAWINPUT_JoystickClose(joystick);
1277 return false;
1278 }
1279
1280 if (SDL_HidP_GetCaps(ctx->preparsed_data, &caps) != HIDP_STATUS_SUCCESS) {
1281 RAWINPUT_JoystickClose(joystick);
1282 return SDL_SetError("Couldn't get device capabilities");
1283 }
1284
1285 button_caps = SDL_stack_alloc(HIDP_BUTTON_CAPS, caps.NumberInputButtonCaps);
1286 if (SDL_HidP_GetButtonCaps(HidP_Input, button_caps, &caps.NumberInputButtonCaps, ctx->preparsed_data) != HIDP_STATUS_SUCCESS) {
1287 RAWINPUT_JoystickClose(joystick);
1288 return SDL_SetError("Couldn't get device button capabilities");
1289 }
1290
1291 value_caps = SDL_stack_alloc(HIDP_VALUE_CAPS, caps.NumberInputValueCaps);
1292 if (SDL_HidP_GetValueCaps(HidP_Input, value_caps, &caps.NumberInputValueCaps, ctx->preparsed_data) != HIDP_STATUS_SUCCESS) {
1293 RAWINPUT_JoystickClose(joystick);
1294 SDL_stack_free(button_caps);
1295 return SDL_SetError("Couldn't get device value capabilities");
1296 }
1297
1298 // Sort the axes by usage, so X comes before Y, etc.
1299 SDL_qsort(value_caps, caps.NumberInputValueCaps, sizeof(*value_caps), RAWINPUT_SortValueCaps);
1300
1301 for (i = 0; i < caps.NumberInputButtonCaps; ++i) {
1302 HIDP_BUTTON_CAPS *cap = &button_caps[i];
1303
1304 if (cap->UsagePage == USB_USAGEPAGE_BUTTON) {
1305 int count;
1306
1307 if (cap->IsRange) {
1308 count = 1 + (cap->Range.DataIndexMax - cap->Range.DataIndexMin);
1309 } else {
1310 count = 1;
1311 }
1312
1313 joystick->nbuttons += count;
1314 }
1315 }
1316
1317 if (joystick->nbuttons > 0) {
1318 int button_index = 0;
1319
1320 ctx->button_indices = (USHORT *)SDL_malloc(joystick->nbuttons * sizeof(*ctx->button_indices));
1321 if (!ctx->button_indices) {
1322 RAWINPUT_JoystickClose(joystick);
1323 SDL_stack_free(value_caps);
1324 SDL_stack_free(button_caps);
1325 return false;
1326 }
1327
1328 for (i = 0; i < caps.NumberInputButtonCaps; ++i) {
1329 HIDP_BUTTON_CAPS *cap = &button_caps[i];
1330
1331 if (cap->UsagePage == USB_USAGEPAGE_BUTTON) {
1332 if (cap->IsRange) {
1333 int j, count = 1 + (cap->Range.DataIndexMax - cap->Range.DataIndexMin);
1334
1335 for (j = 0; j < count; ++j) {
1336 ctx->button_indices[button_index++] = (USHORT)(cap->Range.DataIndexMin + j);
1337 }
1338 } else {
1339 ctx->button_indices[button_index++] = cap->NotRange.DataIndex;
1340 }
1341 }
1342 }
1343 }
1344 if (ctx->is_xinput && joystick->nbuttons == 10) {
1345 ctx->guide_hack = true;
1346 joystick->nbuttons += 1;
1347 }
1348
1349 SDL_stack_free(button_caps);
1350
1351 for (i = 0; i < caps.NumberInputValueCaps; ++i) {
1352 HIDP_VALUE_CAPS *cap = &value_caps[i];
1353
1354 if (cap->IsRange) {
1355 continue;
1356 }
1357
1358 if (ctx->trigger_hack && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) {
1359 continue;
1360 }
1361
1362 if (cap->NotRange.Usage == USB_USAGE_GENERIC_HAT) {
1363 joystick->nhats += 1;
1364 continue;
1365 }
1366
1367 if (ctx->is_xinput && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) {
1368 continue;
1369 }
1370
1371 joystick->naxes += 1;
1372 }
1373
1374 if (joystick->naxes > 0) {
1375 int axis_index = 0;
1376
1377 ctx->axis_indices = (USHORT *)SDL_malloc(joystick->naxes * sizeof(*ctx->axis_indices));
1378 if (!ctx->axis_indices) {
1379 RAWINPUT_JoystickClose(joystick);
1380 SDL_stack_free(value_caps);
1381 return false;
1382 }
1383
1384 for (i = 0; i < caps.NumberInputValueCaps; ++i) {
1385 HIDP_VALUE_CAPS *cap = &value_caps[i];
1386
1387 if (cap->IsRange) {
1388 continue;
1389 }
1390
1391 if (cap->NotRange.Usage == USB_USAGE_GENERIC_HAT) {
1392 continue;
1393 }
1394
1395 if (ctx->is_xinput && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) {
1396 ctx->trigger_hack = true;
1397 ctx->trigger_hack_index = cap->NotRange.DataIndex;
1398 continue;
1399 }
1400
1401 ctx->axis_indices[axis_index++] = cap->NotRange.DataIndex;
1402 }
1403 }
1404 if (ctx->trigger_hack) {
1405 joystick->naxes += 2;
1406 }
1407
1408 if (joystick->nhats > 0) {
1409 int hat_index = 0;
1410
1411 ctx->hat_indices = (USHORT *)SDL_malloc(joystick->nhats * sizeof(*ctx->hat_indices));
1412 if (!ctx->hat_indices) {
1413 RAWINPUT_JoystickClose(joystick);
1414 SDL_stack_free(value_caps);
1415 return false;
1416 }
1417
1418 for (i = 0; i < caps.NumberInputValueCaps; ++i) {
1419 HIDP_VALUE_CAPS *cap = &value_caps[i];
1420
1421 if (cap->IsRange) {
1422 continue;
1423 }
1424
1425 if (cap->NotRange.Usage != USB_USAGE_GENERIC_HAT) {
1426 continue;
1427 }
1428
1429 ctx->hat_indices[hat_index++] = cap->NotRange.DataIndex;
1430 }
1431 }
1432
1433 SDL_stack_free(value_caps);
1434
1435#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1436 if (ctx->is_xinput) {
1437 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
1438 }
1439#endif
1440#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1441 if (ctx->is_xinput) {
1442 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
1443
1444 if (ctx->is_xboxone) {
1445 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
1446 }
1447 }
1448#endif
1449
1450 return true;
1451}
1452
1453static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1454{
1455#if defined(SDL_JOYSTICK_RAWINPUT_WGI) || defined(SDL_JOYSTICK_RAWINPUT_XINPUT)
1456 RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1457#endif
1458 bool rumbled = false;
1459
1460#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1461 // Prefer XInput over WGI because it allows rumble in the background
1462 if (!rumbled && ctx->xinput_correlated) {
1463 XINPUT_VIBRATION XVibration;
1464
1465 if (!XINPUTSETSTATE) {
1466 return SDL_Unsupported();
1467 }
1468
1469 XVibration.wLeftMotorSpeed = low_frequency_rumble;
1470 XVibration.wRightMotorSpeed = high_frequency_rumble;
1471 if (XINPUTSETSTATE(ctx->xinput_slot, &XVibration) == ERROR_SUCCESS) {
1472 rumbled = true;
1473 } else {
1474 return SDL_SetError("XInputSetState() failed");
1475 }
1476 }
1477#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
1478
1479#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1480 if (!rumbled && ctx->wgi_correlated) {
1481 WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot;
1482 HRESULT hr;
1483 gamepad_state->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16;
1484 gamepad_state->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16;
1485 hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration);
1486 if (SUCCEEDED(hr)) {
1487 rumbled = true;
1488 }
1489 }
1490#endif
1491
1492 if (!rumbled) {
1493#if defined(SDL_JOYSTICK_RAWINPUT_WGI) || defined(SDL_JOYSTICK_RAWINPUT_XINPUT)
1494 return SDL_SetError("Controller isn't correlated yet, try hitting a button first");
1495#else
1496 return SDL_Unsupported();
1497#endif
1498 }
1499 return true;
1500}
1501
1502static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
1503{
1504#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1505 RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1506
1507 if (ctx->wgi_correlated) {
1508 WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot;
1509 HRESULT hr;
1510 gamepad_state->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16;
1511 gamepad_state->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16;
1512 hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, gamepad_state->vibration);
1513 if (!SUCCEEDED(hr)) {
1514 return SDL_SetError("Setting vibration failed: 0x%lx", hr);
1515 }
1516 return true;
1517 } else {
1518 return SDL_SetError("Controller isn't correlated yet, try hitting a button first");
1519 }
1520#else
1521 return SDL_Unsupported();
1522#endif
1523}
1524
1525static bool RAWINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1526{
1527 return SDL_Unsupported();
1528}
1529
1530static bool RAWINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
1531{
1532 return SDL_Unsupported();
1533}
1534
1535static bool RAWINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
1536{
1537 return SDL_Unsupported();
1538}
1539
1540static HIDP_DATA *GetData(USHORT index, HIDP_DATA *data, ULONG length)
1541{
1542 ULONG i;
1543
1544 // Check to see if the data is at the expected offset
1545 if (index < length && data[index].DataIndex == index) {
1546 return &data[index];
1547 }
1548
1549 // Loop through the data to find it
1550 for (i = 0; i < length; ++i) {
1551 if (data[i].DataIndex == index) {
1552 return &data[i];
1553 }
1554 }
1555 return NULL;
1556}
1557
1558/* This is the packet format for Xbox 360 and Xbox One controllers on Windows,
1559 however with this interface there is no rumble support, no guide button,
1560 and the left and right triggers are tied together as a single axis.
1561
1562 We use XInput and Windows.Gaming.Input to make up for these shortcomings.
1563 */
1564static void RAWINPUT_HandleStatePacket(SDL_Joystick *joystick, Uint8 *data, int size)
1565{
1566 RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1567#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1568 // Map new buttons and axes into game controller controls
1569 static const int button_map[] = {
1570 SDL_GAMEPAD_BUTTON_SOUTH,
1571 SDL_GAMEPAD_BUTTON_EAST,
1572 SDL_GAMEPAD_BUTTON_WEST,
1573 SDL_GAMEPAD_BUTTON_NORTH,
1574 SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
1575 SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
1576 SDL_GAMEPAD_BUTTON_BACK,
1577 SDL_GAMEPAD_BUTTON_START,
1578 SDL_GAMEPAD_BUTTON_LEFT_STICK,
1579 SDL_GAMEPAD_BUTTON_RIGHT_STICK
1580 };
1581#define HAT_MASK ((1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT))
1582 static const int hat_map[] = {
1583 0,
1584 (1 << SDL_GAMEPAD_BUTTON_DPAD_UP),
1585 (1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT),
1586 (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT),
1587 (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT),
1588 (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN),
1589 (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT),
1590 (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT),
1591 (1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT),
1592 0,
1593 };
1594 Uint64 match_state = ctx->match_state;
1595 // Update match_state with button bit, then fall through
1596#define SDL_SendJoystickButton(timestamp, joystick, button, down) \
1597 if (button < SDL_arraysize(button_map)) { \
1598 Uint64 button_bit = 1ull << button_map[button]; \
1599 match_state = (match_state & ~button_bit) | (button_bit * (down)); \
1600 } \
1601 SDL_SendJoystickButton(timestamp, joystick, button, down)
1602#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
1603 // Grab high 4 bits of value, then fall through
1604#define AddAxisToMatchState(axis, value) \
1605 { \
1606 match_state = (match_state & ~(0xFull << (4 * axis + 16))) | ((value)&0xF000ull) << (4 * axis + 4); \
1607 }
1608#define SDL_SendJoystickAxis(timestamp, joystick, axis, value) \
1609 if (axis < 4) \
1610 AddAxisToMatchState(axis, value); \
1611 SDL_SendJoystickAxis(timestamp, joystick, axis, value)
1612#endif
1613#endif // SDL_JOYSTICK_RAWINPUT_MATCHING
1614
1615 ULONG data_length = ctx->max_data_length;
1616 int i;
1617 int nbuttons = joystick->nbuttons - (ctx->guide_hack * 1);
1618 int naxes = joystick->naxes - (ctx->trigger_hack * 2);
1619 int nhats = joystick->nhats;
1620 Uint32 button_mask = 0;
1621 Uint64 timestamp = SDL_GetTicksNS();
1622
1623 if (SDL_HidP_GetData(HidP_Input, ctx->data, &data_length, ctx->preparsed_data, (PCHAR)data, size) != HIDP_STATUS_SUCCESS) {
1624 return;
1625 }
1626
1627 for (i = 0; i < nbuttons; ++i) {
1628 HIDP_DATA *item = GetData(ctx->button_indices[i], ctx->data, data_length);
1629 if (item && item->On) {
1630 button_mask |= (1 << i);
1631 }
1632 }
1633 for (i = 0; i < nbuttons; ++i) {
1634 SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, ((button_mask & (1 << i)) != 0));
1635 }
1636
1637 for (i = 0; i < naxes; ++i) {
1638 HIDP_DATA *item = GetData(ctx->axis_indices[i], ctx->data, data_length);
1639 if (item) {
1640 Sint16 axis = (int)(Uint16)item->RawValue - 0x8000;
1641 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, axis);
1642 }
1643 }
1644
1645 for (i = 0; i < nhats; ++i) {
1646 HIDP_DATA *item = GetData(ctx->hat_indices[i], ctx->data, data_length);
1647 if (item) {
1648 Uint8 hat = SDL_HAT_CENTERED;
1649 const Uint8 hat_states[] = {
1650 SDL_HAT_CENTERED,
1651 SDL_HAT_UP,
1652 SDL_HAT_UP | SDL_HAT_RIGHT,
1653 SDL_HAT_RIGHT,
1654 SDL_HAT_DOWN | SDL_HAT_RIGHT,
1655 SDL_HAT_DOWN,
1656 SDL_HAT_DOWN | SDL_HAT_LEFT,
1657 SDL_HAT_LEFT,
1658 SDL_HAT_UP | SDL_HAT_LEFT,
1659 SDL_HAT_CENTERED,
1660 };
1661 ULONG state = item->RawValue;
1662
1663 if (state < SDL_arraysize(hat_states)) {
1664#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1665 match_state = (match_state & ~HAT_MASK) | hat_map[state];
1666#endif
1667 hat = hat_states[state];
1668 }
1669 SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat);
1670 }
1671 }
1672
1673#ifdef SDL_SendJoystickButton
1674#undef SDL_SendJoystickButton
1675#endif
1676#ifdef SDL_SendJoystickAxis
1677#undef SDL_SendJoystickAxis
1678#endif
1679
1680#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
1681#define AddTriggerToMatchState(axis, value) \
1682 { \
1683 int match_axis = axis + SDL_JOYSTICK_RAWINPUT_MATCH_COUNT - joystick->naxes; \
1684 AddAxisToMatchState(match_axis, value); \
1685 }
1686#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
1687
1688 if (ctx->trigger_hack) {
1689 bool has_trigger_data = false;
1690 int left_trigger = joystick->naxes - 2;
1691 int right_trigger = joystick->naxes - 1;
1692
1693#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1694 // Prefer XInput over WindowsGamingInput, it continues to provide data in the background
1695 if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) {
1696 has_trigger_data = true;
1697 }
1698#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
1699
1700#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1701 if (!has_trigger_data && ctx->wgi_correlated) {
1702 has_trigger_data = true;
1703 }
1704#endif // SDL_JOYSTICK_RAWINPUT_WGI
1705
1706#ifndef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
1707 if (!has_trigger_data)
1708#endif
1709 {
1710 HIDP_DATA *item = GetData(ctx->trigger_hack_index, ctx->data, data_length);
1711 if (item) {
1712 Sint16 value = (int)(Uint16)item->RawValue - 0x8000;
1713 Sint16 left_value = (value > 0) ? (value * 2 - 32767) : SDL_MIN_SINT16;
1714 Sint16 right_value = (value < 0) ? (-value * 2 - 32769) : SDL_MIN_SINT16;
1715
1716#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
1717 AddTriggerToMatchState(left_trigger, left_value);
1718 AddTriggerToMatchState(right_trigger, right_value);
1719 if (!has_trigger_data)
1720#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
1721 {
1722 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, left_value);
1723 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, right_value);
1724 }
1725 }
1726 }
1727 }
1728
1729#ifdef AddAxisToMatchState
1730#undef AddAxisToMatchState
1731#endif
1732#ifdef AddTriggerToMatchState
1733#undef AddTriggerToMatchState
1734#endif
1735
1736#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1737 if (ctx->is_xinput) {
1738 ctx->match_state = match_state;
1739 ctx->last_state_packet = SDL_GetTicks();
1740 }
1741#endif
1742}
1743
1744static void RAWINPUT_UpdateOtherAPIs(SDL_Joystick *joystick)
1745{
1746#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1747 RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1748 bool has_trigger_data = false;
1749 bool correlated = false;
1750 WindowsMatchState match_state_xinput;
1751 int guide_button = joystick->nbuttons - 1;
1752 int left_trigger = joystick->naxes - 2;
1753 int right_trigger = joystick->naxes - 1;
1754#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1755 bool xinput_correlated;
1756#endif
1757
1758 RAWINPUT_FillMatchState(&match_state_xinput, ctx->match_state);
1759
1760#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1761#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1762 xinput_correlated = ctx->xinput_correlated;
1763#else
1764 xinput_correlated = false;
1765#endif
1766 // Parallel logic to WINDOWS_XINPUT below
1767 RAWINPUT_UpdateWindowsGamingInput();
1768 if (ctx->wgi_correlated &&
1769 !joystick->low_frequency_rumble && !joystick->high_frequency_rumble &&
1770 !joystick->left_trigger_rumble && !joystick->right_trigger_rumble) {
1771 // We have been previously correlated, ensure we are still matching, see comments in XINPUT section
1772 if (RAWINPUT_WindowsGamingInputSlotMatches(&match_state_xinput, ctx->wgi_slot, xinput_correlated)) {
1773 ctx->wgi_uncorrelate_count = 0;
1774 } else {
1775 ++ctx->wgi_uncorrelate_count;
1776 /* Only un-correlate if this is consistent over multiple Update() calls - the timing of polling/event
1777 pumping can easily cause this to uncorrelate for a frame. 2 seemed reliable in my testing, but
1778 let's set it to 5 to be safe. An incorrect un-correlation will simply result in lower precision
1779 triggers for a frame. */
1780 if (ctx->wgi_uncorrelate_count >= 5) {
1781#ifdef DEBUG_RAWINPUT
1782 SDL_Log("UN-Correlated joystick %d to WindowsGamingInput device #%d", joystick->instance_id, ctx->wgi_slot);
1783#endif
1784 RAWINPUT_MarkWindowsGamingInputSlotFree(ctx->wgi_slot);
1785 ctx->wgi_correlated = false;
1786 ctx->wgi_correlation_count = 0;
1787 // Force release of Guide button, it can't possibly be down on this device now.
1788 /* It gets left down if we were actually correlated incorrectly and it was released on the WindowsGamingInput
1789 device but we didn't get a state packet. */
1790 if (ctx->guide_hack) {
1791 SDL_SendJoystickButton(0, joystick, (Uint8)guide_button, false);
1792 }
1793 }
1794 }
1795 }
1796 if (!ctx->wgi_correlated) {
1797 Uint8 new_correlation_count = 0;
1798 if (RAWINPUT_MissingWindowsGamingInputSlot()) {
1799 Uint8 correlation_id = 0;
1800 WindowsGamingInputGamepadState *slot_idx = NULL;
1801 if (RAWINPUT_GuessWindowsGamingInputSlot(&match_state_xinput, &correlation_id, &slot_idx, xinput_correlated)) {
1802 // we match exactly one WindowsGamingInput device
1803 /* Probably can do without wgi_correlation_count, just check and clear wgi_slot to NULL, unless we need
1804 even more frames to be sure. */
1805 if (ctx->wgi_correlation_count && ctx->wgi_slot == slot_idx) {
1806 // was correlated previously, and still the same device
1807 if (ctx->wgi_correlation_id + 1 == correlation_id) {
1808 // no one else was correlated in the meantime
1809 new_correlation_count = ctx->wgi_correlation_count + 1;
1810 if (new_correlation_count == 2) {
1811 // correlation stayed steady and uncontested across multiple frames, guaranteed match
1812 ctx->wgi_correlated = true;
1813#ifdef DEBUG_RAWINPUT
1814 SDL_Log("Correlated joystick %d to WindowsGamingInput device #%d", joystick->instance_id, slot_idx);
1815#endif
1816 correlated = true;
1817 RAWINPUT_MarkWindowsGamingInputSlotUsed(ctx->wgi_slot, ctx);
1818 // If the generalized Guide button was using us, it doesn't need to anymore
1819 if (guide_button_candidate.joystick == joystick) {
1820 guide_button_candidate.joystick = NULL;
1821 }
1822 if (guide_button_candidate.last_joystick == joystick) {
1823 guide_button_candidate.last_joystick = NULL;
1824 }
1825 }
1826 } else {
1827 // someone else also possibly correlated to this device, start over
1828 new_correlation_count = 1;
1829 }
1830 } else {
1831 // new possible correlation
1832 new_correlation_count = 1;
1833 ctx->wgi_slot = slot_idx;
1834 }
1835 ctx->wgi_correlation_id = correlation_id;
1836 } else {
1837 // Match multiple WindowsGamingInput devices, or none (possibly due to no buttons pressed)
1838 }
1839 }
1840 ctx->wgi_correlation_count = new_correlation_count;
1841 } else {
1842 correlated = true;
1843 }
1844#endif // SDL_JOYSTICK_RAWINPUT_WGI
1845
1846#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1847 // Parallel logic to WINDOWS_GAMING_INPUT above
1848 if (ctx->xinput_enabled) {
1849 RAWINPUT_UpdateXInput();
1850 if (ctx->xinput_correlated &&
1851 !joystick->low_frequency_rumble && !joystick->high_frequency_rumble) {
1852 // We have been previously correlated, ensure we are still matching
1853 /* This is required to deal with two (mostly) un-preventable mis-correlation situations:
1854 A) Since the HID data stream does not provide an initial state (but polling XInput does), if we open
1855 5 controllers (#1-4 XInput mapped, #5 is not), and controller 1 had the A button down (and we don't
1856 know), and the user presses A on controller #5, we'll see exactly 1 controller with A down (#5) and
1857 exactly 1 XInput device with A down (#1), and incorrectly correlate. This code will then un-correlate
1858 when A is released from either controller #1 or #5.
1859 B) Since the app may not open all controllers, we could have a similar situation where only controller #5
1860 is opened, and the user holds A on controllers #1 and #5 simultaneously - again we see only 1 controller
1861 with A down and 1 XInput device with A down, and incorrectly correlate. This should be very unusual
1862 (only when apps do not open all controllers, yet are listening to Guide button presses, yet
1863 for some reason want to ignore guide button presses on the un-opened controllers, yet users are
1864 pressing buttons on the unopened controllers), and will resolve itself when either button is released
1865 and we un-correlate. We could prevent this by processing the state packets for *all* controllers,
1866 even un-opened ones, as that would allow more precise correlation.
1867 */
1868 if (RAWINPUT_XInputSlotMatches(&match_state_xinput, ctx->xinput_slot)) {
1869 ctx->xinput_uncorrelate_count = 0;
1870 } else {
1871 ++ctx->xinput_uncorrelate_count;
1872 /* Only un-correlate if this is consistent over multiple Update() calls - the timing of polling/event
1873 pumping can easily cause this to uncorrelate for a frame. 2 seemed reliable in my testing, but
1874 let's set it to 5 to be safe. An incorrect un-correlation will simply result in lower precision
1875 triggers for a frame. */
1876 if (ctx->xinput_uncorrelate_count >= 5) {
1877#ifdef DEBUG_RAWINPUT
1878 SDL_Log("UN-Correlated joystick %d to XInput device #%d", joystick->instance_id, ctx->xinput_slot);
1879#endif
1880 RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot);
1881 ctx->xinput_correlated = false;
1882 ctx->xinput_correlation_count = 0;
1883 // Force release of Guide button, it can't possibly be down on this device now.
1884 /* It gets left down if we were actually correlated incorrectly and it was released on the XInput
1885 device but we didn't get a state packet. */
1886 if (ctx->guide_hack) {
1887 SDL_SendJoystickButton(0, joystick, (Uint8)guide_button, false);
1888 }
1889 }
1890 }
1891 }
1892 if (!ctx->xinput_correlated) {
1893 Uint8 new_correlation_count = 0;
1894 if (RAWINPUT_MissingXInputSlot()) {
1895 Uint8 correlation_id = 0;
1896 Uint8 slot_idx = 0;
1897 if (RAWINPUT_GuessXInputSlot(&match_state_xinput, &correlation_id, &slot_idx)) {
1898 // we match exactly one XInput device
1899 /* Probably can do without xinput_correlation_count, just check and clear xinput_slot to ANY, unless
1900 we need even more frames to be sure */
1901 if (ctx->xinput_correlation_count && ctx->xinput_slot == slot_idx) {
1902 // was correlated previously, and still the same device
1903 if (ctx->xinput_correlation_id + 1 == correlation_id) {
1904 // no one else was correlated in the meantime
1905 new_correlation_count = ctx->xinput_correlation_count + 1;
1906 if (new_correlation_count == 2) {
1907 // correlation stayed steady and uncontested across multiple frames, guaranteed match
1908 ctx->xinput_correlated = true;
1909#ifdef DEBUG_RAWINPUT
1910 SDL_Log("Correlated joystick %d to XInput device #%d", joystick->instance_id, slot_idx);
1911#endif
1912 correlated = true;
1913 RAWINPUT_MarkXInputSlotUsed(ctx->xinput_slot);
1914 // If the generalized Guide button was using us, it doesn't need to anymore
1915 if (guide_button_candidate.joystick == joystick) {
1916 guide_button_candidate.joystick = NULL;
1917 }
1918 if (guide_button_candidate.last_joystick == joystick) {
1919 guide_button_candidate.last_joystick = NULL;
1920 }
1921 }
1922 } else {
1923 // someone else also possibly correlated to this device, start over
1924 new_correlation_count = 1;
1925 }
1926 } else {
1927 // new possible correlation
1928 new_correlation_count = 1;
1929 ctx->xinput_slot = slot_idx;
1930 }
1931 ctx->xinput_correlation_id = correlation_id;
1932 } else {
1933 // Match multiple XInput devices, or none (possibly due to no buttons pressed)
1934 }
1935 }
1936 ctx->xinput_correlation_count = new_correlation_count;
1937 } else {
1938 correlated = true;
1939 }
1940 }
1941#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
1942
1943 // Poll for trigger data once (not per-state-packet)
1944#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1945 // Prefer XInput over WindowsGamingInput, it continues to provide data in the background
1946 if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) {
1947 RAWINPUT_UpdateXInput();
1948 if (xinput_state[ctx->xinput_slot].connected) {
1949 XINPUT_BATTERY_INFORMATION_EX *battery_info = &xinput_state[ctx->xinput_slot].battery;
1950 Uint64 timestamp;
1951
1952 if (ctx->guide_hack || ctx->trigger_hack) {
1953 timestamp = SDL_GetTicksNS();
1954 } else {
1955 // timestamp won't be used
1956 timestamp = 0;
1957 }
1958
1959 if (ctx->guide_hack) {
1960 bool down = ((xinput_state[ctx->xinput_slot].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE) != 0);
1961 SDL_SendJoystickButton(timestamp, joystick, (Uint8)guide_button, down);
1962 }
1963 if (ctx->trigger_hack) {
1964 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bLeftTrigger * 257) - 32768);
1965 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bRightTrigger * 257) - 32768);
1966 }
1967 has_trigger_data = true;
1968
1969 SDL_PowerState state;
1970 int percent;
1971 switch (battery_info->BatteryType) {
1972 case BATTERY_TYPE_WIRED:
1973 state = SDL_POWERSTATE_CHARGING;
1974 break;
1975 case BATTERY_TYPE_UNKNOWN:
1976 case BATTERY_TYPE_DISCONNECTED:
1977 state = SDL_POWERSTATE_UNKNOWN;
1978 break;
1979 default:
1980 state = SDL_POWERSTATE_ON_BATTERY;
1981 break;
1982 }
1983 switch (battery_info->BatteryLevel) {
1984 case BATTERY_LEVEL_EMPTY:
1985 percent = 10;
1986 break;
1987 case BATTERY_LEVEL_LOW:
1988 percent = 40;
1989 break;
1990 case BATTERY_LEVEL_MEDIUM:
1991 percent = 70;
1992 break;
1993 default:
1994 case BATTERY_LEVEL_FULL:
1995 percent = 100;
1996 break;
1997 }
1998 SDL_SendJoystickPowerInfo(joystick, state, percent);
1999 }
2000 }
2001#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
2002
2003#ifdef SDL_JOYSTICK_RAWINPUT_WGI
2004 if (!has_trigger_data && ctx->wgi_correlated) {
2005 RAWINPUT_UpdateWindowsGamingInput(); // May detect disconnect / cause uncorrelation
2006 if (ctx->wgi_correlated) { // Still connected
2007 struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading *state = &ctx->wgi_slot->state;
2008 Uint64 timestamp;
2009
2010 if (ctx->guide_hack || ctx->trigger_hack) {
2011 timestamp = SDL_GetTicksNS();
2012 } else {
2013 // timestamp won't be used
2014 timestamp = 0;
2015 }
2016
2017 if (ctx->guide_hack) {
2018 bool down = ((state->Buttons & GamepadButtons_GUIDE) != 0);
2019 SDL_SendJoystickButton(timestamp, joystick, (Uint8)guide_button, down);
2020 }
2021 if (ctx->trigger_hack) {
2022 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, (Sint16)(((int)(state->LeftTrigger * SDL_MAX_UINT16)) - 32768));
2023 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, (Sint16)(((int)(state->RightTrigger * SDL_MAX_UINT16)) - 32768));
2024 }
2025 has_trigger_data = true;
2026 }
2027 }
2028#endif // SDL_JOYSTICK_RAWINPUT_WGI
2029
2030 if (!correlated) {
2031 if (!guide_button_candidate.joystick ||
2032 (ctx->last_state_packet && (!guide_button_candidate.last_state_packet ||
2033 ctx->last_state_packet >= guide_button_candidate.last_state_packet))) {
2034 guide_button_candidate.joystick = joystick;
2035 guide_button_candidate.last_state_packet = ctx->last_state_packet;
2036 }
2037 }
2038#endif // SDL_JOYSTICK_RAWINPUT_MATCHING
2039}
2040
2041static void RAWINPUT_JoystickUpdate(SDL_Joystick *joystick)
2042{
2043 RAWINPUT_UpdateOtherAPIs(joystick);
2044}
2045
2046static void RAWINPUT_JoystickClose(SDL_Joystick *joystick)
2047{
2048 RAWINPUT_DeviceContext *ctx = joystick->hwdata;
2049
2050#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
2051 if (guide_button_candidate.joystick == joystick) {
2052 guide_button_candidate.joystick = NULL;
2053 }
2054 if (guide_button_candidate.last_joystick == joystick) {
2055 guide_button_candidate.last_joystick = NULL;
2056 }
2057#endif
2058
2059 if (ctx) {
2060 SDL_RAWINPUT_Device *device;
2061
2062#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
2063 xinput_device_change = true;
2064 if (ctx->xinput_enabled) {
2065 if (ctx->xinput_correlated) {
2066 RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot);
2067 }
2068 WIN_UnloadXInputDLL();
2069 }
2070#endif
2071#ifdef SDL_JOYSTICK_RAWINPUT_WGI
2072 RAWINPUT_QuitWindowsGamingInput(ctx);
2073#endif
2074
2075 device = ctx->device;
2076 if (device) {
2077 SDL_assert(device->joystick == joystick);
2078 device->joystick = NULL;
2079 RAWINPUT_ReleaseDevice(device);
2080 }
2081
2082 SDL_free(ctx->data);
2083 SDL_free(ctx->button_indices);
2084 SDL_free(ctx->axis_indices);
2085 SDL_free(ctx->hat_indices);
2086 SDL_free(ctx);
2087 joystick->hwdata = NULL;
2088 }
2089}
2090
2091bool RAWINPUT_RegisterNotifications(HWND hWnd)
2092{
2093 int i;
2094 RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)];
2095
2096 if (!SDL_RAWINPUT_inited) {
2097 return true;
2098 }
2099
2100 for (i = 0; i < SDL_arraysize(subscribed_devices); i++) {
2101 rid[i].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
2102 rid[i].usUsage = subscribed_devices[i];
2103 rid[i].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; // Receive messages when in background, including device add/remove
2104 rid[i].hwndTarget = hWnd;
2105 }
2106
2107 if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) {
2108 return SDL_SetError("Couldn't register for raw input events");
2109 }
2110 return true;
2111}
2112
2113bool RAWINPUT_UnregisterNotifications(void)
2114{
2115 int i;
2116 RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)];
2117
2118 if (!SDL_RAWINPUT_inited) {
2119 return true;
2120 }
2121
2122 for (i = 0; i < SDL_arraysize(subscribed_devices); i++) {
2123 rid[i].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
2124 rid[i].usUsage = subscribed_devices[i];
2125 rid[i].dwFlags = RIDEV_REMOVE;
2126 rid[i].hwndTarget = NULL;
2127 }
2128
2129 if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) {
2130 return SDL_SetError("Couldn't unregister for raw input events");
2131 }
2132 return true;
2133}
2134
2135LRESULT CALLBACK
2136RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2137{
2138 LRESULT result = -1;
2139
2140 if (SDL_RAWINPUT_inited) {
2141 SDL_LockJoysticks();
2142
2143 switch (msg) {
2144 case WM_INPUT_DEVICE_CHANGE:
2145 {
2146 HANDLE hDevice = (HANDLE)lParam;
2147 switch (wParam) {
2148 case GIDC_ARRIVAL:
2149 RAWINPUT_AddDevice(hDevice);
2150 break;
2151 case GIDC_REMOVAL:
2152 {
2153 SDL_RAWINPUT_Device *device;
2154 device = RAWINPUT_DeviceFromHandle(hDevice);
2155 if (device) {
2156 RAWINPUT_DelDevice(device, true);
2157 }
2158 break;
2159 }
2160 default:
2161 break;
2162 }
2163 }
2164 result = 0;
2165 break;
2166
2167 case WM_INPUT:
2168 {
2169 Uint8 data[sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + USB_PACKET_LENGTH];
2170 UINT buffer_size = SDL_arraysize(data);
2171
2172 if ((int)GetRawInputData((HRAWINPUT)lParam, RID_INPUT, data, &buffer_size, sizeof(RAWINPUTHEADER)) > 0) {
2173 PRAWINPUT raw_input = (PRAWINPUT)data;
2174 SDL_RAWINPUT_Device *device = RAWINPUT_DeviceFromHandle(raw_input->header.hDevice);
2175 if (device) {
2176 SDL_Joystick *joystick = device->joystick;
2177 if (joystick) {
2178 RAWINPUT_HandleStatePacket(joystick, raw_input->data.hid.bRawData, raw_input->data.hid.dwSizeHid);
2179 }
2180 }
2181 }
2182 }
2183 result = 0;
2184 break;
2185 }
2186
2187 SDL_UnlockJoysticks();
2188 }
2189
2190 if (result >= 0) {
2191 return result;
2192 }
2193 return CallWindowProc(DefWindowProc, hWnd, msg, wParam, lParam);
2194}
2195
2196static void RAWINPUT_JoystickQuit(void)
2197{
2198 if (!SDL_RAWINPUT_inited) {
2199 return;
2200 }
2201
2202 RAWINPUT_RemoveDevices();
2203
2204 WIN_UnloadHIDDLL();
2205
2206 SDL_RAWINPUT_inited = false;
2207}
2208
2209static bool RAWINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
2210{
2211 return false;
2212}
2213
2214SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver = {
2215 RAWINPUT_JoystickInit,
2216 RAWINPUT_JoystickGetCount,
2217 RAWINPUT_JoystickDetect,
2218 RAWINPUT_JoystickIsDevicePresent,
2219 RAWINPUT_JoystickGetDeviceName,
2220 RAWINPUT_JoystickGetDevicePath,
2221 RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot,
2222 RAWINPUT_JoystickGetDevicePlayerIndex,
2223 RAWINPUT_JoystickSetDevicePlayerIndex,
2224 RAWINPUT_JoystickGetDeviceGUID,
2225 RAWINPUT_JoystickGetDeviceInstanceID,
2226 RAWINPUT_JoystickOpen,
2227 RAWINPUT_JoystickRumble,
2228 RAWINPUT_JoystickRumbleTriggers,
2229 RAWINPUT_JoystickSetLED,
2230 RAWINPUT_JoystickSendEffect,
2231 RAWINPUT_JoystickSetSensorsEnabled,
2232 RAWINPUT_JoystickUpdate,
2233 RAWINPUT_JoystickClose,
2234 RAWINPUT_JoystickQuit,
2235 RAWINPUT_JoystickGetGamepadMapping
2236};
2237
2238#endif // SDL_JOYSTICK_RAWINPUT
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h
new file mode 100644
index 0000000..b67544b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h
@@ -0,0 +1,32 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22#include "../../core/windows/SDL_windows.h"
23
24// Return true if the RawInput driver is enabled
25extern bool RAWINPUT_IsEnabled(void);
26
27// Registers for input events
28extern int RAWINPUT_RegisterNotifications(HWND hWnd);
29extern int RAWINPUT_UnregisterNotifications(void);
30
31// Returns 0 if message was handled
32extern LRESULT CALLBACK RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c
new file mode 100644
index 0000000..dbc5658
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c
@@ -0,0 +1,1039 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_WGI
24
25#include "../SDL_sysjoystick.h"
26#include "../hidapi/SDL_hidapijoystick_c.h"
27#include "SDL_rawinputjoystick_c.h"
28
29#include "../../core/windows/SDL_windows.h"
30#define COBJMACROS
31#include "windows.gaming.input.h"
32#include <cfgmgr32.h>
33#include <objidlbase.h>
34#include <roapi.h>
35#include <initguid.h>
36
37#ifdef ____FIReference_1_INT32_INTERFACE_DEFINED__
38// MinGW-64 uses __FIReference_1_INT32 instead of Microsoft's __FIReference_1_int
39#define __FIReference_1_int __FIReference_1_INT32
40#define __FIReference_1_int_get_Value __FIReference_1_INT32_get_Value
41#define __FIReference_1_int_Release __FIReference_1_INT32_Release
42#endif
43
44struct joystick_hwdata
45{
46 __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller;
47 __x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller;
48 __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo *battery;
49 __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad;
50 __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration;
51 UINT64 timestamp;
52};
53
54typedef struct WindowsGamingInputControllerState
55{
56 SDL_JoystickID instance_id;
57 __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller;
58 char *name;
59 SDL_GUID guid;
60 SDL_JoystickType type;
61 int steam_virtual_gamepad_slot;
62} WindowsGamingInputControllerState;
63
64typedef HRESULT(WINAPI *CoIncrementMTAUsage_t)(CO_MTA_USAGE_COOKIE *pCookie);
65typedef HRESULT(WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory);
66typedef HRESULT(WINAPI *WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING *string);
67typedef HRESULT(WINAPI *WindowsDeleteString_t)(HSTRING string);
68typedef PCWSTR(WINAPI *WindowsGetStringRawBuffer_t)(HSTRING string, UINT32 *length);
69
70static struct
71{
72 CoIncrementMTAUsage_t CoIncrementMTAUsage;
73 RoGetActivationFactory_t RoGetActivationFactory;
74 WindowsCreateStringReference_t WindowsCreateStringReference;
75 WindowsDeleteString_t WindowsDeleteString;
76 WindowsGetStringRawBuffer_t WindowsGetStringRawBuffer;
77 __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics *controller_statics;
78 __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics *arcade_stick_statics;
79 __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2 *arcade_stick_statics2;
80 __x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics *flight_stick_statics;
81 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics;
82 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2 *gamepad_statics2;
83 __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics *racing_wheel_statics;
84 __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2 *racing_wheel_statics2;
85 EventRegistrationToken controller_added_token;
86 EventRegistrationToken controller_removed_token;
87 int controller_count;
88 WindowsGamingInputControllerState *controllers;
89} wgi;
90
91// WinRT headers in official Windows SDK contain only declarations, and we have to define these GUIDs ourselves.
92// https://stackoverflow.com/a/55605485/1795050
93DEFINE_GUID(IID___FIEventHandler_1_Windows__CGaming__CInput__CRawGameController, 0x00621c22, 0x42e8, 0x529f, 0x92, 0x70, 0x83, 0x6b, 0x32, 0x93, 0x1d, 0x72);
94DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics, 0x5c37b8c8, 0x37b1, 0x4ad8, 0x94, 0x58, 0x20, 0x0f, 0x1a, 0x30, 0x01, 0x8e);
95DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2, 0x52b5d744, 0xbb86, 0x445a, 0xb5, 0x9c, 0x59, 0x6f, 0x0e, 0x2a, 0x49, 0xdf);
96DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics, 0x5514924a, 0xfecc, 0x435e, 0x83, 0xdc, 0x5c, 0xec, 0x8a, 0x18, 0xa5, 0x20);
97DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGameController, 0x1baf6522, 0x5f64, 0x42c5, 0x82, 0x67, 0xb9, 0xfe, 0x22, 0x15, 0xbf, 0xbd);
98DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo, 0xdcecc681, 0x3963, 0x4da6, 0x95, 0x5d, 0x55, 0x3f, 0x3b, 0x6f, 0x61, 0x61);
99DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, 0x8bbce529, 0xd49c, 0x39e9, 0x95, 0x60, 0xe4, 0x7d, 0xde, 0x96, 0xb7, 0xc8);
100DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2, 0x42676dc5, 0x0856, 0x47c4, 0x92, 0x13, 0xb3, 0x95, 0x50, 0x4c, 0x3a, 0x3c);
101DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics, 0x3ac12cd5, 0x581b, 0x4936, 0x9f, 0x94, 0x69, 0xf1, 0xe6, 0x51, 0x4c, 0x7d);
102DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2, 0xe666bcaa, 0xedfd, 0x4323, 0xa9, 0xf6, 0x3c, 0x38, 0x40, 0x48, 0xd1, 0xed);
103DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, 0x7cad6d91, 0xa7e1, 0x4f71, 0x9a, 0x78, 0x33, 0xe9, 0xc5, 0xdf, 0xea, 0x62);
104DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, 0x43c0c035, 0xbb73, 0x4756, 0xa7, 0x87, 0x3e, 0xd6, 0xbe, 0xa6, 0x17, 0xbd);
105DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics, 0xeb8d0792, 0xe95a, 0x4b19, 0xaf, 0xc7, 0x0a, 0x59, 0xf8, 0xbf, 0x75, 0x9e);
106
107extern bool SDL_XINPUT_Enabled(void);
108
109
110static bool SDL_IsXInputDevice(Uint16 vendor, Uint16 product, const char *name)
111{
112#if defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT)
113 PRAWINPUTDEVICELIST raw_devices = NULL;
114 UINT i, raw_device_count = 0;
115 LONG vidpid = MAKELONG(vendor, product);
116
117 // XInput and RawInput backends will pick up XInput-compatible devices
118 if (!SDL_XINPUT_Enabled()
119#ifdef SDL_JOYSTICK_RAWINPUT
120 && !RAWINPUT_IsEnabled()
121#endif
122 ) {
123 return false;
124 }
125
126 // Sometimes we'll get a Windows.Gaming.Input callback before the raw input device is even in the list,
127 // so try to do some checks up front to catch these cases.
128 if (SDL_IsJoystickXboxOne(vendor, product) ||
129 (name && SDL_strncmp(name, "Xbox ", 5) == 0)) {
130 return true;
131 }
132
133 // Go through RAWINPUT (WinXP and later) to find HID devices.
134 if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) {
135 return false; // oh well.
136 }
137
138 raw_devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * raw_device_count);
139 if (!raw_devices) {
140 return false;
141 }
142
143 raw_device_count = GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST));
144 if (raw_device_count == (UINT)-1) {
145 SDL_free(raw_devices);
146 raw_devices = NULL;
147 return false; // oh well.
148 }
149
150 for (i = 0; i < raw_device_count; i++) {
151 RID_DEVICE_INFO rdi;
152 char devName[MAX_PATH] = { 0 };
153 UINT rdiSize = sizeof(rdi);
154 UINT nameSize = SDL_arraysize(devName);
155 DEVINST devNode;
156 char devVidPidString[32];
157 int j;
158
159 rdi.cbSize = sizeof(rdi);
160
161 if ((raw_devices[i].dwType != RIM_TYPEHID) ||
162 (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == ((UINT)-1)) ||
163 (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) == ((UINT)-1)) ||
164 (SDL_strstr(devName, "IG_") == NULL)) {
165 // Skip non-XInput devices
166 continue;
167 }
168
169 // First check for a simple VID/PID match. This will work for Xbox 360 controllers.
170 if (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == vidpid) {
171 SDL_free(raw_devices);
172 return true;
173 }
174
175 /* For Xbox One controllers, Microsoft doesn't propagate the VID/PID down to the HID stack.
176 * We'll have to walk the device tree upwards searching for a match for our VID/PID. */
177
178 // Make sure the device interface string is something we know how to parse
179 // Example: \\?\HID#VID_045E&PID_02FF&IG_00#9&2c203035&2&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
180 if ((SDL_strstr(devName, "\\\\?\\") != devName) || (SDL_strstr(devName, "#{") == NULL)) {
181 continue;
182 }
183
184 // Unescape the backslashes in the string and terminate before the GUID portion
185 for (j = 0; devName[j] != '\0'; j++) {
186 if (devName[j] == '#') {
187 if (devName[j + 1] == '{') {
188 devName[j] = '\0';
189 break;
190 } else {
191 devName[j] = '\\';
192 }
193 }
194 }
195
196 /* We'll be left with a string like this: \\?\HID\VID_045E&PID_02FF&IG_00\9&2c203035&2&0000
197 * Simply skip the \\?\ prefix and we'll have a properly formed device instance ID */
198 if (CM_Locate_DevNodeA(&devNode, &devName[4], CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) {
199 continue;
200 }
201
202 (void)SDL_snprintf(devVidPidString, sizeof(devVidPidString), "VID_%04X&PID_%04X", vendor, product);
203
204 while (CM_Get_Parent(&devNode, devNode, 0) == CR_SUCCESS) {
205 char deviceId[MAX_DEVICE_ID_LEN];
206
207 if ((CM_Get_Device_IDA(devNode, deviceId, SDL_arraysize(deviceId), 0) == CR_SUCCESS) &&
208 (SDL_strstr(deviceId, devVidPidString) != NULL)) {
209 // The VID/PID matched a parent device
210 SDL_free(raw_devices);
211 return true;
212 }
213 }
214 }
215
216 SDL_free(raw_devices);
217#endif // SDL_JOYSTICK_XINPUT || SDL_JOYSTICK_RAWINPUT
218
219 return false;
220}
221
222static void WGI_LoadRawGameControllerStatics(void)
223{
224 HRESULT hr;
225 HSTRING_HEADER class_name_header;
226 HSTRING class_name;
227
228 hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_RawGameController, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_RawGameController), &class_name_header, &class_name);
229 if (SUCCEEDED(hr)) {
230 hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics, (void **)&wgi.controller_statics);
231 if (!SUCCEEDED(hr)) {
232 WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IRawGameControllerStatics", hr);
233 }
234 }
235}
236
237static void WGI_LoadOtherControllerStatics(void)
238{
239 HRESULT hr;
240 HSTRING_HEADER class_name_header;
241 HSTRING class_name;
242
243 if (!wgi.arcade_stick_statics) {
244 hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_ArcadeStick, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_ArcadeStick), &class_name_header, &class_name);
245 if (SUCCEEDED(hr)) {
246 hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics, (void **)&wgi.arcade_stick_statics);
247 if (SUCCEEDED(hr)) {
248 __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_QueryInterface(wgi.arcade_stick_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2, (void **)&wgi.arcade_stick_statics2);
249 } else {
250 WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IArcadeStickStatics", hr);
251 }
252 }
253 }
254
255 if (!wgi.flight_stick_statics) {
256 hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_FlightStick, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_FlightStick), &class_name_header, &class_name);
257 if (SUCCEEDED(hr)) {
258 hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics, (void **)&wgi.flight_stick_statics);
259 if (!SUCCEEDED(hr)) {
260 WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IFlightStickStatics", hr);
261 }
262 }
263 }
264
265 if (!wgi.gamepad_statics) {
266 hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_Gamepad), &class_name_header, &class_name);
267 if (SUCCEEDED(hr)) {
268 hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, (void **)&wgi.gamepad_statics);
269 if (SUCCEEDED(hr)) {
270 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_QueryInterface(wgi.gamepad_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2, (void **)&wgi.gamepad_statics2);
271 } else {
272 WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IGamepadStatics", hr);
273 }
274 }
275 }
276
277 if (!wgi.racing_wheel_statics) {
278 hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_RacingWheel, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_RacingWheel), &class_name_header, &class_name);
279 if (SUCCEEDED(hr)) {
280 hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics, (void **)&wgi.racing_wheel_statics);
281 if (SUCCEEDED(hr)) {
282 __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics_QueryInterface(wgi.racing_wheel_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2, (void **)&wgi.racing_wheel_statics2);
283 } else {
284 WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IRacingWheelStatics", hr);
285 }
286 }
287 }
288}
289
290static SDL_JoystickType GetGameControllerType(__x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller)
291{
292 __x_ABI_CWindows_CGaming_CInput_CIArcadeStick *arcade_stick = NULL;
293 __x_ABI_CWindows_CGaming_CInput_CIFlightStick *flight_stick = NULL;
294 __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad = NULL;
295 __x_ABI_CWindows_CGaming_CInput_CIRacingWheel *racing_wheel = NULL;
296
297 /* Wait to initialize these interfaces until we need them.
298 * Initializing the gamepad interface will switch Bluetooth PS4 controllers into enhanced mode, breaking DirectInput
299 */
300 WGI_LoadOtherControllerStatics();
301
302 if (wgi.gamepad_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, game_controller, &gamepad)) && gamepad) {
303 __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad);
304 return SDL_JOYSTICK_TYPE_GAMEPAD;
305 }
306
307 if (wgi.arcade_stick_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2_FromGameController(wgi.arcade_stick_statics2, game_controller, &arcade_stick)) && arcade_stick) {
308 __x_ABI_CWindows_CGaming_CInput_CIArcadeStick_Release(arcade_stick);
309 return SDL_JOYSTICK_TYPE_ARCADE_STICK;
310 }
311
312 if (wgi.flight_stick_statics && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics_FromGameController(wgi.flight_stick_statics, game_controller, &flight_stick)) && flight_stick) {
313 __x_ABI_CWindows_CGaming_CInput_CIFlightStick_Release(flight_stick);
314 return SDL_JOYSTICK_TYPE_FLIGHT_STICK;
315 }
316
317 if (wgi.racing_wheel_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_FromGameController(wgi.racing_wheel_statics2, game_controller, &racing_wheel)) && racing_wheel) {
318 __x_ABI_CWindows_CGaming_CInput_CIRacingWheel_Release(racing_wheel);
319 return SDL_JOYSTICK_TYPE_WHEEL;
320 }
321
322 return SDL_JOYSTICK_TYPE_UNKNOWN;
323}
324
325typedef struct RawGameControllerDelegate
326{
327 __FIEventHandler_1_Windows__CGaming__CInput__CRawGameController iface;
328 SDL_AtomicInt refcount;
329} RawGameControllerDelegate;
330
331static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_QueryInterface(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, REFIID riid, void **ppvObject)
332{
333 if (!ppvObject) {
334 return E_INVALIDARG;
335 }
336
337 *ppvObject = NULL;
338 if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &IID_IAgileObject) || WIN_IsEqualIID(riid, &IID___FIEventHandler_1_Windows__CGaming__CInput__CRawGameController)) {
339 *ppvObject = This;
340 __FIEventHandler_1_Windows__CGaming__CInput__CRawGameController_AddRef(This);
341 return S_OK;
342 } else if (WIN_IsEqualIID(riid, &IID_IMarshal)) {
343 // This seems complicated. Let's hope it doesn't happen.
344 return E_OUTOFMEMORY;
345 } else {
346 return E_NOINTERFACE;
347 }
348}
349
350static ULONG STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_AddRef(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This)
351{
352 RawGameControllerDelegate *self = (RawGameControllerDelegate *)This;
353 return SDL_AddAtomicInt(&self->refcount, 1) + 1UL;
354}
355
356static ULONG STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_Release(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This)
357{
358 RawGameControllerDelegate *self = (RawGameControllerDelegate *)This;
359 int rc = SDL_AddAtomicInt(&self->refcount, -1) - 1;
360 // Should never free the static delegate objects
361 SDL_assert(rc > 0);
362 return rc;
363}
364
365static int GetSteamVirtualGamepadSlot(__x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller, Uint16 vendor_id, Uint16 product_id)
366{
367 int slot = -1;
368
369 if (vendor_id == USB_VENDOR_VALVE &&
370 product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
371 __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL;
372 HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2);
373 if (SUCCEEDED(hr)) {
374 HSTRING hString;
375 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_NonRoamableId(controller2, &hString);
376 if (SUCCEEDED(hr)) {
377 PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL);
378 if (string) {
379 char *id = WIN_StringToUTF8W(string);
380 if (id) {
381 (void)SDL_sscanf(id, "{wgi/nrid/:steam-%*X&%*X&%*X#%d#%*u}", &slot);
382 SDL_free(id);
383 }
384 }
385 wgi.WindowsDeleteString(hString);
386 }
387 __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2);
388 }
389 }
390 return slot;
391}
392
393static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController *e)
394{
395 HRESULT hr;
396 __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL;
397
398 SDL_LockJoysticks();
399
400 // We can get delayed calls to InvokeAdded() after WGI_JoystickQuit()
401 if (SDL_JoysticksQuitting() || !SDL_JoysticksInitialized()) {
402 SDL_UnlockJoysticks();
403 return S_OK;
404 }
405
406 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, (void **)&controller);
407 if (SUCCEEDED(hr)) {
408 char *name = NULL;
409 Uint16 bus = SDL_HARDWARE_BUS_USB;
410 Uint16 vendor = 0;
411 Uint16 product = 0;
412 Uint16 version = 0;
413 SDL_JoystickType type = SDL_JOYSTICK_TYPE_UNKNOWN;
414 __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL;
415 __x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller = NULL;
416 bool ignore_joystick = false;
417
418 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_HardwareVendorId(controller, &vendor);
419 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_HardwareProductId(controller, &product);
420
421 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameController, (void **)&game_controller);
422 if (SUCCEEDED(hr)) {
423 boolean wireless = 0;
424 hr = __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(game_controller, &wireless);
425 if (SUCCEEDED(hr) && wireless) {
426 bus = SDL_HARDWARE_BUS_BLUETOOTH;
427
428 // Fixup for Wireless Xbox 360 Controller
429 if (product == 0) {
430 vendor = USB_VENDOR_MICROSOFT;
431 product = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
432 }
433 }
434
435 __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(game_controller);
436 }
437
438 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2);
439 if (SUCCEEDED(hr)) {
440 HSTRING hString;
441 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_DisplayName(controller2, &hString);
442 if (SUCCEEDED(hr)) {
443 PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL);
444 if (string) {
445 name = WIN_StringToUTF8W(string);
446 }
447 wgi.WindowsDeleteString(hString);
448 }
449 __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2);
450 }
451 if (!name) {
452 name = SDL_strdup("");
453 }
454
455 if (!ignore_joystick && SDL_ShouldIgnoreJoystick(vendor, product, version, name)) {
456 ignore_joystick = true;
457 }
458
459 if (!ignore_joystick && SDL_JoystickHandledByAnotherDriver(&SDL_WGI_JoystickDriver, vendor, product, version, name)) {
460 ignore_joystick = true;
461 }
462
463 if (!ignore_joystick && SDL_IsXInputDevice(vendor, product, name)) {
464 // This hasn't been detected by the RAWINPUT driver yet, but it will be picked up later.
465 ignore_joystick = true;
466 }
467
468 if (!ignore_joystick) {
469 // New device, add it
470 WindowsGamingInputControllerState *controllers = SDL_realloc(wgi.controllers, sizeof(wgi.controllers[0]) * (wgi.controller_count + 1));
471 if (controllers) {
472 WindowsGamingInputControllerState *state = &controllers[wgi.controller_count];
473 SDL_JoystickID joystickID = SDL_GetNextObjectID();
474
475 if (game_controller) {
476 type = GetGameControllerType(game_controller);
477 }
478
479 SDL_zerop(state);
480 state->instance_id = joystickID;
481 state->controller = controller;
482 state->name = name;
483 state->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, NULL, name, 'w', (Uint8)type);
484 state->type = type;
485 state->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(controller, vendor, product);
486
487 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(controller);
488
489 ++wgi.controller_count;
490 wgi.controllers = controllers;
491
492 SDL_PrivateJoystickAdded(joystickID);
493 } else {
494 SDL_free(name);
495 }
496 } else {
497 SDL_free(name);
498 }
499
500 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller);
501 }
502
503 SDL_UnlockJoysticks();
504
505 return S_OK;
506}
507
508static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeRemoved(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController *e)
509{
510 HRESULT hr;
511 __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL;
512
513 SDL_LockJoysticks();
514
515 // Can we get delayed calls to InvokeRemoved() after WGI_JoystickQuit()?
516 if (!SDL_JoysticksInitialized()) {
517 SDL_UnlockJoysticks();
518 return S_OK;
519 }
520
521 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, (void **)&controller);
522 if (SUCCEEDED(hr)) {
523 int i;
524
525 for (i = 0; i < wgi.controller_count; i++) {
526 if (wgi.controllers[i].controller == controller) {
527 WindowsGamingInputControllerState *state = &wgi.controllers[i];
528 SDL_JoystickID joystickID = state->instance_id;
529
530 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(state->controller);
531
532 SDL_free(state->name);
533
534 --wgi.controller_count;
535 if (i < wgi.controller_count) {
536 SDL_memmove(&wgi.controllers[i], &wgi.controllers[i + 1], (wgi.controller_count - i) * sizeof(wgi.controllers[i]));
537 }
538
539 SDL_PrivateJoystickRemoved(joystickID);
540 break;
541 }
542 }
543
544 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller);
545 }
546
547 SDL_UnlockJoysticks();
548
549 return S_OK;
550}
551
552#ifdef _MSC_VER
553#pragma warning(push)
554#pragma warning(disable : 4028) // formal parameter 3 different from declaration, when using older buggy WGI headers
555#pragma warning(disable : 4113) // formal parameter 3 different from declaration (a more specific warning added in VS 2022), when using older buggy WGI headers
556#endif
557
558static __FIEventHandler_1_Windows__CGaming__CInput__CRawGameControllerVtbl controller_added_vtbl = {
559 IEventHandler_CRawGameControllerVtbl_QueryInterface,
560 IEventHandler_CRawGameControllerVtbl_AddRef,
561 IEventHandler_CRawGameControllerVtbl_Release,
562 IEventHandler_CRawGameControllerVtbl_InvokeAdded
563};
564static RawGameControllerDelegate controller_added = {
565 { &controller_added_vtbl },
566 { 1 }
567};
568
569static __FIEventHandler_1_Windows__CGaming__CInput__CRawGameControllerVtbl controller_removed_vtbl = {
570 IEventHandler_CRawGameControllerVtbl_QueryInterface,
571 IEventHandler_CRawGameControllerVtbl_AddRef,
572 IEventHandler_CRawGameControllerVtbl_Release,
573 IEventHandler_CRawGameControllerVtbl_InvokeRemoved
574};
575static RawGameControllerDelegate controller_removed = {
576 { &controller_removed_vtbl },
577 { 1 }
578};
579
580#ifdef _MSC_VER
581#pragma warning(pop)
582#endif
583
584static bool WGI_JoystickInit(void)
585{
586 HRESULT hr;
587
588 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) {
589 return true;
590 }
591
592 if (FAILED(WIN_RoInitialize())) {
593 return SDL_SetError("RoInitialize() failed");
594 }
595
596#define RESOLVE(x) wgi.x = (x##_t)WIN_LoadComBaseFunction(#x); if (!wgi.x) return WIN_SetError("GetProcAddress failed for " #x);
597 RESOLVE(CoIncrementMTAUsage);
598 RESOLVE(RoGetActivationFactory);
599 RESOLVE(WindowsCreateStringReference);
600 RESOLVE(WindowsDeleteString);
601 RESOLVE(WindowsGetStringRawBuffer);
602#undef RESOLVE
603
604 {
605 /* There seems to be a bug in Windows where a dependency of WGI can be unloaded from memory prior to WGI itself.
606 * This results in Windows_Gaming_Input!GameController::~GameController() invoking an unloaded DLL and crashing.
607 * As a workaround, we will keep a reference to the MTA to prevent COM from unloading DLLs later.
608 * See https://github.com/libsdl-org/SDL/issues/5552 for more details.
609 */
610 static CO_MTA_USAGE_COOKIE cookie = NULL;
611 if (!cookie) {
612 hr = wgi.CoIncrementMTAUsage(&cookie);
613 if (FAILED(hr)) {
614 return WIN_SetErrorFromHRESULT("CoIncrementMTAUsage() failed", hr);
615 }
616 }
617 }
618
619 WGI_LoadRawGameControllerStatics();
620
621 if (wgi.controller_statics) {
622 __FIVectorView_1_Windows__CGaming__CInput__CRawGameController *controllers;
623
624 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerAdded(wgi.controller_statics, &controller_added.iface, &wgi.controller_added_token);
625 if (!SUCCEEDED(hr)) {
626 WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IRawGameControllerStatics.add_RawGameControllerAdded failed", hr);
627 }
628
629 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerRemoved(wgi.controller_statics, &controller_removed.iface, &wgi.controller_removed_token);
630 if (!SUCCEEDED(hr)) {
631 WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IRawGameControllerStatics.add_RawGameControllerRemoved failed", hr);
632 }
633
634 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_get_RawGameControllers(wgi.controller_statics, &controllers);
635 if (SUCCEEDED(hr)) {
636 unsigned i, count = 0;
637
638 hr = __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_get_Size(controllers, &count);
639 if (SUCCEEDED(hr)) {
640 for (i = 0; i < count; ++i) {
641 __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL;
642
643 hr = __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_GetAt(controllers, i, &controller);
644 if (SUCCEEDED(hr) && controller) {
645 IEventHandler_CRawGameControllerVtbl_InvokeAdded(&controller_added.iface, NULL, controller);
646 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller);
647 }
648 }
649 }
650
651 __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_Release(controllers);
652 }
653 }
654
655 return true;
656}
657
658static int WGI_JoystickGetCount(void)
659{
660 return wgi.controller_count;
661}
662
663static void WGI_JoystickDetect(void)
664{
665}
666
667static bool WGI_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
668{
669 // We don't override any other drivers
670 return false;
671}
672
673static const char *WGI_JoystickGetDeviceName(int device_index)
674{
675 return wgi.controllers[device_index].name;
676}
677
678static const char *WGI_JoystickGetDevicePath(int device_index)
679{
680 return NULL;
681}
682
683static int WGI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
684{
685 return wgi.controllers[device_index].steam_virtual_gamepad_slot;
686}
687
688static int WGI_JoystickGetDevicePlayerIndex(int device_index)
689{
690 return false;
691}
692
693static void WGI_JoystickSetDevicePlayerIndex(int device_index, int player_index)
694{
695}
696
697static SDL_GUID WGI_JoystickGetDeviceGUID(int device_index)
698{
699 return wgi.controllers[device_index].guid;
700}
701
702static SDL_JoystickID WGI_JoystickGetDeviceInstanceID(int device_index)
703{
704 return wgi.controllers[device_index].instance_id;
705}
706
707static bool WGI_JoystickOpen(SDL_Joystick *joystick, int device_index)
708{
709 WindowsGamingInputControllerState *state = &wgi.controllers[device_index];
710 struct joystick_hwdata *hwdata;
711 boolean wireless = false;
712
713 hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata));
714 if (!hwdata) {
715 return false;
716 }
717 joystick->hwdata = hwdata;
718
719 hwdata->controller = state->controller;
720 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(hwdata->controller);
721 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameController, (void **)&hwdata->game_controller);
722 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo, (void **)&hwdata->battery);
723
724 if (wgi.gamepad_statics2) {
725 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, hwdata->game_controller, &hwdata->gamepad);
726 }
727
728 if (hwdata->game_controller) {
729 __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(hwdata->game_controller, &wireless);
730 }
731
732 // Initialize the joystick capabilities
733 if (wireless) {
734 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
735 } else {
736 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
737 }
738 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_ButtonCount(hwdata->controller, &joystick->nbuttons);
739 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_AxisCount(hwdata->controller, &joystick->naxes);
740 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_SwitchCount(hwdata->controller, &joystick->nhats);
741
742 if (hwdata->gamepad) {
743 // FIXME: Can WGI even tell us if trigger rumble is supported?
744 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
745 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
746 }
747 return true;
748}
749
750static bool WGI_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
751{
752 struct joystick_hwdata *hwdata = joystick->hwdata;
753
754 if (hwdata->gamepad) {
755 HRESULT hr;
756
757 // Note: reusing partially filled vibration data struct
758 hwdata->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16;
759 hwdata->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16;
760 hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(hwdata->gamepad, hwdata->vibration);
761 if (SUCCEEDED(hr)) {
762 return true;
763 } else {
764 return WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IGamepad.put_Vibration failed", hr);
765 }
766 } else {
767 return SDL_Unsupported();
768 }
769}
770
771static bool WGI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
772{
773 struct joystick_hwdata *hwdata = joystick->hwdata;
774
775 if (hwdata->gamepad) {
776 HRESULT hr;
777
778 // Note: reusing partially filled vibration data struct
779 hwdata->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16;
780 hwdata->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16;
781 hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(hwdata->gamepad, hwdata->vibration);
782 if (SUCCEEDED(hr)) {
783 return true;
784 } else {
785 return WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IGamepad.put_Vibration failed", hr);
786 }
787 } else {
788 return SDL_Unsupported();
789 }
790}
791
792static bool WGI_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
793{
794 return SDL_Unsupported();
795}
796
797static bool WGI_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
798{
799 return SDL_Unsupported();
800}
801
802static bool WGI_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
803{
804 return SDL_Unsupported();
805}
806
807static Uint8 ConvertHatValue(__x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition value)
808{
809 switch (value) {
810 case GameControllerSwitchPosition_Up:
811 return SDL_HAT_UP;
812 case GameControllerSwitchPosition_UpRight:
813 return SDL_HAT_RIGHTUP;
814 case GameControllerSwitchPosition_Right:
815 return SDL_HAT_RIGHT;
816 case GameControllerSwitchPosition_DownRight:
817 return SDL_HAT_RIGHTDOWN;
818 case GameControllerSwitchPosition_Down:
819 return SDL_HAT_DOWN;
820 case GameControllerSwitchPosition_DownLeft:
821 return SDL_HAT_LEFTDOWN;
822 case GameControllerSwitchPosition_Left:
823 return SDL_HAT_LEFT;
824 case GameControllerSwitchPosition_UpLeft:
825 return SDL_HAT_LEFTUP;
826 default:
827 return SDL_HAT_CENTERED;
828 }
829}
830
831static void WGI_JoystickUpdate(SDL_Joystick *joystick)
832{
833 struct joystick_hwdata *hwdata = joystick->hwdata;
834 HRESULT hr;
835 UINT32 nbuttons = SDL_min(joystick->nbuttons, SDL_MAX_UINT8);
836 boolean *buttons = NULL;
837 UINT32 nhats = SDL_min(joystick->nhats, SDL_MAX_UINT8);
838 __x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition *hats = NULL;
839 UINT32 naxes = SDL_min(joystick->naxes, SDL_MAX_UINT8);
840 DOUBLE *axes = NULL;
841 UINT64 timestamp;
842
843 if (nbuttons > 0) {
844 buttons = SDL_stack_alloc(boolean, nbuttons);
845 }
846 if (nhats > 0) {
847 hats = SDL_stack_alloc(__x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition, nhats);
848 }
849 if (naxes > 0) {
850 axes = SDL_stack_alloc(DOUBLE, naxes);
851 }
852
853 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_GetCurrentReading(hwdata->controller, nbuttons, buttons, nhats, hats, naxes, axes, &timestamp);
854 if (SUCCEEDED(hr) && (!timestamp || timestamp != hwdata->timestamp)) {
855 UINT32 i;
856 bool all_zero = false;
857
858 hwdata->timestamp = timestamp;
859
860 // The axes are all zero when the application loses focus
861 if (naxes > 0) {
862 all_zero = true;
863 for (i = 0; i < naxes; ++i) {
864 if (axes[i] != 0.0f) {
865 all_zero = false;
866 break;
867 }
868 }
869 }
870 if (all_zero) {
871 SDL_PrivateJoystickForceRecentering(joystick);
872 } else {
873 // FIXME: What units are the timestamp we get from GetCurrentReading()?
874 timestamp = SDL_GetTicksNS();
875 for (i = 0; i < nbuttons; ++i) {
876 SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, buttons[i]);
877 }
878 for (i = 0; i < nhats; ++i) {
879 SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, ConvertHatValue(hats[i]));
880 }
881 for (i = 0; i < naxes; ++i) {
882 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, (Sint16)((int)(axes[i] * 65535) - 32768));
883 }
884 }
885 }
886
887 SDL_stack_free(buttons);
888 SDL_stack_free(hats);
889 SDL_stack_free(axes);
890
891 if (hwdata->battery) {
892 __x_ABI_CWindows_CDevices_CPower_CIBatteryReport *report = NULL;
893
894 hr = __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_TryGetBatteryReport(hwdata->battery, &report);
895 if (SUCCEEDED(hr) && report) {
896 SDL_PowerState state = SDL_POWERSTATE_UNKNOWN;
897 int percent = 0;
898 __x_ABI_CWindows_CSystem_CPower_CBatteryStatus status;
899 int full_capacity = 0, curr_capacity = 0;
900 __FIReference_1_int *full_capacityP, *curr_capacityP;
901
902 hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_Status(report, &status);
903 if (SUCCEEDED(hr)) {
904 switch (status) {
905 case BatteryStatus_NotPresent:
906 state = SDL_POWERSTATE_NO_BATTERY;
907 break;
908 case BatteryStatus_Discharging:
909 state = SDL_POWERSTATE_ON_BATTERY;
910 break;
911 case BatteryStatus_Idle:
912 state = SDL_POWERSTATE_CHARGED;
913 break;
914 case BatteryStatus_Charging:
915 state = SDL_POWERSTATE_CHARGING;
916 break;
917 default:
918 state = SDL_POWERSTATE_UNKNOWN;
919 break;
920 }
921 }
922
923 hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_FullChargeCapacityInMilliwattHours(report, &full_capacityP);
924 if (SUCCEEDED(hr)) {
925 __FIReference_1_int_get_Value(full_capacityP, &full_capacity);
926 __FIReference_1_int_Release(full_capacityP);
927 }
928
929 hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_RemainingCapacityInMilliwattHours(report, &curr_capacityP);
930 if (SUCCEEDED(hr)) {
931 __FIReference_1_int_get_Value(curr_capacityP, &curr_capacity);
932 __FIReference_1_int_Release(curr_capacityP);
933 }
934
935 if (full_capacity > 0) {
936 percent = (int)SDL_roundf(((float)curr_capacity / full_capacity) * 100.0f);
937 }
938
939 SDL_SendJoystickPowerInfo(joystick, state, percent);
940
941 __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_Release(report);
942 }
943 }
944}
945
946static void WGI_JoystickClose(SDL_Joystick *joystick)
947{
948 struct joystick_hwdata *hwdata = joystick->hwdata;
949
950 if (hwdata) {
951 if (hwdata->controller) {
952 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(hwdata->controller);
953 }
954 if (hwdata->game_controller) {
955 __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(hwdata->game_controller);
956 }
957 if (hwdata->battery) {
958 __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_Release(hwdata->battery);
959 }
960 if (hwdata->gamepad) {
961 __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(hwdata->gamepad);
962 }
963 SDL_free(hwdata);
964 }
965 joystick->hwdata = NULL;
966}
967
968static void WGI_JoystickQuit(void)
969{
970 if (wgi.controller_statics) {
971 while (wgi.controller_count > 0) {
972 IEventHandler_CRawGameControllerVtbl_InvokeRemoved(&controller_removed.iface, NULL, wgi.controllers[wgi.controller_count - 1].controller);
973 }
974 if (wgi.controllers) {
975 SDL_free(wgi.controllers);
976 }
977
978 if (wgi.arcade_stick_statics) {
979 __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_Release(wgi.arcade_stick_statics);
980 }
981 if (wgi.arcade_stick_statics2) {
982 __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2_Release(wgi.arcade_stick_statics2);
983 }
984 if (wgi.flight_stick_statics) {
985 __x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics_Release(wgi.flight_stick_statics);
986 }
987 if (wgi.gamepad_statics) {
988 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(wgi.gamepad_statics);
989 }
990 if (wgi.gamepad_statics2) {
991 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_Release(wgi.gamepad_statics2);
992 }
993 if (wgi.racing_wheel_statics) {
994 __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics_Release(wgi.racing_wheel_statics);
995 }
996 if (wgi.racing_wheel_statics2) {
997 __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_Release(wgi.racing_wheel_statics2);
998 }
999
1000 __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerAdded(wgi.controller_statics, wgi.controller_added_token);
1001 __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerRemoved(wgi.controller_statics, wgi.controller_removed_token);
1002 __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_Release(wgi.controller_statics);
1003 }
1004
1005 WIN_RoUninitialize();
1006
1007 SDL_zero(wgi);
1008}
1009
1010static bool WGI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
1011{
1012 return false;
1013}
1014
1015SDL_JoystickDriver SDL_WGI_JoystickDriver = {
1016 WGI_JoystickInit,
1017 WGI_JoystickGetCount,
1018 WGI_JoystickDetect,
1019 WGI_JoystickIsDevicePresent,
1020 WGI_JoystickGetDeviceName,
1021 WGI_JoystickGetDevicePath,
1022 WGI_JoystickGetDeviceSteamVirtualGamepadSlot,
1023 WGI_JoystickGetDevicePlayerIndex,
1024 WGI_JoystickSetDevicePlayerIndex,
1025 WGI_JoystickGetDeviceGUID,
1026 WGI_JoystickGetDeviceInstanceID,
1027 WGI_JoystickOpen,
1028 WGI_JoystickRumble,
1029 WGI_JoystickRumbleTriggers,
1030 WGI_JoystickSetLED,
1031 WGI_JoystickSendEffect,
1032 WGI_JoystickSetSensorsEnabled,
1033 WGI_JoystickUpdate,
1034 WGI_JoystickClose,
1035 WGI_JoystickQuit,
1036 WGI_JoystickGetGamepadMapping
1037};
1038
1039#endif // SDL_JOYSTICK_WGI
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c
new file mode 100644
index 0000000..e7fbfcb
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c
@@ -0,0 +1,693 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT)
24
25/* DirectInput joystick driver; written by Glenn Maynard, based on Andrei de
26 * A. Formiga's WINMM driver.
27 *
28 * Hats and sliders are completely untested; the app I'm writing this for mostly
29 * doesn't use them and I don't own any joysticks with them.
30 *
31 * We don't bother to use event notification here. It doesn't seem to work
32 * with polled devices, and it's fine to call IDirectInputDevice8_GetDeviceData and
33 * let it return 0 events. */
34
35#include "../SDL_sysjoystick.h"
36#include "../../thread/SDL_systhread.h"
37#include "../../core/windows/SDL_windows.h"
38#include "../../core/windows/SDL_hid.h"
39#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
40#include <dbt.h>
41#endif
42
43#define INITGUID // Only set here, if set twice will cause mingw32 to break.
44#include "SDL_windowsjoystick_c.h"
45#include "SDL_dinputjoystick_c.h"
46#include "SDL_xinputjoystick_c.h"
47#include "SDL_rawinputjoystick_c.h"
48
49#include "../../haptic/windows/SDL_dinputhaptic_c.h" // For haptic hot plugging
50
51#ifndef DEVICE_NOTIFY_WINDOW_HANDLE
52#define DEVICE_NOTIFY_WINDOW_HANDLE 0x00000000
53#endif
54
55// local variables
56static bool s_bJoystickThread = false;
57static SDL_Condition *s_condJoystickThread = NULL;
58static SDL_Mutex *s_mutexJoyStickEnum = NULL;
59static SDL_Thread *s_joystickThread = NULL;
60static bool s_bJoystickThreadQuit = false;
61static Uint64 s_lastDeviceChange = 0;
62static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
63
64JoyStick_DeviceData *SYS_Joystick; // array to hold joystick ID values
65
66
67static bool WindowsDeviceChanged(void)
68{
69 return (s_lastDeviceChange != WIN_GetLastDeviceNotification());
70}
71
72static void SetWindowsDeviceChanged(void)
73{
74 s_lastDeviceChange = 0;
75}
76
77void WINDOWS_RAWINPUTEnabledChanged(void)
78{
79 SetWindowsDeviceChanged();
80}
81
82#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
83
84typedef struct
85{
86 HRESULT coinitialized;
87 WNDCLASSEX wincl;
88 HWND messageWindow;
89 HDEVNOTIFY hNotify;
90} SDL_DeviceNotificationData;
91
92#define IDT_SDL_DEVICE_CHANGE_TIMER_1 1200
93#define IDT_SDL_DEVICE_CHANGE_TIMER_2 1201
94
95// windowproc for our joystick detect thread message only window, to detect any USB device addition/removal
96static LRESULT CALLBACK SDL_PrivateJoystickDetectProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
97{
98 switch (msg) {
99 case WM_DEVICECHANGE:
100 switch (wParam) {
101 case DBT_DEVICEARRIVAL:
102 case DBT_DEVICEREMOVECOMPLETE:
103 if (((DEV_BROADCAST_HDR *)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
104 // notify 300ms and 2 seconds later to ensure all APIs have updated status
105 SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_1, 300, NULL);
106 SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_2, 2000, NULL);
107 }
108 break;
109 }
110 return true;
111 case WM_TIMER:
112 if (wParam == IDT_SDL_DEVICE_CHANGE_TIMER_1 ||
113 wParam == IDT_SDL_DEVICE_CHANGE_TIMER_2) {
114 KillTimer(hwnd, wParam);
115 SetWindowsDeviceChanged();
116 return true;
117 }
118 break;
119 }
120
121#ifdef SDL_JOYSTICK_RAWINPUT
122 return CallWindowProc(RAWINPUT_WindowProc, hwnd, msg, wParam, lParam);
123#else
124 return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
125#endif
126}
127
128static void SDL_CleanupDeviceNotification(SDL_DeviceNotificationData *data)
129{
130#ifdef SDL_JOYSTICK_RAWINPUT
131 RAWINPUT_UnregisterNotifications();
132#endif
133
134 if (data->hNotify) {
135 UnregisterDeviceNotification(data->hNotify);
136 }
137
138 if (data->messageWindow) {
139 DestroyWindow(data->messageWindow);
140 }
141
142 UnregisterClass(data->wincl.lpszClassName, data->wincl.hInstance);
143
144 if (data->coinitialized == S_OK) {
145 WIN_CoUninitialize();
146 }
147}
148
149static bool SDL_CreateDeviceNotification(SDL_DeviceNotificationData *data)
150{
151 DEV_BROADCAST_DEVICEINTERFACE dbh;
152
153 SDL_zerop(data);
154
155 data->coinitialized = WIN_CoInitialize();
156
157 data->wincl.hInstance = GetModuleHandle(NULL);
158 data->wincl.lpszClassName = TEXT("Message");
159 data->wincl.lpfnWndProc = SDL_PrivateJoystickDetectProc; // This function is called by windows
160 data->wincl.cbSize = sizeof(WNDCLASSEX);
161
162 if (!RegisterClassEx(&data->wincl)) {
163 WIN_SetError("Failed to create register class for joystick autodetect");
164 SDL_CleanupDeviceNotification(data);
165 return false;
166 }
167
168 data->messageWindow = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
169 if (!data->messageWindow) {
170 WIN_SetError("Failed to create message window for joystick autodetect");
171 SDL_CleanupDeviceNotification(data);
172 return false;
173 }
174
175 SDL_zero(dbh);
176 dbh.dbcc_size = sizeof(dbh);
177 dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
178 dbh.dbcc_classguid = GUID_DEVINTERFACE_HID;
179
180 data->hNotify = RegisterDeviceNotification(data->messageWindow, &dbh, DEVICE_NOTIFY_WINDOW_HANDLE);
181 if (!data->hNotify) {
182 WIN_SetError("Failed to create notify device for joystick autodetect");
183 SDL_CleanupDeviceNotification(data);
184 return false;
185 }
186
187#ifdef SDL_JOYSTICK_RAWINPUT
188 RAWINPUT_RegisterNotifications(data->messageWindow);
189#endif
190 return true;
191}
192
193static bool SDL_WaitForDeviceNotification(SDL_DeviceNotificationData *data, SDL_Mutex *mutex)
194{
195 MSG msg;
196 int lastret = 1;
197
198 if (!data->messageWindow) {
199 return false; // device notifications require a window
200 }
201
202 SDL_UnlockMutex(mutex);
203 while (lastret > 0 && !WindowsDeviceChanged()) {
204 lastret = GetMessage(&msg, NULL, 0, 0); // WM_QUIT causes return value of 0
205 if (lastret > 0) {
206 TranslateMessage(&msg);
207 DispatchMessage(&msg);
208 }
209 }
210 SDL_LockMutex(mutex);
211 return (lastret != -1);
212}
213
214#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
215
216#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
217static SDL_DeviceNotificationData s_notification_data;
218#endif
219
220// Function/thread to scan the system for joysticks.
221static int SDLCALL SDL_JoystickThread(void *_data)
222{
223#ifdef SDL_JOYSTICK_XINPUT
224 bool bOpenedXInputDevices[XUSER_MAX_COUNT];
225 SDL_zeroa(bOpenedXInputDevices);
226#endif
227
228#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
229 if (!SDL_CreateDeviceNotification(&s_notification_data)) {
230 return 0;
231 }
232#endif
233
234 SDL_LockMutex(s_mutexJoyStickEnum);
235 while (s_bJoystickThreadQuit == false) {
236#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
237 if (SDL_WaitForDeviceNotification(&s_notification_data, s_mutexJoyStickEnum) == false) {
238#else
239 {
240#endif
241#ifdef SDL_JOYSTICK_XINPUT
242 // WM_DEVICECHANGE not working, poll for new XINPUT controllers
243 SDL_WaitConditionTimeout(s_condJoystickThread, s_mutexJoyStickEnum, 1000);
244 if (SDL_XINPUT_Enabled()) {
245 // scan for any change in XInput devices
246 Uint8 userId;
247 for (userId = 0; userId < XUSER_MAX_COUNT; userId++) {
248 XINPUT_CAPABILITIES capabilities;
249 const DWORD result = XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities);
250 const bool available = (result == ERROR_SUCCESS);
251 if (bOpenedXInputDevices[userId] != available) {
252 SetWindowsDeviceChanged();
253 bOpenedXInputDevices[userId] = available;
254 }
255 }
256 }
257#else
258 // WM_DEVICECHANGE not working, no XINPUT, no point in keeping thread alive
259 break;
260#endif // SDL_JOYSTICK_XINPUT
261 }
262 }
263
264 SDL_UnlockMutex(s_mutexJoyStickEnum);
265
266#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
267 SDL_CleanupDeviceNotification(&s_notification_data);
268#endif
269
270 return 1;
271}
272
273// spin up the thread to detect hotplug of devices
274static bool SDL_StartJoystickThread(void)
275{
276 s_mutexJoyStickEnum = SDL_CreateMutex();
277 if (!s_mutexJoyStickEnum) {
278 return false;
279 }
280
281 s_condJoystickThread = SDL_CreateCondition();
282 if (!s_condJoystickThread) {
283 return false;
284 }
285
286 s_bJoystickThreadQuit = false;
287 s_joystickThread = SDL_CreateThread(SDL_JoystickThread, "SDL_joystick", NULL);
288 if (!s_joystickThread) {
289 return false;
290 }
291 return true;
292}
293
294static void SDL_StopJoystickThread(void)
295{
296 if (!s_joystickThread) {
297 return;
298 }
299
300 SDL_LockMutex(s_mutexJoyStickEnum);
301 s_bJoystickThreadQuit = true;
302 SDL_BroadcastCondition(s_condJoystickThread); // signal the joystick thread to quit
303 SDL_UnlockMutex(s_mutexJoyStickEnum);
304 PostThreadMessage((DWORD)SDL_GetThreadID(s_joystickThread), WM_QUIT, 0, 0);
305
306 // Unlock joysticks while the joystick thread finishes processing messages
307 SDL_AssertJoysticksLocked();
308 SDL_UnlockJoysticks();
309 SDL_WaitThread(s_joystickThread, NULL); // wait for it to bugger off
310 SDL_LockJoysticks();
311
312 SDL_DestroyCondition(s_condJoystickThread);
313 s_condJoystickThread = NULL;
314
315 SDL_DestroyMutex(s_mutexJoyStickEnum);
316 s_mutexJoyStickEnum = NULL;
317
318 s_joystickThread = NULL;
319}
320
321void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device)
322{
323 device->send_add_event = true;
324 device->nInstanceID = SDL_GetNextObjectID();
325 device->pNext = SYS_Joystick;
326 SYS_Joystick = device;
327}
328
329void WINDOWS_JoystickDetect(void);
330void WINDOWS_JoystickQuit(void);
331
332static bool WINDOWS_JoystickInit(void)
333{
334 if (!SDL_XINPUT_JoystickInit()) {
335 WINDOWS_JoystickQuit();
336 return false;
337 }
338
339 if (!SDL_DINPUT_JoystickInit()) {
340 WINDOWS_JoystickQuit();
341 return false;
342 }
343
344 WIN_InitDeviceNotification();
345
346#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
347 s_bJoystickThread = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_THREAD, true);
348 if (s_bJoystickThread) {
349 if (!SDL_StartJoystickThread()) {
350 return false;
351 }
352 } else {
353 if (!SDL_CreateDeviceNotification(&s_notification_data)) {
354 return false;
355 }
356 }
357#endif
358
359#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
360 // On Xbox, force create the joystick thread for device detection (since other methods don't work
361 s_bJoystickThread = true;
362 if (!SDL_StartJoystickThread()) {
363 return false;
364 }
365#endif
366
367 SetWindowsDeviceChanged(); // force a scan of the system for joysticks this first time
368
369 WINDOWS_JoystickDetect();
370
371 return true;
372}
373
374// return the number of joysticks that are connected right now
375static int WINDOWS_JoystickGetCount(void)
376{
377 int nJoysticks = 0;
378 JoyStick_DeviceData *device = SYS_Joystick;
379 while (device) {
380 nJoysticks++;
381 device = device->pNext;
382 }
383
384 return nJoysticks;
385}
386
387// detect any new joysticks being inserted into the system
388void WINDOWS_JoystickDetect(void)
389{
390 JoyStick_DeviceData *pCurList = NULL;
391
392 // only enum the devices if the joystick thread told us something changed
393 if (!WindowsDeviceChanged()) {
394 return; // thread hasn't signaled, nothing to do right now.
395 }
396
397 if (s_mutexJoyStickEnum) {
398 SDL_LockMutex(s_mutexJoyStickEnum);
399 }
400
401 s_lastDeviceChange = WIN_GetLastDeviceNotification();
402
403 pCurList = SYS_Joystick;
404 SYS_Joystick = NULL;
405
406 // Look for DirectInput joysticks, wheels, head trackers, gamepads, etc..
407 SDL_DINPUT_JoystickDetect(&pCurList);
408
409 // Look for XInput devices. Do this last, so they're first in the final list.
410 SDL_XINPUT_JoystickDetect(&pCurList);
411
412 if (s_mutexJoyStickEnum) {
413 SDL_UnlockMutex(s_mutexJoyStickEnum);
414 }
415
416 while (pCurList) {
417 JoyStick_DeviceData *pListNext = NULL;
418
419 if (!pCurList->bXInputDevice) {
420#ifdef SDL_HAPTIC_DINPUT
421 SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice);
422#endif
423 }
424
425 SDL_PrivateJoystickRemoved(pCurList->nInstanceID);
426
427 pListNext = pCurList->pNext;
428 SDL_free(pCurList->joystickname);
429 SDL_free(pCurList);
430 pCurList = pListNext;
431 }
432
433 for (pCurList = SYS_Joystick; pCurList; pCurList = pCurList->pNext) {
434 if (pCurList->send_add_event) {
435 if (!pCurList->bXInputDevice) {
436#ifdef SDL_HAPTIC_DINPUT
437 SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice);
438#endif
439 }
440
441 SDL_PrivateJoystickAdded(pCurList->nInstanceID);
442
443 pCurList->send_add_event = false;
444 }
445 }
446}
447
448static bool WINDOWS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
449{
450 if (SDL_DINPUT_JoystickPresent(vendor_id, product_id, version)) {
451 return true;
452 }
453 if (SDL_XINPUT_JoystickPresent(vendor_id, product_id, version)) {
454 return true;
455 }
456 return false;
457}
458
459static const char *WINDOWS_JoystickGetDeviceName(int device_index)
460{
461 JoyStick_DeviceData *device = SYS_Joystick;
462 int index;
463
464 for (index = device_index; index > 0; index--) {
465 device = device->pNext;
466 }
467
468 return device->joystickname;
469}
470
471static const char *WINDOWS_JoystickGetDevicePath(int device_index)
472{
473 JoyStick_DeviceData *device = SYS_Joystick;
474 int index;
475
476 for (index = device_index; index > 0; index--) {
477 device = device->pNext;
478 }
479
480 return device->path;
481}
482
483static int WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
484{
485 JoyStick_DeviceData *device = SYS_Joystick;
486 int index;
487
488 for (index = device_index; index > 0; index--) {
489 device = device->pNext;
490 }
491
492 if (device->bXInputDevice) {
493 // The slot for XInput devices can change as controllers are seated
494 return SDL_XINPUT_GetSteamVirtualGamepadSlot(device->XInputUserId);
495 } else {
496 return device->steam_virtual_gamepad_slot;
497 }
498}
499
500static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index)
501{
502 JoyStick_DeviceData *device = SYS_Joystick;
503 int index;
504
505 for (index = device_index; index > 0; index--) {
506 device = device->pNext;
507 }
508
509 return device->bXInputDevice ? (int)device->XInputUserId : -1;
510}
511
512static void WINDOWS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
513{
514}
515
516// return the stable device guid for this device index
517static SDL_GUID WINDOWS_JoystickGetDeviceGUID(int device_index)
518{
519 JoyStick_DeviceData *device = SYS_Joystick;
520 int index;
521
522 for (index = device_index; index > 0; index--) {
523 device = device->pNext;
524 }
525
526 return device->guid;
527}
528
529// Function to perform the mapping between current device instance and this joysticks instance id
530static SDL_JoystickID WINDOWS_JoystickGetDeviceInstanceID(int device_index)
531{
532 JoyStick_DeviceData *device = SYS_Joystick;
533 int index;
534
535 for (index = device_index; index > 0; index--) {
536 device = device->pNext;
537 }
538
539 return device->nInstanceID;
540}
541
542/* Function to open a joystick for use.
543 The joystick to open is specified by the device index.
544 This should fill the nbuttons and naxes fields of the joystick structure.
545 It returns 0, or -1 if there is an error.
546 */
547static bool WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index)
548{
549 JoyStick_DeviceData *device = SYS_Joystick;
550 int index;
551
552 for (index = device_index; index > 0; index--) {
553 device = device->pNext;
554 }
555
556 // allocate memory for system specific hardware data
557 joystick->hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(struct joystick_hwdata));
558 if (!joystick->hwdata) {
559 return false;
560 }
561 joystick->hwdata->guid = device->guid;
562
563 if (device->bXInputDevice) {
564 return SDL_XINPUT_JoystickOpen(joystick, device);
565 } else {
566 return SDL_DINPUT_JoystickOpen(joystick, device);
567 }
568}
569
570static bool WINDOWS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
571{
572 if (joystick->hwdata->bXInputDevice) {
573 return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
574 } else {
575 return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
576 }
577}
578
579static bool WINDOWS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
580{
581 return SDL_Unsupported();
582}
583
584static bool WINDOWS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
585{
586 return SDL_Unsupported();
587}
588
589static bool WINDOWS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
590{
591 return SDL_Unsupported();
592}
593
594static bool WINDOWS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
595{
596 return SDL_Unsupported();
597}
598
599static void WINDOWS_JoystickUpdate(SDL_Joystick *joystick)
600{
601 if (!joystick->hwdata) {
602 return;
603 }
604
605 if (joystick->hwdata->bXInputDevice) {
606 SDL_XINPUT_JoystickUpdate(joystick);
607 } else {
608 SDL_DINPUT_JoystickUpdate(joystick);
609 }
610}
611
612// Function to close a joystick after use
613static void WINDOWS_JoystickClose(SDL_Joystick *joystick)
614{
615 if (joystick->hwdata->bXInputDevice) {
616 SDL_XINPUT_JoystickClose(joystick);
617 } else {
618 SDL_DINPUT_JoystickClose(joystick);
619 }
620
621 SDL_free(joystick->hwdata);
622}
623
624// Function to perform any system-specific joystick related cleanup
625void WINDOWS_JoystickQuit(void)
626{
627 JoyStick_DeviceData *device = SYS_Joystick;
628
629 while (device) {
630 JoyStick_DeviceData *device_next = device->pNext;
631 SDL_free(device->joystickname);
632 SDL_free(device);
633 device = device_next;
634 }
635 SYS_Joystick = NULL;
636
637#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
638 if (s_bJoystickThread) {
639 SDL_StopJoystickThread();
640 } else {
641 SDL_CleanupDeviceNotification(&s_notification_data);
642 }
643#endif
644
645#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
646 if (s_bJoystickThread) {
647 SDL_StopJoystickThread();
648 }
649#endif
650
651 SDL_DINPUT_JoystickQuit();
652 SDL_XINPUT_JoystickQuit();
653
654 WIN_QuitDeviceNotification();
655}
656
657static bool WINDOWS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
658{
659 return false;
660}
661
662SDL_JoystickDriver SDL_WINDOWS_JoystickDriver = {
663 WINDOWS_JoystickInit,
664 WINDOWS_JoystickGetCount,
665 WINDOWS_JoystickDetect,
666 WINDOWS_JoystickIsDevicePresent,
667 WINDOWS_JoystickGetDeviceName,
668 WINDOWS_JoystickGetDevicePath,
669 WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot,
670 WINDOWS_JoystickGetDevicePlayerIndex,
671 WINDOWS_JoystickSetDevicePlayerIndex,
672 WINDOWS_JoystickGetDeviceGUID,
673 WINDOWS_JoystickGetDeviceInstanceID,
674 WINDOWS_JoystickOpen,
675 WINDOWS_JoystickRumble,
676 WINDOWS_JoystickRumbleTriggers,
677 WINDOWS_JoystickSetLED,
678 WINDOWS_JoystickSendEffect,
679 WINDOWS_JoystickSetSensorsEnabled,
680 WINDOWS_JoystickUpdate,
681 WINDOWS_JoystickClose,
682 WINDOWS_JoystickQuit,
683 WINDOWS_JoystickGetGamepadMapping
684};
685
686#else
687
688#ifdef SDL_JOYSTICK_RAWINPUT
689// The RAWINPUT driver needs the device notification setup above
690#error SDL_JOYSTICK_RAWINPUT requires SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT
691#endif
692
693#endif // SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h
new file mode 100644
index 0000000..16b9184
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h
@@ -0,0 +1,103 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_sysjoystick.h"
24#include "../../core/windows/SDL_windows.h"
25#include "../../core/windows/SDL_directx.h"
26
27#define MAX_INPUTS 256 // each joystick can have up to 256 inputs
28
29// Set up for C function definitions, even when using C++
30#ifdef __cplusplus
31extern "C" {
32#endif
33
34typedef struct JoyStick_DeviceData
35{
36 SDL_GUID guid;
37 char *joystickname;
38 Uint8 send_add_event;
39 SDL_JoystickID nInstanceID;
40 bool bXInputDevice;
41 BYTE SubType;
42 Uint8 XInputUserId;
43 DIDEVICEINSTANCE dxdevice;
44 char path[MAX_PATH];
45 int steam_virtual_gamepad_slot;
46 struct JoyStick_DeviceData *pNext;
47} JoyStick_DeviceData;
48
49extern JoyStick_DeviceData *SYS_Joystick; // array to hold joystick ID values
50
51typedef enum Type
52{
53 BUTTON,
54 AXIS,
55 HAT
56} Type;
57
58typedef struct input_t
59{
60 // DirectInput offset for this input type:
61 DWORD ofs;
62
63 // Button, axis or hat:
64 Type type;
65
66 // SDL input offset:
67 Uint8 num;
68} input_t;
69
70// The private structure used to keep track of a joystick
71struct joystick_hwdata
72{
73 SDL_GUID guid;
74
75#ifdef SDL_JOYSTICK_DINPUT
76 LPDIRECTINPUTDEVICE8 InputDevice;
77 DIDEVCAPS Capabilities;
78 bool buffered;
79 bool first_update;
80 input_t Inputs[MAX_INPUTS];
81 int NumInputs;
82 int NumSliders;
83 bool ff_initialized;
84 DIEFFECT *ffeffect;
85 LPDIRECTINPUTEFFECT ffeffect_ref;
86#endif
87
88 bool bXInputDevice; // true if this device supports using the xinput API rather than DirectInput
89 bool bXInputHaptic; // Supports force feedback via XInput.
90 Uint8 userid; // XInput userid index for this joystick
91 DWORD dwPacketNumber;
92};
93
94#ifdef SDL_JOYSTICK_DINPUT
95extern const DIDATAFORMAT SDL_c_dfDIJoystick2;
96#endif
97
98extern void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device);
99
100// Ends C function definitions when using C++
101#ifdef __cplusplus
102}
103#endif
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c
new file mode 100644
index 0000000..9f6ce10
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c
@@ -0,0 +1,473 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_sysjoystick.h"
24
25#ifdef SDL_JOYSTICK_XINPUT
26
27#include "SDL_windowsjoystick_c.h"
28#include "SDL_xinputjoystick_c.h"
29#include "SDL_rawinputjoystick_c.h"
30#include "../hidapi/SDL_hidapijoystick_c.h"
31
32// Set up for C function definitions, even when using C++
33#ifdef __cplusplus
34extern "C" {
35#endif
36
37/*
38 * Internal stuff.
39 */
40static bool s_bXInputEnabled = false;
41
42bool SDL_XINPUT_Enabled(void)
43{
44 return s_bXInputEnabled;
45}
46
47bool SDL_XINPUT_JoystickInit(void)
48{
49 bool enabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, true);
50
51 if (enabled && !WIN_LoadXInputDLL()) {
52 enabled = false; // oh well.
53 }
54 s_bXInputEnabled = enabled;
55
56 return true;
57}
58
59static const char *GetXInputName(const Uint8 userid, BYTE SubType)
60{
61 static char name[32];
62
63 switch (SubType) {
64 case XINPUT_DEVSUBTYPE_GAMEPAD:
65 (void)SDL_snprintf(name, sizeof(name), "XInput Controller #%d", 1 + userid);
66 break;
67 case XINPUT_DEVSUBTYPE_WHEEL:
68 (void)SDL_snprintf(name, sizeof(name), "XInput Wheel #%d", 1 + userid);
69 break;
70 case XINPUT_DEVSUBTYPE_ARCADE_STICK:
71 (void)SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%d", 1 + userid);
72 break;
73 case XINPUT_DEVSUBTYPE_FLIGHT_STICK:
74 (void)SDL_snprintf(name, sizeof(name), "XInput FlightStick #%d", 1 + userid);
75 break;
76 case XINPUT_DEVSUBTYPE_DANCE_PAD:
77 (void)SDL_snprintf(name, sizeof(name), "XInput DancePad #%d", 1 + userid);
78 break;
79 case XINPUT_DEVSUBTYPE_GUITAR:
80 case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE:
81 case XINPUT_DEVSUBTYPE_GUITAR_BASS:
82 (void)SDL_snprintf(name, sizeof(name), "XInput Guitar #%d", 1 + userid);
83 break;
84 case XINPUT_DEVSUBTYPE_DRUM_KIT:
85 (void)SDL_snprintf(name, sizeof(name), "XInput DrumKit #%d", 1 + userid);
86 break;
87 case XINPUT_DEVSUBTYPE_ARCADE_PAD:
88 (void)SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%d", 1 + userid);
89 break;
90 default:
91 (void)SDL_snprintf(name, sizeof(name), "XInput Device #%d", 1 + userid);
92 break;
93 }
94 return name;
95}
96
97static bool GetXInputDeviceInfo(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion)
98{
99 SDL_XINPUT_CAPABILITIES_EX capabilities;
100
101 if (!XINPUTGETCAPABILITIESEX || XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) != ERROR_SUCCESS) {
102 // Use a generic VID/PID representing an XInput controller
103 if (pVID) {
104 *pVID = USB_VENDOR_MICROSOFT;
105 }
106 if (pPID) {
107 *pPID = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
108 }
109 return false;
110 }
111
112 // Fixup for Wireless Xbox 360 Controller
113 if (capabilities.ProductId == 0 && capabilities.Capabilities.Flags & XINPUT_CAPS_WIRELESS) {
114 capabilities.VendorId = USB_VENDOR_MICROSOFT;
115 capabilities.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
116 }
117
118 if (pVID) {
119 *pVID = capabilities.VendorId;
120 }
121 if (pPID) {
122 *pPID = capabilities.ProductId;
123 }
124 if (pVersion) {
125 *pVersion = capabilities.ProductVersion;
126 }
127 return true;
128}
129
130int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid)
131{
132 SDL_XINPUT_CAPABILITIES_EX capabilities;
133
134 if (XINPUTGETCAPABILITIESEX &&
135 XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) == ERROR_SUCCESS &&
136 capabilities.VendorId == USB_VENDOR_VALVE &&
137 capabilities.ProductId == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
138 return (int)capabilities.unk2;
139 }
140 return -1;
141}
142
143static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext)
144{
145 const char *name = NULL;
146 Uint16 vendor = 0;
147 Uint16 product = 0;
148 Uint16 version = 0;
149 JoyStick_DeviceData *pPrevJoystick = NULL;
150 JoyStick_DeviceData *pNewJoystick = *pContext;
151
152#ifdef SDL_JOYSTICK_RAWINPUT
153 if (RAWINPUT_IsEnabled()) {
154 // The raw input driver handles more than 4 controllers, so prefer that when available
155 /* We do this check here rather than at the top of SDL_XINPUT_JoystickDetect() because
156 we need to check XInput state before RAWINPUT gets a hold of the device, otherwise
157 when a controller is connected via the wireless adapter, it will shut down at the
158 first subsequent XInput call. This seems like a driver stack bug?
159
160 Reference: https://github.com/libsdl-org/SDL/issues/3468
161 */
162 return;
163 }
164#endif
165
166 if (SubType == XINPUT_DEVSUBTYPE_UNKNOWN) {
167 return;
168 }
169
170 while (pNewJoystick) {
171 if (pNewJoystick->bXInputDevice && (pNewJoystick->XInputUserId == userid) && (pNewJoystick->SubType == SubType)) {
172 // if we are replacing the front of the list then update it
173 if (pNewJoystick == *pContext) {
174 *pContext = pNewJoystick->pNext;
175 } else if (pPrevJoystick) {
176 pPrevJoystick->pNext = pNewJoystick->pNext;
177 }
178
179 pNewJoystick->pNext = SYS_Joystick;
180 SYS_Joystick = pNewJoystick;
181 return; // already in the list.
182 }
183
184 pPrevJoystick = pNewJoystick;
185 pNewJoystick = pNewJoystick->pNext;
186 }
187
188 name = GetXInputName(userid, SubType);
189 GetXInputDeviceInfo(userid, &vendor, &product, &version);
190 if (SDL_ShouldIgnoreJoystick(vendor, product, version, name) ||
191 SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, name)) {
192 return;
193 }
194
195 pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData));
196 if (!pNewJoystick) {
197 return; // better luck next time?
198 }
199
200 pNewJoystick->bXInputDevice = true;
201 pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, NULL, name);
202 if (!pNewJoystick->joystickname) {
203 SDL_free(pNewJoystick);
204 return; // better luck next time?
205 }
206 (void)SDL_snprintf(pNewJoystick->path, sizeof(pNewJoystick->path), "XInput#%u", userid);
207 pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, NULL, name, 'x', SubType);
208 pNewJoystick->SubType = SubType;
209 pNewJoystick->XInputUserId = userid;
210
211 WINDOWS_AddJoystickDevice(pNewJoystick);
212}
213
214void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
215{
216 int iuserid;
217
218 if (!s_bXInputEnabled) {
219 return;
220 }
221
222 // iterate in reverse, so these are in the final list in ascending numeric order.
223 for (iuserid = XUSER_MAX_COUNT - 1; iuserid >= 0; iuserid--) {
224 const Uint8 userid = (Uint8)iuserid;
225 XINPUT_CAPABILITIES capabilities;
226 if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) {
227 AddXInputDevice(userid, capabilities.SubType, pContext);
228 }
229 }
230}
231
232bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)
233{
234 int iuserid;
235
236 if (!s_bXInputEnabled) {
237 return false;
238 }
239
240 // iterate in reverse, so these are in the final list in ascending numeric order.
241 for (iuserid = 0; iuserid < XUSER_MAX_COUNT; ++iuserid) {
242 const Uint8 userid = (Uint8)iuserid;
243 Uint16 slot_vendor;
244 Uint16 slot_product;
245 Uint16 slot_version;
246 if (GetXInputDeviceInfo(userid, &slot_vendor, &slot_product, &slot_version)) {
247 if (vendor == slot_vendor && product == slot_product && version == slot_version) {
248 return true;
249 }
250 }
251 }
252 return false;
253}
254
255bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
256{
257 const Uint8 userId = joystickdevice->XInputUserId;
258 XINPUT_CAPABILITIES capabilities;
259 XINPUT_VIBRATION state;
260
261 SDL_assert(s_bXInputEnabled);
262 SDL_assert(XINPUTGETCAPABILITIES);
263 SDL_assert(XINPUTSETSTATE);
264 SDL_assert(userId < XUSER_MAX_COUNT);
265
266 joystick->hwdata->bXInputDevice = true;
267
268 if (XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities) != ERROR_SUCCESS) {
269 SDL_free(joystick->hwdata);
270 joystick->hwdata = NULL;
271 return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?");
272 }
273 SDL_zero(state);
274 joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS);
275 joystick->hwdata->userid = userId;
276
277 // The XInput API has a hard coded button/axis mapping, so we just match it
278 joystick->naxes = 6;
279 joystick->nbuttons = 11;
280 joystick->nhats = 1;
281
282 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
283
284 return true;
285}
286
287static void UpdateXInputJoystickBatteryInformation(SDL_Joystick *joystick, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)
288{
289 SDL_PowerState state;
290 int percent;
291 switch (pBatteryInformation->BatteryType) {
292 case BATTERY_TYPE_WIRED:
293 state = SDL_POWERSTATE_CHARGING;
294 break;
295 case BATTERY_TYPE_UNKNOWN:
296 case BATTERY_TYPE_DISCONNECTED:
297 state = SDL_POWERSTATE_UNKNOWN;
298 break;
299 default:
300 state = SDL_POWERSTATE_ON_BATTERY;
301 break;
302 }
303 switch (pBatteryInformation->BatteryLevel) {
304 case BATTERY_LEVEL_EMPTY:
305 percent = 10;
306 break;
307 case BATTERY_LEVEL_LOW:
308 percent = 40;
309 break;
310 case BATTERY_LEVEL_MEDIUM:
311 percent = 70;
312 break;
313 default:
314 case BATTERY_LEVEL_FULL:
315 percent = 100;
316 break;
317 }
318 SDL_SendJoystickPowerInfo(joystick, state, percent);
319}
320
321static void UpdateXInputJoystickState(SDL_Joystick *joystick, XINPUT_STATE *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)
322{
323 static WORD s_XInputButtons[] = {
324 XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y,
325 XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START,
326 XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB,
327 XINPUT_GAMEPAD_GUIDE
328 };
329 WORD wButtons = pXInputState->Gamepad.wButtons;
330 Uint8 button;
331 Uint8 hat = SDL_HAT_CENTERED;
332 Uint64 timestamp = SDL_GetTicksNS();
333
334 SDL_SendJoystickAxis(timestamp, joystick, 0, pXInputState->Gamepad.sThumbLX);
335 SDL_SendJoystickAxis(timestamp, joystick, 1, ~pXInputState->Gamepad.sThumbLY);
336 SDL_SendJoystickAxis(timestamp, joystick, 2, ((int)pXInputState->Gamepad.bLeftTrigger * 257) - 32768);
337 SDL_SendJoystickAxis(timestamp, joystick, 3, pXInputState->Gamepad.sThumbRX);
338 SDL_SendJoystickAxis(timestamp, joystick, 4, ~pXInputState->Gamepad.sThumbRY);
339 SDL_SendJoystickAxis(timestamp, joystick, 5, ((int)pXInputState->Gamepad.bRightTrigger * 257) - 32768);
340
341 for (button = 0; button < (Uint8)SDL_arraysize(s_XInputButtons); ++button) {
342 bool down = ((wButtons & s_XInputButtons[button]) != 0);
343 SDL_SendJoystickButton(timestamp, joystick, button, down);
344 }
345
346 if (wButtons & XINPUT_GAMEPAD_DPAD_UP) {
347 hat |= SDL_HAT_UP;
348 }
349 if (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) {
350 hat |= SDL_HAT_DOWN;
351 }
352 if (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) {
353 hat |= SDL_HAT_LEFT;
354 }
355 if (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) {
356 hat |= SDL_HAT_RIGHT;
357 }
358 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
359
360 UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation);
361}
362
363bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
364{
365 XINPUT_VIBRATION XVibration;
366
367 if (!XINPUTSETSTATE) {
368 return SDL_Unsupported();
369 }
370
371 XVibration.wLeftMotorSpeed = low_frequency_rumble;
372 XVibration.wRightMotorSpeed = high_frequency_rumble;
373 if (XINPUTSETSTATE(joystick->hwdata->userid, &XVibration) != ERROR_SUCCESS) {
374 return SDL_SetError("XInputSetState() failed");
375 }
376 return true;
377}
378
379void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick)
380{
381 DWORD result;
382 XINPUT_STATE XInputState;
383 XINPUT_BATTERY_INFORMATION_EX XBatteryInformation;
384
385 if (!XINPUTGETSTATE) {
386 return;
387 }
388
389 result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState);
390 if (result == ERROR_DEVICE_NOT_CONNECTED) {
391 return;
392 }
393
394 SDL_zero(XBatteryInformation);
395 if (XINPUTGETBATTERYINFORMATION) {
396 result = XINPUTGETBATTERYINFORMATION(joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation);
397 }
398
399#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
400 // XInputOnGameInput doesn't ever change dwPacketNumber, so have to just update every frame
401 UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation);
402#else
403 // only fire events if the data changed from last time
404 if (XInputState.dwPacketNumber && XInputState.dwPacketNumber != joystick->hwdata->dwPacketNumber) {
405 UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation);
406 joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber;
407 }
408#endif
409}
410
411void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick)
412{
413}
414
415void SDL_XINPUT_JoystickQuit(void)
416{
417 if (s_bXInputEnabled) {
418 s_bXInputEnabled = false;
419 WIN_UnloadXInputDLL();
420 }
421}
422
423// Ends C function definitions when using C++
424#ifdef __cplusplus
425}
426#endif
427
428#else // !SDL_JOYSTICK_XINPUT
429
430typedef struct JoyStick_DeviceData JoyStick_DeviceData;
431
432bool SDL_XINPUT_Enabled(void)
433{
434 return false;
435}
436
437bool SDL_XINPUT_JoystickInit(void)
438{
439 return true;
440}
441
442void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
443{
444}
445
446bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)
447{
448 return false;
449}
450
451bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
452{
453 return SDL_Unsupported();
454}
455
456bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
457{
458 return SDL_Unsupported();
459}
460
461void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick)
462{
463}
464
465void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick)
466{
467}
468
469void SDL_XINPUT_JoystickQuit(void)
470{
471}
472
473#endif // SDL_JOYSTICK_XINPUT
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h
new file mode 100644
index 0000000..305b090
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h
@@ -0,0 +1,44 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../../core/windows/SDL_xinput.h"
24
25// Set up for C function definitions, even when using C++
26#ifdef __cplusplus
27extern "C" {
28#endif
29
30extern bool SDL_XINPUT_Enabled(void);
31extern bool SDL_XINPUT_JoystickInit(void);
32extern void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext);
33extern bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version);
34extern bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice);
35extern bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble);
36extern void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick);
37extern void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick);
38extern void SDL_XINPUT_JoystickQuit(void);
39extern int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid);
40
41// Ends C function definitions when using C++
42#ifdef __cplusplus
43}
44#endif