1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 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 SDL_JOYSTICK_DINPUT || 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_error.h"
36 #include "SDL_assert.h"
37 #include "SDL_events.h"
38 #include "SDL_timer.h"
39 #include "SDL_mutex.h"
40 #include "SDL_joystick.h"
41 #include "../SDL_sysjoystick.h"
42 #include "../../thread/SDL_systhread.h"
43 #include "../../core/windows/SDL_windows.h"
44 #if !defined(__WINRT__)
45 #include <dbt.h>
46 #endif
47
48 #define INITGUID /* Only set here, if set twice will cause mingw32 to break. */
49 #include "SDL_windowsjoystick_c.h"
50 #include "SDL_dinputjoystick_c.h"
51 #include "SDL_xinputjoystick_c.h"
52
53 #include "../../haptic/windows/SDL_dinputhaptic_c.h" /* For haptic hot plugging */
54 #include "../../haptic/windows/SDL_xinputhaptic_c.h" /* For haptic hot plugging */
55
56
57 #ifndef DEVICE_NOTIFY_WINDOW_HANDLE
58 #define DEVICE_NOTIFY_WINDOW_HANDLE 0x00000000
59 #endif
60
61 /* local variables */
62 static SDL_bool s_bDeviceAdded = SDL_FALSE;
63 static SDL_bool s_bDeviceRemoved = SDL_FALSE;
64 static SDL_cond *s_condJoystickThread = NULL;
65 static SDL_mutex *s_mutexJoyStickEnum = NULL;
66 static SDL_Thread *s_threadJoystick = NULL;
67 static SDL_bool s_bJoystickThreadQuit = SDL_FALSE;
68
69 JoyStick_DeviceData *SYS_Joystick; /* array to hold joystick ID values */
70
71 static SDL_bool s_bWindowsDeviceChanged = SDL_FALSE;
72
73 #ifdef __WINRT__
74
75 typedef struct
76 {
77 int unused;
78 } SDL_DeviceNotificationData;
79
80 static void
SDL_CleanupDeviceNotification(SDL_DeviceNotificationData * data)81 SDL_CleanupDeviceNotification(SDL_DeviceNotificationData *data)
82 {
83 }
84
85 static int
SDL_CreateDeviceNotification(SDL_DeviceNotificationData * data)86 SDL_CreateDeviceNotification(SDL_DeviceNotificationData *data)
87 {
88 return 0;
89 }
90
91 static SDL_bool
SDL_WaitForDeviceNotification(SDL_DeviceNotificationData * data,SDL_mutex * mutex)92 SDL_WaitForDeviceNotification(SDL_DeviceNotificationData *data, SDL_mutex *mutex)
93 {
94 return SDL_FALSE;
95 }
96
97 #else /* !__WINRT__ */
98
99 typedef struct
100 {
101 HRESULT coinitialized;
102 WNDCLASSEX wincl;
103 HWND messageWindow;
104 HDEVNOTIFY hNotify;
105 } SDL_DeviceNotificationData;
106
107 #define IDT_SDL_DEVICE_CHANGE_TIMER_1 1200
108 #define IDT_SDL_DEVICE_CHANGE_TIMER_2 1201
109
110 /* windowproc for our joystick detect thread message only window, to detect any USB device addition/removal */
111 static LRESULT CALLBACK
SDL_PrivateJoystickDetectProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)112 SDL_PrivateJoystickDetectProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
113 {
114 switch (message) {
115 case WM_DEVICECHANGE:
116 switch (wParam) {
117 case DBT_DEVICEARRIVAL:
118 case DBT_DEVICEREMOVECOMPLETE:
119 if (((DEV_BROADCAST_HDR*)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
120 /* notify 300ms and 2 seconds later to ensure all APIs have updated status */
121 SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_1, 300, NULL);
122 SetTimer(hwnd, IDT_SDL_DEVICE_CHANGE_TIMER_2, 2000, NULL);
123 }
124 break;
125 }
126 return 0;
127 case WM_TIMER:
128 KillTimer(hwnd, wParam);
129 s_bWindowsDeviceChanged = SDL_TRUE;
130 return 0;
131 }
132
133 return DefWindowProc (hwnd, message, wParam, lParam);
134 }
135
136 static void
SDL_CleanupDeviceNotification(SDL_DeviceNotificationData * data)137 SDL_CleanupDeviceNotification(SDL_DeviceNotificationData *data)
138 {
139 if (data->hNotify)
140 UnregisterDeviceNotification(data->hNotify);
141
142 if (data->messageWindow)
143 DestroyWindow(data->messageWindow);
144
145 UnregisterClass(data->wincl.lpszClassName, data->wincl.hInstance);
146
147 if (data->coinitialized == S_OK) {
148 WIN_CoUninitialize();
149 }
150 }
151
152 static int
SDL_CreateDeviceNotification(SDL_DeviceNotificationData * data)153 SDL_CreateDeviceNotification(SDL_DeviceNotificationData *data)
154 {
155 DEV_BROADCAST_DEVICEINTERFACE dbh;
156 GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
157
158 SDL_zerop(data);
159
160 data->coinitialized = WIN_CoInitialize();
161
162 data->wincl.hInstance = GetModuleHandle(NULL);
163 data->wincl.lpszClassName = L"Message";
164 data->wincl.lpfnWndProc = SDL_PrivateJoystickDetectProc; /* This function is called by windows */
165 data->wincl.cbSize = sizeof (WNDCLASSEX);
166
167 if (!RegisterClassEx(&data->wincl)) {
168 WIN_SetError("Failed to create register class for joystick autodetect");
169 SDL_CleanupDeviceNotification(data);
170 return -1;
171 }
172
173 data->messageWindow = (HWND)CreateWindowEx(0, L"Message", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
174 if (!data->messageWindow) {
175 WIN_SetError("Failed to create message window for joystick autodetect");
176 SDL_CleanupDeviceNotification(data);
177 return -1;
178 }
179
180 SDL_zero(dbh);
181 dbh.dbcc_size = sizeof(dbh);
182 dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
183 dbh.dbcc_classguid = GUID_DEVINTERFACE_HID;
184
185 data->hNotify = RegisterDeviceNotification(data->messageWindow, &dbh, DEVICE_NOTIFY_WINDOW_HANDLE);
186 if (!data->hNotify) {
187 WIN_SetError("Failed to create notify device for joystick autodetect");
188 SDL_CleanupDeviceNotification(data);
189 return -1;
190 }
191 return 0;
192 }
193
194 static SDL_bool
SDL_WaitForDeviceNotification(SDL_DeviceNotificationData * data,SDL_mutex * mutex)195 SDL_WaitForDeviceNotification(SDL_DeviceNotificationData *data, SDL_mutex *mutex)
196 {
197 MSG msg;
198 int lastret = 1;
199
200 if (!data->messageWindow) {
201 return SDL_FALSE; /* device notifications require a window */
202 }
203
204 SDL_UnlockMutex(mutex);
205 while (lastret > 0 && s_bWindowsDeviceChanged == SDL_FALSE) {
206 lastret = GetMessage(&msg, NULL, 0, 0); /* WM_QUIT causes return value of 0 */
207 if (lastret > 0) {
208 TranslateMessage(&msg);
209 DispatchMessage(&msg);
210 }
211 }
212 SDL_LockMutex(mutex);
213 return (lastret != -1) ? SDL_TRUE : SDL_FALSE;
214 }
215
216 #endif /* __WINRT__ */
217
218 /* Function/thread to scan the system for joysticks. */
219 static int
SDL_JoystickThread(void * _data)220 SDL_JoystickThread(void *_data)
221 {
222 SDL_DeviceNotificationData notification_data;
223
224 #if SDL_JOYSTICK_XINPUT
225 SDL_bool bOpenedXInputDevices[XUSER_MAX_COUNT];
226 SDL_zeroa(bOpenedXInputDevices);
227 #endif
228
229 if (SDL_CreateDeviceNotification(¬ification_data) < 0) {
230 return -1;
231 }
232
233 SDL_LockMutex(s_mutexJoyStickEnum);
234 while (s_bJoystickThreadQuit == SDL_FALSE) {
235 SDL_bool bXInputChanged = SDL_FALSE;
236
237 if (SDL_WaitForDeviceNotification(¬ification_data, s_mutexJoyStickEnum) == SDL_FALSE) {
238 #if SDL_JOYSTICK_XINPUT
239 /* WM_DEVICECHANGE not working, poll for new XINPUT controllers */
240 SDL_CondWaitTimeout(s_condJoystickThread, s_mutexJoyStickEnum, 1000);
241 if (SDL_XINPUT_Enabled() && XINPUTGETCAPABILITIES) {
242 /* scan for any change in XInput devices */
243 Uint8 userId;
244 for (userId = 0; userId < XUSER_MAX_COUNT; userId++) {
245 XINPUT_CAPABILITIES capabilities;
246 const DWORD result = XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities);
247 const SDL_bool available = (result == ERROR_SUCCESS);
248 if (bOpenedXInputDevices[userId] != available) {
249 bXInputChanged = SDL_TRUE;
250 bOpenedXInputDevices[userId] = available;
251 }
252 }
253 }
254 #else
255 /* WM_DEVICECHANGE not working, no XINPUT, no point in keeping thread alive */
256 break;
257 #endif /* SDL_JOYSTICK_XINPUT */
258 }
259
260 if (s_bWindowsDeviceChanged || bXInputChanged) {
261 s_bDeviceRemoved = SDL_TRUE;
262 s_bDeviceAdded = SDL_TRUE;
263 s_bWindowsDeviceChanged = SDL_FALSE;
264 }
265 }
266 SDL_UnlockMutex(s_mutexJoyStickEnum);
267
268 SDL_CleanupDeviceNotification(¬ification_data);
269
270 return 1;
271 }
272
WINDOWS_AddJoystickDevice(JoyStick_DeviceData * device)273 void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device)
274 {
275 device->send_add_event = SDL_TRUE;
276 device->nInstanceID = SDL_GetNextJoystickInstanceID();
277 device->pNext = SYS_Joystick;
278 SYS_Joystick = device;
279
280 s_bDeviceAdded = SDL_TRUE;
281 }
282
283 static void WINDOWS_JoystickDetect(void);
284 static void WINDOWS_JoystickQuit(void);
285
286 /* Function to scan the system for joysticks.
287 * Joystick 0 should be the system default joystick.
288 * It should return 0, or -1 on an unrecoverable fatal error.
289 */
290 static int
WINDOWS_JoystickInit(void)291 WINDOWS_JoystickInit(void)
292 {
293 if (SDL_DINPUT_JoystickInit() < 0) {
294 WINDOWS_JoystickQuit();
295 return -1;
296 }
297
298 if (SDL_XINPUT_JoystickInit() < 0) {
299 WINDOWS_JoystickQuit();
300 return -1;
301 }
302
303 s_mutexJoyStickEnum = SDL_CreateMutex();
304 s_condJoystickThread = SDL_CreateCond();
305 s_bDeviceAdded = SDL_TRUE; /* force a scan of the system for joysticks this first time */
306
307 WINDOWS_JoystickDetect();
308
309 if (!s_threadJoystick) {
310 /* spin up the thread to detect hotplug of devices */
311 s_bJoystickThreadQuit = SDL_FALSE;
312 s_threadJoystick = SDL_CreateThreadInternal(SDL_JoystickThread, "SDL_joystick", 64 * 1024, NULL);
313 }
314 return 0;
315 }
316
317 /* return the number of joysticks that are connected right now */
318 static int
WINDOWS_JoystickGetCount(void)319 WINDOWS_JoystickGetCount(void)
320 {
321 int nJoysticks = 0;
322 JoyStick_DeviceData *device = SYS_Joystick;
323 while (device) {
324 nJoysticks++;
325 device = device->pNext;
326 }
327
328 return nJoysticks;
329 }
330
331 /* detect any new joysticks being inserted into the system */
332 static void
WINDOWS_JoystickDetect(void)333 WINDOWS_JoystickDetect(void)
334 {
335 JoyStick_DeviceData *pCurList = NULL;
336
337 /* only enum the devices if the joystick thread told us something changed */
338 if (!s_bDeviceAdded && !s_bDeviceRemoved) {
339 return; /* thread hasn't signaled, nothing to do right now. */
340 }
341
342 SDL_LockMutex(s_mutexJoyStickEnum);
343
344 s_bDeviceAdded = SDL_FALSE;
345 s_bDeviceRemoved = SDL_FALSE;
346
347 pCurList = SYS_Joystick;
348 SYS_Joystick = NULL;
349
350 /* Look for DirectInput joysticks, wheels, head trackers, gamepads, etc.. */
351 SDL_DINPUT_JoystickDetect(&pCurList);
352
353 /* Look for XInput devices. Do this last, so they're first in the final list. */
354 SDL_XINPUT_JoystickDetect(&pCurList);
355
356 SDL_UnlockMutex(s_mutexJoyStickEnum);
357
358 while (pCurList) {
359 JoyStick_DeviceData *pListNext = NULL;
360
361 if (pCurList->bXInputDevice) {
362 SDL_XINPUT_MaybeRemoveDevice(pCurList->XInputUserId);
363 } else {
364 SDL_DINPUT_MaybeRemoveDevice(&pCurList->dxdevice);
365 }
366
367 SDL_PrivateJoystickRemoved(pCurList->nInstanceID);
368
369 pListNext = pCurList->pNext;
370 SDL_free(pCurList->joystickname);
371 SDL_free(pCurList);
372 pCurList = pListNext;
373 }
374
375 if (s_bDeviceAdded) {
376 JoyStick_DeviceData *pNewJoystick;
377 int device_index = 0;
378 s_bDeviceAdded = SDL_FALSE;
379 pNewJoystick = SYS_Joystick;
380 while (pNewJoystick) {
381 if (pNewJoystick->send_add_event) {
382 if (pNewJoystick->bXInputDevice) {
383 SDL_XINPUT_MaybeAddDevice(pNewJoystick->XInputUserId);
384 } else {
385 SDL_DINPUT_MaybeAddDevice(&pNewJoystick->dxdevice);
386 }
387
388 SDL_PrivateJoystickAdded(pNewJoystick->nInstanceID);
389
390 pNewJoystick->send_add_event = SDL_FALSE;
391 }
392 device_index++;
393 pNewJoystick = pNewJoystick->pNext;
394 }
395 }
396 }
397
398 /* Function to get the device-dependent name of a joystick */
399 static const char *
WINDOWS_JoystickGetDeviceName(int device_index)400 WINDOWS_JoystickGetDeviceName(int device_index)
401 {
402 JoyStick_DeviceData *device = SYS_Joystick;
403 int index;
404
405 for (index = device_index; index > 0; index--)
406 device = device->pNext;
407
408 return device->joystickname;
409 }
410
411 static int
WINDOWS_JoystickGetDevicePlayerIndex(int device_index)412 WINDOWS_JoystickGetDevicePlayerIndex(int device_index)
413 {
414 JoyStick_DeviceData *device = SYS_Joystick;
415 int index;
416
417 for (index = device_index; index > 0; index--)
418 device = device->pNext;
419
420 return device->bXInputDevice ? (int)device->XInputUserId : -1;
421 }
422
423 static void
WINDOWS_JoystickSetDevicePlayerIndex(int device_index,int player_index)424 WINDOWS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
425 {
426 }
427
428 /* return the stable device guid for this device index */
429 static SDL_JoystickGUID
WINDOWS_JoystickGetDeviceGUID(int device_index)430 WINDOWS_JoystickGetDeviceGUID(int device_index)
431 {
432 JoyStick_DeviceData *device = SYS_Joystick;
433 int index;
434
435 for (index = device_index; index > 0; index--)
436 device = device->pNext;
437
438 return device->guid;
439 }
440
441 /* Function to perform the mapping between current device instance and this joysticks instance id */
442 static SDL_JoystickID
WINDOWS_JoystickGetDeviceInstanceID(int device_index)443 WINDOWS_JoystickGetDeviceInstanceID(int device_index)
444 {
445 JoyStick_DeviceData *device = SYS_Joystick;
446 int index;
447
448 for (index = device_index; index > 0; index--)
449 device = device->pNext;
450
451 return device->nInstanceID;
452 }
453
454 /* Function to open a joystick for use.
455 The joystick to open is specified by the device index.
456 This should fill the nbuttons and naxes fields of the joystick structure.
457 It returns 0, or -1 if there is an error.
458 */
459 static int
WINDOWS_JoystickOpen(SDL_Joystick * joystick,int device_index)460 WINDOWS_JoystickOpen(SDL_Joystick * joystick, int device_index)
461 {
462 JoyStick_DeviceData *device = SYS_Joystick;
463 int index;
464
465 for (index = device_index; index > 0; index--)
466 device = device->pNext;
467
468 /* allocate memory for system specific hardware data */
469 joystick->instance_id = device->nInstanceID;
470 joystick->hwdata =
471 (struct joystick_hwdata *) SDL_malloc(sizeof(struct joystick_hwdata));
472 if (joystick->hwdata == NULL) {
473 return SDL_OutOfMemory();
474 }
475 SDL_zerop(joystick->hwdata);
476 joystick->hwdata->guid = device->guid;
477
478 if (device->bXInputDevice) {
479 return SDL_XINPUT_JoystickOpen(joystick, device);
480 } else {
481 return SDL_DINPUT_JoystickOpen(joystick, device);
482 }
483 }
484
485 static int
WINDOWS_JoystickRumble(SDL_Joystick * joystick,Uint16 low_frequency_rumble,Uint16 high_frequency_rumble)486 WINDOWS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
487 {
488 if (joystick->hwdata->bXInputDevice) {
489 return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
490 } else {
491 return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
492 }
493 }
494
495 static void
WINDOWS_JoystickUpdate(SDL_Joystick * joystick)496 WINDOWS_JoystickUpdate(SDL_Joystick * joystick)
497 {
498 if (!joystick->hwdata) {
499 return;
500 }
501
502 if (joystick->hwdata->bXInputDevice) {
503 SDL_XINPUT_JoystickUpdate(joystick);
504 } else {
505 SDL_DINPUT_JoystickUpdate(joystick);
506 }
507 }
508
509 /* Function to close a joystick after use */
510 static void
WINDOWS_JoystickClose(SDL_Joystick * joystick)511 WINDOWS_JoystickClose(SDL_Joystick * joystick)
512 {
513 if (joystick->hwdata->bXInputDevice) {
514 SDL_XINPUT_JoystickClose(joystick);
515 } else {
516 SDL_DINPUT_JoystickClose(joystick);
517 }
518
519 SDL_free(joystick->hwdata);
520 }
521
522 /* Function to perform any system-specific joystick related cleanup */
523 static void
WINDOWS_JoystickQuit(void)524 WINDOWS_JoystickQuit(void)
525 {
526 JoyStick_DeviceData *device = SYS_Joystick;
527
528 while (device) {
529 JoyStick_DeviceData *device_next = device->pNext;
530 SDL_free(device->joystickname);
531 SDL_free(device);
532 device = device_next;
533 }
534 SYS_Joystick = NULL;
535
536 if (s_threadJoystick) {
537 SDL_LockMutex(s_mutexJoyStickEnum);
538 s_bJoystickThreadQuit = SDL_TRUE;
539 SDL_CondBroadcast(s_condJoystickThread); /* signal the joystick thread to quit */
540 SDL_UnlockMutex(s_mutexJoyStickEnum);
541 #ifndef __WINRT__
542 PostThreadMessage(SDL_GetThreadID(s_threadJoystick), WM_QUIT, 0, 0);
543 #endif
544 SDL_WaitThread(s_threadJoystick, NULL); /* wait for it to bugger off */
545
546 SDL_DestroyMutex(s_mutexJoyStickEnum);
547 SDL_DestroyCond(s_condJoystickThread);
548 s_condJoystickThread= NULL;
549 s_mutexJoyStickEnum = NULL;
550 s_threadJoystick = NULL;
551 }
552
553 SDL_DINPUT_JoystickQuit();
554 SDL_XINPUT_JoystickQuit();
555
556 s_bDeviceAdded = SDL_FALSE;
557 s_bDeviceRemoved = SDL_FALSE;
558 }
559
560 static SDL_bool
WINDOWS_JoystickGetGamepadMapping(int device_index,SDL_GamepadMapping * out)561 WINDOWS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
562 {
563 return SDL_FALSE;
564 }
565
566 SDL_JoystickDriver SDL_WINDOWS_JoystickDriver =
567 {
568 WINDOWS_JoystickInit,
569 WINDOWS_JoystickGetCount,
570 WINDOWS_JoystickDetect,
571 WINDOWS_JoystickGetDeviceName,
572 WINDOWS_JoystickGetDevicePlayerIndex,
573 WINDOWS_JoystickSetDevicePlayerIndex,
574 WINDOWS_JoystickGetDeviceGUID,
575 WINDOWS_JoystickGetDeviceInstanceID,
576 WINDOWS_JoystickOpen,
577 WINDOWS_JoystickRumble,
578 WINDOWS_JoystickUpdate,
579 WINDOWS_JoystickClose,
580 WINDOWS_JoystickQuit,
581 WINDOWS_JoystickGetGamepadMapping
582 };
583
584 #endif /* SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT */
585
586 /* vi: set ts=4 sw=4 expandtab: */
587