summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/joystick/windows
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/joystick/windows
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/joystick/windows')
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c1210
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h40
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick.c2238
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h32
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c1039
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c693
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h103
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c473
-rw-r--r--contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h44
9 files changed, 5872 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c
new file mode 100644
index 0000000..b00218d
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick.c
@@ -0,0 +1,1210 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_sysjoystick.h"
24
25#ifdef SDL_JOYSTICK_DINPUT
26
27#include "SDL_windowsjoystick_c.h"
28#include "SDL_dinputjoystick_c.h"
29#include "SDL_rawinputjoystick_c.h"
30#include "SDL_xinputjoystick_c.h"
31#include "../hidapi/SDL_hidapijoystick_c.h"
32
33#ifndef DIDFT_OPTIONAL
34#define DIDFT_OPTIONAL 0x80000000
35#endif
36
37#define INPUT_QSIZE 128 // Buffer up to 128 input messages
38#define JOY_AXIS_THRESHOLD (((SDL_JOYSTICK_AXIS_MAX) - (SDL_JOYSTICK_AXIS_MIN)) / 100) // 1% motion
39
40#define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF)
41
42// external variables referenced.
43#ifdef SDL_VIDEO_DRIVER_WINDOWS
44extern HWND SDL_HelperWindow;
45#else
46static const HWND SDL_HelperWindow = NULL;
47#endif
48
49// local variables
50static bool coinitialized = false;
51static LPDIRECTINPUT8 dinput = NULL;
52
53// Taken from Wine - Thanks!
54static DIOBJECTDATAFORMAT dfDIJoystick2[] = {
55 { &GUID_XAxis, DIJOFS_X, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
56 { &GUID_YAxis, DIJOFS_Y, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
57 { &GUID_ZAxis, DIJOFS_Z, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
58 { &GUID_RxAxis, DIJOFS_RX, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
59 { &GUID_RyAxis, DIJOFS_RY, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
60 { &GUID_RzAxis, DIJOFS_RZ, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
61 { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
62 { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTPOSITION },
63 { &GUID_POV, DIJOFS_POV(0), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
64 { &GUID_POV, DIJOFS_POV(1), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
65 { &GUID_POV, DIJOFS_POV(2), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
66 { &GUID_POV, DIJOFS_POV(3), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
67 { NULL, DIJOFS_BUTTON(0), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
68 { NULL, DIJOFS_BUTTON(1), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
69 { NULL, DIJOFS_BUTTON(2), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
70 { NULL, DIJOFS_BUTTON(3), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
71 { NULL, DIJOFS_BUTTON(4), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
72 { NULL, DIJOFS_BUTTON(5), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
73 { NULL, DIJOFS_BUTTON(6), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
74 { NULL, DIJOFS_BUTTON(7), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
75 { NULL, DIJOFS_BUTTON(8), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
76 { NULL, DIJOFS_BUTTON(9), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
77 { NULL, DIJOFS_BUTTON(10), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
78 { NULL, DIJOFS_BUTTON(11), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
79 { NULL, DIJOFS_BUTTON(12), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
80 { NULL, DIJOFS_BUTTON(13), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
81 { NULL, DIJOFS_BUTTON(14), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
82 { NULL, DIJOFS_BUTTON(15), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
83 { NULL, DIJOFS_BUTTON(16), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
84 { NULL, DIJOFS_BUTTON(17), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
85 { NULL, DIJOFS_BUTTON(18), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
86 { NULL, DIJOFS_BUTTON(19), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
87 { NULL, DIJOFS_BUTTON(20), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
88 { NULL, DIJOFS_BUTTON(21), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
89 { NULL, DIJOFS_BUTTON(22), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
90 { NULL, DIJOFS_BUTTON(23), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
91 { NULL, DIJOFS_BUTTON(24), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
92 { NULL, DIJOFS_BUTTON(25), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
93 { NULL, DIJOFS_BUTTON(26), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
94 { NULL, DIJOFS_BUTTON(27), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
95 { NULL, DIJOFS_BUTTON(28), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
96 { NULL, DIJOFS_BUTTON(29), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
97 { NULL, DIJOFS_BUTTON(30), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
98 { NULL, DIJOFS_BUTTON(31), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
99 { NULL, DIJOFS_BUTTON(32), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
100 { NULL, DIJOFS_BUTTON(33), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
101 { NULL, DIJOFS_BUTTON(34), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
102 { NULL, DIJOFS_BUTTON(35), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
103 { NULL, DIJOFS_BUTTON(36), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
104 { NULL, DIJOFS_BUTTON(37), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
105 { NULL, DIJOFS_BUTTON(38), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
106 { NULL, DIJOFS_BUTTON(39), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
107 { NULL, DIJOFS_BUTTON(40), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
108 { NULL, DIJOFS_BUTTON(41), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
109 { NULL, DIJOFS_BUTTON(42), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
110 { NULL, DIJOFS_BUTTON(43), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
111 { NULL, DIJOFS_BUTTON(44), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
112 { NULL, DIJOFS_BUTTON(45), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
113 { NULL, DIJOFS_BUTTON(46), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
114 { NULL, DIJOFS_BUTTON(47), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
115 { NULL, DIJOFS_BUTTON(48), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
116 { NULL, DIJOFS_BUTTON(49), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
117 { NULL, DIJOFS_BUTTON(50), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
118 { NULL, DIJOFS_BUTTON(51), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
119 { NULL, DIJOFS_BUTTON(52), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
120 { NULL, DIJOFS_BUTTON(53), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
121 { NULL, DIJOFS_BUTTON(54), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
122 { NULL, DIJOFS_BUTTON(55), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
123 { NULL, DIJOFS_BUTTON(56), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
124 { NULL, DIJOFS_BUTTON(57), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
125 { NULL, DIJOFS_BUTTON(58), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
126 { NULL, DIJOFS_BUTTON(59), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
127 { NULL, DIJOFS_BUTTON(60), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
128 { NULL, DIJOFS_BUTTON(61), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
129 { NULL, DIJOFS_BUTTON(62), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
130 { NULL, DIJOFS_BUTTON(63), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
131 { NULL, DIJOFS_BUTTON(64), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
132 { NULL, DIJOFS_BUTTON(65), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
133 { NULL, DIJOFS_BUTTON(66), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
134 { NULL, DIJOFS_BUTTON(67), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
135 { NULL, DIJOFS_BUTTON(68), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
136 { NULL, DIJOFS_BUTTON(69), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
137 { NULL, DIJOFS_BUTTON(70), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
138 { NULL, DIJOFS_BUTTON(71), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
139 { NULL, DIJOFS_BUTTON(72), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
140 { NULL, DIJOFS_BUTTON(73), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
141 { NULL, DIJOFS_BUTTON(74), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
142 { NULL, DIJOFS_BUTTON(75), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
143 { NULL, DIJOFS_BUTTON(76), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
144 { NULL, DIJOFS_BUTTON(77), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
145 { NULL, DIJOFS_BUTTON(78), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
146 { NULL, DIJOFS_BUTTON(79), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
147 { NULL, DIJOFS_BUTTON(80), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
148 { NULL, DIJOFS_BUTTON(81), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
149 { NULL, DIJOFS_BUTTON(82), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
150 { NULL, DIJOFS_BUTTON(83), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
151 { NULL, DIJOFS_BUTTON(84), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
152 { NULL, DIJOFS_BUTTON(85), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
153 { NULL, DIJOFS_BUTTON(86), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
154 { NULL, DIJOFS_BUTTON(87), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
155 { NULL, DIJOFS_BUTTON(88), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
156 { NULL, DIJOFS_BUTTON(89), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
157 { NULL, DIJOFS_BUTTON(90), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
158 { NULL, DIJOFS_BUTTON(91), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
159 { NULL, DIJOFS_BUTTON(92), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
160 { NULL, DIJOFS_BUTTON(93), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
161 { NULL, DIJOFS_BUTTON(94), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
162 { NULL, DIJOFS_BUTTON(95), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
163 { NULL, DIJOFS_BUTTON(96), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
164 { NULL, DIJOFS_BUTTON(97), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
165 { NULL, DIJOFS_BUTTON(98), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
166 { NULL, DIJOFS_BUTTON(99), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
167 { NULL, DIJOFS_BUTTON(100), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
168 { NULL, DIJOFS_BUTTON(101), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
169 { NULL, DIJOFS_BUTTON(102), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
170 { NULL, DIJOFS_BUTTON(103), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
171 { NULL, DIJOFS_BUTTON(104), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
172 { NULL, DIJOFS_BUTTON(105), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
173 { NULL, DIJOFS_BUTTON(106), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
174 { NULL, DIJOFS_BUTTON(107), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
175 { NULL, DIJOFS_BUTTON(108), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
176 { NULL, DIJOFS_BUTTON(109), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
177 { NULL, DIJOFS_BUTTON(110), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
178 { NULL, DIJOFS_BUTTON(111), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
179 { NULL, DIJOFS_BUTTON(112), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
180 { NULL, DIJOFS_BUTTON(113), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
181 { NULL, DIJOFS_BUTTON(114), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
182 { NULL, DIJOFS_BUTTON(115), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
183 { NULL, DIJOFS_BUTTON(116), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
184 { NULL, DIJOFS_BUTTON(117), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
185 { NULL, DIJOFS_BUTTON(118), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
186 { NULL, DIJOFS_BUTTON(119), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
187 { NULL, DIJOFS_BUTTON(120), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
188 { NULL, DIJOFS_BUTTON(121), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
189 { NULL, DIJOFS_BUTTON(122), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
190 { NULL, DIJOFS_BUTTON(123), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
191 { NULL, DIJOFS_BUTTON(124), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
192 { NULL, DIJOFS_BUTTON(125), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
193 { NULL, DIJOFS_BUTTON(126), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
194 { NULL, DIJOFS_BUTTON(127), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
195 { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lVX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
196 { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lVY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
197 { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lVZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
198 { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lVRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
199 { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lVRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
200 { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lVRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
201 // note: dwOfs value matches Windows
202 { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
203 { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTVELOCITY },
204 { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lAX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
205 { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lAY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
206 { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lAZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
207 { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lARx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
208 { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lARy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
209 { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lARz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
210 // note: dwOfs value matches Windows
211 { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
212 { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTACCEL },
213 { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lFX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
214 { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lFY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
215 { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lFZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
216 { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lFRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
217 { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lFRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
218 { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lFRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
219 // note: dwOfs value matches Windows
220 { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
221 { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, DIDOI_ASPECTFORCE },
222};
223
224const DIDATAFORMAT SDL_c_dfDIJoystick2 = {
225 sizeof(DIDATAFORMAT),
226 sizeof(DIOBJECTDATAFORMAT),
227 DIDF_ABSAXIS,
228 sizeof(DIJOYSTATE2),
229 SDL_arraysize(dfDIJoystick2),
230 dfDIJoystick2
231};
232
233// Convert a DirectInput return code to a text message
234static bool SetDIerror(const char *function, HRESULT code)
235{
236 return SDL_SetError("%s() DirectX error 0x%8.8lx", function, code);
237}
238
239static bool SDL_IsXInputDevice(Uint16 vendor_id, Uint16 product_id, const char *hidPath)
240{
241#if defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT)
242 SDL_GamepadType type;
243
244 // XInput and RawInput backends will pick up XInput-compatible devices
245 if (!SDL_XINPUT_Enabled()
246#ifdef SDL_JOYSTICK_RAWINPUT
247 && !RAWINPUT_IsEnabled()
248#endif
249 ) {
250 return false;
251 }
252
253 // If device path contains "IG_" then its an XInput device
254 // See: https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput
255 if (SDL_strstr(hidPath, "IG_") != NULL) {
256 return true;
257 }
258
259 type = SDL_GetGamepadTypeFromVIDPID(vendor_id, product_id, NULL, false);
260 if (type == SDL_GAMEPAD_TYPE_XBOX360 ||
261 type == SDL_GAMEPAD_TYPE_XBOXONE ||
262 (vendor_id == USB_VENDOR_VALVE && product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) {
263 return true;
264 }
265#endif // SDL_JOYSTICK_XINPUT || SDL_JOYSTICK_RAWINPUT
266
267 return false;
268}
269
270static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, Uint16 vendor_id, Uint16 product_id, char **manufacturer_string, char **product_string)
271{
272 DIPROPSTRING dipstr;
273
274 if (!device || !manufacturer_string || !product_string) {
275 return false;
276 }
277
278#ifdef SDL_JOYSTICK_HIDAPI
279 *manufacturer_string = HIDAPI_GetDeviceManufacturerName(vendor_id, product_id);
280 *product_string = HIDAPI_GetDeviceProductName(vendor_id, product_id);
281 if (*product_string) {
282 return true;
283 }
284#endif
285
286 dipstr.diph.dwSize = sizeof(dipstr);
287 dipstr.diph.dwHeaderSize = sizeof(dipstr.diph);
288 dipstr.diph.dwObj = 0;
289 dipstr.diph.dwHow = DIPH_DEVICE;
290
291 if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_PRODUCTNAME, &dipstr.diph))) {
292 return false;
293 }
294
295 *manufacturer_string = NULL;
296 *product_string = WIN_StringToUTF8(dipstr.wsz);
297
298 return true;
299}
300
301static bool QueryDevicePath(LPDIRECTINPUTDEVICE8 device, char **device_path)
302{
303 DIPROPGUIDANDPATH dippath;
304
305 if (!device || !device_path) {
306 return false;
307 }
308
309 dippath.diph.dwSize = sizeof(dippath);
310 dippath.diph.dwHeaderSize = sizeof(dippath.diph);
311 dippath.diph.dwObj = 0;
312 dippath.diph.dwHow = DIPH_DEVICE;
313
314 if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_GUIDANDPATH, &dippath.diph))) {
315 return false;
316 }
317
318 *device_path = WIN_StringToUTF8W(dippath.wszPath);
319
320 // Normalize path to upper case.
321 SDL_strupr(*device_path);
322
323 return true;
324}
325
326static bool QueryDeviceInfo(LPDIRECTINPUTDEVICE8 device, Uint16 *vendor_id, Uint16 *product_id)
327{
328 DIPROPDWORD dipdw;
329
330 if (!device || !vendor_id || !product_id) {
331 return false;
332 }
333
334 dipdw.diph.dwSize = sizeof(dipdw);
335 dipdw.diph.dwHeaderSize = sizeof(dipdw.diph);
336 dipdw.diph.dwObj = 0;
337 dipdw.diph.dwHow = DIPH_DEVICE;
338 dipdw.dwData = 0;
339
340 if (FAILED(IDirectInputDevice8_GetProperty(device, DIPROP_VIDPID, &dipdw.diph))) {
341 return false;
342 }
343
344 *vendor_id = LOWORD(dipdw.dwData);
345 *product_id = HIWORD(dipdw.dwData);
346
347 return true;
348}
349
350void FreeRumbleEffectData(DIEFFECT *effect)
351{
352 if (!effect) {
353 return;
354 }
355 SDL_free(effect->rgdwAxes);
356 SDL_free(effect->rglDirection);
357 SDL_free(effect->lpvTypeSpecificParams);
358 SDL_free(effect);
359}
360
361DIEFFECT *CreateRumbleEffectData(Sint16 magnitude)
362{
363 DIEFFECT *effect;
364 DIPERIODIC *periodic;
365
366 // Create the effect
367 effect = (DIEFFECT *)SDL_calloc(1, sizeof(*effect));
368 if (!effect) {
369 return NULL;
370 }
371 effect->dwSize = sizeof(*effect);
372 effect->dwGain = 10000;
373 effect->dwFlags = DIEFF_OBJECTOFFSETS;
374 effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000; // In microseconds.
375 effect->dwTriggerButton = DIEB_NOTRIGGER;
376
377 effect->cAxes = 2;
378 effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD));
379 if (!effect->rgdwAxes) {
380 FreeRumbleEffectData(effect);
381 return NULL;
382 }
383
384 effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG));
385 if (!effect->rglDirection) {
386 FreeRumbleEffectData(effect);
387 return NULL;
388 }
389 effect->dwFlags |= DIEFF_CARTESIAN;
390
391 periodic = (DIPERIODIC *)SDL_calloc(1, sizeof(*periodic));
392 if (!periodic) {
393 FreeRumbleEffectData(effect);
394 return NULL;
395 }
396 periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
397 periodic->dwPeriod = 1000000;
398
399 effect->cbTypeSpecificParams = sizeof(*periodic);
400 effect->lpvTypeSpecificParams = periodic;
401
402 return effect;
403}
404
405bool SDL_DINPUT_JoystickInit(void)
406{
407 HRESULT result;
408 HINSTANCE instance;
409
410 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true)) {
411 // In some environments, IDirectInput8_Initialize / _EnumDevices can take a minute even with no controllers.
412 dinput = NULL;
413 return true;
414 }
415
416 result = WIN_CoInitialize();
417 if (FAILED(result)) {
418 return SetDIerror("CoInitialize", result);
419 }
420
421 coinitialized = true;
422
423 result = CoCreateInstance(&CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER,
424 &IID_IDirectInput8, (LPVOID *)&dinput);
425
426 if (FAILED(result)) {
427 return SetDIerror("CoCreateInstance", result);
428 }
429
430 // Because we used CoCreateInstance, we need to Initialize it, first.
431 instance = GetModuleHandle(NULL);
432 if (!instance) {
433 IDirectInput8_Release(dinput);
434 dinput = NULL;
435 return SDL_SetError("GetModuleHandle() failed with error code %lu.", GetLastError());
436 }
437 result = IDirectInput8_Initialize(dinput, instance, DIRECTINPUT_VERSION);
438
439 if (FAILED(result)) {
440 IDirectInput8_Release(dinput);
441 dinput = NULL;
442 return SetDIerror("IDirectInput::Initialize", result);
443 }
444 return true;
445}
446
447static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path)
448{
449 int slot = -1;
450
451 if (vendor_id == USB_VENDOR_VALVE &&
452 product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
453 (void)SDL_sscanf(device_path, "\\\\?\\HID#VID_28DE&PID_11FF&IG_0%d", &slot);
454 }
455 return slot;
456}
457
458// helper function for direct input, gets called for each connected joystick
459static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInstance, LPVOID pContext)
460{
461#define CHECK(expression) \
462 { \
463 if (!(expression)) \
464 goto err; \
465 }
466 JoyStick_DeviceData *pNewJoystick = NULL;
467 JoyStick_DeviceData *pPrevJoystick = NULL;
468 Uint16 vendor = 0;
469 Uint16 product = 0;
470 Uint16 version = 0;
471 char *hidPath = NULL;
472 char *manufacturer_string = NULL;
473 char *product_string = NULL;
474 LPDIRECTINPUTDEVICE8 device = NULL;
475
476 // We are only supporting HID devices.
477 CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID);
478
479 CHECK(SUCCEEDED(IDirectInput8_CreateDevice(dinput, &pDeviceInstance->guidInstance, &device, NULL)));
480 CHECK(QueryDevicePath(device, &hidPath));
481 CHECK(QueryDeviceInfo(device, &vendor, &product));
482 CHECK(QueryDeviceName(device, vendor, product, &manufacturer_string, &product_string));
483
484 CHECK(!SDL_IsXInputDevice(vendor, product, hidPath));
485 CHECK(!SDL_ShouldIgnoreJoystick(vendor, product, version, product_string));
486 CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, product_string));
487
488 pNewJoystick = *(JoyStick_DeviceData **)pContext;
489 while (pNewJoystick) {
490 // update GUIDs of joysticks with matching paths, in case they're not open yet
491 if (SDL_strcmp(pNewJoystick->path, hidPath) == 0) {
492 // if we are replacing the front of the list then update it
493 if (pNewJoystick == *(JoyStick_DeviceData **)pContext) {
494 *(JoyStick_DeviceData **)pContext = pNewJoystick->pNext;
495 } else if (pPrevJoystick) {
496 pPrevJoystick->pNext = pNewJoystick->pNext;
497 }
498
499 // Update with new guid/etc, if it has changed
500 SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE));
501
502 pNewJoystick->pNext = SYS_Joystick;
503 SYS_Joystick = pNewJoystick;
504
505 pNewJoystick = NULL;
506 CHECK(FALSE);
507 }
508
509 pPrevJoystick = pNewJoystick;
510 pNewJoystick = pNewJoystick->pNext;
511 }
512
513 pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData));
514 CHECK(pNewJoystick);
515
516 pNewJoystick->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(vendor, product, hidPath);
517 SDL_strlcpy(pNewJoystick->path, hidPath, SDL_arraysize(pNewJoystick->path));
518 SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE));
519
520 pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);
521 CHECK(pNewJoystick->joystickname);
522
523 if (vendor && product) {
524 pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, manufacturer_string, product_string, 0, 0);
525 } else {
526 pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, version, manufacturer_string, product_string, 0, 0);
527 }
528
529 WINDOWS_AddJoystickDevice(pNewJoystick);
530 pNewJoystick = NULL;
531
532err:
533 if (pNewJoystick) {
534 SDL_free(pNewJoystick->joystickname);
535 SDL_free(pNewJoystick);
536 }
537
538 SDL_free(hidPath);
539 SDL_free(manufacturer_string);
540 SDL_free(product_string);
541
542 if (device) {
543 IDirectInputDevice8_Release(device);
544 }
545
546 return DIENUM_CONTINUE; // get next device, please
547#undef CHECK
548}
549
550void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
551{
552 if (!dinput) {
553 return;
554 }
555
556 IDirectInput8_EnumDevices(dinput, DI8DEVCLASS_GAMECTRL, EnumJoystickDetectCallback, pContext, DIEDFL_ATTACHEDONLY);
557}
558
559// helper function for direct input, gets called for each connected joystick
560typedef struct
561{
562 Uint16 vendor;
563 Uint16 product;
564 bool present;
565} Joystick_PresentData;
566
567static BOOL CALLBACK EnumJoystickPresentCallback(LPCDIDEVICEINSTANCE pDeviceInstance, LPVOID pContext)
568{
569#define CHECK(expression) \
570 { \
571 if (!(expression)) \
572 goto err; \
573 }
574 Joystick_PresentData *pData = (Joystick_PresentData *)pContext;
575 Uint16 vendor = 0;
576 Uint16 product = 0;
577 LPDIRECTINPUTDEVICE8 device = NULL;
578 BOOL result = DIENUM_CONTINUE;
579
580 // We are only supporting HID devices.
581 CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID);
582
583 CHECK(SUCCEEDED(IDirectInput8_CreateDevice(dinput, &pDeviceInstance->guidInstance, &device, NULL)));
584 CHECK(QueryDeviceInfo(device, &vendor, &product));
585
586 if (vendor == pData->vendor && product == pData->product) {
587 pData->present = true;
588 result = DIENUM_STOP; // found it
589 }
590
591err:
592 if (device) {
593 IDirectInputDevice8_Release(device);
594 }
595
596 return result;
597#undef CHECK
598}
599
600bool SDL_DINPUT_JoystickPresent(Uint16 vendor_id, Uint16 product_id, Uint16 version_number)
601{
602 Joystick_PresentData data;
603
604 if (!dinput) {
605 return false;
606 }
607
608 data.vendor = vendor_id;
609 data.product = product_id;
610 data.present = false;
611 IDirectInput8_EnumDevices(dinput, DI8DEVCLASS_GAMECTRL, EnumJoystickPresentCallback, &data, DIEDFL_ATTACHEDONLY);
612 return data.present;
613}
614
615static BOOL CALLBACK EnumDevObjectsCallback(LPCDIDEVICEOBJECTINSTANCE pDeviceObject, LPVOID pContext)
616{
617 SDL_Joystick *joystick = (SDL_Joystick *)pContext;
618 HRESULT result;
619 input_t *in = &joystick->hwdata->Inputs[joystick->hwdata->NumInputs];
620
621 if (pDeviceObject->dwType & DIDFT_BUTTON) {
622 in->type = BUTTON;
623 in->num = (Uint8)joystick->nbuttons;
624 in->ofs = DIJOFS_BUTTON(in->num);
625 joystick->nbuttons++;
626 } else if (pDeviceObject->dwType & DIDFT_POV) {
627 in->type = HAT;
628 in->num = (Uint8)joystick->nhats;
629 in->ofs = DIJOFS_POV(in->num);
630 joystick->nhats++;
631 } else if (pDeviceObject->dwType & DIDFT_AXIS) {
632 DIPROPRANGE diprg;
633 DIPROPDWORD dilong;
634
635 in->type = AXIS;
636 in->num = (Uint8)joystick->naxes;
637 if (SDL_memcmp(&pDeviceObject->guidType, &GUID_XAxis, sizeof(pDeviceObject->guidType)) == 0) {
638 in->ofs = DIJOFS_X;
639 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_YAxis, sizeof(pDeviceObject->guidType)) == 0) {
640 in->ofs = DIJOFS_Y;
641 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_ZAxis, sizeof(pDeviceObject->guidType)) == 0) {
642 in->ofs = DIJOFS_Z;
643 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RxAxis, sizeof(pDeviceObject->guidType)) == 0) {
644 in->ofs = DIJOFS_RX;
645 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RyAxis, sizeof(pDeviceObject->guidType)) == 0) {
646 in->ofs = DIJOFS_RY;
647 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_RzAxis, sizeof(pDeviceObject->guidType)) == 0) {
648 in->ofs = DIJOFS_RZ;
649 } else if (SDL_memcmp(&pDeviceObject->guidType, &GUID_Slider, sizeof(pDeviceObject->guidType)) == 0) {
650 in->ofs = DIJOFS_SLIDER(joystick->hwdata->NumSliders);
651 ++joystick->hwdata->NumSliders;
652 } else {
653 return DIENUM_CONTINUE; // not an axis we can grok
654 }
655
656 diprg.diph.dwSize = sizeof(diprg);
657 diprg.diph.dwHeaderSize = sizeof(diprg.diph);
658 diprg.diph.dwObj = pDeviceObject->dwType;
659 diprg.diph.dwHow = DIPH_BYID;
660 diprg.lMin = SDL_JOYSTICK_AXIS_MIN;
661 diprg.lMax = SDL_JOYSTICK_AXIS_MAX;
662
663 result =
664 IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice,
665 DIPROP_RANGE, &diprg.diph);
666 if (FAILED(result)) {
667 return DIENUM_CONTINUE; // don't use this axis
668 }
669
670 // Set dead zone to 0.
671 dilong.diph.dwSize = sizeof(dilong);
672 dilong.diph.dwHeaderSize = sizeof(dilong.diph);
673 dilong.diph.dwObj = pDeviceObject->dwType;
674 dilong.diph.dwHow = DIPH_BYID;
675 dilong.dwData = 0;
676 result =
677 IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice,
678 DIPROP_DEADZONE, &dilong.diph);
679 if (FAILED(result)) {
680 return DIENUM_CONTINUE; // don't use this axis
681 }
682
683 joystick->naxes++;
684 } else {
685 // not supported at this time
686 return DIENUM_CONTINUE;
687 }
688
689 joystick->hwdata->NumInputs++;
690
691 if (joystick->hwdata->NumInputs == MAX_INPUTS) {
692 return DIENUM_STOP; // too many
693 }
694
695 return DIENUM_CONTINUE;
696}
697
698/* Sort using the data offset into the DInput struct.
699 * This gives a reasonable ordering for the inputs.
700 */
701static int SDLCALL SortDevFunc(const void *a, const void *b)
702{
703 const input_t *inputA = (const input_t *)a;
704 const input_t *inputB = (const input_t *)b;
705
706 if (inputA->ofs < inputB->ofs) {
707 return -1;
708 }
709 if (inputA->ofs > inputB->ofs) {
710 return 1;
711 }
712 return 0;
713}
714
715// Sort the input objects and recalculate the indices for each input.
716static void SortDevObjects(SDL_Joystick *joystick)
717{
718 input_t *inputs = joystick->hwdata->Inputs;
719 Uint8 nButtons = 0;
720 Uint8 nHats = 0;
721 Uint8 nAxis = 0;
722 int n;
723
724 SDL_qsort(inputs, joystick->hwdata->NumInputs, sizeof(input_t), SortDevFunc);
725
726 for (n = 0; n < joystick->hwdata->NumInputs; n++) {
727 switch (inputs[n].type) {
728 case BUTTON:
729 inputs[n].num = nButtons;
730 nButtons++;
731 break;
732
733 case HAT:
734 inputs[n].num = nHats;
735 nHats++;
736 break;
737
738 case AXIS:
739 inputs[n].num = nAxis;
740 nAxis++;
741 break;
742 }
743 }
744}
745
746bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
747{
748 HRESULT result;
749 DIPROPDWORD dipdw;
750
751 joystick->hwdata->buffered = true;
752 joystick->hwdata->Capabilities.dwSize = sizeof(DIDEVCAPS);
753
754 SDL_zero(dipdw);
755 dipdw.diph.dwSize = sizeof(DIPROPDWORD);
756 dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
757
758 result =
759 IDirectInput8_CreateDevice(dinput,
760 &joystickdevice->dxdevice.guidInstance,
761 &joystick->hwdata->InputDevice,
762 NULL);
763 if (FAILED(result)) {
764 return SetDIerror("IDirectInput::CreateDevice", result);
765 }
766
767 /* Acquire shared access. Exclusive access is required for forces,
768 * though. */
769 result =
770 IDirectInputDevice8_SetCooperativeLevel(joystick->hwdata->InputDevice, SDL_HelperWindow,
771 DISCL_EXCLUSIVE |
772 DISCL_BACKGROUND);
773 if (FAILED(result)) {
774 return SetDIerror("IDirectInputDevice8::SetCooperativeLevel", result);
775 }
776
777 // Use the extended data structure: DIJOYSTATE2.
778 result =
779 IDirectInputDevice8_SetDataFormat(joystick->hwdata->InputDevice,
780 &SDL_c_dfDIJoystick2);
781 if (FAILED(result)) {
782 return SetDIerror("IDirectInputDevice8::SetDataFormat", result);
783 }
784
785 // Get device capabilities
786 result =
787 IDirectInputDevice8_GetCapabilities(joystick->hwdata->InputDevice,
788 &joystick->hwdata->Capabilities);
789 if (FAILED(result)) {
790 return SetDIerror("IDirectInputDevice8::GetCapabilities", result);
791 }
792
793 // Force capable?
794 if (joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK) {
795 result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
796 if (FAILED(result)) {
797 return SetDIerror("IDirectInputDevice8::Acquire", result);
798 }
799
800 // reset all actuators.
801 result =
802 IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice,
803 DISFFC_RESET);
804
805 /* Not necessarily supported, ignore if not supported.
806 if (FAILED(result)) {
807 return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand", result);
808 }
809 */
810
811 result = IDirectInputDevice8_Unacquire(joystick->hwdata->InputDevice);
812
813 if (FAILED(result)) {
814 return SetDIerror("IDirectInputDevice8::Unacquire", result);
815 }
816
817 /* Turn on auto-centering for a ForceFeedback device (until told
818 * otherwise). */
819 dipdw.diph.dwObj = 0;
820 dipdw.diph.dwHow = DIPH_DEVICE;
821 dipdw.dwData = DIPROPAUTOCENTER_ON;
822
823 result =
824 IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice,
825 DIPROP_AUTOCENTER, &dipdw.diph);
826
827 /* Not necessarily supported, ignore if not supported.
828 if (FAILED(result)) {
829 return SetDIerror("IDirectInputDevice8::SetProperty", result);
830 }
831 */
832
833 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
834 }
835
836 // What buttons and axes does it have?
837 IDirectInputDevice8_EnumObjects(joystick->hwdata->InputDevice,
838 EnumDevObjectsCallback, joystick,
839 DIDFT_BUTTON | DIDFT_AXIS | DIDFT_POV);
840
841 /* Reorder the input objects. Some devices do not report the X axis as
842 * the first axis, for example. */
843 SortDevObjects(joystick);
844
845 dipdw.diph.dwObj = 0;
846 dipdw.diph.dwHow = DIPH_DEVICE;
847 dipdw.dwData = INPUT_QSIZE;
848
849 // Set the buffer size
850 result =
851 IDirectInputDevice8_SetProperty(joystick->hwdata->InputDevice,
852 DIPROP_BUFFERSIZE, &dipdw.diph);
853
854 if (result == DI_POLLEDDEVICE) {
855 /* This device doesn't support buffering, so we're forced
856 * to use less reliable polling. */
857 joystick->hwdata->buffered = false;
858 } else if (FAILED(result)) {
859 return SetDIerror("IDirectInputDevice8::SetProperty", result);
860 }
861 joystick->hwdata->first_update = true;
862
863 // Poll and wait for initial device state to be populated
864 result = IDirectInputDevice8_Poll(joystick->hwdata->InputDevice);
865 if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
866 IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
867 IDirectInputDevice8_Poll(joystick->hwdata->InputDevice);
868 }
869 SDL_Delay(50);
870
871 return true;
872}
873
874static bool SDL_DINPUT_JoystickInitRumble(SDL_Joystick *joystick, Sint16 magnitude)
875{
876 HRESULT result;
877
878 // Reset and then enable actuators
879 result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_RESET);
880 if (result == DIERR_INPUTLOST || result == DIERR_NOTEXCLUSIVEACQUIRED) {
881 result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
882 if (SUCCEEDED(result)) {
883 result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_RESET);
884 }
885 }
886 if (FAILED(result)) {
887 return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand(DISFFC_RESET)", result);
888 }
889
890 result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_SETACTUATORSON);
891 if (FAILED(result)) {
892 return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand(DISFFC_SETACTUATORSON)", result);
893 }
894
895 // Create the effect
896 joystick->hwdata->ffeffect = CreateRumbleEffectData(magnitude);
897 if (!joystick->hwdata->ffeffect) {
898 return false;
899 }
900
901 result = IDirectInputDevice8_CreateEffect(joystick->hwdata->InputDevice, &GUID_Sine,
902 joystick->hwdata->ffeffect, &joystick->hwdata->ffeffect_ref, NULL);
903 if (FAILED(result)) {
904 return SetDIerror("IDirectInputDevice8::CreateEffect", result);
905 }
906 return true;
907}
908
909bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
910{
911 HRESULT result;
912
913 // Scale and average the two rumble strengths
914 Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
915
916 if (!(joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK)) {
917 return SDL_Unsupported();
918 }
919
920 if (joystick->hwdata->ff_initialized) {
921 DIPERIODIC *periodic = ((DIPERIODIC *)joystick->hwdata->ffeffect->lpvTypeSpecificParams);
922 periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
923
924 result = IDirectInputEffect_SetParameters(joystick->hwdata->ffeffect_ref, joystick->hwdata->ffeffect, (DIEP_DURATION | DIEP_TYPESPECIFICPARAMS));
925 if (result == DIERR_INPUTLOST) {
926 result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
927 if (SUCCEEDED(result)) {
928 result = IDirectInputEffect_SetParameters(joystick->hwdata->ffeffect_ref, joystick->hwdata->ffeffect, (DIEP_DURATION | DIEP_TYPESPECIFICPARAMS));
929 }
930 }
931 if (FAILED(result)) {
932 return SetDIerror("IDirectInputDevice8::SetParameters", result);
933 }
934 } else {
935 if (!SDL_DINPUT_JoystickInitRumble(joystick, magnitude)) {
936 return false;
937 }
938 joystick->hwdata->ff_initialized = true;
939 }
940
941 result = IDirectInputEffect_Start(joystick->hwdata->ffeffect_ref, 1, 0);
942 if (result == DIERR_INPUTLOST || result == DIERR_NOTEXCLUSIVEACQUIRED) {
943 result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
944 if (SUCCEEDED(result)) {
945 result = IDirectInputEffect_Start(joystick->hwdata->ffeffect_ref, 1, 0);
946 }
947 }
948 if (FAILED(result)) {
949 return SetDIerror("IDirectInputDevice8::Start", result);
950 }
951 return true;
952}
953
954static Uint8 TranslatePOV(DWORD value)
955{
956 const Uint8 HAT_VALS[] = {
957 SDL_HAT_UP,
958 SDL_HAT_UP | SDL_HAT_RIGHT,
959 SDL_HAT_RIGHT,
960 SDL_HAT_DOWN | SDL_HAT_RIGHT,
961 SDL_HAT_DOWN,
962 SDL_HAT_DOWN | SDL_HAT_LEFT,
963 SDL_HAT_LEFT,
964 SDL_HAT_UP | SDL_HAT_LEFT
965 };
966
967 if (LOWORD(value) == 0xFFFF) {
968 return SDL_HAT_CENTERED;
969 }
970
971 // Round the value up:
972 value += 4500 / 2;
973 value %= 36000;
974 value /= 4500;
975
976 if (value >= 8) {
977 return SDL_HAT_CENTERED; // shouldn't happen
978 }
979
980 return HAT_VALS[value];
981}
982
983/* Function to update the state of a joystick - called as a device poll.
984 * This function shouldn't update the joystick structure directly,
985 * but instead should call SDL_PrivateJoystick*() to deliver events
986 * and update joystick device state.
987 */
988static void UpdateDINPUTJoystickState_Polled(SDL_Joystick *joystick)
989{
990 DIJOYSTATE2 state;
991 HRESULT result;
992 int i;
993 Uint64 timestamp = SDL_GetTicksNS();
994
995 result =
996 IDirectInputDevice8_GetDeviceState(joystick->hwdata->InputDevice,
997 sizeof(DIJOYSTATE2), &state);
998 if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
999 IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
1000 result =
1001 IDirectInputDevice8_GetDeviceState(joystick->hwdata->InputDevice,
1002 sizeof(DIJOYSTATE2), &state);
1003 }
1004
1005 if (result != DI_OK) {
1006 return;
1007 }
1008
1009 // Set each known axis, button and POV.
1010 for (i = 0; i < joystick->hwdata->NumInputs; ++i) {
1011 const input_t *in = &joystick->hwdata->Inputs[i];
1012
1013 switch (in->type) {
1014 case AXIS:
1015 switch (in->ofs) {
1016 case DIJOFS_X:
1017 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lX);
1018 break;
1019 case DIJOFS_Y:
1020 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lY);
1021 break;
1022 case DIJOFS_Z:
1023 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lZ);
1024 break;
1025 case DIJOFS_RX:
1026 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRx);
1027 break;
1028 case DIJOFS_RY:
1029 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRy);
1030 break;
1031 case DIJOFS_RZ:
1032 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.lRz);
1033 break;
1034 case DIJOFS_SLIDER(0):
1035 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.rglSlider[0]);
1036 break;
1037 case DIJOFS_SLIDER(1):
1038 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)state.rglSlider[1]);
1039 break;
1040 }
1041 break;
1042
1043 case BUTTON:
1044 SDL_SendJoystickButton(timestamp, joystick, in->num,
1045 (state.rgbButtons[in->ofs - DIJOFS_BUTTON0] != 0));
1046 break;
1047 case HAT:
1048 {
1049 Uint8 pos = TranslatePOV(state.rgdwPOV[in->ofs - DIJOFS_POV(0)]);
1050 SDL_SendJoystickHat(timestamp, joystick, in->num, pos);
1051 break;
1052 }
1053 }
1054 }
1055}
1056
1057static void UpdateDINPUTJoystickState_Buffered(SDL_Joystick *joystick)
1058{
1059 int i;
1060 HRESULT result;
1061 DWORD numevents;
1062 DIDEVICEOBJECTDATA evtbuf[INPUT_QSIZE];
1063 Uint64 timestamp = SDL_GetTicksNS();
1064
1065 numevents = INPUT_QSIZE;
1066 result =
1067 IDirectInputDevice8_GetDeviceData(joystick->hwdata->InputDevice,
1068 sizeof(DIDEVICEOBJECTDATA), evtbuf,
1069 &numevents, 0);
1070 if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
1071 IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
1072 result =
1073 IDirectInputDevice8_GetDeviceData(joystick->hwdata->InputDevice,
1074 sizeof(DIDEVICEOBJECTDATA),
1075 evtbuf, &numevents, 0);
1076 }
1077
1078 // Handle the events or punt
1079 if (FAILED(result)) {
1080 return;
1081 }
1082
1083 for (i = 0; i < (int)numevents; ++i) {
1084 int j;
1085
1086 for (j = 0; j < joystick->hwdata->NumInputs; ++j) {
1087 const input_t *in = &joystick->hwdata->Inputs[j];
1088
1089 if (evtbuf[i].dwOfs != in->ofs) {
1090 continue;
1091 }
1092
1093 switch (in->type) {
1094 case AXIS:
1095 SDL_SendJoystickAxis(timestamp, joystick, in->num, (Sint16)evtbuf[i].dwData);
1096 break;
1097 case BUTTON:
1098 SDL_SendJoystickButton(timestamp, joystick, in->num,
1099 (evtbuf[i].dwData != 0));
1100 break;
1101 case HAT:
1102 {
1103 Uint8 pos = TranslatePOV(evtbuf[i].dwData);
1104 SDL_SendJoystickHat(timestamp, joystick, in->num, pos);
1105 } break;
1106 }
1107 }
1108 }
1109
1110 if (result == DI_BUFFEROVERFLOW) {
1111 /* Our buffer wasn't big enough to hold all the queued events,
1112 * so poll the device to make sure we have the complete state.
1113 */
1114 UpdateDINPUTJoystickState_Polled(joystick);
1115 }
1116}
1117
1118void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick)
1119{
1120 HRESULT result;
1121
1122 result = IDirectInputDevice8_Poll(joystick->hwdata->InputDevice);
1123 if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
1124 IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
1125 IDirectInputDevice8_Poll(joystick->hwdata->InputDevice);
1126 }
1127
1128 if (joystick->hwdata->first_update) {
1129 // Poll to get the initial state of the joystick
1130 UpdateDINPUTJoystickState_Polled(joystick);
1131 joystick->hwdata->first_update = false;
1132 return;
1133 }
1134
1135 if (joystick->hwdata->buffered ) {
1136 UpdateDINPUTJoystickState_Buffered(joystick);
1137 } else {
1138 UpdateDINPUTJoystickState_Polled(joystick);
1139 }
1140}
1141
1142void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick)
1143{
1144 if (joystick->hwdata->ffeffect_ref) {
1145 IDirectInputEffect_Unload(joystick->hwdata->ffeffect_ref);
1146 joystick->hwdata->ffeffect_ref = NULL;
1147 }
1148 if (joystick->hwdata->ffeffect) {
1149 FreeRumbleEffectData(joystick->hwdata->ffeffect);
1150 joystick->hwdata->ffeffect = NULL;
1151 }
1152 IDirectInputDevice8_Unacquire(joystick->hwdata->InputDevice);
1153 IDirectInputDevice8_Release(joystick->hwdata->InputDevice);
1154 joystick->hwdata->ff_initialized = false;
1155}
1156
1157void SDL_DINPUT_JoystickQuit(void)
1158{
1159 if (dinput != NULL) {
1160 IDirectInput8_Release(dinput);
1161 dinput = NULL;
1162 }
1163
1164 if (coinitialized) {
1165 WIN_CoUninitialize();
1166 coinitialized = false;
1167 }
1168}
1169
1170#else // !SDL_JOYSTICK_DINPUT
1171
1172typedef struct JoyStick_DeviceData JoyStick_DeviceData;
1173
1174bool SDL_DINPUT_JoystickInit(void)
1175{
1176 return true;
1177}
1178
1179void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
1180{
1181}
1182
1183bool SDL_DINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)
1184{
1185 return false;
1186}
1187
1188bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
1189{
1190 return SDL_Unsupported();
1191}
1192
1193bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1194{
1195 return SDL_Unsupported();
1196}
1197
1198void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick)
1199{
1200}
1201
1202void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick)
1203{
1204}
1205
1206void SDL_DINPUT_JoystickQuit(void)
1207{
1208}
1209
1210#endif // SDL_JOYSTICK_DINPUT
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h
new file mode 100644
index 0000000..0643ce1
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_dinputjoystick_c.h
@@ -0,0 +1,40 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// Set up for C function definitions, even when using C++
24#ifdef __cplusplus
25extern "C" {
26#endif
27
28extern bool SDL_DINPUT_JoystickInit(void);
29extern void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext);
30extern bool SDL_DINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version);
31extern bool SDL_DINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice);
32extern bool SDL_DINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble);
33extern void SDL_DINPUT_JoystickUpdate(SDL_Joystick *joystick);
34extern void SDL_DINPUT_JoystickClose(SDL_Joystick *joystick);
35extern void SDL_DINPUT_JoystickQuit(void);
36
37// Ends C function definitions when using C++
38#ifdef __cplusplus
39}
40#endif
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
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h
new file mode 100644
index 0000000..b67544b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_rawinputjoystick_c.h
@@ -0,0 +1,32 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22#include "../../core/windows/SDL_windows.h"
23
24// Return true if the RawInput driver is enabled
25extern bool RAWINPUT_IsEnabled(void);
26
27// Registers for input events
28extern int RAWINPUT_RegisterNotifications(HWND hWnd);
29extern int RAWINPUT_UnregisterNotifications(void);
30
31// Returns 0 if message was handled
32extern LRESULT CALLBACK RAWINPUT_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c
new file mode 100644
index 0000000..dbc5658
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windows_gaming_input.c
@@ -0,0 +1,1039 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_WGI
24
25#include "../SDL_sysjoystick.h"
26#include "../hidapi/SDL_hidapijoystick_c.h"
27#include "SDL_rawinputjoystick_c.h"
28
29#include "../../core/windows/SDL_windows.h"
30#define COBJMACROS
31#include "windows.gaming.input.h"
32#include <cfgmgr32.h>
33#include <objidlbase.h>
34#include <roapi.h>
35#include <initguid.h>
36
37#ifdef ____FIReference_1_INT32_INTERFACE_DEFINED__
38// MinGW-64 uses __FIReference_1_INT32 instead of Microsoft's __FIReference_1_int
39#define __FIReference_1_int __FIReference_1_INT32
40#define __FIReference_1_int_get_Value __FIReference_1_INT32_get_Value
41#define __FIReference_1_int_Release __FIReference_1_INT32_Release
42#endif
43
44struct joystick_hwdata
45{
46 __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller;
47 __x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller;
48 __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo *battery;
49 __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad;
50 __x_ABI_CWindows_CGaming_CInput_CGamepadVibration vibration;
51 UINT64 timestamp;
52};
53
54typedef struct WindowsGamingInputControllerState
55{
56 SDL_JoystickID instance_id;
57 __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller;
58 char *name;
59 SDL_GUID guid;
60 SDL_JoystickType type;
61 int steam_virtual_gamepad_slot;
62} WindowsGamingInputControllerState;
63
64typedef HRESULT(WINAPI *CoIncrementMTAUsage_t)(CO_MTA_USAGE_COOKIE *pCookie);
65typedef HRESULT(WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void **factory);
66typedef HRESULT(WINAPI *WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING *string);
67typedef HRESULT(WINAPI *WindowsDeleteString_t)(HSTRING string);
68typedef PCWSTR(WINAPI *WindowsGetStringRawBuffer_t)(HSTRING string, UINT32 *length);
69
70static struct
71{
72 CoIncrementMTAUsage_t CoIncrementMTAUsage;
73 RoGetActivationFactory_t RoGetActivationFactory;
74 WindowsCreateStringReference_t WindowsCreateStringReference;
75 WindowsDeleteString_t WindowsDeleteString;
76 WindowsGetStringRawBuffer_t WindowsGetStringRawBuffer;
77 __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics *controller_statics;
78 __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics *arcade_stick_statics;
79 __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2 *arcade_stick_statics2;
80 __x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics *flight_stick_statics;
81 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics *gamepad_statics;
82 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2 *gamepad_statics2;
83 __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics *racing_wheel_statics;
84 __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2 *racing_wheel_statics2;
85 EventRegistrationToken controller_added_token;
86 EventRegistrationToken controller_removed_token;
87 int controller_count;
88 WindowsGamingInputControllerState *controllers;
89} wgi;
90
91// WinRT headers in official Windows SDK contain only declarations, and we have to define these GUIDs ourselves.
92// https://stackoverflow.com/a/55605485/1795050
93DEFINE_GUID(IID___FIEventHandler_1_Windows__CGaming__CInput__CRawGameController, 0x00621c22, 0x42e8, 0x529f, 0x92, 0x70, 0x83, 0x6b, 0x32, 0x93, 0x1d, 0x72);
94DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics, 0x5c37b8c8, 0x37b1, 0x4ad8, 0x94, 0x58, 0x20, 0x0f, 0x1a, 0x30, 0x01, 0x8e);
95DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2, 0x52b5d744, 0xbb86, 0x445a, 0xb5, 0x9c, 0x59, 0x6f, 0x0e, 0x2a, 0x49, 0xdf);
96DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics, 0x5514924a, 0xfecc, 0x435e, 0x83, 0xdc, 0x5c, 0xec, 0x8a, 0x18, 0xa5, 0x20);
97DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGameController, 0x1baf6522, 0x5f64, 0x42c5, 0x82, 0x67, 0xb9, 0xfe, 0x22, 0x15, 0xbf, 0xbd);
98DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo, 0xdcecc681, 0x3963, 0x4da6, 0x95, 0x5d, 0x55, 0x3f, 0x3b, 0x6f, 0x61, 0x61);
99DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, 0x8bbce529, 0xd49c, 0x39e9, 0x95, 0x60, 0xe4, 0x7d, 0xde, 0x96, 0xb7, 0xc8);
100DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2, 0x42676dc5, 0x0856, 0x47c4, 0x92, 0x13, 0xb3, 0x95, 0x50, 0x4c, 0x3a, 0x3c);
101DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics, 0x3ac12cd5, 0x581b, 0x4936, 0x9f, 0x94, 0x69, 0xf1, 0xe6, 0x51, 0x4c, 0x7d);
102DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2, 0xe666bcaa, 0xedfd, 0x4323, 0xa9, 0xf6, 0x3c, 0x38, 0x40, 0x48, 0xd1, 0xed);
103DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, 0x7cad6d91, 0xa7e1, 0x4f71, 0x9a, 0x78, 0x33, 0xe9, 0xc5, 0xdf, 0xea, 0x62);
104DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, 0x43c0c035, 0xbb73, 0x4756, 0xa7, 0x87, 0x3e, 0xd6, 0xbe, 0xa6, 0x17, 0xbd);
105DEFINE_GUID(IID___x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics, 0xeb8d0792, 0xe95a, 0x4b19, 0xaf, 0xc7, 0x0a, 0x59, 0xf8, 0xbf, 0x75, 0x9e);
106
107extern bool SDL_XINPUT_Enabled(void);
108
109
110static bool SDL_IsXInputDevice(Uint16 vendor, Uint16 product, const char *name)
111{
112#if defined(SDL_JOYSTICK_XINPUT) || defined(SDL_JOYSTICK_RAWINPUT)
113 PRAWINPUTDEVICELIST raw_devices = NULL;
114 UINT i, raw_device_count = 0;
115 LONG vidpid = MAKELONG(vendor, product);
116
117 // XInput and RawInput backends will pick up XInput-compatible devices
118 if (!SDL_XINPUT_Enabled()
119#ifdef SDL_JOYSTICK_RAWINPUT
120 && !RAWINPUT_IsEnabled()
121#endif
122 ) {
123 return false;
124 }
125
126 // Sometimes we'll get a Windows.Gaming.Input callback before the raw input device is even in the list,
127 // so try to do some checks up front to catch these cases.
128 if (SDL_IsJoystickXboxOne(vendor, product) ||
129 (name && SDL_strncmp(name, "Xbox ", 5) == 0)) {
130 return true;
131 }
132
133 // Go through RAWINPUT (WinXP and later) to find HID devices.
134 if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) {
135 return false; // oh well.
136 }
137
138 raw_devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * raw_device_count);
139 if (!raw_devices) {
140 return false;
141 }
142
143 raw_device_count = GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST));
144 if (raw_device_count == (UINT)-1) {
145 SDL_free(raw_devices);
146 raw_devices = NULL;
147 return false; // oh well.
148 }
149
150 for (i = 0; i < raw_device_count; i++) {
151 RID_DEVICE_INFO rdi;
152 char devName[MAX_PATH] = { 0 };
153 UINT rdiSize = sizeof(rdi);
154 UINT nameSize = SDL_arraysize(devName);
155 DEVINST devNode;
156 char devVidPidString[32];
157 int j;
158
159 rdi.cbSize = sizeof(rdi);
160
161 if ((raw_devices[i].dwType != RIM_TYPEHID) ||
162 (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == ((UINT)-1)) ||
163 (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) == ((UINT)-1)) ||
164 (SDL_strstr(devName, "IG_") == NULL)) {
165 // Skip non-XInput devices
166 continue;
167 }
168
169 // First check for a simple VID/PID match. This will work for Xbox 360 controllers.
170 if (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == vidpid) {
171 SDL_free(raw_devices);
172 return true;
173 }
174
175 /* For Xbox One controllers, Microsoft doesn't propagate the VID/PID down to the HID stack.
176 * We'll have to walk the device tree upwards searching for a match for our VID/PID. */
177
178 // Make sure the device interface string is something we know how to parse
179 // Example: \\?\HID#VID_045E&PID_02FF&IG_00#9&2c203035&2&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
180 if ((SDL_strstr(devName, "\\\\?\\") != devName) || (SDL_strstr(devName, "#{") == NULL)) {
181 continue;
182 }
183
184 // Unescape the backslashes in the string and terminate before the GUID portion
185 for (j = 0; devName[j] != '\0'; j++) {
186 if (devName[j] == '#') {
187 if (devName[j + 1] == '{') {
188 devName[j] = '\0';
189 break;
190 } else {
191 devName[j] = '\\';
192 }
193 }
194 }
195
196 /* We'll be left with a string like this: \\?\HID\VID_045E&PID_02FF&IG_00\9&2c203035&2&0000
197 * Simply skip the \\?\ prefix and we'll have a properly formed device instance ID */
198 if (CM_Locate_DevNodeA(&devNode, &devName[4], CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) {
199 continue;
200 }
201
202 (void)SDL_snprintf(devVidPidString, sizeof(devVidPidString), "VID_%04X&PID_%04X", vendor, product);
203
204 while (CM_Get_Parent(&devNode, devNode, 0) == CR_SUCCESS) {
205 char deviceId[MAX_DEVICE_ID_LEN];
206
207 if ((CM_Get_Device_IDA(devNode, deviceId, SDL_arraysize(deviceId), 0) == CR_SUCCESS) &&
208 (SDL_strstr(deviceId, devVidPidString) != NULL)) {
209 // The VID/PID matched a parent device
210 SDL_free(raw_devices);
211 return true;
212 }
213 }
214 }
215
216 SDL_free(raw_devices);
217#endif // SDL_JOYSTICK_XINPUT || SDL_JOYSTICK_RAWINPUT
218
219 return false;
220}
221
222static void WGI_LoadRawGameControllerStatics(void)
223{
224 HRESULT hr;
225 HSTRING_HEADER class_name_header;
226 HSTRING class_name;
227
228 hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_RawGameController, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_RawGameController), &class_name_header, &class_name);
229 if (SUCCEEDED(hr)) {
230 hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics, (void **)&wgi.controller_statics);
231 if (!SUCCEEDED(hr)) {
232 WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IRawGameControllerStatics", hr);
233 }
234 }
235}
236
237static void WGI_LoadOtherControllerStatics(void)
238{
239 HRESULT hr;
240 HSTRING_HEADER class_name_header;
241 HSTRING class_name;
242
243 if (!wgi.arcade_stick_statics) {
244 hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_ArcadeStick, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_ArcadeStick), &class_name_header, &class_name);
245 if (SUCCEEDED(hr)) {
246 hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics, (void **)&wgi.arcade_stick_statics);
247 if (SUCCEEDED(hr)) {
248 __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_QueryInterface(wgi.arcade_stick_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2, (void **)&wgi.arcade_stick_statics2);
249 } else {
250 WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IArcadeStickStatics", hr);
251 }
252 }
253 }
254
255 if (!wgi.flight_stick_statics) {
256 hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_FlightStick, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_FlightStick), &class_name_header, &class_name);
257 if (SUCCEEDED(hr)) {
258 hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics, (void **)&wgi.flight_stick_statics);
259 if (!SUCCEEDED(hr)) {
260 WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IFlightStickStatics", hr);
261 }
262 }
263 }
264
265 if (!wgi.gamepad_statics) {
266 hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_Gamepad), &class_name_header, &class_name);
267 if (SUCCEEDED(hr)) {
268 hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics, (void **)&wgi.gamepad_statics);
269 if (SUCCEEDED(hr)) {
270 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_QueryInterface(wgi.gamepad_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2, (void **)&wgi.gamepad_statics2);
271 } else {
272 WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IGamepadStatics", hr);
273 }
274 }
275 }
276
277 if (!wgi.racing_wheel_statics) {
278 hr = wgi.WindowsCreateStringReference(RuntimeClass_Windows_Gaming_Input_RacingWheel, (UINT32)SDL_wcslen(RuntimeClass_Windows_Gaming_Input_RacingWheel), &class_name_header, &class_name);
279 if (SUCCEEDED(hr)) {
280 hr = wgi.RoGetActivationFactory(class_name, &IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics, (void **)&wgi.racing_wheel_statics);
281 if (SUCCEEDED(hr)) {
282 __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics_QueryInterface(wgi.racing_wheel_statics, &IID___x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2, (void **)&wgi.racing_wheel_statics2);
283 } else {
284 WIN_SetErrorFromHRESULT("Couldn't find Windows.Gaming.Input.IRacingWheelStatics", hr);
285 }
286 }
287 }
288}
289
290static SDL_JoystickType GetGameControllerType(__x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller)
291{
292 __x_ABI_CWindows_CGaming_CInput_CIArcadeStick *arcade_stick = NULL;
293 __x_ABI_CWindows_CGaming_CInput_CIFlightStick *flight_stick = NULL;
294 __x_ABI_CWindows_CGaming_CInput_CIGamepad *gamepad = NULL;
295 __x_ABI_CWindows_CGaming_CInput_CIRacingWheel *racing_wheel = NULL;
296
297 /* Wait to initialize these interfaces until we need them.
298 * Initializing the gamepad interface will switch Bluetooth PS4 controllers into enhanced mode, breaking DirectInput
299 */
300 WGI_LoadOtherControllerStatics();
301
302 if (wgi.gamepad_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, game_controller, &gamepad)) && gamepad) {
303 __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(gamepad);
304 return SDL_JOYSTICK_TYPE_GAMEPAD;
305 }
306
307 if (wgi.arcade_stick_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2_FromGameController(wgi.arcade_stick_statics2, game_controller, &arcade_stick)) && arcade_stick) {
308 __x_ABI_CWindows_CGaming_CInput_CIArcadeStick_Release(arcade_stick);
309 return SDL_JOYSTICK_TYPE_ARCADE_STICK;
310 }
311
312 if (wgi.flight_stick_statics && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics_FromGameController(wgi.flight_stick_statics, game_controller, &flight_stick)) && flight_stick) {
313 __x_ABI_CWindows_CGaming_CInput_CIFlightStick_Release(flight_stick);
314 return SDL_JOYSTICK_TYPE_FLIGHT_STICK;
315 }
316
317 if (wgi.racing_wheel_statics2 && SUCCEEDED(__x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_FromGameController(wgi.racing_wheel_statics2, game_controller, &racing_wheel)) && racing_wheel) {
318 __x_ABI_CWindows_CGaming_CInput_CIRacingWheel_Release(racing_wheel);
319 return SDL_JOYSTICK_TYPE_WHEEL;
320 }
321
322 return SDL_JOYSTICK_TYPE_UNKNOWN;
323}
324
325typedef struct RawGameControllerDelegate
326{
327 __FIEventHandler_1_Windows__CGaming__CInput__CRawGameController iface;
328 SDL_AtomicInt refcount;
329} RawGameControllerDelegate;
330
331static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_QueryInterface(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, REFIID riid, void **ppvObject)
332{
333 if (!ppvObject) {
334 return E_INVALIDARG;
335 }
336
337 *ppvObject = NULL;
338 if (WIN_IsEqualIID(riid, &IID_IUnknown) || WIN_IsEqualIID(riid, &IID_IAgileObject) || WIN_IsEqualIID(riid, &IID___FIEventHandler_1_Windows__CGaming__CInput__CRawGameController)) {
339 *ppvObject = This;
340 __FIEventHandler_1_Windows__CGaming__CInput__CRawGameController_AddRef(This);
341 return S_OK;
342 } else if (WIN_IsEqualIID(riid, &IID_IMarshal)) {
343 // This seems complicated. Let's hope it doesn't happen.
344 return E_OUTOFMEMORY;
345 } else {
346 return E_NOINTERFACE;
347 }
348}
349
350static ULONG STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_AddRef(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This)
351{
352 RawGameControllerDelegate *self = (RawGameControllerDelegate *)This;
353 return SDL_AddAtomicInt(&self->refcount, 1) + 1UL;
354}
355
356static ULONG STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_Release(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This)
357{
358 RawGameControllerDelegate *self = (RawGameControllerDelegate *)This;
359 int rc = SDL_AddAtomicInt(&self->refcount, -1) - 1;
360 // Should never free the static delegate objects
361 SDL_assert(rc > 0);
362 return rc;
363}
364
365static int GetSteamVirtualGamepadSlot(__x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller, Uint16 vendor_id, Uint16 product_id)
366{
367 int slot = -1;
368
369 if (vendor_id == USB_VENDOR_VALVE &&
370 product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
371 __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL;
372 HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2);
373 if (SUCCEEDED(hr)) {
374 HSTRING hString;
375 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_NonRoamableId(controller2, &hString);
376 if (SUCCEEDED(hr)) {
377 PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL);
378 if (string) {
379 char *id = WIN_StringToUTF8W(string);
380 if (id) {
381 (void)SDL_sscanf(id, "{wgi/nrid/:steam-%*X&%*X&%*X#%d#%*u}", &slot);
382 SDL_free(id);
383 }
384 }
385 wgi.WindowsDeleteString(hString);
386 }
387 __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2);
388 }
389 }
390 return slot;
391}
392
393static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController *e)
394{
395 HRESULT hr;
396 __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL;
397
398 SDL_LockJoysticks();
399
400 // We can get delayed calls to InvokeAdded() after WGI_JoystickQuit()
401 if (SDL_JoysticksQuitting() || !SDL_JoysticksInitialized()) {
402 SDL_UnlockJoysticks();
403 return S_OK;
404 }
405
406 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, (void **)&controller);
407 if (SUCCEEDED(hr)) {
408 char *name = NULL;
409 Uint16 bus = SDL_HARDWARE_BUS_USB;
410 Uint16 vendor = 0;
411 Uint16 product = 0;
412 Uint16 version = 0;
413 SDL_JoystickType type = SDL_JOYSTICK_TYPE_UNKNOWN;
414 __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL;
415 __x_ABI_CWindows_CGaming_CInput_CIGameController *game_controller = NULL;
416 bool ignore_joystick = false;
417
418 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_HardwareVendorId(controller, &vendor);
419 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_HardwareProductId(controller, &product);
420
421 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameController, (void **)&game_controller);
422 if (SUCCEEDED(hr)) {
423 boolean wireless = 0;
424 hr = __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(game_controller, &wireless);
425 if (SUCCEEDED(hr) && wireless) {
426 bus = SDL_HARDWARE_BUS_BLUETOOTH;
427
428 // Fixup for Wireless Xbox 360 Controller
429 if (product == 0) {
430 vendor = USB_VENDOR_MICROSOFT;
431 product = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
432 }
433 }
434
435 __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(game_controller);
436 }
437
438 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2);
439 if (SUCCEEDED(hr)) {
440 HSTRING hString;
441 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_DisplayName(controller2, &hString);
442 if (SUCCEEDED(hr)) {
443 PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL);
444 if (string) {
445 name = WIN_StringToUTF8W(string);
446 }
447 wgi.WindowsDeleteString(hString);
448 }
449 __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2);
450 }
451 if (!name) {
452 name = SDL_strdup("");
453 }
454
455 if (!ignore_joystick && SDL_ShouldIgnoreJoystick(vendor, product, version, name)) {
456 ignore_joystick = true;
457 }
458
459 if (!ignore_joystick && SDL_JoystickHandledByAnotherDriver(&SDL_WGI_JoystickDriver, vendor, product, version, name)) {
460 ignore_joystick = true;
461 }
462
463 if (!ignore_joystick && SDL_IsXInputDevice(vendor, product, name)) {
464 // This hasn't been detected by the RAWINPUT driver yet, but it will be picked up later.
465 ignore_joystick = true;
466 }
467
468 if (!ignore_joystick) {
469 // New device, add it
470 WindowsGamingInputControllerState *controllers = SDL_realloc(wgi.controllers, sizeof(wgi.controllers[0]) * (wgi.controller_count + 1));
471 if (controllers) {
472 WindowsGamingInputControllerState *state = &controllers[wgi.controller_count];
473 SDL_JoystickID joystickID = SDL_GetNextObjectID();
474
475 if (game_controller) {
476 type = GetGameControllerType(game_controller);
477 }
478
479 SDL_zerop(state);
480 state->instance_id = joystickID;
481 state->controller = controller;
482 state->name = name;
483 state->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, NULL, name, 'w', (Uint8)type);
484 state->type = type;
485 state->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(controller, vendor, product);
486
487 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(controller);
488
489 ++wgi.controller_count;
490 wgi.controllers = controllers;
491
492 SDL_PrivateJoystickAdded(joystickID);
493 } else {
494 SDL_free(name);
495 }
496 } else {
497 SDL_free(name);
498 }
499
500 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller);
501 }
502
503 SDL_UnlockJoysticks();
504
505 return S_OK;
506}
507
508static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeRemoved(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController *e)
509{
510 HRESULT hr;
511 __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL;
512
513 SDL_LockJoysticks();
514
515 // Can we get delayed calls to InvokeRemoved() after WGI_JoystickQuit()?
516 if (!SDL_JoysticksInitialized()) {
517 SDL_UnlockJoysticks();
518 return S_OK;
519 }
520
521 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(e, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController, (void **)&controller);
522 if (SUCCEEDED(hr)) {
523 int i;
524
525 for (i = 0; i < wgi.controller_count; i++) {
526 if (wgi.controllers[i].controller == controller) {
527 WindowsGamingInputControllerState *state = &wgi.controllers[i];
528 SDL_JoystickID joystickID = state->instance_id;
529
530 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(state->controller);
531
532 SDL_free(state->name);
533
534 --wgi.controller_count;
535 if (i < wgi.controller_count) {
536 SDL_memmove(&wgi.controllers[i], &wgi.controllers[i + 1], (wgi.controller_count - i) * sizeof(wgi.controllers[i]));
537 }
538
539 SDL_PrivateJoystickRemoved(joystickID);
540 break;
541 }
542 }
543
544 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller);
545 }
546
547 SDL_UnlockJoysticks();
548
549 return S_OK;
550}
551
552#ifdef _MSC_VER
553#pragma warning(push)
554#pragma warning(disable : 4028) // formal parameter 3 different from declaration, when using older buggy WGI headers
555#pragma warning(disable : 4113) // formal parameter 3 different from declaration (a more specific warning added in VS 2022), when using older buggy WGI headers
556#endif
557
558static __FIEventHandler_1_Windows__CGaming__CInput__CRawGameControllerVtbl controller_added_vtbl = {
559 IEventHandler_CRawGameControllerVtbl_QueryInterface,
560 IEventHandler_CRawGameControllerVtbl_AddRef,
561 IEventHandler_CRawGameControllerVtbl_Release,
562 IEventHandler_CRawGameControllerVtbl_InvokeAdded
563};
564static RawGameControllerDelegate controller_added = {
565 { &controller_added_vtbl },
566 { 1 }
567};
568
569static __FIEventHandler_1_Windows__CGaming__CInput__CRawGameControllerVtbl controller_removed_vtbl = {
570 IEventHandler_CRawGameControllerVtbl_QueryInterface,
571 IEventHandler_CRawGameControllerVtbl_AddRef,
572 IEventHandler_CRawGameControllerVtbl_Release,
573 IEventHandler_CRawGameControllerVtbl_InvokeRemoved
574};
575static RawGameControllerDelegate controller_removed = {
576 { &controller_removed_vtbl },
577 { 1 }
578};
579
580#ifdef _MSC_VER
581#pragma warning(pop)
582#endif
583
584static bool WGI_JoystickInit(void)
585{
586 HRESULT hr;
587
588 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_WGI, true)) {
589 return true;
590 }
591
592 if (FAILED(WIN_RoInitialize())) {
593 return SDL_SetError("RoInitialize() failed");
594 }
595
596#define RESOLVE(x) wgi.x = (x##_t)WIN_LoadComBaseFunction(#x); if (!wgi.x) return WIN_SetError("GetProcAddress failed for " #x);
597 RESOLVE(CoIncrementMTAUsage);
598 RESOLVE(RoGetActivationFactory);
599 RESOLVE(WindowsCreateStringReference);
600 RESOLVE(WindowsDeleteString);
601 RESOLVE(WindowsGetStringRawBuffer);
602#undef RESOLVE
603
604 {
605 /* There seems to be a bug in Windows where a dependency of WGI can be unloaded from memory prior to WGI itself.
606 * This results in Windows_Gaming_Input!GameController::~GameController() invoking an unloaded DLL and crashing.
607 * As a workaround, we will keep a reference to the MTA to prevent COM from unloading DLLs later.
608 * See https://github.com/libsdl-org/SDL/issues/5552 for more details.
609 */
610 static CO_MTA_USAGE_COOKIE cookie = NULL;
611 if (!cookie) {
612 hr = wgi.CoIncrementMTAUsage(&cookie);
613 if (FAILED(hr)) {
614 return WIN_SetErrorFromHRESULT("CoIncrementMTAUsage() failed", hr);
615 }
616 }
617 }
618
619 WGI_LoadRawGameControllerStatics();
620
621 if (wgi.controller_statics) {
622 __FIVectorView_1_Windows__CGaming__CInput__CRawGameController *controllers;
623
624 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerAdded(wgi.controller_statics, &controller_added.iface, &wgi.controller_added_token);
625 if (!SUCCEEDED(hr)) {
626 WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IRawGameControllerStatics.add_RawGameControllerAdded failed", hr);
627 }
628
629 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_add_RawGameControllerRemoved(wgi.controller_statics, &controller_removed.iface, &wgi.controller_removed_token);
630 if (!SUCCEEDED(hr)) {
631 WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IRawGameControllerStatics.add_RawGameControllerRemoved failed", hr);
632 }
633
634 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_get_RawGameControllers(wgi.controller_statics, &controllers);
635 if (SUCCEEDED(hr)) {
636 unsigned i, count = 0;
637
638 hr = __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_get_Size(controllers, &count);
639 if (SUCCEEDED(hr)) {
640 for (i = 0; i < count; ++i) {
641 __x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller = NULL;
642
643 hr = __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_GetAt(controllers, i, &controller);
644 if (SUCCEEDED(hr) && controller) {
645 IEventHandler_CRawGameControllerVtbl_InvokeAdded(&controller_added.iface, NULL, controller);
646 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(controller);
647 }
648 }
649 }
650
651 __FIVectorView_1_Windows__CGaming__CInput__CRawGameController_Release(controllers);
652 }
653 }
654
655 return true;
656}
657
658static int WGI_JoystickGetCount(void)
659{
660 return wgi.controller_count;
661}
662
663static void WGI_JoystickDetect(void)
664{
665}
666
667static bool WGI_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
668{
669 // We don't override any other drivers
670 return false;
671}
672
673static const char *WGI_JoystickGetDeviceName(int device_index)
674{
675 return wgi.controllers[device_index].name;
676}
677
678static const char *WGI_JoystickGetDevicePath(int device_index)
679{
680 return NULL;
681}
682
683static int WGI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
684{
685 return wgi.controllers[device_index].steam_virtual_gamepad_slot;
686}
687
688static int WGI_JoystickGetDevicePlayerIndex(int device_index)
689{
690 return false;
691}
692
693static void WGI_JoystickSetDevicePlayerIndex(int device_index, int player_index)
694{
695}
696
697static SDL_GUID WGI_JoystickGetDeviceGUID(int device_index)
698{
699 return wgi.controllers[device_index].guid;
700}
701
702static SDL_JoystickID WGI_JoystickGetDeviceInstanceID(int device_index)
703{
704 return wgi.controllers[device_index].instance_id;
705}
706
707static bool WGI_JoystickOpen(SDL_Joystick *joystick, int device_index)
708{
709 WindowsGamingInputControllerState *state = &wgi.controllers[device_index];
710 struct joystick_hwdata *hwdata;
711 boolean wireless = false;
712
713 hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata));
714 if (!hwdata) {
715 return false;
716 }
717 joystick->hwdata = hwdata;
718
719 hwdata->controller = state->controller;
720 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(hwdata->controller);
721 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameController, (void **)&hwdata->game_controller);
722 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(hwdata->controller, &IID___x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo, (void **)&hwdata->battery);
723
724 if (wgi.gamepad_statics2) {
725 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_FromGameController(wgi.gamepad_statics2, hwdata->game_controller, &hwdata->gamepad);
726 }
727
728 if (hwdata->game_controller) {
729 __x_ABI_CWindows_CGaming_CInput_CIGameController_get_IsWireless(hwdata->game_controller, &wireless);
730 }
731
732 // Initialize the joystick capabilities
733 if (wireless) {
734 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
735 } else {
736 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
737 }
738 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_ButtonCount(hwdata->controller, &joystick->nbuttons);
739 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_AxisCount(hwdata->controller, &joystick->naxes);
740 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_SwitchCount(hwdata->controller, &joystick->nhats);
741
742 if (hwdata->gamepad) {
743 // FIXME: Can WGI even tell us if trigger rumble is supported?
744 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
745 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
746 }
747 return true;
748}
749
750static bool WGI_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
751{
752 struct joystick_hwdata *hwdata = joystick->hwdata;
753
754 if (hwdata->gamepad) {
755 HRESULT hr;
756
757 // Note: reusing partially filled vibration data struct
758 hwdata->vibration.LeftMotor = (DOUBLE)low_frequency_rumble / SDL_MAX_UINT16;
759 hwdata->vibration.RightMotor = (DOUBLE)high_frequency_rumble / SDL_MAX_UINT16;
760 hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(hwdata->gamepad, hwdata->vibration);
761 if (SUCCEEDED(hr)) {
762 return true;
763 } else {
764 return WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IGamepad.put_Vibration failed", hr);
765 }
766 } else {
767 return SDL_Unsupported();
768 }
769}
770
771static bool WGI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
772{
773 struct joystick_hwdata *hwdata = joystick->hwdata;
774
775 if (hwdata->gamepad) {
776 HRESULT hr;
777
778 // Note: reusing partially filled vibration data struct
779 hwdata->vibration.LeftTrigger = (DOUBLE)left_rumble / SDL_MAX_UINT16;
780 hwdata->vibration.RightTrigger = (DOUBLE)right_rumble / SDL_MAX_UINT16;
781 hr = __x_ABI_CWindows_CGaming_CInput_CIGamepad_put_Vibration(hwdata->gamepad, hwdata->vibration);
782 if (SUCCEEDED(hr)) {
783 return true;
784 } else {
785 return WIN_SetErrorFromHRESULT("Windows.Gaming.Input.IGamepad.put_Vibration failed", hr);
786 }
787 } else {
788 return SDL_Unsupported();
789 }
790}
791
792static bool WGI_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
793{
794 return SDL_Unsupported();
795}
796
797static bool WGI_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
798{
799 return SDL_Unsupported();
800}
801
802static bool WGI_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
803{
804 return SDL_Unsupported();
805}
806
807static Uint8 ConvertHatValue(__x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition value)
808{
809 switch (value) {
810 case GameControllerSwitchPosition_Up:
811 return SDL_HAT_UP;
812 case GameControllerSwitchPosition_UpRight:
813 return SDL_HAT_RIGHTUP;
814 case GameControllerSwitchPosition_Right:
815 return SDL_HAT_RIGHT;
816 case GameControllerSwitchPosition_DownRight:
817 return SDL_HAT_RIGHTDOWN;
818 case GameControllerSwitchPosition_Down:
819 return SDL_HAT_DOWN;
820 case GameControllerSwitchPosition_DownLeft:
821 return SDL_HAT_LEFTDOWN;
822 case GameControllerSwitchPosition_Left:
823 return SDL_HAT_LEFT;
824 case GameControllerSwitchPosition_UpLeft:
825 return SDL_HAT_LEFTUP;
826 default:
827 return SDL_HAT_CENTERED;
828 }
829}
830
831static void WGI_JoystickUpdate(SDL_Joystick *joystick)
832{
833 struct joystick_hwdata *hwdata = joystick->hwdata;
834 HRESULT hr;
835 UINT32 nbuttons = SDL_min(joystick->nbuttons, SDL_MAX_UINT8);
836 boolean *buttons = NULL;
837 UINT32 nhats = SDL_min(joystick->nhats, SDL_MAX_UINT8);
838 __x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition *hats = NULL;
839 UINT32 naxes = SDL_min(joystick->naxes, SDL_MAX_UINT8);
840 DOUBLE *axes = NULL;
841 UINT64 timestamp;
842
843 if (nbuttons > 0) {
844 buttons = SDL_stack_alloc(boolean, nbuttons);
845 }
846 if (nhats > 0) {
847 hats = SDL_stack_alloc(__x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition, nhats);
848 }
849 if (naxes > 0) {
850 axes = SDL_stack_alloc(DOUBLE, naxes);
851 }
852
853 hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_GetCurrentReading(hwdata->controller, nbuttons, buttons, nhats, hats, naxes, axes, &timestamp);
854 if (SUCCEEDED(hr) && (!timestamp || timestamp != hwdata->timestamp)) {
855 UINT32 i;
856 bool all_zero = false;
857
858 hwdata->timestamp = timestamp;
859
860 // The axes are all zero when the application loses focus
861 if (naxes > 0) {
862 all_zero = true;
863 for (i = 0; i < naxes; ++i) {
864 if (axes[i] != 0.0f) {
865 all_zero = false;
866 break;
867 }
868 }
869 }
870 if (all_zero) {
871 SDL_PrivateJoystickForceRecentering(joystick);
872 } else {
873 // FIXME: What units are the timestamp we get from GetCurrentReading()?
874 timestamp = SDL_GetTicksNS();
875 for (i = 0; i < nbuttons; ++i) {
876 SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, buttons[i]);
877 }
878 for (i = 0; i < nhats; ++i) {
879 SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, ConvertHatValue(hats[i]));
880 }
881 for (i = 0; i < naxes; ++i) {
882 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, (Sint16)((int)(axes[i] * 65535) - 32768));
883 }
884 }
885 }
886
887 SDL_stack_free(buttons);
888 SDL_stack_free(hats);
889 SDL_stack_free(axes);
890
891 if (hwdata->battery) {
892 __x_ABI_CWindows_CDevices_CPower_CIBatteryReport *report = NULL;
893
894 hr = __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_TryGetBatteryReport(hwdata->battery, &report);
895 if (SUCCEEDED(hr) && report) {
896 SDL_PowerState state = SDL_POWERSTATE_UNKNOWN;
897 int percent = 0;
898 __x_ABI_CWindows_CSystem_CPower_CBatteryStatus status;
899 int full_capacity = 0, curr_capacity = 0;
900 __FIReference_1_int *full_capacityP, *curr_capacityP;
901
902 hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_Status(report, &status);
903 if (SUCCEEDED(hr)) {
904 switch (status) {
905 case BatteryStatus_NotPresent:
906 state = SDL_POWERSTATE_NO_BATTERY;
907 break;
908 case BatteryStatus_Discharging:
909 state = SDL_POWERSTATE_ON_BATTERY;
910 break;
911 case BatteryStatus_Idle:
912 state = SDL_POWERSTATE_CHARGED;
913 break;
914 case BatteryStatus_Charging:
915 state = SDL_POWERSTATE_CHARGING;
916 break;
917 default:
918 state = SDL_POWERSTATE_UNKNOWN;
919 break;
920 }
921 }
922
923 hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_FullChargeCapacityInMilliwattHours(report, &full_capacityP);
924 if (SUCCEEDED(hr)) {
925 __FIReference_1_int_get_Value(full_capacityP, &full_capacity);
926 __FIReference_1_int_Release(full_capacityP);
927 }
928
929 hr = __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_get_RemainingCapacityInMilliwattHours(report, &curr_capacityP);
930 if (SUCCEEDED(hr)) {
931 __FIReference_1_int_get_Value(curr_capacityP, &curr_capacity);
932 __FIReference_1_int_Release(curr_capacityP);
933 }
934
935 if (full_capacity > 0) {
936 percent = (int)SDL_roundf(((float)curr_capacity / full_capacity) * 100.0f);
937 }
938
939 SDL_SendJoystickPowerInfo(joystick, state, percent);
940
941 __x_ABI_CWindows_CDevices_CPower_CIBatteryReport_Release(report);
942 }
943 }
944}
945
946static void WGI_JoystickClose(SDL_Joystick *joystick)
947{
948 struct joystick_hwdata *hwdata = joystick->hwdata;
949
950 if (hwdata) {
951 if (hwdata->controller) {
952 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_Release(hwdata->controller);
953 }
954 if (hwdata->game_controller) {
955 __x_ABI_CWindows_CGaming_CInput_CIGameController_Release(hwdata->game_controller);
956 }
957 if (hwdata->battery) {
958 __x_ABI_CWindows_CGaming_CInput_CIGameControllerBatteryInfo_Release(hwdata->battery);
959 }
960 if (hwdata->gamepad) {
961 __x_ABI_CWindows_CGaming_CInput_CIGamepad_Release(hwdata->gamepad);
962 }
963 SDL_free(hwdata);
964 }
965 joystick->hwdata = NULL;
966}
967
968static void WGI_JoystickQuit(void)
969{
970 if (wgi.controller_statics) {
971 while (wgi.controller_count > 0) {
972 IEventHandler_CRawGameControllerVtbl_InvokeRemoved(&controller_removed.iface, NULL, wgi.controllers[wgi.controller_count - 1].controller);
973 }
974 if (wgi.controllers) {
975 SDL_free(wgi.controllers);
976 }
977
978 if (wgi.arcade_stick_statics) {
979 __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics_Release(wgi.arcade_stick_statics);
980 }
981 if (wgi.arcade_stick_statics2) {
982 __x_ABI_CWindows_CGaming_CInput_CIArcadeStickStatics2_Release(wgi.arcade_stick_statics2);
983 }
984 if (wgi.flight_stick_statics) {
985 __x_ABI_CWindows_CGaming_CInput_CIFlightStickStatics_Release(wgi.flight_stick_statics);
986 }
987 if (wgi.gamepad_statics) {
988 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics_Release(wgi.gamepad_statics);
989 }
990 if (wgi.gamepad_statics2) {
991 __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2_Release(wgi.gamepad_statics2);
992 }
993 if (wgi.racing_wheel_statics) {
994 __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics_Release(wgi.racing_wheel_statics);
995 }
996 if (wgi.racing_wheel_statics2) {
997 __x_ABI_CWindows_CGaming_CInput_CIRacingWheelStatics2_Release(wgi.racing_wheel_statics2);
998 }
999
1000 __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerAdded(wgi.controller_statics, wgi.controller_added_token);
1001 __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_remove_RawGameControllerRemoved(wgi.controller_statics, wgi.controller_removed_token);
1002 __x_ABI_CWindows_CGaming_CInput_CIRawGameControllerStatics_Release(wgi.controller_statics);
1003 }
1004
1005 WIN_RoUninitialize();
1006
1007 SDL_zero(wgi);
1008}
1009
1010static bool WGI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
1011{
1012 return false;
1013}
1014
1015SDL_JoystickDriver SDL_WGI_JoystickDriver = {
1016 WGI_JoystickInit,
1017 WGI_JoystickGetCount,
1018 WGI_JoystickDetect,
1019 WGI_JoystickIsDevicePresent,
1020 WGI_JoystickGetDeviceName,
1021 WGI_JoystickGetDevicePath,
1022 WGI_JoystickGetDeviceSteamVirtualGamepadSlot,
1023 WGI_JoystickGetDevicePlayerIndex,
1024 WGI_JoystickSetDevicePlayerIndex,
1025 WGI_JoystickGetDeviceGUID,
1026 WGI_JoystickGetDeviceInstanceID,
1027 WGI_JoystickOpen,
1028 WGI_JoystickRumble,
1029 WGI_JoystickRumbleTriggers,
1030 WGI_JoystickSetLED,
1031 WGI_JoystickSendEffect,
1032 WGI_JoystickSetSensorsEnabled,
1033 WGI_JoystickUpdate,
1034 WGI_JoystickClose,
1035 WGI_JoystickQuit,
1036 WGI_JoystickGetGamepadMapping
1037};
1038
1039#endif // SDL_JOYSTICK_WGI
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c
new file mode 100644
index 0000000..e7fbfcb
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick.c
@@ -0,0 +1,693 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT)
24
25/* DirectInput joystick driver; written by Glenn Maynard, based on Andrei de
26 * A. Formiga's WINMM driver.
27 *
28 * Hats and sliders are completely untested; the app I'm writing this for mostly
29 * doesn't use them and I don't own any joysticks with them.
30 *
31 * We don't bother to use event notification here. It doesn't seem to work
32 * with polled devices, and it's fine to call IDirectInputDevice8_GetDeviceData and
33 * let it return 0 events. */
34
35#include "../SDL_sysjoystick.h"
36#include "../../thread/SDL_systhread.h"
37#include "../../core/windows/SDL_windows.h"
38#include "../../core/windows/SDL_hid.h"
39#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
40#include <dbt.h>
41#endif
42
43#define INITGUID // Only set here, if set twice will cause mingw32 to break.
44#include "SDL_windowsjoystick_c.h"
45#include "SDL_dinputjoystick_c.h"
46#include "SDL_xinputjoystick_c.h"
47#include "SDL_rawinputjoystick_c.h"
48
49#include "../../haptic/windows/SDL_dinputhaptic_c.h" // For haptic hot plugging
50
51#ifndef DEVICE_NOTIFY_WINDOW_HANDLE
52#define DEVICE_NOTIFY_WINDOW_HANDLE 0x00000000
53#endif
54
55// local variables
56static bool s_bJoystickThread = false;
57static SDL_Condition *s_condJoystickThread = NULL;
58static SDL_Mutex *s_mutexJoyStickEnum = NULL;
59static SDL_Thread *s_joystickThread = NULL;
60static bool s_bJoystickThreadQuit = false;
61static Uint64 s_lastDeviceChange = 0;
62static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
63
64JoyStick_DeviceData *SYS_Joystick; // array to hold joystick ID values
65
66
67static bool WindowsDeviceChanged(void)
68{
69 return (s_lastDeviceChange != WIN_GetLastDeviceNotification());
70}
71
72static void SetWindowsDeviceChanged(void)
73{
74 s_lastDeviceChange = 0;
75}
76
77void WINDOWS_RAWINPUTEnabledChanged(void)
78{
79 SetWindowsDeviceChanged();
80}
81
82#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
83
84typedef struct
85{
86 HRESULT coinitialized;
87 WNDCLASSEX wincl;
88 HWND messageWindow;
89 HDEVNOTIFY hNotify;
90} SDL_DeviceNotificationData;
91
92#define IDT_SDL_DEVICE_CHANGE_TIMER_1 1200
93#define IDT_SDL_DEVICE_CHANGE_TIMER_2 1201
94
95// windowproc for our joystick detect thread message only window, to detect any USB device addition/removal
96static LRESULT CALLBACK SDL_PrivateJoystickDetectProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
97{
98 switch (msg) {
99 case WM_DEVICECHANGE:
100 switch (wParam) {
101 case DBT_DEVICEARRIVAL:
102 case DBT_DEVICEREMOVECOMPLETE:
103 if (((DEV_BROADCAST_HDR *)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
104 // notify 300ms and 2 seconds later to ensure all APIs have updated status
105 SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_1, 300, NULL);
106 SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_2, 2000, NULL);
107 }
108 break;
109 }
110 return true;
111 case WM_TIMER:
112 if (wParam == IDT_SDL_DEVICE_CHANGE_TIMER_1 ||
113 wParam == IDT_SDL_DEVICE_CHANGE_TIMER_2) {
114 KillTimer(hwnd, wParam);
115 SetWindowsDeviceChanged();
116 return true;
117 }
118 break;
119 }
120
121#ifdef SDL_JOYSTICK_RAWINPUT
122 return CallWindowProc(RAWINPUT_WindowProc, hwnd, msg, wParam, lParam);
123#else
124 return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
125#endif
126}
127
128static void SDL_CleanupDeviceNotification(SDL_DeviceNotificationData *data)
129{
130#ifdef SDL_JOYSTICK_RAWINPUT
131 RAWINPUT_UnregisterNotifications();
132#endif
133
134 if (data->hNotify) {
135 UnregisterDeviceNotification(data->hNotify);
136 }
137
138 if (data->messageWindow) {
139 DestroyWindow(data->messageWindow);
140 }
141
142 UnregisterClass(data->wincl.lpszClassName, data->wincl.hInstance);
143
144 if (data->coinitialized == S_OK) {
145 WIN_CoUninitialize();
146 }
147}
148
149static bool SDL_CreateDeviceNotification(SDL_DeviceNotificationData *data)
150{
151 DEV_BROADCAST_DEVICEINTERFACE dbh;
152
153 SDL_zerop(data);
154
155 data->coinitialized = WIN_CoInitialize();
156
157 data->wincl.hInstance = GetModuleHandle(NULL);
158 data->wincl.lpszClassName = TEXT("Message");
159 data->wincl.lpfnWndProc = SDL_PrivateJoystickDetectProc; // This function is called by windows
160 data->wincl.cbSize = sizeof(WNDCLASSEX);
161
162 if (!RegisterClassEx(&data->wincl)) {
163 WIN_SetError("Failed to create register class for joystick autodetect");
164 SDL_CleanupDeviceNotification(data);
165 return false;
166 }
167
168 data->messageWindow = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
169 if (!data->messageWindow) {
170 WIN_SetError("Failed to create message window for joystick autodetect");
171 SDL_CleanupDeviceNotification(data);
172 return false;
173 }
174
175 SDL_zero(dbh);
176 dbh.dbcc_size = sizeof(dbh);
177 dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
178 dbh.dbcc_classguid = GUID_DEVINTERFACE_HID;
179
180 data->hNotify = RegisterDeviceNotification(data->messageWindow, &dbh, DEVICE_NOTIFY_WINDOW_HANDLE);
181 if (!data->hNotify) {
182 WIN_SetError("Failed to create notify device for joystick autodetect");
183 SDL_CleanupDeviceNotification(data);
184 return false;
185 }
186
187#ifdef SDL_JOYSTICK_RAWINPUT
188 RAWINPUT_RegisterNotifications(data->messageWindow);
189#endif
190 return true;
191}
192
193static bool SDL_WaitForDeviceNotification(SDL_DeviceNotificationData *data, SDL_Mutex *mutex)
194{
195 MSG msg;
196 int lastret = 1;
197
198 if (!data->messageWindow) {
199 return false; // device notifications require a window
200 }
201
202 SDL_UnlockMutex(mutex);
203 while (lastret > 0 && !WindowsDeviceChanged()) {
204 lastret = GetMessage(&msg, NULL, 0, 0); // WM_QUIT causes return value of 0
205 if (lastret > 0) {
206 TranslateMessage(&msg);
207 DispatchMessage(&msg);
208 }
209 }
210 SDL_LockMutex(mutex);
211 return (lastret != -1);
212}
213
214#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
215
216#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
217static SDL_DeviceNotificationData s_notification_data;
218#endif
219
220// Function/thread to scan the system for joysticks.
221static int SDLCALL SDL_JoystickThread(void *_data)
222{
223#ifdef SDL_JOYSTICK_XINPUT
224 bool bOpenedXInputDevices[XUSER_MAX_COUNT];
225 SDL_zeroa(bOpenedXInputDevices);
226#endif
227
228#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
229 if (!SDL_CreateDeviceNotification(&s_notification_data)) {
230 return 0;
231 }
232#endif
233
234 SDL_LockMutex(s_mutexJoyStickEnum);
235 while (s_bJoystickThreadQuit == false) {
236#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
237 if (SDL_WaitForDeviceNotification(&s_notification_data, s_mutexJoyStickEnum) == false) {
238#else
239 {
240#endif
241#ifdef SDL_JOYSTICK_XINPUT
242 // WM_DEVICECHANGE not working, poll for new XINPUT controllers
243 SDL_WaitConditionTimeout(s_condJoystickThread, s_mutexJoyStickEnum, 1000);
244 if (SDL_XINPUT_Enabled()) {
245 // scan for any change in XInput devices
246 Uint8 userId;
247 for (userId = 0; userId < XUSER_MAX_COUNT; userId++) {
248 XINPUT_CAPABILITIES capabilities;
249 const DWORD result = XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities);
250 const bool available = (result == ERROR_SUCCESS);
251 if (bOpenedXInputDevices[userId] != available) {
252 SetWindowsDeviceChanged();
253 bOpenedXInputDevices[userId] = available;
254 }
255 }
256 }
257#else
258 // WM_DEVICECHANGE not working, no XINPUT, no point in keeping thread alive
259 break;
260#endif // SDL_JOYSTICK_XINPUT
261 }
262 }
263
264 SDL_UnlockMutex(s_mutexJoyStickEnum);
265
266#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
267 SDL_CleanupDeviceNotification(&s_notification_data);
268#endif
269
270 return 1;
271}
272
273// spin up the thread to detect hotplug of devices
274static bool SDL_StartJoystickThread(void)
275{
276 s_mutexJoyStickEnum = SDL_CreateMutex();
277 if (!s_mutexJoyStickEnum) {
278 return false;
279 }
280
281 s_condJoystickThread = SDL_CreateCondition();
282 if (!s_condJoystickThread) {
283 return false;
284 }
285
286 s_bJoystickThreadQuit = false;
287 s_joystickThread = SDL_CreateThread(SDL_JoystickThread, "SDL_joystick", NULL);
288 if (!s_joystickThread) {
289 return false;
290 }
291 return true;
292}
293
294static void SDL_StopJoystickThread(void)
295{
296 if (!s_joystickThread) {
297 return;
298 }
299
300 SDL_LockMutex(s_mutexJoyStickEnum);
301 s_bJoystickThreadQuit = true;
302 SDL_BroadcastCondition(s_condJoystickThread); // signal the joystick thread to quit
303 SDL_UnlockMutex(s_mutexJoyStickEnum);
304 PostThreadMessage((DWORD)SDL_GetThreadID(s_joystickThread), WM_QUIT, 0, 0);
305
306 // Unlock joysticks while the joystick thread finishes processing messages
307 SDL_AssertJoysticksLocked();
308 SDL_UnlockJoysticks();
309 SDL_WaitThread(s_joystickThread, NULL); // wait for it to bugger off
310 SDL_LockJoysticks();
311
312 SDL_DestroyCondition(s_condJoystickThread);
313 s_condJoystickThread = NULL;
314
315 SDL_DestroyMutex(s_mutexJoyStickEnum);
316 s_mutexJoyStickEnum = NULL;
317
318 s_joystickThread = NULL;
319}
320
321void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device)
322{
323 device->send_add_event = true;
324 device->nInstanceID = SDL_GetNextObjectID();
325 device->pNext = SYS_Joystick;
326 SYS_Joystick = device;
327}
328
329void WINDOWS_JoystickDetect(void);
330void WINDOWS_JoystickQuit(void);
331
332static bool WINDOWS_JoystickInit(void)
333{
334 if (!SDL_XINPUT_JoystickInit()) {
335 WINDOWS_JoystickQuit();
336 return false;
337 }
338
339 if (!SDL_DINPUT_JoystickInit()) {
340 WINDOWS_JoystickQuit();
341 return false;
342 }
343
344 WIN_InitDeviceNotification();
345
346#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
347 s_bJoystickThread = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_THREAD, true);
348 if (s_bJoystickThread) {
349 if (!SDL_StartJoystickThread()) {
350 return false;
351 }
352 } else {
353 if (!SDL_CreateDeviceNotification(&s_notification_data)) {
354 return false;
355 }
356 }
357#endif
358
359#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
360 // On Xbox, force create the joystick thread for device detection (since other methods don't work
361 s_bJoystickThread = true;
362 if (!SDL_StartJoystickThread()) {
363 return false;
364 }
365#endif
366
367 SetWindowsDeviceChanged(); // force a scan of the system for joysticks this first time
368
369 WINDOWS_JoystickDetect();
370
371 return true;
372}
373
374// return the number of joysticks that are connected right now
375static int WINDOWS_JoystickGetCount(void)
376{
377 int nJoysticks = 0;
378 JoyStick_DeviceData *device = SYS_Joystick;
379 while (device) {
380 nJoysticks++;
381 device = device->pNext;
382 }
383
384 return nJoysticks;
385}
386
387// detect any new joysticks being inserted into the system
388void WINDOWS_JoystickDetect(void)
389{
390 JoyStick_DeviceData *pCurList = NULL;
391
392 // only enum the devices if the joystick thread told us something changed
393 if (!WindowsDeviceChanged()) {
394 return; // thread hasn't signaled, nothing to do right now.
395 }
396
397 if (s_mutexJoyStickEnum) {
398 SDL_LockMutex(s_mutexJoyStickEnum);
399 }
400
401 s_lastDeviceChange = WIN_GetLastDeviceNotification();
402
403 pCurList = SYS_Joystick;
404 SYS_Joystick = NULL;
405
406 // Look for DirectInput joysticks, wheels, head trackers, gamepads, etc..
407 SDL_DINPUT_JoystickDetect(&pCurList);
408
409 // Look for XInput devices. Do this last, so they're first in the final list.
410 SDL_XINPUT_JoystickDetect(&pCurList);
411
412 if (s_mutexJoyStickEnum) {
413 SDL_UnlockMutex(s_mutexJoyStickEnum);
414 }
415
416 while (pCurList) {
417 JoyStick_DeviceData *pListNext = NULL;
418
419 if (!pCurList->bXInputDevice) {
420#ifdef SDL_HAPTIC_DINPUT
421 SDL_DINPUT_HapticMaybeRemoveDevice(&pCurList->dxdevice);
422#endif
423 }
424
425 SDL_PrivateJoystickRemoved(pCurList->nInstanceID);
426
427 pListNext = pCurList->pNext;
428 SDL_free(pCurList->joystickname);
429 SDL_free(pCurList);
430 pCurList = pListNext;
431 }
432
433 for (pCurList = SYS_Joystick; pCurList; pCurList = pCurList->pNext) {
434 if (pCurList->send_add_event) {
435 if (!pCurList->bXInputDevice) {
436#ifdef SDL_HAPTIC_DINPUT
437 SDL_DINPUT_HapticMaybeAddDevice(&pCurList->dxdevice);
438#endif
439 }
440
441 SDL_PrivateJoystickAdded(pCurList->nInstanceID);
442
443 pCurList->send_add_event = false;
444 }
445 }
446}
447
448static bool WINDOWS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
449{
450 if (SDL_DINPUT_JoystickPresent(vendor_id, product_id, version)) {
451 return true;
452 }
453 if (SDL_XINPUT_JoystickPresent(vendor_id, product_id, version)) {
454 return true;
455 }
456 return false;
457}
458
459static const char *WINDOWS_JoystickGetDeviceName(int device_index)
460{
461 JoyStick_DeviceData *device = SYS_Joystick;
462 int index;
463
464 for (index = device_index; index > 0; index--) {
465 device = device->pNext;
466 }
467
468 return device->joystickname;
469}
470
471static const char *WINDOWS_JoystickGetDevicePath(int device_index)
472{
473 JoyStick_DeviceData *device = SYS_Joystick;
474 int index;
475
476 for (index = device_index; index > 0; index--) {
477 device = device->pNext;
478 }
479
480 return device->path;
481}
482
483static int WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
484{
485 JoyStick_DeviceData *device = SYS_Joystick;
486 int index;
487
488 for (index = device_index; index > 0; index--) {
489 device = device->pNext;
490 }
491
492 if (device->bXInputDevice) {
493 // The slot for XInput devices can change as controllers are seated
494 return SDL_XINPUT_GetSteamVirtualGamepadSlot(device->XInputUserId);
495 } else {
496 return device->steam_virtual_gamepad_slot;
497 }
498}
499
500static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index)
501{
502 JoyStick_DeviceData *device = SYS_Joystick;
503 int index;
504
505 for (index = device_index; index > 0; index--) {
506 device = device->pNext;
507 }
508
509 return device->bXInputDevice ? (int)device->XInputUserId : -1;
510}
511
512static void WINDOWS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
513{
514}
515
516// return the stable device guid for this device index
517static SDL_GUID WINDOWS_JoystickGetDeviceGUID(int device_index)
518{
519 JoyStick_DeviceData *device = SYS_Joystick;
520 int index;
521
522 for (index = device_index; index > 0; index--) {
523 device = device->pNext;
524 }
525
526 return device->guid;
527}
528
529// Function to perform the mapping between current device instance and this joysticks instance id
530static SDL_JoystickID WINDOWS_JoystickGetDeviceInstanceID(int device_index)
531{
532 JoyStick_DeviceData *device = SYS_Joystick;
533 int index;
534
535 for (index = device_index; index > 0; index--) {
536 device = device->pNext;
537 }
538
539 return device->nInstanceID;
540}
541
542/* Function to open a joystick for use.
543 The joystick to open is specified by the device index.
544 This should fill the nbuttons and naxes fields of the joystick structure.
545 It returns 0, or -1 if there is an error.
546 */
547static bool WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index)
548{
549 JoyStick_DeviceData *device = SYS_Joystick;
550 int index;
551
552 for (index = device_index; index > 0; index--) {
553 device = device->pNext;
554 }
555
556 // allocate memory for system specific hardware data
557 joystick->hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(struct joystick_hwdata));
558 if (!joystick->hwdata) {
559 return false;
560 }
561 joystick->hwdata->guid = device->guid;
562
563 if (device->bXInputDevice) {
564 return SDL_XINPUT_JoystickOpen(joystick, device);
565 } else {
566 return SDL_DINPUT_JoystickOpen(joystick, device);
567 }
568}
569
570static bool WINDOWS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
571{
572 if (joystick->hwdata->bXInputDevice) {
573 return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
574 } else {
575 return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
576 }
577}
578
579static bool WINDOWS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
580{
581 return SDL_Unsupported();
582}
583
584static bool WINDOWS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
585{
586 return SDL_Unsupported();
587}
588
589static bool WINDOWS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
590{
591 return SDL_Unsupported();
592}
593
594static bool WINDOWS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
595{
596 return SDL_Unsupported();
597}
598
599static void WINDOWS_JoystickUpdate(SDL_Joystick *joystick)
600{
601 if (!joystick->hwdata) {
602 return;
603 }
604
605 if (joystick->hwdata->bXInputDevice) {
606 SDL_XINPUT_JoystickUpdate(joystick);
607 } else {
608 SDL_DINPUT_JoystickUpdate(joystick);
609 }
610}
611
612// Function to close a joystick after use
613static void WINDOWS_JoystickClose(SDL_Joystick *joystick)
614{
615 if (joystick->hwdata->bXInputDevice) {
616 SDL_XINPUT_JoystickClose(joystick);
617 } else {
618 SDL_DINPUT_JoystickClose(joystick);
619 }
620
621 SDL_free(joystick->hwdata);
622}
623
624// Function to perform any system-specific joystick related cleanup
625void WINDOWS_JoystickQuit(void)
626{
627 JoyStick_DeviceData *device = SYS_Joystick;
628
629 while (device) {
630 JoyStick_DeviceData *device_next = device->pNext;
631 SDL_free(device->joystickname);
632 SDL_free(device);
633 device = device_next;
634 }
635 SYS_Joystick = NULL;
636
637#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
638 if (s_bJoystickThread) {
639 SDL_StopJoystickThread();
640 } else {
641 SDL_CleanupDeviceNotification(&s_notification_data);
642 }
643#endif
644
645#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
646 if (s_bJoystickThread) {
647 SDL_StopJoystickThread();
648 }
649#endif
650
651 SDL_DINPUT_JoystickQuit();
652 SDL_XINPUT_JoystickQuit();
653
654 WIN_QuitDeviceNotification();
655}
656
657static bool WINDOWS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
658{
659 return false;
660}
661
662SDL_JoystickDriver SDL_WINDOWS_JoystickDriver = {
663 WINDOWS_JoystickInit,
664 WINDOWS_JoystickGetCount,
665 WINDOWS_JoystickDetect,
666 WINDOWS_JoystickIsDevicePresent,
667 WINDOWS_JoystickGetDeviceName,
668 WINDOWS_JoystickGetDevicePath,
669 WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot,
670 WINDOWS_JoystickGetDevicePlayerIndex,
671 WINDOWS_JoystickSetDevicePlayerIndex,
672 WINDOWS_JoystickGetDeviceGUID,
673 WINDOWS_JoystickGetDeviceInstanceID,
674 WINDOWS_JoystickOpen,
675 WINDOWS_JoystickRumble,
676 WINDOWS_JoystickRumbleTriggers,
677 WINDOWS_JoystickSetLED,
678 WINDOWS_JoystickSendEffect,
679 WINDOWS_JoystickSetSensorsEnabled,
680 WINDOWS_JoystickUpdate,
681 WINDOWS_JoystickClose,
682 WINDOWS_JoystickQuit,
683 WINDOWS_JoystickGetGamepadMapping
684};
685
686#else
687
688#ifdef SDL_JOYSTICK_RAWINPUT
689// The RAWINPUT driver needs the device notification setup above
690#error SDL_JOYSTICK_RAWINPUT requires SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT
691#endif
692
693#endif // SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h
new file mode 100644
index 0000000..16b9184
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_windowsjoystick_c.h
@@ -0,0 +1,103 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_sysjoystick.h"
24#include "../../core/windows/SDL_windows.h"
25#include "../../core/windows/SDL_directx.h"
26
27#define MAX_INPUTS 256 // each joystick can have up to 256 inputs
28
29// Set up for C function definitions, even when using C++
30#ifdef __cplusplus
31extern "C" {
32#endif
33
34typedef struct JoyStick_DeviceData
35{
36 SDL_GUID guid;
37 char *joystickname;
38 Uint8 send_add_event;
39 SDL_JoystickID nInstanceID;
40 bool bXInputDevice;
41 BYTE SubType;
42 Uint8 XInputUserId;
43 DIDEVICEINSTANCE dxdevice;
44 char path[MAX_PATH];
45 int steam_virtual_gamepad_slot;
46 struct JoyStick_DeviceData *pNext;
47} JoyStick_DeviceData;
48
49extern JoyStick_DeviceData *SYS_Joystick; // array to hold joystick ID values
50
51typedef enum Type
52{
53 BUTTON,
54 AXIS,
55 HAT
56} Type;
57
58typedef struct input_t
59{
60 // DirectInput offset for this input type:
61 DWORD ofs;
62
63 // Button, axis or hat:
64 Type type;
65
66 // SDL input offset:
67 Uint8 num;
68} input_t;
69
70// The private structure used to keep track of a joystick
71struct joystick_hwdata
72{
73 SDL_GUID guid;
74
75#ifdef SDL_JOYSTICK_DINPUT
76 LPDIRECTINPUTDEVICE8 InputDevice;
77 DIDEVCAPS Capabilities;
78 bool buffered;
79 bool first_update;
80 input_t Inputs[MAX_INPUTS];
81 int NumInputs;
82 int NumSliders;
83 bool ff_initialized;
84 DIEFFECT *ffeffect;
85 LPDIRECTINPUTEFFECT ffeffect_ref;
86#endif
87
88 bool bXInputDevice; // true if this device supports using the xinput API rather than DirectInput
89 bool bXInputHaptic; // Supports force feedback via XInput.
90 Uint8 userid; // XInput userid index for this joystick
91 DWORD dwPacketNumber;
92};
93
94#ifdef SDL_JOYSTICK_DINPUT
95extern const DIDATAFORMAT SDL_c_dfDIJoystick2;
96#endif
97
98extern void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device);
99
100// Ends C function definitions when using C++
101#ifdef __cplusplus
102}
103#endif
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c
new file mode 100644
index 0000000..9f6ce10
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick.c
@@ -0,0 +1,473 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_sysjoystick.h"
24
25#ifdef SDL_JOYSTICK_XINPUT
26
27#include "SDL_windowsjoystick_c.h"
28#include "SDL_xinputjoystick_c.h"
29#include "SDL_rawinputjoystick_c.h"
30#include "../hidapi/SDL_hidapijoystick_c.h"
31
32// Set up for C function definitions, even when using C++
33#ifdef __cplusplus
34extern "C" {
35#endif
36
37/*
38 * Internal stuff.
39 */
40static bool s_bXInputEnabled = false;
41
42bool SDL_XINPUT_Enabled(void)
43{
44 return s_bXInputEnabled;
45}
46
47bool SDL_XINPUT_JoystickInit(void)
48{
49 bool enabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, true);
50
51 if (enabled && !WIN_LoadXInputDLL()) {
52 enabled = false; // oh well.
53 }
54 s_bXInputEnabled = enabled;
55
56 return true;
57}
58
59static const char *GetXInputName(const Uint8 userid, BYTE SubType)
60{
61 static char name[32];
62
63 switch (SubType) {
64 case XINPUT_DEVSUBTYPE_GAMEPAD:
65 (void)SDL_snprintf(name, sizeof(name), "XInput Controller #%d", 1 + userid);
66 break;
67 case XINPUT_DEVSUBTYPE_WHEEL:
68 (void)SDL_snprintf(name, sizeof(name), "XInput Wheel #%d", 1 + userid);
69 break;
70 case XINPUT_DEVSUBTYPE_ARCADE_STICK:
71 (void)SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%d", 1 + userid);
72 break;
73 case XINPUT_DEVSUBTYPE_FLIGHT_STICK:
74 (void)SDL_snprintf(name, sizeof(name), "XInput FlightStick #%d", 1 + userid);
75 break;
76 case XINPUT_DEVSUBTYPE_DANCE_PAD:
77 (void)SDL_snprintf(name, sizeof(name), "XInput DancePad #%d", 1 + userid);
78 break;
79 case XINPUT_DEVSUBTYPE_GUITAR:
80 case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE:
81 case XINPUT_DEVSUBTYPE_GUITAR_BASS:
82 (void)SDL_snprintf(name, sizeof(name), "XInput Guitar #%d", 1 + userid);
83 break;
84 case XINPUT_DEVSUBTYPE_DRUM_KIT:
85 (void)SDL_snprintf(name, sizeof(name), "XInput DrumKit #%d", 1 + userid);
86 break;
87 case XINPUT_DEVSUBTYPE_ARCADE_PAD:
88 (void)SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%d", 1 + userid);
89 break;
90 default:
91 (void)SDL_snprintf(name, sizeof(name), "XInput Device #%d", 1 + userid);
92 break;
93 }
94 return name;
95}
96
97static bool GetXInputDeviceInfo(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion)
98{
99 SDL_XINPUT_CAPABILITIES_EX capabilities;
100
101 if (!XINPUTGETCAPABILITIESEX || XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) != ERROR_SUCCESS) {
102 // Use a generic VID/PID representing an XInput controller
103 if (pVID) {
104 *pVID = USB_VENDOR_MICROSOFT;
105 }
106 if (pPID) {
107 *pPID = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
108 }
109 return false;
110 }
111
112 // Fixup for Wireless Xbox 360 Controller
113 if (capabilities.ProductId == 0 && capabilities.Capabilities.Flags & XINPUT_CAPS_WIRELESS) {
114 capabilities.VendorId = USB_VENDOR_MICROSOFT;
115 capabilities.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
116 }
117
118 if (pVID) {
119 *pVID = capabilities.VendorId;
120 }
121 if (pPID) {
122 *pPID = capabilities.ProductId;
123 }
124 if (pVersion) {
125 *pVersion = capabilities.ProductVersion;
126 }
127 return true;
128}
129
130int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid)
131{
132 SDL_XINPUT_CAPABILITIES_EX capabilities;
133
134 if (XINPUTGETCAPABILITIESEX &&
135 XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) == ERROR_SUCCESS &&
136 capabilities.VendorId == USB_VENDOR_VALVE &&
137 capabilities.ProductId == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
138 return (int)capabilities.unk2;
139 }
140 return -1;
141}
142
143static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext)
144{
145 const char *name = NULL;
146 Uint16 vendor = 0;
147 Uint16 product = 0;
148 Uint16 version = 0;
149 JoyStick_DeviceData *pPrevJoystick = NULL;
150 JoyStick_DeviceData *pNewJoystick = *pContext;
151
152#ifdef SDL_JOYSTICK_RAWINPUT
153 if (RAWINPUT_IsEnabled()) {
154 // The raw input driver handles more than 4 controllers, so prefer that when available
155 /* We do this check here rather than at the top of SDL_XINPUT_JoystickDetect() because
156 we need to check XInput state before RAWINPUT gets a hold of the device, otherwise
157 when a controller is connected via the wireless adapter, it will shut down at the
158 first subsequent XInput call. This seems like a driver stack bug?
159
160 Reference: https://github.com/libsdl-org/SDL/issues/3468
161 */
162 return;
163 }
164#endif
165
166 if (SubType == XINPUT_DEVSUBTYPE_UNKNOWN) {
167 return;
168 }
169
170 while (pNewJoystick) {
171 if (pNewJoystick->bXInputDevice && (pNewJoystick->XInputUserId == userid) && (pNewJoystick->SubType == SubType)) {
172 // if we are replacing the front of the list then update it
173 if (pNewJoystick == *pContext) {
174 *pContext = pNewJoystick->pNext;
175 } else if (pPrevJoystick) {
176 pPrevJoystick->pNext = pNewJoystick->pNext;
177 }
178
179 pNewJoystick->pNext = SYS_Joystick;
180 SYS_Joystick = pNewJoystick;
181 return; // already in the list.
182 }
183
184 pPrevJoystick = pNewJoystick;
185 pNewJoystick = pNewJoystick->pNext;
186 }
187
188 name = GetXInputName(userid, SubType);
189 GetXInputDeviceInfo(userid, &vendor, &product, &version);
190 if (SDL_ShouldIgnoreJoystick(vendor, product, version, name) ||
191 SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, name)) {
192 return;
193 }
194
195 pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData));
196 if (!pNewJoystick) {
197 return; // better luck next time?
198 }
199
200 pNewJoystick->bXInputDevice = true;
201 pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, NULL, name);
202 if (!pNewJoystick->joystickname) {
203 SDL_free(pNewJoystick);
204 return; // better luck next time?
205 }
206 (void)SDL_snprintf(pNewJoystick->path, sizeof(pNewJoystick->path), "XInput#%u", userid);
207 pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, NULL, name, 'x', SubType);
208 pNewJoystick->SubType = SubType;
209 pNewJoystick->XInputUserId = userid;
210
211 WINDOWS_AddJoystickDevice(pNewJoystick);
212}
213
214void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
215{
216 int iuserid;
217
218 if (!s_bXInputEnabled) {
219 return;
220 }
221
222 // iterate in reverse, so these are in the final list in ascending numeric order.
223 for (iuserid = XUSER_MAX_COUNT - 1; iuserid >= 0; iuserid--) {
224 const Uint8 userid = (Uint8)iuserid;
225 XINPUT_CAPABILITIES capabilities;
226 if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) {
227 AddXInputDevice(userid, capabilities.SubType, pContext);
228 }
229 }
230}
231
232bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)
233{
234 int iuserid;
235
236 if (!s_bXInputEnabled) {
237 return false;
238 }
239
240 // iterate in reverse, so these are in the final list in ascending numeric order.
241 for (iuserid = 0; iuserid < XUSER_MAX_COUNT; ++iuserid) {
242 const Uint8 userid = (Uint8)iuserid;
243 Uint16 slot_vendor;
244 Uint16 slot_product;
245 Uint16 slot_version;
246 if (GetXInputDeviceInfo(userid, &slot_vendor, &slot_product, &slot_version)) {
247 if (vendor == slot_vendor && product == slot_product && version == slot_version) {
248 return true;
249 }
250 }
251 }
252 return false;
253}
254
255bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
256{
257 const Uint8 userId = joystickdevice->XInputUserId;
258 XINPUT_CAPABILITIES capabilities;
259 XINPUT_VIBRATION state;
260
261 SDL_assert(s_bXInputEnabled);
262 SDL_assert(XINPUTGETCAPABILITIES);
263 SDL_assert(XINPUTSETSTATE);
264 SDL_assert(userId < XUSER_MAX_COUNT);
265
266 joystick->hwdata->bXInputDevice = true;
267
268 if (XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities) != ERROR_SUCCESS) {
269 SDL_free(joystick->hwdata);
270 joystick->hwdata = NULL;
271 return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?");
272 }
273 SDL_zero(state);
274 joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS);
275 joystick->hwdata->userid = userId;
276
277 // The XInput API has a hard coded button/axis mapping, so we just match it
278 joystick->naxes = 6;
279 joystick->nbuttons = 11;
280 joystick->nhats = 1;
281
282 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
283
284 return true;
285}
286
287static void UpdateXInputJoystickBatteryInformation(SDL_Joystick *joystick, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)
288{
289 SDL_PowerState state;
290 int percent;
291 switch (pBatteryInformation->BatteryType) {
292 case BATTERY_TYPE_WIRED:
293 state = SDL_POWERSTATE_CHARGING;
294 break;
295 case BATTERY_TYPE_UNKNOWN:
296 case BATTERY_TYPE_DISCONNECTED:
297 state = SDL_POWERSTATE_UNKNOWN;
298 break;
299 default:
300 state = SDL_POWERSTATE_ON_BATTERY;
301 break;
302 }
303 switch (pBatteryInformation->BatteryLevel) {
304 case BATTERY_LEVEL_EMPTY:
305 percent = 10;
306 break;
307 case BATTERY_LEVEL_LOW:
308 percent = 40;
309 break;
310 case BATTERY_LEVEL_MEDIUM:
311 percent = 70;
312 break;
313 default:
314 case BATTERY_LEVEL_FULL:
315 percent = 100;
316 break;
317 }
318 SDL_SendJoystickPowerInfo(joystick, state, percent);
319}
320
321static void UpdateXInputJoystickState(SDL_Joystick *joystick, XINPUT_STATE *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)
322{
323 static WORD s_XInputButtons[] = {
324 XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y,
325 XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START,
326 XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB,
327 XINPUT_GAMEPAD_GUIDE
328 };
329 WORD wButtons = pXInputState->Gamepad.wButtons;
330 Uint8 button;
331 Uint8 hat = SDL_HAT_CENTERED;
332 Uint64 timestamp = SDL_GetTicksNS();
333
334 SDL_SendJoystickAxis(timestamp, joystick, 0, pXInputState->Gamepad.sThumbLX);
335 SDL_SendJoystickAxis(timestamp, joystick, 1, ~pXInputState->Gamepad.sThumbLY);
336 SDL_SendJoystickAxis(timestamp, joystick, 2, ((int)pXInputState->Gamepad.bLeftTrigger * 257) - 32768);
337 SDL_SendJoystickAxis(timestamp, joystick, 3, pXInputState->Gamepad.sThumbRX);
338 SDL_SendJoystickAxis(timestamp, joystick, 4, ~pXInputState->Gamepad.sThumbRY);
339 SDL_SendJoystickAxis(timestamp, joystick, 5, ((int)pXInputState->Gamepad.bRightTrigger * 257) - 32768);
340
341 for (button = 0; button < (Uint8)SDL_arraysize(s_XInputButtons); ++button) {
342 bool down = ((wButtons & s_XInputButtons[button]) != 0);
343 SDL_SendJoystickButton(timestamp, joystick, button, down);
344 }
345
346 if (wButtons & XINPUT_GAMEPAD_DPAD_UP) {
347 hat |= SDL_HAT_UP;
348 }
349 if (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) {
350 hat |= SDL_HAT_DOWN;
351 }
352 if (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) {
353 hat |= SDL_HAT_LEFT;
354 }
355 if (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) {
356 hat |= SDL_HAT_RIGHT;
357 }
358 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
359
360 UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation);
361}
362
363bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
364{
365 XINPUT_VIBRATION XVibration;
366
367 if (!XINPUTSETSTATE) {
368 return SDL_Unsupported();
369 }
370
371 XVibration.wLeftMotorSpeed = low_frequency_rumble;
372 XVibration.wRightMotorSpeed = high_frequency_rumble;
373 if (XINPUTSETSTATE(joystick->hwdata->userid, &XVibration) != ERROR_SUCCESS) {
374 return SDL_SetError("XInputSetState() failed");
375 }
376 return true;
377}
378
379void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick)
380{
381 DWORD result;
382 XINPUT_STATE XInputState;
383 XINPUT_BATTERY_INFORMATION_EX XBatteryInformation;
384
385 if (!XINPUTGETSTATE) {
386 return;
387 }
388
389 result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState);
390 if (result == ERROR_DEVICE_NOT_CONNECTED) {
391 return;
392 }
393
394 SDL_zero(XBatteryInformation);
395 if (XINPUTGETBATTERYINFORMATION) {
396 result = XINPUTGETBATTERYINFORMATION(joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation);
397 }
398
399#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
400 // XInputOnGameInput doesn't ever change dwPacketNumber, so have to just update every frame
401 UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation);
402#else
403 // only fire events if the data changed from last time
404 if (XInputState.dwPacketNumber && XInputState.dwPacketNumber != joystick->hwdata->dwPacketNumber) {
405 UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation);
406 joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber;
407 }
408#endif
409}
410
411void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick)
412{
413}
414
415void SDL_XINPUT_JoystickQuit(void)
416{
417 if (s_bXInputEnabled) {
418 s_bXInputEnabled = false;
419 WIN_UnloadXInputDLL();
420 }
421}
422
423// Ends C function definitions when using C++
424#ifdef __cplusplus
425}
426#endif
427
428#else // !SDL_JOYSTICK_XINPUT
429
430typedef struct JoyStick_DeviceData JoyStick_DeviceData;
431
432bool SDL_XINPUT_Enabled(void)
433{
434 return false;
435}
436
437bool SDL_XINPUT_JoystickInit(void)
438{
439 return true;
440}
441
442void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
443{
444}
445
446bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)
447{
448 return false;
449}
450
451bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)
452{
453 return SDL_Unsupported();
454}
455
456bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
457{
458 return SDL_Unsupported();
459}
460
461void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick)
462{
463}
464
465void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick)
466{
467}
468
469void SDL_XINPUT_JoystickQuit(void)
470{
471}
472
473#endif // SDL_JOYSTICK_XINPUT
diff --git a/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h
new file mode 100644
index 0000000..305b090
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/windows/SDL_xinputjoystick_c.h
@@ -0,0 +1,44 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../../core/windows/SDL_xinput.h"
24
25// Set up for C function definitions, even when using C++
26#ifdef __cplusplus
27extern "C" {
28#endif
29
30extern bool SDL_XINPUT_Enabled(void);
31extern bool SDL_XINPUT_JoystickInit(void);
32extern void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext);
33extern bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version);
34extern bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice);
35extern bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble);
36extern void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick);
37extern void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick);
38extern void SDL_XINPUT_JoystickQuit(void);
39extern int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid);
40
41// Ends C function definitions when using C++
42#ifdef __cplusplus
43}
44#endif