summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c')
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c473
1 files changed, 473 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c
new file mode 100644
index 0000000..9f6ce10
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c
@@ -0,0 +1,473 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_sysjoystick.h"
24
25#ifdef SDL_JOYSTICK_XINPUT
26
27#include "SDL_windowsjoystick_c.h"
28#include "SDL_xinputjoystick_c.h"
29#include "SDL_rawinputjoystick_c.h"
30#include "../hidapi/SDL_hidapijoystick_c.h"
31
32// Set up for C function definitions, even when using C++
33#ifdef __cplusplus
34extern "C" {
35#endif
36
37/*
38 * Internal stuff.
39 */
40static bool s_bXInputEnabled = false;
41
42bool SDL_XINPUT_Enabled(void)
43{
44 return s_bXInputEnabled;
45}
46
47bool SDL_XINPUT_JoystickInit(void)
48{
49 bool enabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, true);
50
51 if (enabled && !WIN_LoadXInputDLL()) {
52 enabled = false; // oh well.
53 }
54 s_bXInputEnabled = enabled;
55
56 return true;
57}
58
59static const char *GetXInputName(const Uint8 userid, BYTE SubType)
60{
61 static char name[32];
62
63 switch (SubType) {
64 case XINPUT_DEVSUBTYPE_GAMEPAD:
65 (void)SDL_snprintf(name, sizeof(name), "XInput Controller #%d", 1 + userid);
66 break;
67 case XINPUT_DEVSUBTYPE_WHEEL:
68 (void)SDL_snprintf(name, sizeof(name), "XInput Wheel #%d", 1 + userid);
69 break;
70 case XINPUT_DEVSUBTYPE_ARCADE_STICK:
71 (void)SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%d", 1 + userid);
72 break;
73 case XINPUT_DEVSUBTYPE_FLIGHT_STICK:
74 (void)SDL_snprintf(name, sizeof(name), "XInput FlightStick #%d", 1 + userid);
75 break;
76 case XINPUT_DEVSUBTYPE_DANCE_PAD:
77 (void)SDL_snprintf(name, sizeof(name), "XInput DancePad #%d", 1 + userid);
78 break;
79 case XINPUT_DEVSUBTYPE_GUITAR:
80 case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE:
81 case XINPUT_DEVSUBTYPE_GUITAR_BASS:
82 (void)SDL_snprintf(name, sizeof(name), "XInput Guitar #%d", 1 + userid);
83 break;
84 case XINPUT_DEVSUBTYPE_DRUM_KIT:
85 (void)SDL_snprintf(name, sizeof(name), "XInput DrumKit #%d", 1 + userid);
86 break;
87 case XINPUT_DEVSUBTYPE_ARCADE_PAD:
88 (void)SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%d", 1 + userid);
89 break;
90 default:
91 (void)SDL_snprintf(name, sizeof(name), "XInput Device #%d", 1 + userid);
92 break;
93 }
94 return name;
95}
96
97static bool GetXInputDeviceInfo(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion)
98{
99 SDL_XINPUT_CAPABILITIES_EX capabilities;
100
101 if (!XINPUTGETCAPABILITIESEX || XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) != ERROR_SUCCESS) {
102 // Use a generic VID/PID representing an XInput controller
103 if (pVID) {
104 *pVID = USB_VENDOR_MICROSOFT;
105 }
106 if (pPID) {
107 *pPID = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
108 }
109 return false;
110 }
111
112 // Fixup for Wireless Xbox 360 Controller
113 if (capabilities.ProductId == 0 && capabilities.Capabilities.Flags & XINPUT_CAPS_WIRELESS) {
114 capabilities.VendorId = USB_VENDOR_MICROSOFT;
115 capabilities.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
116 }
117
118 if (pVID) {
119 *pVID = capabilities.VendorId;
120 }
121 if (pPID) {
122 *pPID = capabilities.ProductId;
123 }
124 if (pVersion) {
125 *pVersion = capabilities.ProductVersion;
126 }
127 return true;
128}
129
130int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid)
131{
132 SDL_XINPUT_CAPABILITIES_EX capabilities;
133
134 if (XINPUTGETCAPABILITIESEX &&
135 XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) == ERROR_SUCCESS &&
136 capabilities.VendorId == USB_VENDOR_VALVE &&
137 capabilities.ProductId == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
138 return (int)capabilities.unk2;
139 }
140 return -1;
141}
142
143static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext)
144{
145 const char *name = NULL;
146 Uint16 vendor = 0;
147 Uint16 product = 0;
148 Uint16 version = 0;
149 JoyStick_DeviceData *pPrevJoystick = NULL;
150 JoyStick_DeviceData *pNewJoystick = *pContext;
151
152#ifdef SDL_JOYSTICK_RAWINPUT
153 if (RAWINPUT_IsEnabled()) {
154 // The raw input driver handles more than 4 controllers, so prefer that when available
155 /* We do this check here rather than at the top of SDL_XINPUT_JoystickDetect() because
156 we need to check XInput state before RAWINPUT gets a hold of the device, otherwise
157 when a controller is connected via the wireless adapter, it will shut down at the
158 first subsequent XInput call. This seems like a driver stack bug?
159
160 Reference: https://github.com/libsdl-org/SDL/issues/3468
161 */
162 return;
163 }
164#endif
165
166 if (SubType == XINPUT_DEVSUBTYPE_UNKNOWN) {
167 return;
168 }
169
170 while (pNewJoystick) {
171 if (pNewJoystick->bXInputDevice && (pNewJoystick->XInputUserId == userid) && (pNewJoystick->SubType == SubType)) {
172 // if we are replacing the front of the list then update it
173 if (pNewJoystick == *pContext) {
174 *pContext = pNewJoystick->pNext;
175 } else if (pPrevJoystick) {
176 pPrevJoystick->pNext = pNewJoystick->pNext;
177 }
178
179 pNewJoystick->pNext = SYS_Joystick;
180 SYS_Joystick = pNewJoystick;
181 return; // already in the list.
182 }
183
184 pPrevJoystick = pNewJoystick;
185 pNewJoystick = pNewJoystick->pNext;
186 }
187
188 name = GetXInputName(userid, SubType);
189 GetXInputDeviceInfo(userid, &vendor, &product, &version);
190 if (SDL_ShouldIgnoreJoystick(vendor, product, version, name) ||
191 SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, name)) {
192 return;
193 }
194
195 pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData));
196 if (!pNewJoystick) {
197 return; // better luck next time?
198 }
199
200 pNewJoystick->bXInputDevice = true;
201 pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, NULL, name);
202 if (!pNewJoystick->joystickname) {
203 SDL_free(pNewJoystick);
204 return; // better luck next time?
205 }
206 (void)SDL_snprintf(pNewJoystick->path, sizeof(pNewJoystick->path), "XInput#%u", userid);
207 pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, NULL, name, 'x', SubType);
208 pNewJoystick->SubType = SubType;
209 pNewJoystick->XInputUserId = userid;
210
211 WINDOWS_AddJoystickDevice(pNewJoystick);
212}
213
214void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
215{
216 int iuserid;
217
218 if (!s_bXInputEnabled) {
219 return;
220 }
221
222 // iterate in reverse, so these are in the final list in ascending numeric order.
223 for (iuserid = XUSER_MAX_COUNT - 1; iuserid >= 0; iuserid--) {
224 const Uint8 userid = (Uint8)iuserid;
225 XINPUT_CAPABILITIES capabilities;
226 if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) {
227 AddXInputDevice(userid, capabilities.SubType, pContext);
228 }
229 }
230}
231
232bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)
233{
234 int iuserid;
235
236 if (!s_bXInputEnabled) {
237 return false;
238 }
239
240 // iterate in reverse, so these are in the final list in ascending numeric order.
241 for (iuserid = 0; iuserid < XUSER_MAX_COUNT; ++iuserid) {
242 const Uint8 userid = (Uint8)iuserid;
243 Uint16 slot_vendor;
244 Uint16 slot_product;
245 Uint16 slot_version;
246 if (GetXInputDeviceInfo(userid, &slot_vendor, &slot_product, &slot_version)) {
247 if (vendor == slot_vendor && product == slot_product && version == slot_version) {
248 return true;
249 }
250 }
251 }
252 return false;
253}
254
255bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
256{
257 const Uint8 userId = joystickdevice->XInputUserId;
258 XINPUT_CAPABILITIES capabilities;
259 XINPUT_VIBRATION state;
260
261 SDL_assert(s_bXInputEnabled);
262 SDL_assert(XINPUTGETCAPABILITIES);
263 SDL_assert(XINPUTSETSTATE);
264 SDL_assert(userId < XUSER_MAX_COUNT);
265
266 joystick->hwdata->bXInputDevice = true;
267
268 if (XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities) != ERROR_SUCCESS) {
269 SDL_free(joystick->hwdata);
270 joystick->hwdata = NULL;
271 return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?");
272 }
273 SDL_zero(state);
274 joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS);
275 joystick->hwdata->userid = userId;
276
277 // The XInput API has a hard coded button/axis mapping, so we just match it
278 joystick->naxes = 6;
279 joystick->nbuttons = 11;
280 joystick->nhats = 1;
281
282 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
283
284 return true;
285}
286
287static void UpdateXInputJoystickBatteryInformation(SDL_Joystick *joystick, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)
288{
289 SDL_PowerState state;
290 int percent;
291 switch (pBatteryInformation->BatteryType) {
292 case BATTERY_TYPE_WIRED:
293 state = SDL_POWERSTATE_CHARGING;
294 break;
295 case BATTERY_TYPE_UNKNOWN:
296 case BATTERY_TYPE_DISCONNECTED:
297 state = SDL_POWERSTATE_UNKNOWN;
298 break;
299 default:
300 state = SDL_POWERSTATE_ON_BATTERY;
301 break;
302 }
303 switch (pBatteryInformation->BatteryLevel) {
304 case BATTERY_LEVEL_EMPTY:
305 percent = 10;
306 break;
307 case BATTERY_LEVEL_LOW:
308 percent = 40;
309 break;
310 case BATTERY_LEVEL_MEDIUM:
311 percent = 70;
312 break;
313 default:
314 case BATTERY_LEVEL_FULL:
315 percent = 100;
316 break;
317 }
318 SDL_SendJoystickPowerInfo(joystick, state, percent);
319}
320
321static void UpdateXInputJoystickState(SDL_Joystick *joystick, XINPUT_STATE *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)
322{
323 static WORD s_XInputButtons[] = {
324 XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y,
325 XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START,
326 XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB,
327 XINPUT_GAMEPAD_GUIDE
328 };
329 WORD wButtons = pXInputState->Gamepad.wButtons;
330 Uint8 button;
331 Uint8 hat = SDL_HAT_CENTERED;
332 Uint64 timestamp = SDL_GetTicksNS();
333
334 SDL_SendJoystickAxis(timestamp, joystick, 0, pXInputState->Gamepad.sThumbLX);
335 SDL_SendJoystickAxis(timestamp, joystick, 1, ~pXInputState->Gamepad.sThumbLY);
336 SDL_SendJoystickAxis(timestamp, joystick, 2, ((int)pXInputState->Gamepad.bLeftTrigger * 257) - 32768);
337 SDL_SendJoystickAxis(timestamp, joystick, 3, pXInputState->Gamepad.sThumbRX);
338 SDL_SendJoystickAxis(timestamp, joystick, 4, ~pXInputState->Gamepad.sThumbRY);
339 SDL_SendJoystickAxis(timestamp, joystick, 5, ((int)pXInputState->Gamepad.bRightTrigger * 257) - 32768);
340
341 for (button = 0; button < (Uint8)SDL_arraysize(s_XInputButtons); ++button) {
342 bool down = ((wButtons & s_XInputButtons[button]) != 0);
343 SDL_SendJoystickButton(timestamp, joystick, button, down);
344 }
345
346 if (wButtons & XINPUT_GAMEPAD_DPAD_UP) {
347 hat |= SDL_HAT_UP;
348 }
349 if (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) {
350 hat |= SDL_HAT_DOWN;
351 }
352 if (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) {
353 hat |= SDL_HAT_LEFT;
354 }
355 if (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) {
356 hat |= SDL_HAT_RIGHT;
357 }
358 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
359
360 UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation);
361}
362
363bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
364{
365 XINPUT_VIBRATION XVibration;
366
367 if (!XINPUTSETSTATE) {
368 return SDL_Unsupported();
369 }
370
371 XVibration.wLeftMotorSpeed = low_frequency_rumble;
372 XVibration.wRightMotorSpeed = high_frequency_rumble;
373 if (XINPUTSETSTATE(joystick->hwdata->userid, &XVibration) != ERROR_SUCCESS) {
374 return SDL_SetError("XInputSetState() failed");
375 }
376 return true;
377}
378
379void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick)
380{
381 DWORD result;
382 XINPUT_STATE XInputState;
383 XINPUT_BATTERY_INFORMATION_EX XBatteryInformation;
384
385 if (!XINPUTGETSTATE) {
386 return;
387 }
388
389 result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState);
390 if (result == ERROR_DEVICE_NOT_CONNECTED) {
391 return;
392 }
393
394 SDL_zero(XBatteryInformation);
395 if (XINPUTGETBATTERYINFORMATION) {
396 result = XINPUTGETBATTERYINFORMATION(joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation);
397 }
398
399#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
400 // XInputOnGameInput doesn't ever change dwPacketNumber, so have to just update every frame
401 UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation);
402#else
403 // only fire events if the data changed from last time
404 if (XInputState.dwPacketNumber && XInputState.dwPacketNumber != joystick->hwdata->dwPacketNumber) {
405 UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation);
406 joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber;
407 }
408#endif
409}
410
411void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick)
412{
413}
414
415void SDL_XINPUT_JoystickQuit(void)
416{
417 if (s_bXInputEnabled) {
418 s_bXInputEnabled = false;
419 WIN_UnloadXInputDLL();
420 }
421}
422
423// Ends C function definitions when using C++
424#ifdef __cplusplus
425}
426#endif
427
428#else // !SDL_JOYSTICK_XINPUT
429
430typedef struct JoyStick_DeviceData JoyStick_DeviceData;
431
432bool SDL_XINPUT_Enabled(void)
433{
434 return false;
435}
436
437bool SDL_XINPUT_JoystickInit(void)
438{
439 return true;
440}
441
442void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
443{
444}
445
446bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)
447{
448 return false;
449}
450
451bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
452{
453 return SDL_Unsupported();
454}
455
456bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
457{
458 return SDL_Unsupported();
459}
460
461void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick)
462{
463}
464
465void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick)
466{
467}
468
469void SDL_XINPUT_JoystickQuit(void)
470{
471}
472
473#endif // SDL_JOYSTICK_XINPUT