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