Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/joystick/windows/SDL_xinputjoystick.c
9905 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
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
34
extern "C" {
35
#endif
36
37
/*
38
* Internal stuff.
39
*/
40
static bool s_bXInputEnabled = false;
41
42
bool SDL_XINPUT_Enabled(void)
43
{
44
return s_bXInputEnabled;
45
}
46
47
bool 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
59
static 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
97
static 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
130
int 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
143
static 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
214
void 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
232
bool 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
255
bool 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
287
static 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
321
static 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
363
bool 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
379
void 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
411
void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick)
412
{
413
}
414
415
void 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
430
typedef struct JoyStick_DeviceData JoyStick_DeviceData;
431
432
bool SDL_XINPUT_Enabled(void)
433
{
434
return false;
435
}
436
437
bool SDL_XINPUT_JoystickInit(void)
438
{
439
return true;
440
}
441
442
void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
443
{
444
}
445
446
bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)
447
{
448
return false;
449
}
450
451
bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
452
{
453
return SDL_Unsupported();
454
}
455
456
bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
457
{
458
return SDL_Unsupported();
459
}
460
461
void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick)
462
{
463
}
464
465
void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick)
466
{
467
}
468
469
void SDL_XINPUT_JoystickQuit(void)
470
{
471
}
472
473
#endif // SDL_JOYSTICK_XINPUT
474
475