summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/joystick/hidapi
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/joystick/hidapi')
-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
23 files changed, 19217 insertions, 0 deletions
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