Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/joystick/windows/SDL_rawinputjoystick.c
9905 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 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
*/
22
/*
23
RAWINPUT Joystick API for better handling XInput-capable devices on Windows.
24
25
XInput is limited to 4 devices.
26
Windows.Gaming.Input does not get inputs from XBox One controllers when not in the foreground.
27
DirectInput does not get inputs from XBox One controllers when not in the foreground, nor rumble or accurate triggers.
28
RawInput does not get rumble or accurate triggers.
29
30
So, combine them as best we can!
31
*/
32
#include "SDL_internal.h"
33
34
#ifdef SDL_JOYSTICK_RAWINPUT
35
36
#include "../usb_ids.h"
37
#include "../SDL_sysjoystick.h"
38
#include "../../core/windows/SDL_windows.h"
39
#include "../../core/windows/SDL_hid.h"
40
#include "../hidapi/SDL_hidapijoystick_c.h"
41
42
/* SDL_JOYSTICK_RAWINPUT_XINPUT is disabled because using XInput at the same time as
43
raw input will turn off the Xbox Series X controller when it is connected via the
44
Xbox One Wireless Adapter.
45
*/
46
#ifdef HAVE_XINPUT_H
47
#define SDL_JOYSTICK_RAWINPUT_XINPUT
48
#endif
49
#ifdef HAVE_WINDOWS_GAMING_INPUT_H
50
#define SDL_JOYSTICK_RAWINPUT_WGI
51
#endif
52
53
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
54
#include "../../core/windows/SDL_xinput.h"
55
#endif
56
57
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
58
#include "../../core/windows/SDL_windows.h"
59
typedef struct WindowsGamingInputGamepadState WindowsGamingInputGamepadState;
60
#define GamepadButtons_GUIDE 0x40000000
61
#define COBJMACROS
62
#include "windows.gaming.input.h"
63
#include <roapi.h>
64
#endif
65
66
#if defined(SDL_JOYSTICK_RAWINPUT_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT_WGI)
67
#define SDL_JOYSTICK_RAWINPUT_MATCHING
68
#define SDL_JOYSTICK_RAWINPUT_MATCH_AXES
69
#define SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
70
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
71
#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 6 // stick + trigger axes
72
#else
73
#define SDL_JOYSTICK_RAWINPUT_MATCH_COUNT 4 // stick axes
74
#endif
75
#endif
76
77
#if 0
78
#define DEBUG_RAWINPUT
79
#endif
80
81
#ifndef RIDEV_EXINPUTSINK
82
#define RIDEV_EXINPUTSINK 0x00001000
83
#define RIDEV_DEVNOTIFY 0x00002000
84
#endif
85
86
#ifndef WM_INPUT_DEVICE_CHANGE
87
#define WM_INPUT_DEVICE_CHANGE 0x00FE
88
#endif
89
#ifndef WM_INPUT
90
#define WM_INPUT 0x00FF
91
#endif
92
#ifndef GIDC_ARRIVAL
93
#define GIDC_ARRIVAL 1
94
#define GIDC_REMOVAL 2
95
#endif
96
97
extern void WINDOWS_RAWINPUTEnabledChanged(void);
98
extern void WINDOWS_JoystickDetect(void);
99
100
static bool SDL_RAWINPUT_inited = false;
101
static bool SDL_RAWINPUT_remote_desktop = false;
102
static int SDL_RAWINPUT_numjoysticks = 0;
103
104
static void RAWINPUT_JoystickClose(SDL_Joystick *joystick);
105
106
typedef struct SDL_RAWINPUT_Device
107
{
108
SDL_AtomicInt refcount;
109
char *name;
110
char *path;
111
Uint16 vendor_id;
112
Uint16 product_id;
113
Uint16 version;
114
SDL_GUID guid;
115
bool is_xinput;
116
bool is_xboxone;
117
int steam_virtual_gamepad_slot;
118
PHIDP_PREPARSED_DATA preparsed_data;
119
120
HANDLE hDevice;
121
SDL_Joystick *joystick;
122
SDL_JoystickID joystick_id;
123
124
struct SDL_RAWINPUT_Device *next;
125
} SDL_RAWINPUT_Device;
126
127
struct joystick_hwdata
128
{
129
bool is_xinput;
130
bool is_xboxone;
131
PHIDP_PREPARSED_DATA preparsed_data;
132
ULONG max_data_length;
133
HIDP_DATA *data;
134
USHORT *button_indices;
135
USHORT *axis_indices;
136
USHORT *hat_indices;
137
bool guide_hack;
138
bool trigger_hack;
139
USHORT trigger_hack_index;
140
141
#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
142
Uint64 match_state; // Lowest 16 bits for button states, higher 24 for 6 4bit axes
143
Uint64 last_state_packet;
144
#endif
145
146
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
147
bool xinput_enabled;
148
bool xinput_correlated;
149
Uint8 xinput_correlation_id;
150
Uint8 xinput_correlation_count;
151
Uint8 xinput_uncorrelate_count;
152
Uint8 xinput_slot;
153
#endif
154
155
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
156
bool wgi_correlated;
157
Uint8 wgi_correlation_id;
158
Uint8 wgi_correlation_count;
159
Uint8 wgi_uncorrelate_count;
160
WindowsGamingInputGamepadState *wgi_slot;
161
struct __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration;
162
#endif
163
164
bool triggers_rumbling;
165
166
SDL_RAWINPUT_Device *device;
167
};
168
typedef struct joystick_hwdata RAWINPUT_DeviceContext;
169
170
SDL_RAWINPUT_Device *SDL_RAWINPUT_devices;
171
172
static const Uint16 subscribed_devices[] = {
173
USB_USAGE_GENERIC_GAMEPAD,
174
/* Don't need Joystick for any devices we're handling here (XInput-capable)
175
USB_USAGE_GENERIC_JOYSTICK,
176
USB_USAGE_GENERIC_MULTIAXISCONTROLLER,
177
*/
178
};
179
180
#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
181
182
static struct
183
{
184
Uint64 last_state_packet;
185
SDL_Joystick *joystick;
186
SDL_Joystick *last_joystick;
187
} guide_button_candidate;
188
189
typedef struct WindowsMatchState
190
{
191
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
192
SHORT match_axes[SDL_JOYSTICK_RAWINPUT_MATCH_COUNT];
193
#endif
194
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
195
WORD xinput_buttons;
196
#endif
197
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
198
Uint32 wgi_buttons;
199
#endif
200
bool any_data;
201
} WindowsMatchState;
202
203
static void RAWINPUT_FillMatchState(WindowsMatchState *state, Uint64 match_state)
204
{
205
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
206
int ii;
207
#endif
208
209
bool any_axes_data = false;
210
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
211
/* SHORT state->match_axes[4] = {
212
(match_state & 0x000F0000) >> 4,
213
(match_state & 0x00F00000) >> 8,
214
(match_state & 0x0F000000) >> 12,
215
(match_state & 0xF0000000) >> 16,
216
}; */
217
for (ii = 0; ii < 4; ii++) {
218
state->match_axes[ii] = (SHORT)((match_state & (0x000F0000ull << (ii * 4))) >> (4 + ii * 4));
219
any_axes_data |= ((Uint32)(state->match_axes[ii] + 0x1000) > 0x2000); // match_state bit is not 0xF, 0x1, or 0x2
220
}
221
#endif // SDL_JOYSTICK_RAWINPUT_MATCH_AXES
222
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
223
for (; ii < SDL_JOYSTICK_RAWINPUT_MATCH_COUNT; ii++) {
224
state->match_axes[ii] = (SHORT)((match_state & (0x000F0000ull << (ii * 4))) >> (4 + ii * 4));
225
any_axes_data |= (state->match_axes[ii] != SDL_MIN_SINT16);
226
}
227
#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
228
229
state->any_data = any_axes_data;
230
231
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
232
// Match axes by checking if the distance between the high 4 bits of axis and the 4 bits from match_state is 1 or less
233
#define XInputAxesMatch(gamepad) ( \
234
(Uint32)(gamepad.sThumbLX - state->match_axes[0] + 0x1000) <= 0x2fff && \
235
(Uint32)(~gamepad.sThumbLY - state->match_axes[1] + 0x1000) <= 0x2fff && \
236
(Uint32)(gamepad.sThumbRX - state->match_axes[2] + 0x1000) <= 0x2fff && \
237
(Uint32)(~gamepad.sThumbRY - state->match_axes[3] + 0x1000) <= 0x2fff)
238
/* Explicit
239
#define XInputAxesMatch(gamepad) (\
240
SDL_abs((Sint8)((gamepad.sThumbLX & 0xF000) >> 8) - ((match_state & 0x000F0000) >> 12)) <= 0x10 && \
241
SDL_abs((Sint8)((~gamepad.sThumbLY & 0xF000) >> 8) - ((match_state & 0x00F00000) >> 16)) <= 0x10 && \
242
SDL_abs((Sint8)((gamepad.sThumbRX & 0xF000) >> 8) - ((match_state & 0x0F000000) >> 20)) <= 0x10 && \
243
SDL_abs((Sint8)((~gamepad.sThumbRY & 0xF000) >> 8) - ((match_state & 0xF0000000) >> 24)) <= 0x10) */
244
245
// Can only match trigger values if a single trigger has a value.
246
#define XInputTriggersMatch(gamepad) ( \
247
((state->match_axes[4] == SDL_MIN_SINT16) && (state->match_axes[5] == SDL_MIN_SINT16)) || \
248
((gamepad.bLeftTrigger != 0) && (gamepad.bRightTrigger != 0)) || \
249
((Uint32)((((int)gamepad.bLeftTrigger * 257) - 32768) - state->match_axes[4]) <= 0x2fff) || \
250
((Uint32)((((int)gamepad.bRightTrigger * 257) - 32768) - state->match_axes[5]) <= 0x2fff))
251
252
state->xinput_buttons =
253
// Bitwise map .RLDUWVQTS.KYXBA -> YXBA..WVQTKSRLDU
254
(WORD)(match_state << 12 | (match_state & 0x0780) >> 1 | (match_state & 0x0010) << 1 | (match_state & 0x0040) >> 2 | (match_state & 0x7800) >> 11);
255
/* Explicit
256
((match_state & (1<<SDL_GAMEPAD_BUTTON_SOUTH)) ? XINPUT_GAMEPAD_A : 0) |
257
((match_state & (1<<SDL_GAMEPAD_BUTTON_EAST)) ? XINPUT_GAMEPAD_B : 0) |
258
((match_state & (1<<SDL_GAMEPAD_BUTTON_WEST)) ? XINPUT_GAMEPAD_X : 0) |
259
((match_state & (1<<SDL_GAMEPAD_BUTTON_NORTH)) ? XINPUT_GAMEPAD_Y : 0) |
260
((match_state & (1<<SDL_GAMEPAD_BUTTON_BACK)) ? XINPUT_GAMEPAD_BACK : 0) |
261
((match_state & (1<<SDL_GAMEPAD_BUTTON_START)) ? XINPUT_GAMEPAD_START : 0) |
262
((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_STICK)) ? XINPUT_GAMEPAD_LEFT_THUMB : 0) |
263
((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_STICK)) ? XINPUT_GAMEPAD_RIGHT_THUMB: 0) |
264
((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) ? XINPUT_GAMEPAD_LEFT_SHOULDER : 0) |
265
((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) ? XINPUT_GAMEPAD_RIGHT_SHOULDER : 0) |
266
((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_UP)) ? XINPUT_GAMEPAD_DPAD_UP : 0) |
267
((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_DOWN)) ? XINPUT_GAMEPAD_DPAD_DOWN : 0) |
268
((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_LEFT)) ? XINPUT_GAMEPAD_DPAD_LEFT : 0) |
269
((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) ? XINPUT_GAMEPAD_DPAD_RIGHT : 0);
270
*/
271
272
if (state->xinput_buttons) {
273
state->any_data = true;
274
}
275
#endif
276
277
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
278
// Match axes by checking if the distance between the high 4 bits of axis and the 4 bits from match_state is 1 or less
279
#define WindowsGamingInputAxesMatch(gamepad) ( \
280
(Uint16)(((Sint16)(gamepad.LeftThumbstickX * SDL_MAX_SINT16) & 0xF000) - state->match_axes[0] + 0x1000) <= 0x2fff && \
281
(Uint16)((~(Sint16)(gamepad.LeftThumbstickY * SDL_MAX_SINT16) & 0xF000) - state->match_axes[1] + 0x1000) <= 0x2fff && \
282
(Uint16)(((Sint16)(gamepad.RightThumbstickX * SDL_MAX_SINT16) & 0xF000) - state->match_axes[2] + 0x1000) <= 0x2fff && \
283
(Uint16)((~(Sint16)(gamepad.RightThumbstickY * SDL_MAX_SINT16) & 0xF000) - state->match_axes[3] + 0x1000) <= 0x2fff)
284
285
#define WindowsGamingInputTriggersMatch(gamepad) ( \
286
((state->match_axes[4] == SDL_MIN_SINT16) && (state->match_axes[5] == SDL_MIN_SINT16)) || \
287
((gamepad.LeftTrigger == 0.0f) && (gamepad.RightTrigger == 0.0f)) || \
288
((Uint16)((((int)(gamepad.LeftTrigger * SDL_MAX_UINT16)) - 32768) - state->match_axes[4]) <= 0x2fff) || \
289
((Uint16)((((int)(gamepad.RightTrigger * SDL_MAX_UINT16)) - 32768) - state->match_axes[5]) <= 0x2fff))
290
291
state->wgi_buttons =
292
// Bitwise map .RLD UWVQ TS.K YXBA -> ..QT WVRL DUYX BAKS
293
// RStick/LStick (QT) RShould/LShould (WV) DPad R/L/D/U YXBA bac(K) (S)tart
294
(match_state & 0x0180) << 5 | (match_state & 0x0600) << 1 | (match_state & 0x7800) >> 5 | (match_state & 0x000F) << 2 | (match_state & 0x0010) >> 3 | (match_state & 0x0040) >> 6;
295
/* Explicit
296
((match_state & (1<<SDL_GAMEPAD_BUTTON_SOUTH)) ? GamepadButtons_A : 0) |
297
((match_state & (1<<SDL_GAMEPAD_BUTTON_EAST)) ? GamepadButtons_B : 0) |
298
((match_state & (1<<SDL_GAMEPAD_BUTTON_WEST)) ? GamepadButtons_X : 0) |
299
((match_state & (1<<SDL_GAMEPAD_BUTTON_NORTH)) ? GamepadButtons_Y : 0) |
300
((match_state & (1<<SDL_GAMEPAD_BUTTON_BACK)) ? GamepadButtons_View : 0) |
301
((match_state & (1<<SDL_GAMEPAD_BUTTON_START)) ? GamepadButtons_Menu : 0) |
302
((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_STICK)) ? GamepadButtons_LeftThumbstick : 0) |
303
((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_STICK)) ? GamepadButtons_RightThumbstick: 0) |
304
((match_state & (1<<SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) ? GamepadButtons_LeftShoulder: 0) |
305
((match_state & (1<<SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) ? GamepadButtons_RightShoulder: 0) |
306
((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_UP)) ? GamepadButtons_DPadUp : 0) |
307
((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_DOWN)) ? GamepadButtons_DPadDown : 0) |
308
((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_LEFT)) ? GamepadButtons_DPadLeft : 0) |
309
((match_state & (1<<SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) ? GamepadButtons_DPadRight : 0); */
310
311
if (state->wgi_buttons) {
312
state->any_data = true;
313
}
314
#endif
315
}
316
317
#endif // SDL_JOYSTICK_RAWINPUT_MATCHING
318
319
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
320
321
static struct
322
{
323
XINPUT_STATE state;
324
XINPUT_BATTERY_INFORMATION_EX battery;
325
bool connected; // Currently has an active XInput device
326
bool used; // Is currently mapped to an SDL device
327
Uint8 correlation_id;
328
} xinput_state[XUSER_MAX_COUNT];
329
static bool xinput_device_change = true;
330
static bool xinput_state_dirty = true;
331
332
static void RAWINPUT_UpdateXInput(void)
333
{
334
DWORD user_index;
335
if (xinput_device_change) {
336
for (user_index = 0; user_index < XUSER_MAX_COUNT; user_index++) {
337
XINPUT_CAPABILITIES capabilities;
338
xinput_state[user_index].connected = (XINPUTGETCAPABILITIES(user_index, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS);
339
}
340
xinput_device_change = false;
341
xinput_state_dirty = true;
342
}
343
if (xinput_state_dirty) {
344
xinput_state_dirty = false;
345
for (user_index = 0; user_index < SDL_arraysize(xinput_state); ++user_index) {
346
if (xinput_state[user_index].connected) {
347
if (XINPUTGETSTATE(user_index, &xinput_state[user_index].state) != ERROR_SUCCESS) {
348
xinput_state[user_index].connected = false;
349
}
350
xinput_state[user_index].battery.BatteryType = BATTERY_TYPE_UNKNOWN;
351
if (XINPUTGETBATTERYINFORMATION) {
352
XINPUTGETBATTERYINFORMATION(user_index, BATTERY_DEVTYPE_GAMEPAD, &xinput_state[user_index].battery);
353
}
354
}
355
}
356
}
357
}
358
359
static void RAWINPUT_MarkXInputSlotUsed(Uint8 xinput_slot)
360
{
361
if (xinput_slot != XUSER_INDEX_ANY) {
362
xinput_state[xinput_slot].used = true;
363
}
364
}
365
366
static void RAWINPUT_MarkXInputSlotFree(Uint8 xinput_slot)
367
{
368
if (xinput_slot != XUSER_INDEX_ANY) {
369
xinput_state[xinput_slot].used = false;
370
}
371
}
372
static bool RAWINPUT_MissingXInputSlot(void)
373
{
374
int ii;
375
for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) {
376
if (xinput_state[ii].connected && !xinput_state[ii].used) {
377
return true;
378
}
379
}
380
return false;
381
}
382
383
static bool RAWINPUT_XInputSlotMatches(const WindowsMatchState *state, Uint8 slot_idx)
384
{
385
if (xinput_state[slot_idx].connected) {
386
WORD xinput_buttons = xinput_state[slot_idx].state.Gamepad.wButtons;
387
if ((xinput_buttons & ~XINPUT_GAMEPAD_GUIDE) == state->xinput_buttons
388
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
389
&& XInputAxesMatch(xinput_state[slot_idx].state.Gamepad)
390
#endif
391
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
392
&& XInputTriggersMatch(xinput_state[slot_idx].state.Gamepad)
393
#endif
394
) {
395
return true;
396
}
397
}
398
return false;
399
}
400
401
static bool RAWINPUT_GuessXInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, Uint8 *slot_idx)
402
{
403
Uint8 user_index;
404
int match_count;
405
406
/* If there is only one available slot, let's use that
407
* That will be right most of the time, and uncorrelation will fix any bad guesses
408
*/
409
match_count = 0;
410
for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) {
411
if (xinput_state[user_index].connected && !xinput_state[user_index].used) {
412
*slot_idx = user_index;
413
++match_count;
414
}
415
}
416
if (match_count == 1) {
417
*correlation_id = ++xinput_state[*slot_idx].correlation_id;
418
return true;
419
}
420
421
*slot_idx = 0;
422
423
match_count = 0;
424
for (user_index = 0; user_index < XUSER_MAX_COUNT; ++user_index) {
425
if (!xinput_state[user_index].used && RAWINPUT_XInputSlotMatches(state, user_index)) {
426
++match_count;
427
*slot_idx = user_index;
428
// Incrementing correlation_id for any match, as negative evidence for others being correlated
429
*correlation_id = ++xinput_state[user_index].correlation_id;
430
}
431
}
432
/* Only return a match if we match exactly one, and we have some non-zero data (buttons or axes) that matched.
433
Note that we're still invalidating *other* potential correlations if we have more than one match or we have no
434
data. */
435
if (match_count == 1 && state->any_data) {
436
return true;
437
}
438
return false;
439
}
440
441
#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
442
443
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
444
445
typedef struct WindowsGamingInputGamepadState
446
{
447
__x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad;
448
struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading state;
449
RAWINPUT_DeviceContext *correlated_context;
450
bool used; // Is currently mapped to an SDL device
451
bool connected; // Just used during update to track disconnected
452
Uint8 correlation_id;
453
} WindowsGamingInputGamepadState;
454
455
static struct
456
{
457
WindowsGamingInputGamepadState **per_gamepad;
458
int per_gamepad_count;
459
bool initialized;
460
bool dirty;
461
bool need_device_list_update;
462
int ref_count;
463
__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics;
464
EventRegistrationToken gamepad_added_token;
465
EventRegistrationToken gamepad_removed_token;
466
} wgi_state;
467
468
typedef struct GamepadDelegate
469
{
470
__FIEventHandler_1_Windows__CGaming__CInput__CGamepad iface;
471
SDL_AtomicInt refcount;
472
} GamepadDelegate;
473
474
static const IID IID_IEventHandler_Gamepad = { 0x8a7639ee, 0x624a, 0x501a, { 0xbb, 0x53, 0x56, 0x2d, 0x1e, 0xc1, 0x1b, 0x52 } };
475
476
static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_QueryInterface(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, REFIID riid, void **ppvObject)
477
{
478
if (!ppvObject) {
479
return E_INVALIDARG;
480
}
481
482
*ppvObject = NULL;
483
if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &IID_IAgileObject) || WIN_IsEqualIID(riid, &IID_IEventHandler_Gamepad)) {
484
*ppvObject = This;
485
__FIEventHandler_1_Windows__CGaming__CInput__CGamepad_AddRef(This);
486
return S_OK;
487
} else if (WIN_IsEqualIID(riid, &IID_IMarshal)) {
488
// This seems complicated. Let's hope it doesn't happen.
489
return E_OUTOFMEMORY;
490
} else {
491
return E_NOINTERFACE;
492
}
493
}
494
495
static ULONG STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_AddRef(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This)
496
{
497
GamepadDelegate *self = (GamepadDelegate *)This;
498
return SDL_AddAtomicInt(&self->refcount, 1) + 1UL;
499
}
500
501
static ULONG STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_Release(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This)
502
{
503
GamepadDelegate *self = (GamepadDelegate *)This;
504
int rc = SDL_AddAtomicInt(&self->refcount, -1) - 1;
505
// Should never free the static delegate objects
506
SDL_assert(rc > 0);
507
return rc;
508
}
509
510
static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIGamepad *e)
511
{
512
wgi_state.need_device_list_update = true;
513
return S_OK;
514
}
515
516
static HRESULT STDMETHODCALLTYPE IEventHandler_CGamepadVtbl_InvokeRemoved(__FIEventHandler_1_Windows__CGaming__CInput__CGamepad *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIGamepad *e)
517
{
518
wgi_state.need_device_list_update = true;
519
return S_OK;
520
}
521
522
#ifdef _MSC_VER
523
#pragma warning(push)
524
#pragma warning(disable : 4028) // formal parameter 3 different from declaration, when using older buggy WGI headers
525
#pragma warning(disable : 4113) // X differs in parameter lists from Y, when using older buggy WGI headers
526
#endif
527
528
static __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl gamepad_added_vtbl = {
529
IEventHandler_CGamepadVtbl_QueryInterface,
530
IEventHandler_CGamepadVtbl_AddRef,
531
IEventHandler_CGamepadVtbl_Release,
532
IEventHandler_CGamepadVtbl_InvokeAdded
533
};
534
static GamepadDelegate gamepad_added = {
535
{ &gamepad_added_vtbl },
536
{ 1 }
537
};
538
539
static __FIEventHandler_1_Windows__CGaming__CInput__CGamepadVtbl gamepad_removed_vtbl = {
540
IEventHandler_CGamepadVtbl_QueryInterface,
541
IEventHandler_CGamepadVtbl_AddRef,
542
IEventHandler_CGamepadVtbl_Release,
543
IEventHandler_CGamepadVtbl_InvokeRemoved
544
};
545
static GamepadDelegate gamepad_removed = {
546
{ &gamepad_removed_vtbl },
547
{ 1 }
548
};
549
550
#ifdef _MSC_VER
551
#pragma warning(pop)
552
#endif
553
554
static void RAWINPUT_MarkWindowsGamingInputSlotUsed(WindowsGamingInputGamepadState *wgi_slot, RAWINPUT_DeviceContext *ctx)
555
{
556
wgi_slot->used = true;
557
wgi_slot->correlated_context = ctx;
558
}
559
560
static void RAWINPUT_MarkWindowsGamingInputSlotFree(WindowsGamingInputGamepadState *wgi_slot)
561
{
562
wgi_slot->used = false;
563
wgi_slot->correlated_context = NULL;
564
}
565
566
static bool RAWINPUT_MissingWindowsGamingInputSlot(void)
567
{
568
int ii;
569
for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {
570
if (!wgi_state.per_gamepad[ii]->used) {
571
return true;
572
}
573
}
574
return false;
575
}
576
577
static bool RAWINPUT_UpdateWindowsGamingInput(void)
578
{
579
int ii;
580
if (!wgi_state.gamepad_statics) {
581
return true;
582
}
583
584
if (!wgi_state.dirty) {
585
return true;
586
}
587
588
wgi_state.dirty = false;
589
590
if (wgi_state.need_device_list_update) {
591
HRESULT hr;
592
__FIVectorView_1_Windows__CGaming__CInput__CGamepad *gamepads;
593
wgi_state.need_device_list_update = false;
594
for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {
595
wgi_state.per_gamepad[ii]->connected = false;
596
}
597
598
hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_get_Gamepads(wgi_state.gamepad_statics, &gamepads);
599
if (SUCCEEDED(hr)) {
600
unsigned int num_gamepads;
601
602
hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_get_Size(gamepads, &num_gamepads);
603
if (SUCCEEDED(hr)) {
604
unsigned int i;
605
for (i = 0; i < num_gamepads; ++i) {
606
__x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad;
607
608
hr = __FIVectorView_1_Windows__CGaming__CInput__CGamepad_GetAt(gamepads, i, &gamepad);
609
if (SUCCEEDED(hr)) {
610
bool found = false;
611
int jj;
612
for (jj = 0; jj < wgi_state.per_gamepad_count; jj++) {
613
if (wgi_state.per_gamepad[jj]->gamepad == gamepad) {
614
found = true;
615
wgi_state.per_gamepad[jj]->connected = true;
616
break;
617
}
618
}
619
if (!found) {
620
// New device, add it
621
WindowsGamingInputGamepadState *gamepad_state;
622
WindowsGamingInputGamepadState **new_per_gamepad;
623
gamepad_state = SDL_calloc(1, sizeof(*gamepad_state));
624
if (!gamepad_state) {
625
return false;
626
}
627
new_per_gamepad = SDL_realloc(wgi_state.per_gamepad, sizeof(wgi_state.per_gamepad[0]) * (wgi_state.per_gamepad_count + 1));
628
if (!new_per_gamepad) {
629
SDL_free(gamepad_state);
630
return false;
631
}
632
wgi_state.per_gamepad = new_per_gamepad;
633
wgi_state.per_gamepad_count++;
634
wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1] = gamepad_state;
635
gamepad_state->gamepad = gamepad;
636
gamepad_state->connected = true;
637
} else {
638
// Already tracked
639
__x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad);
640
}
641
}
642
}
643
for (ii = wgi_state.per_gamepad_count - 1; ii >= 0; ii--) {
644
WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[ii];
645
if (!gamepad_state->connected) {
646
// Device missing, must be disconnected
647
if (gamepad_state->correlated_context) {
648
gamepad_state->correlated_context->wgi_correlated = false;
649
gamepad_state->correlated_context->wgi_slot = NULL;
650
}
651
__x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad_state->gamepad);
652
SDL_free(gamepad_state);
653
wgi_state.per_gamepad[ii] = wgi_state.per_gamepad[wgi_state.per_gamepad_count - 1];
654
--wgi_state.per_gamepad_count;
655
}
656
}
657
}
658
__FIVectorView_1_Windows__CGaming__CInput__CGamepad_Release(gamepads);
659
}
660
} // need_device_list_update
661
662
for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {
663
HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_GetCurrentReading(wgi_state.per_gamepad[ii]->gamepad, &wgi_state.per_gamepad[ii]->state);
664
if (!SUCCEEDED(hr)) {
665
wgi_state.per_gamepad[ii]->connected = false; // Not used by anything, currently
666
}
667
}
668
return true;
669
}
670
static void RAWINPUT_InitWindowsGamingInput(RAWINPUT_DeviceContext *ctx)
671
{
672
if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) {
673
return;
674
}
675
676
wgi_state.ref_count++;
677
if (!wgi_state.initialized) {
678
static const IID SDL_IID_IGamepadStatics = { 0x8BBCE529, 0xD49C, 0x39E9, { 0x95, 0x60, 0xE4, 0x7D, 0xDE, 0x96, 0xB7, 0xC8 } };
679
HRESULT hr;
680
681
if (FAILED(WIN_RoInitialize())) {
682
return;
683
}
684
wgi_state.initialized = true;
685
wgi_state.dirty = true;
686
687
{
688
typedef HRESULT(WINAPI * WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER * hstringHeader, HSTRING * string);
689
typedef HRESULT(WINAPI * RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory);
690
691
WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference");
692
RoGetActivationFactory_t RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory");
693
if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc) {
694
PCWSTR pNamespace = L"Windows.Gaming.Input.Gamepad";
695
HSTRING_HEADER hNamespaceStringHeader;
696
HSTRING hNamespaceString;
697
698
hr = WindowsCreateStringReferenceFunc(pNamespace, (UINT32)SDL_wcslen(pNamespace), &hNamespaceStringHeader, &hNamespaceString);
699
if (SUCCEEDED(hr)) {
700
RoGetActivationFactoryFunc(hNamespaceString, &SDL_IID_IGamepadStatics, (void **)&wgi_state.gamepad_statics);
701
}
702
703
if (wgi_state.gamepad_statics) {
704
wgi_state.need_device_list_update = true;
705
706
hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadAdded(wgi_state.gamepad_statics, &gamepad_added.iface, &wgi_state.gamepad_added_token);
707
if (!SUCCEEDED(hr)) {
708
SDL_SetError("add_GamepadAdded() failed: 0x%lx", hr);
709
}
710
711
hr = __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_add_GamepadRemoved(wgi_state.gamepad_statics, &gamepad_removed.iface, &wgi_state.gamepad_removed_token);
712
if (!SUCCEEDED(hr)) {
713
SDL_SetError("add_GamepadRemoved() failed: 0x%lx", hr);
714
}
715
}
716
}
717
}
718
}
719
}
720
721
static bool RAWINPUT_WindowsGamingInputSlotMatches(const WindowsMatchState *state, WindowsGamingInputGamepadState *slot, bool xinput_correlated)
722
{
723
Uint32 wgi_buttons = slot->state.Buttons;
724
if ((wgi_buttons & 0x3FFF) == state->wgi_buttons
725
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
726
&& WindowsGamingInputAxesMatch(slot->state)
727
#endif
728
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
729
// Don't try to match WGI triggers if getting values from XInput
730
&& (xinput_correlated || WindowsGamingInputTriggersMatch(slot->state))
731
#endif
732
) {
733
return true;
734
}
735
return false;
736
}
737
738
static bool RAWINPUT_GuessWindowsGamingInputSlot(const WindowsMatchState *state, Uint8 *correlation_id, WindowsGamingInputGamepadState **slot, bool xinput_correlated)
739
{
740
int match_count, user_index;
741
WindowsGamingInputGamepadState *gamepad_state = NULL;
742
743
/* If there is only one available slot, let's use that
744
* That will be right most of the time, and uncorrelation will fix any bad guesses
745
*/
746
match_count = 0;
747
for (user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) {
748
gamepad_state = wgi_state.per_gamepad[user_index];
749
if (gamepad_state->connected && !gamepad_state->used) {
750
*slot = gamepad_state;
751
++match_count;
752
}
753
}
754
if (match_count == 1) {
755
*correlation_id = ++gamepad_state->correlation_id;
756
return true;
757
}
758
759
match_count = 0;
760
for (user_index = 0; user_index < wgi_state.per_gamepad_count; ++user_index) {
761
gamepad_state = wgi_state.per_gamepad[user_index];
762
if (RAWINPUT_WindowsGamingInputSlotMatches(state, gamepad_state, xinput_correlated)) {
763
++match_count;
764
*slot = gamepad_state;
765
// Incrementing correlation_id for any match, as negative evidence for others being correlated
766
*correlation_id = ++gamepad_state->correlation_id;
767
}
768
}
769
/* Only return a match if we match exactly one, and we have some non-zero data (buttons or axes) that matched.
770
Note that we're still invalidating *other* potential correlations if we have more than one match or we have no
771
data. */
772
if (match_count == 1 && state->any_data) {
773
return true;
774
}
775
return false;
776
}
777
778
static void RAWINPUT_QuitWindowsGamingInput(RAWINPUT_DeviceContext *ctx)
779
{
780
--wgi_state.ref_count;
781
if (!wgi_state.ref_count && wgi_state.initialized) {
782
int ii;
783
for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {
784
__x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(wgi_state.per_gamepad[ii]->gamepad);
785
}
786
if (wgi_state.per_gamepad) {
787
SDL_free(wgi_state.per_gamepad);
788
wgi_state.per_gamepad = NULL;
789
}
790
wgi_state.per_gamepad_count = 0;
791
if (wgi_state.gamepad_statics) {
792
__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_remove_GamepadAdded(wgi_state.gamepad_statics, wgi_state.gamepad_added_token);
793
__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_remove_GamepadRemoved(wgi_state.gamepad_statics, wgi_state.gamepad_removed_token);
794
__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(wgi_state.gamepad_statics);
795
wgi_state.gamepad_statics = NULL;
796
}
797
WIN_RoUninitialize();
798
wgi_state.initialized = false;
799
}
800
}
801
802
#endif // SDL_JOYSTICK_RAWINPUT_WGI
803
804
static SDL_RAWINPUT_Device *RAWINPUT_AcquireDevice(SDL_RAWINPUT_Device *device)
805
{
806
SDL_AtomicIncRef(&device->refcount);
807
return device;
808
}
809
810
static void RAWINPUT_ReleaseDevice(SDL_RAWINPUT_Device *device)
811
{
812
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
813
if (device->joystick) {
814
RAWINPUT_DeviceContext *ctx = device->joystick->hwdata;
815
816
if (ctx->xinput_enabled && ctx->xinput_correlated) {
817
RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot);
818
ctx->xinput_correlated = false;
819
}
820
}
821
#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
822
823
if (SDL_AtomicDecRef(&device->refcount)) {
824
SDL_free(device->preparsed_data);
825
SDL_free(device->name);
826
SDL_free(device->path);
827
SDL_free(device);
828
}
829
}
830
831
static SDL_RAWINPUT_Device *RAWINPUT_DeviceFromHandle(HANDLE hDevice)
832
{
833
SDL_RAWINPUT_Device *curr;
834
835
for (curr = SDL_RAWINPUT_devices; curr; curr = curr->next) {
836
if (curr->hDevice == hDevice) {
837
return curr;
838
}
839
}
840
return NULL;
841
}
842
843
static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path)
844
{
845
int slot = -1;
846
847
// The format for the raw input device path is documented here:
848
// https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices
849
if (vendor_id == USB_VENDOR_VALVE &&
850
product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
851
(void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot);
852
}
853
return slot;
854
}
855
856
static void RAWINPUT_AddDevice(HANDLE hDevice)
857
{
858
#define CHECK(expression) \
859
{ \
860
if (!(expression)) \
861
goto err; \
862
}
863
SDL_RAWINPUT_Device *device = NULL;
864
SDL_RAWINPUT_Device *curr, *last;
865
RID_DEVICE_INFO rdi;
866
UINT size;
867
char dev_name[MAX_PATH] = { 0 };
868
HANDLE hFile = INVALID_HANDLE_VALUE;
869
870
// Make sure we're not trying to add the same device twice
871
if (RAWINPUT_DeviceFromHandle(hDevice)) {
872
return;
873
}
874
875
// Figure out what kind of device it is
876
size = sizeof(rdi);
877
SDL_zero(rdi);
878
CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICEINFO, &rdi, &size) != (UINT)-1);
879
CHECK(rdi.dwType == RIM_TYPEHID);
880
881
// Get the device "name" (HID Path)
882
size = SDL_arraysize(dev_name);
883
CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, dev_name, &size) != (UINT)-1);
884
// Only take XInput-capable devices
885
CHECK(SDL_strstr(dev_name, "IG_") != NULL);
886
CHECK(!SDL_ShouldIgnoreJoystick((Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber, ""));
887
CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_RAWINPUT_JoystickDriver, (Uint16)rdi.hid.dwVendorId, (Uint16)rdi.hid.dwProductId, (Uint16)rdi.hid.dwVersionNumber, ""));
888
889
device = (SDL_RAWINPUT_Device *)SDL_calloc(1, sizeof(SDL_RAWINPUT_Device));
890
CHECK(device);
891
device->hDevice = hDevice;
892
device->vendor_id = (Uint16)rdi.hid.dwVendorId;
893
device->product_id = (Uint16)rdi.hid.dwProductId;
894
device->version = (Uint16)rdi.hid.dwVersionNumber;
895
device->is_xinput = true;
896
device->is_xboxone = SDL_IsJoystickXboxOne(device->vendor_id, device->product_id);
897
device->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(device->vendor_id, device->product_id, dev_name);
898
899
// Get HID Top-Level Collection Preparsed Data
900
size = 0;
901
CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_PREPARSEDDATA, NULL, &size) != (UINT)-1);
902
device->preparsed_data = (PHIDP_PREPARSED_DATA)SDL_calloc(size, sizeof(BYTE));
903
CHECK(device->preparsed_data);
904
CHECK(GetRawInputDeviceInfoA(hDevice, RIDI_PREPARSEDDATA, device->preparsed_data, &size) != (UINT)-1);
905
906
hFile = CreateFileA(dev_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
907
CHECK(hFile != INVALID_HANDLE_VALUE);
908
909
{
910
char *manufacturer_string = NULL;
911
char *product_string = NULL;
912
WCHAR string[128];
913
914
string[0] = 0;
915
if (SDL_HidD_GetManufacturerString(hFile, string, sizeof(string))) {
916
manufacturer_string = WIN_StringToUTF8W(string);
917
}
918
string[0] = 0;
919
if (SDL_HidD_GetProductString(hFile, string, sizeof(string))) {
920
product_string = WIN_StringToUTF8W(string);
921
}
922
923
device->name = SDL_CreateJoystickName(device->vendor_id, device->product_id, manufacturer_string, product_string);
924
device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, manufacturer_string, product_string, 'r', 0);
925
926
if (manufacturer_string) {
927
SDL_free(manufacturer_string);
928
}
929
if (product_string) {
930
SDL_free(product_string);
931
}
932
}
933
934
device->path = SDL_strdup(dev_name);
935
936
CloseHandle(hFile);
937
hFile = INVALID_HANDLE_VALUE;
938
939
device->joystick_id = SDL_GetNextObjectID();
940
941
#ifdef DEBUG_RAWINPUT
942
SDL_Log("Adding RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle 0x%.8x", device->name, device->vendor_id, device->product_id, device->version, device->hDevice);
943
#endif
944
945
// Add it to the list
946
RAWINPUT_AcquireDevice(device);
947
for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) {
948
}
949
if (last) {
950
last->next = device;
951
} else {
952
SDL_RAWINPUT_devices = device;
953
}
954
955
++SDL_RAWINPUT_numjoysticks;
956
957
SDL_PrivateJoystickAdded(device->joystick_id);
958
959
return;
960
961
err:
962
if (hFile != INVALID_HANDLE_VALUE) {
963
CloseHandle(hFile);
964
}
965
if (device) {
966
if (device->name) {
967
SDL_free(device->name);
968
}
969
if (device->path) {
970
SDL_free(device->path);
971
}
972
SDL_free(device);
973
}
974
#undef CHECK
975
}
976
977
static void RAWINPUT_DelDevice(SDL_RAWINPUT_Device *device, bool send_event)
978
{
979
SDL_RAWINPUT_Device *curr, *last;
980
for (curr = SDL_RAWINPUT_devices, last = NULL; curr; last = curr, curr = curr->next) {
981
if (curr == device) {
982
if (last) {
983
last->next = curr->next;
984
} else {
985
SDL_RAWINPUT_devices = curr->next;
986
}
987
--SDL_RAWINPUT_numjoysticks;
988
989
SDL_PrivateJoystickRemoved(device->joystick_id);
990
991
#ifdef DEBUG_RAWINPUT
992
SDL_Log("Removing RAWINPUT device '%s' VID 0x%.4x, PID 0x%.4x, version %d, handle %p", device->name, device->vendor_id, device->product_id, device->version, device->hDevice);
993
#endif
994
RAWINPUT_ReleaseDevice(device);
995
return;
996
}
997
}
998
}
999
1000
static void RAWINPUT_DetectDevices(void)
1001
{
1002
UINT device_count = 0;
1003
1004
if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) != -1) && device_count > 0) {
1005
PRAWINPUTDEVICELIST devices = NULL;
1006
UINT i;
1007
1008
devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * device_count);
1009
if (devices) {
1010
device_count = GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST));
1011
if (device_count != (UINT)-1) {
1012
for (i = 0; i < device_count; ++i) {
1013
RAWINPUT_AddDevice(devices[i].hDevice);
1014
}
1015
}
1016
SDL_free(devices);
1017
}
1018
}
1019
}
1020
1021
static void RAWINPUT_RemoveDevices(void)
1022
{
1023
while (SDL_RAWINPUT_devices) {
1024
RAWINPUT_DelDevice(SDL_RAWINPUT_devices, false);
1025
}
1026
SDL_assert(SDL_RAWINPUT_numjoysticks == 0);
1027
}
1028
1029
static bool RAWINPUT_JoystickInit(void)
1030
{
1031
SDL_assert(!SDL_RAWINPUT_inited);
1032
1033
if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT, false)) {
1034
return true;
1035
}
1036
1037
if (!WIN_IsWindowsVistaOrGreater()) {
1038
// According to bug 6400, this doesn't work on Windows XP
1039
return false;
1040
}
1041
1042
if (!WIN_LoadHIDDLL()) {
1043
return false;
1044
}
1045
1046
SDL_RAWINPUT_inited = true;
1047
1048
RAWINPUT_DetectDevices();
1049
1050
return true;
1051
}
1052
1053
static int RAWINPUT_JoystickGetCount(void)
1054
{
1055
return SDL_RAWINPUT_numjoysticks;
1056
}
1057
1058
bool RAWINPUT_IsEnabled(void)
1059
{
1060
return SDL_RAWINPUT_inited && !SDL_RAWINPUT_remote_desktop;
1061
}
1062
1063
static void RAWINPUT_PostUpdate(void)
1064
{
1065
#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1066
bool unmapped_guide_pressed = false;
1067
1068
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1069
if (!wgi_state.dirty) {
1070
int ii;
1071
for (ii = 0; ii < wgi_state.per_gamepad_count; ii++) {
1072
WindowsGamingInputGamepadState *gamepad_state = wgi_state.per_gamepad[ii];
1073
if (!gamepad_state->used && (gamepad_state->state.Buttons & GamepadButtons_GUIDE)) {
1074
unmapped_guide_pressed = true;
1075
break;
1076
}
1077
}
1078
}
1079
wgi_state.dirty = true;
1080
#endif
1081
1082
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1083
if (!xinput_state_dirty) {
1084
int ii;
1085
for (ii = 0; ii < SDL_arraysize(xinput_state); ii++) {
1086
if (xinput_state[ii].connected && !xinput_state[ii].used && (xinput_state[ii].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE)) {
1087
unmapped_guide_pressed = true;
1088
break;
1089
}
1090
}
1091
}
1092
xinput_state_dirty = true;
1093
#endif
1094
1095
if (unmapped_guide_pressed) {
1096
if (guide_button_candidate.joystick && !guide_button_candidate.last_joystick) {
1097
SDL_Joystick *joystick = guide_button_candidate.joystick;
1098
RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1099
if (ctx->guide_hack) {
1100
int guide_button = joystick->nbuttons - 1;
1101
1102
SDL_SendJoystickButton(SDL_GetTicksNS(), guide_button_candidate.joystick, (Uint8)guide_button, true);
1103
}
1104
guide_button_candidate.last_joystick = guide_button_candidate.joystick;
1105
}
1106
} else if (guide_button_candidate.last_joystick) {
1107
SDL_Joystick *joystick = guide_button_candidate.last_joystick;
1108
RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1109
if (ctx->guide_hack) {
1110
int guide_button = joystick->nbuttons - 1;
1111
1112
SDL_SendJoystickButton(SDL_GetTicksNS(), joystick, (Uint8)guide_button, false);
1113
}
1114
guide_button_candidate.last_joystick = NULL;
1115
}
1116
guide_button_candidate.joystick = NULL;
1117
1118
#endif // SDL_JOYSTICK_RAWINPUT_MATCHING
1119
}
1120
1121
static void RAWINPUT_JoystickDetect(void)
1122
{
1123
bool remote_desktop;
1124
1125
if (!SDL_RAWINPUT_inited) {
1126
return;
1127
}
1128
1129
remote_desktop = GetSystemMetrics(SM_REMOTESESSION) ? true : false;
1130
if (remote_desktop != SDL_RAWINPUT_remote_desktop) {
1131
SDL_RAWINPUT_remote_desktop = remote_desktop;
1132
1133
WINDOWS_RAWINPUTEnabledChanged();
1134
1135
if (remote_desktop) {
1136
RAWINPUT_RemoveDevices();
1137
WINDOWS_JoystickDetect();
1138
} else {
1139
WINDOWS_JoystickDetect();
1140
RAWINPUT_DetectDevices();
1141
}
1142
}
1143
RAWINPUT_PostUpdate();
1144
}
1145
1146
static bool RAWINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
1147
{
1148
SDL_RAWINPUT_Device *device;
1149
1150
// If we're being asked about a device, that means another API just detected one, so rescan
1151
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1152
xinput_device_change = true;
1153
#endif
1154
1155
device = SDL_RAWINPUT_devices;
1156
while (device) {
1157
if (vendor_id == device->vendor_id && product_id == device->product_id) {
1158
return true;
1159
}
1160
1161
/* The Xbox 360 wireless controller shows up as product 0 in WGI.
1162
Try to match it to a Raw Input device via name or known product ID. */
1163
if (vendor_id == device->vendor_id && product_id == 0 &&
1164
((name && SDL_strstr(device->name, name) != NULL) ||
1165
(device->vendor_id == USB_VENDOR_MICROSOFT &&
1166
device->product_id == USB_PRODUCT_XBOX360_XUSB_CONTROLLER))) {
1167
return true;
1168
}
1169
1170
// The Xbox One controller shows up as a hardcoded raw input VID/PID
1171
if (name && SDL_strcmp(name, "Xbox One Game Controller") == 0 &&
1172
device->vendor_id == USB_VENDOR_MICROSOFT &&
1173
device->product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) {
1174
return true;
1175
}
1176
1177
device = device->next;
1178
}
1179
return false;
1180
}
1181
1182
static SDL_RAWINPUT_Device *RAWINPUT_GetDeviceByIndex(int device_index)
1183
{
1184
SDL_RAWINPUT_Device *device = SDL_RAWINPUT_devices;
1185
while (device) {
1186
if (device_index == 0) {
1187
break;
1188
}
1189
--device_index;
1190
device = device->next;
1191
}
1192
return device;
1193
}
1194
1195
static const char *RAWINPUT_JoystickGetDeviceName(int device_index)
1196
{
1197
return RAWINPUT_GetDeviceByIndex(device_index)->name;
1198
}
1199
1200
static const char *RAWINPUT_JoystickGetDevicePath(int device_index)
1201
{
1202
return RAWINPUT_GetDeviceByIndex(device_index)->path;
1203
}
1204
1205
static int RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
1206
{
1207
return RAWINPUT_GetDeviceByIndex(device_index)->steam_virtual_gamepad_slot;
1208
}
1209
1210
static int RAWINPUT_JoystickGetDevicePlayerIndex(int device_index)
1211
{
1212
return false;
1213
}
1214
1215
static void RAWINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index)
1216
{
1217
}
1218
1219
static SDL_GUID RAWINPUT_JoystickGetDeviceGUID(int device_index)
1220
{
1221
return RAWINPUT_GetDeviceByIndex(device_index)->guid;
1222
}
1223
1224
static SDL_JoystickID RAWINPUT_JoystickGetDeviceInstanceID(int device_index)
1225
{
1226
return RAWINPUT_GetDeviceByIndex(device_index)->joystick_id;
1227
}
1228
1229
static int SDLCALL RAWINPUT_SortValueCaps(const void *A, const void *B)
1230
{
1231
HIDP_VALUE_CAPS *capsA = (HIDP_VALUE_CAPS *)A;
1232
HIDP_VALUE_CAPS *capsB = (HIDP_VALUE_CAPS *)B;
1233
1234
// Sort by Usage for single values, or UsageMax for range of values
1235
return (int)capsA->NotRange.Usage - capsB->NotRange.Usage;
1236
}
1237
1238
static bool RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)
1239
{
1240
SDL_RAWINPUT_Device *device = RAWINPUT_GetDeviceByIndex(device_index);
1241
RAWINPUT_DeviceContext *ctx;
1242
HIDP_CAPS caps;
1243
HIDP_BUTTON_CAPS *button_caps;
1244
HIDP_VALUE_CAPS *value_caps;
1245
ULONG i;
1246
1247
ctx = (RAWINPUT_DeviceContext *)SDL_calloc(1, sizeof(RAWINPUT_DeviceContext));
1248
if (!ctx) {
1249
return false;
1250
}
1251
joystick->hwdata = ctx;
1252
1253
ctx->device = RAWINPUT_AcquireDevice(device);
1254
device->joystick = joystick;
1255
1256
if (device->is_xinput) {
1257
// We'll try to get guide button and trigger axes from XInput
1258
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1259
xinput_device_change = true;
1260
ctx->xinput_enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, true);
1261
if (ctx->xinput_enabled && (!WIN_LoadXInputDLL() || !XINPUTGETSTATE)) {
1262
ctx->xinput_enabled = false;
1263
}
1264
ctx->xinput_slot = XUSER_INDEX_ANY;
1265
#endif
1266
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1267
RAWINPUT_InitWindowsGamingInput(ctx);
1268
#endif
1269
}
1270
1271
ctx->is_xinput = device->is_xinput;
1272
ctx->is_xboxone = device->is_xboxone;
1273
#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1274
ctx->match_state = 0x0000008800000000ULL; // Trigger axes at rest
1275
#endif
1276
ctx->preparsed_data = device->preparsed_data;
1277
ctx->max_data_length = SDL_HidP_MaxDataListLength(HidP_Input, ctx->preparsed_data);
1278
ctx->data = (HIDP_DATA *)SDL_malloc(ctx->max_data_length * sizeof(*ctx->data));
1279
if (!ctx->data) {
1280
RAWINPUT_JoystickClose(joystick);
1281
return false;
1282
}
1283
1284
if (SDL_HidP_GetCaps(ctx->preparsed_data, &caps) != HIDP_STATUS_SUCCESS) {
1285
RAWINPUT_JoystickClose(joystick);
1286
return SDL_SetError("Couldn't get device capabilities");
1287
}
1288
1289
button_caps = SDL_stack_alloc(HIDP_BUTTON_CAPS, caps.NumberInputButtonCaps);
1290
if (SDL_HidP_GetButtonCaps(HidP_Input, button_caps, &caps.NumberInputButtonCaps, ctx->preparsed_data) != HIDP_STATUS_SUCCESS) {
1291
RAWINPUT_JoystickClose(joystick);
1292
return SDL_SetError("Couldn't get device button capabilities");
1293
}
1294
1295
value_caps = SDL_stack_alloc(HIDP_VALUE_CAPS, caps.NumberInputValueCaps);
1296
if (SDL_HidP_GetValueCaps(HidP_Input, value_caps, &caps.NumberInputValueCaps, ctx->preparsed_data) != HIDP_STATUS_SUCCESS) {
1297
RAWINPUT_JoystickClose(joystick);
1298
SDL_stack_free(button_caps);
1299
return SDL_SetError("Couldn't get device value capabilities");
1300
}
1301
1302
// Sort the axes by usage, so X comes before Y, etc.
1303
SDL_qsort(value_caps, caps.NumberInputValueCaps, sizeof(*value_caps), RAWINPUT_SortValueCaps);
1304
1305
for (i = 0; i < caps.NumberInputButtonCaps; ++i) {
1306
HIDP_BUTTON_CAPS *cap = &button_caps[i];
1307
1308
if (cap->UsagePage == USB_USAGEPAGE_BUTTON) {
1309
int count;
1310
1311
if (cap->IsRange) {
1312
count = 1 + (cap->Range.DataIndexMax - cap->Range.DataIndexMin);
1313
} else {
1314
count = 1;
1315
}
1316
1317
joystick->nbuttons += count;
1318
}
1319
}
1320
1321
if (joystick->nbuttons > 0) {
1322
int button_index = 0;
1323
1324
ctx->button_indices = (USHORT *)SDL_malloc(joystick->nbuttons * sizeof(*ctx->button_indices));
1325
if (!ctx->button_indices) {
1326
RAWINPUT_JoystickClose(joystick);
1327
SDL_stack_free(value_caps);
1328
SDL_stack_free(button_caps);
1329
return false;
1330
}
1331
1332
for (i = 0; i < caps.NumberInputButtonCaps; ++i) {
1333
HIDP_BUTTON_CAPS *cap = &button_caps[i];
1334
1335
if (cap->UsagePage == USB_USAGEPAGE_BUTTON) {
1336
if (cap->IsRange) {
1337
int j, count = 1 + (cap->Range.DataIndexMax - cap->Range.DataIndexMin);
1338
1339
for (j = 0; j < count; ++j) {
1340
ctx->button_indices[button_index++] = (USHORT)(cap->Range.DataIndexMin + j);
1341
}
1342
} else {
1343
ctx->button_indices[button_index++] = cap->NotRange.DataIndex;
1344
}
1345
}
1346
}
1347
}
1348
if (ctx->is_xinput && joystick->nbuttons == 10) {
1349
ctx->guide_hack = true;
1350
joystick->nbuttons += 1;
1351
}
1352
1353
SDL_stack_free(button_caps);
1354
1355
for (i = 0; i < caps.NumberInputValueCaps; ++i) {
1356
HIDP_VALUE_CAPS *cap = &value_caps[i];
1357
1358
if (cap->IsRange) {
1359
continue;
1360
}
1361
1362
if (ctx->trigger_hack && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) {
1363
continue;
1364
}
1365
1366
if (cap->NotRange.Usage == USB_USAGE_GENERIC_HAT) {
1367
joystick->nhats += 1;
1368
continue;
1369
}
1370
1371
if (ctx->is_xinput && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) {
1372
continue;
1373
}
1374
1375
joystick->naxes += 1;
1376
}
1377
1378
if (joystick->naxes > 0) {
1379
int axis_index = 0;
1380
1381
ctx->axis_indices = (USHORT *)SDL_malloc(joystick->naxes * sizeof(*ctx->axis_indices));
1382
if (!ctx->axis_indices) {
1383
RAWINPUT_JoystickClose(joystick);
1384
SDL_stack_free(value_caps);
1385
return false;
1386
}
1387
1388
for (i = 0; i < caps.NumberInputValueCaps; ++i) {
1389
HIDP_VALUE_CAPS *cap = &value_caps[i];
1390
1391
if (cap->IsRange) {
1392
continue;
1393
}
1394
1395
if (cap->NotRange.Usage == USB_USAGE_GENERIC_HAT) {
1396
continue;
1397
}
1398
1399
if (ctx->is_xinput && cap->NotRange.Usage == USB_USAGE_GENERIC_Z) {
1400
ctx->trigger_hack = true;
1401
ctx->trigger_hack_index = cap->NotRange.DataIndex;
1402
continue;
1403
}
1404
1405
ctx->axis_indices[axis_index++] = cap->NotRange.DataIndex;
1406
}
1407
}
1408
if (ctx->trigger_hack) {
1409
joystick->naxes += 2;
1410
}
1411
1412
if (joystick->nhats > 0) {
1413
int hat_index = 0;
1414
1415
ctx->hat_indices = (USHORT *)SDL_malloc(joystick->nhats * sizeof(*ctx->hat_indices));
1416
if (!ctx->hat_indices) {
1417
RAWINPUT_JoystickClose(joystick);
1418
SDL_stack_free(value_caps);
1419
return false;
1420
}
1421
1422
for (i = 0; i < caps.NumberInputValueCaps; ++i) {
1423
HIDP_VALUE_CAPS *cap = &value_caps[i];
1424
1425
if (cap->IsRange) {
1426
continue;
1427
}
1428
1429
if (cap->NotRange.Usage != USB_USAGE_GENERIC_HAT) {
1430
continue;
1431
}
1432
1433
ctx->hat_indices[hat_index++] = cap->NotRange.DataIndex;
1434
}
1435
}
1436
1437
SDL_stack_free(value_caps);
1438
1439
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1440
if (ctx->is_xinput) {
1441
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
1442
}
1443
#endif
1444
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1445
if (ctx->is_xinput) {
1446
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
1447
1448
if (ctx->is_xboxone) {
1449
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
1450
}
1451
}
1452
#endif
1453
1454
return true;
1455
}
1456
1457
static bool RAWINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1458
{
1459
#if defined(SDL_JOYSTICK_RAWINPUT_WGI) || defined(SDL_JOYSTICK_RAWINPUT_XINPUT)
1460
RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1461
#endif
1462
bool rumbled = false;
1463
1464
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1465
// Prefer XInput over WGI because it allows rumble in the background
1466
if (!rumbled && ctx->xinput_correlated && !ctx->triggers_rumbling) {
1467
XINPUT_VIBRATION XVibration;
1468
1469
if (!XINPUTSETSTATE) {
1470
return SDL_Unsupported();
1471
}
1472
1473
XVibration.wLeftMotorSpeed = low_frequency_rumble;
1474
XVibration.wRightMotorSpeed = high_frequency_rumble;
1475
if (XINPUTSETSTATE(ctx->xinput_slot, &XVibration) == ERROR_SUCCESS) {
1476
rumbled = true;
1477
} else {
1478
return SDL_SetError("XInputSetState() failed");
1479
}
1480
}
1481
#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
1482
1483
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1484
// Save off the motor state in case trigger rumble is started
1485
ctx->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16;
1486
ctx->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16;
1487
if (!rumbled && ctx->wgi_correlated) {
1488
WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot;
1489
HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration);
1490
if (SUCCEEDED(hr)) {
1491
rumbled = true;
1492
}
1493
}
1494
#endif
1495
1496
if (!rumbled) {
1497
#if defined(SDL_JOYSTICK_RAWINPUT_WGI) || defined(SDL_JOYSTICK_RAWINPUT_XINPUT)
1498
return SDL_SetError("Controller isn't correlated yet, try hitting a button first");
1499
#else
1500
return SDL_Unsupported();
1501
#endif
1502
}
1503
return true;
1504
}
1505
1506
static bool RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
1507
{
1508
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1509
RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1510
1511
ctx->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16;
1512
ctx->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16;
1513
if (ctx->wgi_correlated) {
1514
WindowsGamingInputGamepadState *gamepad_state = ctx->wgi_slot;
1515
HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(gamepad_state->gamepad, ctx->vibration);
1516
if (!SUCCEEDED(hr)) {
1517
return SDL_SetError("Setting vibration failed: 0x%lx", hr);
1518
}
1519
ctx->triggers_rumbling = (left_rumble > 0 || right_rumble > 0);
1520
return true;
1521
} else {
1522
return SDL_SetError("Controller isn't correlated yet, try hitting a button first");
1523
}
1524
#else
1525
return SDL_Unsupported();
1526
#endif
1527
}
1528
1529
static bool RAWINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1530
{
1531
return SDL_Unsupported();
1532
}
1533
1534
static bool RAWINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
1535
{
1536
return SDL_Unsupported();
1537
}
1538
1539
static bool RAWINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
1540
{
1541
return SDL_Unsupported();
1542
}
1543
1544
static HIDP_DATA *GetData(USHORT index, HIDP_DATA *data, ULONG length)
1545
{
1546
ULONG i;
1547
1548
// Check to see if the data is at the expected offset
1549
if (index < length && data[index].DataIndex == index) {
1550
return &data[index];
1551
}
1552
1553
// Loop through the data to find it
1554
for (i = 0; i < length; ++i) {
1555
if (data[i].DataIndex == index) {
1556
return &data[i];
1557
}
1558
}
1559
return NULL;
1560
}
1561
1562
/* This is the packet format for Xbox 360 and Xbox One controllers on Windows,
1563
however with this interface there is no rumble support, no guide button,
1564
and the left and right triggers are tied together as a single axis.
1565
1566
We use XInput and Windows.Gaming.Input to make up for these shortcomings.
1567
*/
1568
static void RAWINPUT_HandleStatePacket(SDL_Joystick *joystick, Uint8 *data, int size)
1569
{
1570
RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1571
#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1572
// Map new buttons and axes into game controller controls
1573
static const int button_map[] = {
1574
SDL_GAMEPAD_BUTTON_SOUTH,
1575
SDL_GAMEPAD_BUTTON_EAST,
1576
SDL_GAMEPAD_BUTTON_WEST,
1577
SDL_GAMEPAD_BUTTON_NORTH,
1578
SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
1579
SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
1580
SDL_GAMEPAD_BUTTON_BACK,
1581
SDL_GAMEPAD_BUTTON_START,
1582
SDL_GAMEPAD_BUTTON_LEFT_STICK,
1583
SDL_GAMEPAD_BUTTON_RIGHT_STICK
1584
};
1585
#define HAT_MASK ((1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT))
1586
static const int hat_map[] = {
1587
0,
1588
(1 << SDL_GAMEPAD_BUTTON_DPAD_UP),
1589
(1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT),
1590
(1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT),
1591
(1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT),
1592
(1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN),
1593
(1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT),
1594
(1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT),
1595
(1 << SDL_GAMEPAD_BUTTON_DPAD_UP) | (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT),
1596
0,
1597
};
1598
Uint64 match_state = ctx->match_state;
1599
// Update match_state with button bit, then fall through
1600
#define SDL_SendJoystickButton(timestamp, joystick, button, down) \
1601
if (button < SDL_arraysize(button_map)) { \
1602
Uint64 button_bit = 1ull << button_map[button]; \
1603
match_state = (match_state & ~button_bit) | (button_bit * (down)); \
1604
} \
1605
SDL_SendJoystickButton(timestamp, joystick, button, down)
1606
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_AXES
1607
// Grab high 4 bits of value, then fall through
1608
#define AddAxisToMatchState(axis, value) \
1609
{ \
1610
match_state = (match_state & ~(0xFull << (4 * axis + 16))) | ((value)&0xF000ull) << (4 * axis + 4); \
1611
}
1612
#define SDL_SendJoystickAxis(timestamp, joystick, axis, value) \
1613
if (axis < 4) \
1614
AddAxisToMatchState(axis, value); \
1615
SDL_SendJoystickAxis(timestamp, joystick, axis, value)
1616
#endif
1617
#endif // SDL_JOYSTICK_RAWINPUT_MATCHING
1618
1619
ULONG data_length = ctx->max_data_length;
1620
int i;
1621
int nbuttons = joystick->nbuttons - (ctx->guide_hack * 1);
1622
int naxes = joystick->naxes - (ctx->trigger_hack * 2);
1623
int nhats = joystick->nhats;
1624
Uint32 button_mask = 0;
1625
Uint64 timestamp = SDL_GetTicksNS();
1626
1627
if (SDL_HidP_GetData(HidP_Input, ctx->data, &data_length, ctx->preparsed_data, (PCHAR)data, size) != HIDP_STATUS_SUCCESS) {
1628
return;
1629
}
1630
1631
for (i = 0; i < nbuttons; ++i) {
1632
HIDP_DATA *item = GetData(ctx->button_indices[i], ctx->data, data_length);
1633
if (item && item->On) {
1634
button_mask |= (1 << i);
1635
}
1636
}
1637
for (i = 0; i < nbuttons; ++i) {
1638
SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, ((button_mask & (1 << i)) != 0));
1639
}
1640
1641
for (i = 0; i < naxes; ++i) {
1642
HIDP_DATA *item = GetData(ctx->axis_indices[i], ctx->data, data_length);
1643
if (item) {
1644
Sint16 axis = (int)(Uint16)item->RawValue - 0x8000;
1645
SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, axis);
1646
}
1647
}
1648
1649
for (i = 0; i < nhats; ++i) {
1650
HIDP_DATA *item = GetData(ctx->hat_indices[i], ctx->data, data_length);
1651
if (item) {
1652
Uint8 hat = SDL_HAT_CENTERED;
1653
const Uint8 hat_states[] = {
1654
SDL_HAT_CENTERED,
1655
SDL_HAT_UP,
1656
SDL_HAT_UP | SDL_HAT_RIGHT,
1657
SDL_HAT_RIGHT,
1658
SDL_HAT_DOWN | SDL_HAT_RIGHT,
1659
SDL_HAT_DOWN,
1660
SDL_HAT_DOWN | SDL_HAT_LEFT,
1661
SDL_HAT_LEFT,
1662
SDL_HAT_UP | SDL_HAT_LEFT,
1663
SDL_HAT_CENTERED,
1664
};
1665
ULONG state = item->RawValue;
1666
1667
if (state < SDL_arraysize(hat_states)) {
1668
#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1669
match_state = (match_state & ~HAT_MASK) | hat_map[state];
1670
#endif
1671
hat = hat_states[state];
1672
}
1673
SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat);
1674
}
1675
}
1676
1677
#ifdef SDL_SendJoystickButton
1678
#undef SDL_SendJoystickButton
1679
#endif
1680
#ifdef SDL_SendJoystickAxis
1681
#undef SDL_SendJoystickAxis
1682
#endif
1683
1684
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
1685
#define AddTriggerToMatchState(axis, value) \
1686
{ \
1687
int match_axis = axis + SDL_JOYSTICK_RAWINPUT_MATCH_COUNT - joystick->naxes; \
1688
AddAxisToMatchState(match_axis, value); \
1689
}
1690
#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
1691
1692
if (ctx->trigger_hack) {
1693
bool has_trigger_data = false;
1694
int left_trigger = joystick->naxes - 2;
1695
int right_trigger = joystick->naxes - 1;
1696
1697
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1698
// Prefer XInput over WindowsGamingInput, it continues to provide data in the background
1699
if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) {
1700
has_trigger_data = true;
1701
}
1702
#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
1703
1704
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1705
if (!has_trigger_data && ctx->wgi_correlated) {
1706
has_trigger_data = true;
1707
}
1708
#endif // SDL_JOYSTICK_RAWINPUT_WGI
1709
1710
#ifndef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
1711
if (!has_trigger_data)
1712
#endif
1713
{
1714
HIDP_DATA *item = GetData(ctx->trigger_hack_index, ctx->data, data_length);
1715
if (item) {
1716
Sint16 value = (int)(Uint16)item->RawValue - 0x8000;
1717
Sint16 left_value = (value > 0) ? (value * 2 - 32767) : SDL_MIN_SINT16;
1718
Sint16 right_value = (value < 0) ? (-value * 2 - 32769) : SDL_MIN_SINT16;
1719
1720
#ifdef SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
1721
AddTriggerToMatchState(left_trigger, left_value);
1722
AddTriggerToMatchState(right_trigger, right_value);
1723
if (!has_trigger_data)
1724
#endif // SDL_JOYSTICK_RAWINPUT_MATCH_TRIGGERS
1725
{
1726
SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, left_value);
1727
SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, right_value);
1728
}
1729
}
1730
}
1731
}
1732
1733
#ifdef AddAxisToMatchState
1734
#undef AddAxisToMatchState
1735
#endif
1736
#ifdef AddTriggerToMatchState
1737
#undef AddTriggerToMatchState
1738
#endif
1739
1740
#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1741
if (ctx->is_xinput) {
1742
ctx->match_state = match_state;
1743
ctx->last_state_packet = SDL_GetTicks();
1744
}
1745
#endif
1746
}
1747
1748
static void RAWINPUT_UpdateOtherAPIs(SDL_Joystick *joystick)
1749
{
1750
#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
1751
RAWINPUT_DeviceContext *ctx = joystick->hwdata;
1752
bool has_trigger_data = false;
1753
bool correlated = false;
1754
WindowsMatchState match_state_xinput;
1755
int guide_button = joystick->nbuttons - 1;
1756
int left_trigger = joystick->naxes - 2;
1757
int right_trigger = joystick->naxes - 1;
1758
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1759
bool xinput_correlated;
1760
#endif
1761
1762
RAWINPUT_FillMatchState(&match_state_xinput, ctx->match_state);
1763
1764
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
1765
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1766
xinput_correlated = ctx->xinput_correlated;
1767
#else
1768
xinput_correlated = false;
1769
#endif
1770
// Parallel logic to WINDOWS_XINPUT below
1771
RAWINPUT_UpdateWindowsGamingInput();
1772
if (ctx->wgi_correlated &&
1773
!joystick->low_frequency_rumble && !joystick->high_frequency_rumble &&
1774
!joystick->left_trigger_rumble && !joystick->right_trigger_rumble) {
1775
// We have been previously correlated, ensure we are still matching, see comments in XINPUT section
1776
if (RAWINPUT_WindowsGamingInputSlotMatches(&match_state_xinput, ctx->wgi_slot, xinput_correlated)) {
1777
ctx->wgi_uncorrelate_count = 0;
1778
} else {
1779
++ctx->wgi_uncorrelate_count;
1780
/* Only un-correlate if this is consistent over multiple Update() calls - the timing of polling/event
1781
pumping can easily cause this to uncorrelate for a frame. 2 seemed reliable in my testing, but
1782
let's set it to 5 to be safe. An incorrect un-correlation will simply result in lower precision
1783
triggers for a frame. */
1784
if (ctx->wgi_uncorrelate_count >= 5) {
1785
#ifdef DEBUG_RAWINPUT
1786
SDL_Log("UN-Correlated joystick %d to WindowsGamingInput device #%d", joystick->instance_id, ctx->wgi_slot);
1787
#endif
1788
RAWINPUT_MarkWindowsGamingInputSlotFree(ctx->wgi_slot);
1789
ctx->wgi_correlated = false;
1790
ctx->wgi_correlation_count = 0;
1791
// Force release of Guide button, it can't possibly be down on this device now.
1792
/* It gets left down if we were actually correlated incorrectly and it was released on the WindowsGamingInput
1793
device but we didn't get a state packet. */
1794
if (ctx->guide_hack) {
1795
SDL_SendJoystickButton(0, joystick, (Uint8)guide_button, false);
1796
}
1797
}
1798
}
1799
}
1800
if (!ctx->wgi_correlated) {
1801
Uint8 new_correlation_count = 0;
1802
if (RAWINPUT_MissingWindowsGamingInputSlot()) {
1803
Uint8 correlation_id = 0;
1804
WindowsGamingInputGamepadState *slot_idx = NULL;
1805
if (RAWINPUT_GuessWindowsGamingInputSlot(&match_state_xinput, &correlation_id, &slot_idx, xinput_correlated)) {
1806
// we match exactly one WindowsGamingInput device
1807
/* Probably can do without wgi_correlation_count, just check and clear wgi_slot to NULL, unless we need
1808
even more frames to be sure. */
1809
if (ctx->wgi_correlation_count && ctx->wgi_slot == slot_idx) {
1810
// was correlated previously, and still the same device
1811
if (ctx->wgi_correlation_id + 1 == correlation_id) {
1812
// no one else was correlated in the meantime
1813
new_correlation_count = ctx->wgi_correlation_count + 1;
1814
if (new_correlation_count == 2) {
1815
// correlation stayed steady and uncontested across multiple frames, guaranteed match
1816
ctx->wgi_correlated = true;
1817
#ifdef DEBUG_RAWINPUT
1818
SDL_Log("Correlated joystick %d to WindowsGamingInput device #%d", joystick->instance_id, slot_idx);
1819
#endif
1820
correlated = true;
1821
RAWINPUT_MarkWindowsGamingInputSlotUsed(ctx->wgi_slot, ctx);
1822
// If the generalized Guide button was using us, it doesn't need to anymore
1823
if (guide_button_candidate.joystick == joystick) {
1824
guide_button_candidate.joystick = NULL;
1825
}
1826
if (guide_button_candidate.last_joystick == joystick) {
1827
guide_button_candidate.last_joystick = NULL;
1828
}
1829
}
1830
} else {
1831
// someone else also possibly correlated to this device, start over
1832
new_correlation_count = 1;
1833
}
1834
} else {
1835
// new possible correlation
1836
new_correlation_count = 1;
1837
ctx->wgi_slot = slot_idx;
1838
}
1839
ctx->wgi_correlation_id = correlation_id;
1840
} else {
1841
// Match multiple WindowsGamingInput devices, or none (possibly due to no buttons pressed)
1842
}
1843
}
1844
ctx->wgi_correlation_count = new_correlation_count;
1845
} else {
1846
correlated = true;
1847
}
1848
#endif // SDL_JOYSTICK_RAWINPUT_WGI
1849
1850
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1851
// Parallel logic to WINDOWS_GAMING_INPUT above
1852
if (ctx->xinput_enabled) {
1853
RAWINPUT_UpdateXInput();
1854
if (ctx->xinput_correlated &&
1855
!joystick->low_frequency_rumble && !joystick->high_frequency_rumble) {
1856
// We have been previously correlated, ensure we are still matching
1857
/* This is required to deal with two (mostly) un-preventable mis-correlation situations:
1858
A) Since the HID data stream does not provide an initial state (but polling XInput does), if we open
1859
5 controllers (#1-4 XInput mapped, #5 is not), and controller 1 had the A button down (and we don't
1860
know), and the user presses A on controller #5, we'll see exactly 1 controller with A down (#5) and
1861
exactly 1 XInput device with A down (#1), and incorrectly correlate. This code will then un-correlate
1862
when A is released from either controller #1 or #5.
1863
B) Since the app may not open all controllers, we could have a similar situation where only controller #5
1864
is opened, and the user holds A on controllers #1 and #5 simultaneously - again we see only 1 controller
1865
with A down and 1 XInput device with A down, and incorrectly correlate. This should be very unusual
1866
(only when apps do not open all controllers, yet are listening to Guide button presses, yet
1867
for some reason want to ignore guide button presses on the un-opened controllers, yet users are
1868
pressing buttons on the unopened controllers), and will resolve itself when either button is released
1869
and we un-correlate. We could prevent this by processing the state packets for *all* controllers,
1870
even un-opened ones, as that would allow more precise correlation.
1871
*/
1872
if (RAWINPUT_XInputSlotMatches(&match_state_xinput, ctx->xinput_slot)) {
1873
ctx->xinput_uncorrelate_count = 0;
1874
} else {
1875
++ctx->xinput_uncorrelate_count;
1876
/* Only un-correlate if this is consistent over multiple Update() calls - the timing of polling/event
1877
pumping can easily cause this to uncorrelate for a frame. 2 seemed reliable in my testing, but
1878
let's set it to 5 to be safe. An incorrect un-correlation will simply result in lower precision
1879
triggers for a frame. */
1880
if (ctx->xinput_uncorrelate_count >= 5) {
1881
#ifdef DEBUG_RAWINPUT
1882
SDL_Log("UN-Correlated joystick %d to XInput device #%d", joystick->instance_id, ctx->xinput_slot);
1883
#endif
1884
RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot);
1885
ctx->xinput_correlated = false;
1886
ctx->xinput_correlation_count = 0;
1887
// Force release of Guide button, it can't possibly be down on this device now.
1888
/* It gets left down if we were actually correlated incorrectly and it was released on the XInput
1889
device but we didn't get a state packet. */
1890
if (ctx->guide_hack) {
1891
SDL_SendJoystickButton(0, joystick, (Uint8)guide_button, false);
1892
}
1893
}
1894
}
1895
}
1896
if (!ctx->xinput_correlated) {
1897
Uint8 new_correlation_count = 0;
1898
if (RAWINPUT_MissingXInputSlot()) {
1899
Uint8 correlation_id = 0;
1900
Uint8 slot_idx = 0;
1901
if (RAWINPUT_GuessXInputSlot(&match_state_xinput, &correlation_id, &slot_idx)) {
1902
// we match exactly one XInput device
1903
/* Probably can do without xinput_correlation_count, just check and clear xinput_slot to ANY, unless
1904
we need even more frames to be sure */
1905
if (ctx->xinput_correlation_count && ctx->xinput_slot == slot_idx) {
1906
// was correlated previously, and still the same device
1907
if (ctx->xinput_correlation_id + 1 == correlation_id) {
1908
// no one else was correlated in the meantime
1909
new_correlation_count = ctx->xinput_correlation_count + 1;
1910
if (new_correlation_count == 2) {
1911
// correlation stayed steady and uncontested across multiple frames, guaranteed match
1912
ctx->xinput_correlated = true;
1913
#ifdef DEBUG_RAWINPUT
1914
SDL_Log("Correlated joystick %d to XInput device #%d", joystick->instance_id, slot_idx);
1915
#endif
1916
correlated = true;
1917
RAWINPUT_MarkXInputSlotUsed(ctx->xinput_slot);
1918
// If the generalized Guide button was using us, it doesn't need to anymore
1919
if (guide_button_candidate.joystick == joystick) {
1920
guide_button_candidate.joystick = NULL;
1921
}
1922
if (guide_button_candidate.last_joystick == joystick) {
1923
guide_button_candidate.last_joystick = NULL;
1924
}
1925
}
1926
} else {
1927
// someone else also possibly correlated to this device, start over
1928
new_correlation_count = 1;
1929
}
1930
} else {
1931
// new possible correlation
1932
new_correlation_count = 1;
1933
ctx->xinput_slot = slot_idx;
1934
}
1935
ctx->xinput_correlation_id = correlation_id;
1936
} else {
1937
// Match multiple XInput devices, or none (possibly due to no buttons pressed)
1938
}
1939
}
1940
ctx->xinput_correlation_count = new_correlation_count;
1941
} else {
1942
correlated = true;
1943
}
1944
}
1945
#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
1946
1947
// Poll for trigger data once (not per-state-packet)
1948
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
1949
// Prefer XInput over WindowsGamingInput, it continues to provide data in the background
1950
if (!has_trigger_data && ctx->xinput_enabled && ctx->xinput_correlated) {
1951
RAWINPUT_UpdateXInput();
1952
if (xinput_state[ctx->xinput_slot].connected) {
1953
XINPUT_BATTERY_INFORMATION_EX *battery_info = &xinput_state[ctx->xinput_slot].battery;
1954
Uint64 timestamp;
1955
1956
if (ctx->guide_hack || ctx->trigger_hack) {
1957
timestamp = SDL_GetTicksNS();
1958
} else {
1959
// timestamp won't be used
1960
timestamp = 0;
1961
}
1962
1963
if (ctx->guide_hack) {
1964
bool down = ((xinput_state[ctx->xinput_slot].state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE) != 0);
1965
SDL_SendJoystickButton(timestamp, joystick, (Uint8)guide_button, down);
1966
}
1967
if (ctx->trigger_hack) {
1968
SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bLeftTrigger * 257) - 32768);
1969
SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, ((int)xinput_state[ctx->xinput_slot].state.Gamepad.bRightTrigger * 257) - 32768);
1970
}
1971
has_trigger_data = true;
1972
1973
SDL_PowerState state;
1974
int percent;
1975
switch (battery_info->BatteryType) {
1976
case BATTERY_TYPE_WIRED:
1977
state = SDL_POWERSTATE_CHARGING;
1978
break;
1979
case BATTERY_TYPE_UNKNOWN:
1980
case BATTERY_TYPE_DISCONNECTED:
1981
state = SDL_POWERSTATE_UNKNOWN;
1982
break;
1983
default:
1984
state = SDL_POWERSTATE_ON_BATTERY;
1985
break;
1986
}
1987
switch (battery_info->BatteryLevel) {
1988
case BATTERY_LEVEL_EMPTY:
1989
percent = 10;
1990
break;
1991
case BATTERY_LEVEL_LOW:
1992
percent = 40;
1993
break;
1994
case BATTERY_LEVEL_MEDIUM:
1995
percent = 70;
1996
break;
1997
default:
1998
case BATTERY_LEVEL_FULL:
1999
percent = 100;
2000
break;
2001
}
2002
SDL_SendJoystickPowerInfo(joystick, state, percent);
2003
}
2004
}
2005
#endif // SDL_JOYSTICK_RAWINPUT_XINPUT
2006
2007
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
2008
if (!has_trigger_data && ctx->wgi_correlated) {
2009
RAWINPUT_UpdateWindowsGamingInput(); // May detect disconnect / cause uncorrelation
2010
if (ctx->wgi_correlated) { // Still connected
2011
struct __x_ABI_CWindows_CGaming_CInput_CGamepadReading *state = &ctx->wgi_slot->state;
2012
Uint64 timestamp;
2013
2014
if (ctx->guide_hack || ctx->trigger_hack) {
2015
timestamp = SDL_GetTicksNS();
2016
} else {
2017
// timestamp won't be used
2018
timestamp = 0;
2019
}
2020
2021
if (ctx->guide_hack) {
2022
bool down = ((state->Buttons & GamepadButtons_GUIDE) != 0);
2023
SDL_SendJoystickButton(timestamp, joystick, (Uint8)guide_button, down);
2024
}
2025
if (ctx->trigger_hack) {
2026
SDL_SendJoystickAxis(timestamp, joystick, (Uint8)left_trigger, (Sint16)(((int)(state->LeftTrigger * SDL_MAX_UINT16)) - 32768));
2027
SDL_SendJoystickAxis(timestamp, joystick, (Uint8)right_trigger, (Sint16)(((int)(state->RightTrigger * SDL_MAX_UINT16)) - 32768));
2028
}
2029
has_trigger_data = true;
2030
}
2031
}
2032
#endif // SDL_JOYSTICK_RAWINPUT_WGI
2033
2034
if (!correlated) {
2035
if (!guide_button_candidate.joystick ||
2036
(ctx->last_state_packet && (!guide_button_candidate.last_state_packet ||
2037
ctx->last_state_packet >= guide_button_candidate.last_state_packet))) {
2038
guide_button_candidate.joystick = joystick;
2039
guide_button_candidate.last_state_packet = ctx->last_state_packet;
2040
}
2041
}
2042
#endif // SDL_JOYSTICK_RAWINPUT_MATCHING
2043
}
2044
2045
static void RAWINPUT_JoystickUpdate(SDL_Joystick *joystick)
2046
{
2047
RAWINPUT_UpdateOtherAPIs(joystick);
2048
}
2049
2050
static void RAWINPUT_JoystickClose(SDL_Joystick *joystick)
2051
{
2052
RAWINPUT_DeviceContext *ctx = joystick->hwdata;
2053
2054
#ifdef SDL_JOYSTICK_RAWINPUT_MATCHING
2055
if (guide_button_candidate.joystick == joystick) {
2056
guide_button_candidate.joystick = NULL;
2057
}
2058
if (guide_button_candidate.last_joystick == joystick) {
2059
guide_button_candidate.last_joystick = NULL;
2060
}
2061
#endif
2062
2063
if (ctx) {
2064
SDL_RAWINPUT_Device *device;
2065
2066
#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
2067
xinput_device_change = true;
2068
if (ctx->xinput_enabled) {
2069
if (ctx->xinput_correlated) {
2070
RAWINPUT_MarkXInputSlotFree(ctx->xinput_slot);
2071
}
2072
WIN_UnloadXInputDLL();
2073
}
2074
#endif
2075
#ifdef SDL_JOYSTICK_RAWINPUT_WGI
2076
RAWINPUT_QuitWindowsGamingInput(ctx);
2077
#endif
2078
2079
device = ctx->device;
2080
if (device) {
2081
SDL_assert(device->joystick == joystick);
2082
device->joystick = NULL;
2083
RAWINPUT_ReleaseDevice(device);
2084
}
2085
2086
SDL_free(ctx->data);
2087
SDL_free(ctx->button_indices);
2088
SDL_free(ctx->axis_indices);
2089
SDL_free(ctx->hat_indices);
2090
SDL_free(ctx);
2091
joystick->hwdata = NULL;
2092
}
2093
}
2094
2095
bool RAWINPUT_RegisterNotifications(HWND hWnd)
2096
{
2097
int i;
2098
RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)];
2099
2100
if (!SDL_RAWINPUT_inited) {
2101
return true;
2102
}
2103
2104
for (i = 0; i < SDL_arraysize(subscribed_devices); i++) {
2105
rid[i].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
2106
rid[i].usUsage = subscribed_devices[i];
2107
rid[i].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; // Receive messages when in background, including device add/remove
2108
rid[i].hwndTarget = hWnd;
2109
}
2110
2111
if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) {
2112
return SDL_SetError("Couldn't register for raw input events");
2113
}
2114
return true;
2115
}
2116
2117
bool RAWINPUT_UnregisterNotifications(void)
2118
{
2119
int i;
2120
RAWINPUTDEVICE rid[SDL_arraysize(subscribed_devices)];
2121
2122
if (!SDL_RAWINPUT_inited) {
2123
return true;
2124
}
2125
2126
for (i = 0; i < SDL_arraysize(subscribed_devices); i++) {
2127
rid[i].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
2128
rid[i].usUsage = subscribed_devices[i];
2129
rid[i].dwFlags = RIDEV_REMOVE;
2130
rid[i].hwndTarget = NULL;
2131
}
2132
2133
if (!RegisterRawInputDevices(rid, SDL_arraysize(rid), sizeof(RAWINPUTDEVICE))) {
2134
return SDL_SetError("Couldn't unregister for raw input events");
2135
}
2136
return true;
2137
}
2138
2139
LRESULT CALLBACK
2140
RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
2141
{
2142
LRESULT result = -1;
2143
2144
if (SDL_RAWINPUT_inited) {
2145
SDL_LockJoysticks();
2146
2147
switch (msg) {
2148
case WM_INPUT_DEVICE_CHANGE:
2149
{
2150
HANDLE hDevice = (HANDLE)lParam;
2151
switch (wParam) {
2152
case GIDC_ARRIVAL:
2153
RAWINPUT_AddDevice(hDevice);
2154
break;
2155
case GIDC_REMOVAL:
2156
{
2157
SDL_RAWINPUT_Device *device;
2158
device = RAWINPUT_DeviceFromHandle(hDevice);
2159
if (device) {
2160
RAWINPUT_DelDevice(device, true);
2161
}
2162
break;
2163
}
2164
default:
2165
break;
2166
}
2167
}
2168
result = 0;
2169
break;
2170
2171
case WM_INPUT:
2172
{
2173
Uint8 data[sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + USB_PACKET_LENGTH];
2174
UINT buffer_size = SDL_arraysize(data);
2175
2176
if ((int)GetRawInputData((HRAWINPUT)lParam, RID_INPUT, data, &buffer_size, sizeof(RAWINPUTHEADER)) > 0) {
2177
PRAWINPUT raw_input = (PRAWINPUT)data;
2178
SDL_RAWINPUT_Device *device = RAWINPUT_DeviceFromHandle(raw_input->header.hDevice);
2179
if (device) {
2180
SDL_Joystick *joystick = device->joystick;
2181
if (joystick) {
2182
RAWINPUT_HandleStatePacket(joystick, raw_input->data.hid.bRawData, raw_input->data.hid.dwSizeHid);
2183
}
2184
}
2185
}
2186
}
2187
result = 0;
2188
break;
2189
}
2190
2191
SDL_UnlockJoysticks();
2192
}
2193
2194
if (result >= 0) {
2195
return result;
2196
}
2197
return CallWindowProc(DefWindowProc, hWnd, msg, wParam, lParam);
2198
}
2199
2200
static void RAWINPUT_JoystickQuit(void)
2201
{
2202
if (!SDL_RAWINPUT_inited) {
2203
return;
2204
}
2205
2206
RAWINPUT_RemoveDevices();
2207
2208
WIN_UnloadHIDDLL();
2209
2210
SDL_RAWINPUT_inited = false;
2211
}
2212
2213
static bool RAWINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
2214
{
2215
return false;
2216
}
2217
2218
SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver = {
2219
RAWINPUT_JoystickInit,
2220
RAWINPUT_JoystickGetCount,
2221
RAWINPUT_JoystickDetect,
2222
RAWINPUT_JoystickIsDevicePresent,
2223
RAWINPUT_JoystickGetDeviceName,
2224
RAWINPUT_JoystickGetDevicePath,
2225
RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot,
2226
RAWINPUT_JoystickGetDevicePlayerIndex,
2227
RAWINPUT_JoystickSetDevicePlayerIndex,
2228
RAWINPUT_JoystickGetDeviceGUID,
2229
RAWINPUT_JoystickGetDeviceInstanceID,
2230
RAWINPUT_JoystickOpen,
2231
RAWINPUT_JoystickRumble,
2232
RAWINPUT_JoystickRumbleTriggers,
2233
RAWINPUT_JoystickSetLED,
2234
RAWINPUT_JoystickSendEffect,
2235
RAWINPUT_JoystickSetSensorsEnabled,
2236
RAWINPUT_JoystickUpdate,
2237
RAWINPUT_JoystickClose,
2238
RAWINPUT_JoystickQuit,
2239
RAWINPUT_JoystickGetGamepadMapping
2240
};
2241
2242
#endif // SDL_JOYSTICK_RAWINPUT
2243
2244