src/joystick/windows/SDL_windowsjoystick.c
author stfx
Wed, 08 Jul 2020 17:28:34 +0200
changeset 13944 b8c8ce11efc7
parent 13873 5bb6be4f2425
permissions -rw-r--r--
cmake: Fix building with -DSDL_HAPTIC=Off
     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
    81 SDL_CleanupDeviceNotification(SDL_DeviceNotificationData *data)
    82 {
    83 }
    84 
    85 static int
    86 SDL_CreateDeviceNotification(SDL_DeviceNotificationData *data)
    87 {
    88     return 0;
    89 }
    90 
    91 static SDL_bool
    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
   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
   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
   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
   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
   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(&notification_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(&notification_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(&notification_data);
   269 
   270     return 1;
   271 }
   272 
   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
   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
   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
   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 #if SDL_HAPTIC_XINPUT
   363             SDL_XINPUT_MaybeRemoveDevice(pCurList->XInputUserId);
   364 #endif
   365         } else {
   366 #if SDL_HAPTIC_DINPUT
   367             SDL_DINPUT_MaybeRemoveDevice(&pCurList->dxdevice);
   368 #endif
   369         }
   370 
   371         SDL_PrivateJoystickRemoved(pCurList->nInstanceID);
   372 
   373         pListNext = pCurList->pNext;
   374         SDL_free(pCurList->joystickname);
   375         SDL_free(pCurList);
   376         pCurList = pListNext;
   377     }
   378 
   379     if (s_bDeviceAdded) {
   380         JoyStick_DeviceData *pNewJoystick;
   381         int device_index = 0;
   382         s_bDeviceAdded = SDL_FALSE;
   383         pNewJoystick = SYS_Joystick;
   384         while (pNewJoystick) {
   385             if (pNewJoystick->send_add_event) {
   386                 if (pNewJoystick->bXInputDevice) {
   387 #if SDL_HAPTIC_XINPUT
   388                     SDL_XINPUT_MaybeAddDevice(pNewJoystick->XInputUserId);
   389 #endif
   390                 } else {
   391 #if SDL_HAPTIC_DINPUT
   392                     SDL_DINPUT_MaybeAddDevice(&pNewJoystick->dxdevice);
   393 #endif
   394                 }
   395 
   396                 SDL_PrivateJoystickAdded(pNewJoystick->nInstanceID);
   397 
   398                 pNewJoystick->send_add_event = SDL_FALSE;
   399             }
   400             device_index++;
   401             pNewJoystick = pNewJoystick->pNext;
   402         }
   403     }
   404 }
   405 
   406 /* Function to get the device-dependent name of a joystick */
   407 static const char *
   408 WINDOWS_JoystickGetDeviceName(int device_index)
   409 {
   410     JoyStick_DeviceData *device = SYS_Joystick;
   411     int index;
   412 
   413     for (index = device_index; index > 0; index--)
   414         device = device->pNext;
   415 
   416     return device->joystickname;
   417 }
   418 
   419 static int
   420 WINDOWS_JoystickGetDevicePlayerIndex(int device_index)
   421 {
   422     JoyStick_DeviceData *device = SYS_Joystick;
   423     int index;
   424 
   425     for (index = device_index; index > 0; index--)
   426         device = device->pNext;
   427 
   428     return device->bXInputDevice ? (int)device->XInputUserId : -1;
   429 }
   430 
   431 static void
   432 WINDOWS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
   433 {
   434 }
   435 
   436 /* return the stable device guid for this device index */
   437 static SDL_JoystickGUID
   438 WINDOWS_JoystickGetDeviceGUID(int device_index)
   439 {
   440     JoyStick_DeviceData *device = SYS_Joystick;
   441     int index;
   442 
   443     for (index = device_index; index > 0; index--)
   444         device = device->pNext;
   445 
   446     return device->guid;
   447 }
   448 
   449 /* Function to perform the mapping between current device instance and this joysticks instance id */
   450 static SDL_JoystickID
   451 WINDOWS_JoystickGetDeviceInstanceID(int device_index)
   452 {
   453     JoyStick_DeviceData *device = SYS_Joystick;
   454     int index;
   455 
   456     for (index = device_index; index > 0; index--)
   457         device = device->pNext;
   458 
   459     return device->nInstanceID;
   460 }
   461 
   462 /* Function to open a joystick for use.
   463    The joystick to open is specified by the device index.
   464    This should fill the nbuttons and naxes fields of the joystick structure.
   465    It returns 0, or -1 if there is an error.
   466  */
   467 static int
   468 WINDOWS_JoystickOpen(SDL_Joystick * joystick, int device_index)
   469 {
   470     JoyStick_DeviceData *device = SYS_Joystick;
   471     int index;
   472 
   473     for (index = device_index; index > 0; index--)
   474         device = device->pNext;
   475 
   476     /* allocate memory for system specific hardware data */
   477     joystick->instance_id = device->nInstanceID;
   478     joystick->hwdata =
   479         (struct joystick_hwdata *) SDL_malloc(sizeof(struct joystick_hwdata));
   480     if (joystick->hwdata == NULL) {
   481         return SDL_OutOfMemory();
   482     }
   483     SDL_zerop(joystick->hwdata);
   484     joystick->hwdata->guid = device->guid;
   485 
   486     if (device->bXInputDevice) {
   487         return SDL_XINPUT_JoystickOpen(joystick, device);
   488     } else {
   489         return SDL_DINPUT_JoystickOpen(joystick, device);
   490     }
   491 }
   492 
   493 static int
   494 WINDOWS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
   495 {
   496     if (joystick->hwdata->bXInputDevice) {
   497         return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
   498     } else {
   499         return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
   500     }
   501 }
   502 
   503 static void
   504 WINDOWS_JoystickUpdate(SDL_Joystick * joystick)
   505 {
   506     if (!joystick->hwdata) {
   507         return;
   508     }
   509 
   510     if (joystick->hwdata->bXInputDevice) {
   511         SDL_XINPUT_JoystickUpdate(joystick);
   512     } else {
   513         SDL_DINPUT_JoystickUpdate(joystick);
   514     }
   515 }
   516 
   517 /* Function to close a joystick after use */
   518 static void
   519 WINDOWS_JoystickClose(SDL_Joystick * joystick)
   520 {
   521     if (joystick->hwdata->bXInputDevice) {
   522         SDL_XINPUT_JoystickClose(joystick);
   523     } else {
   524         SDL_DINPUT_JoystickClose(joystick);
   525     }
   526 
   527     SDL_free(joystick->hwdata);
   528 }
   529 
   530 /* Function to perform any system-specific joystick related cleanup */
   531 static void
   532 WINDOWS_JoystickQuit(void)
   533 {
   534     JoyStick_DeviceData *device = SYS_Joystick;
   535 
   536     while (device) {
   537         JoyStick_DeviceData *device_next = device->pNext;
   538         SDL_free(device->joystickname);
   539         SDL_free(device);
   540         device = device_next;
   541     }
   542     SYS_Joystick = NULL;
   543 
   544     if (s_threadJoystick) {
   545         SDL_LockMutex(s_mutexJoyStickEnum);
   546         s_bJoystickThreadQuit = SDL_TRUE;
   547         SDL_CondBroadcast(s_condJoystickThread); /* signal the joystick thread to quit */
   548         SDL_UnlockMutex(s_mutexJoyStickEnum);
   549 #ifndef __WINRT__
   550         PostThreadMessage(SDL_GetThreadID(s_threadJoystick), WM_QUIT, 0, 0);
   551 #endif
   552         SDL_WaitThread(s_threadJoystick, NULL); /* wait for it to bugger off */
   553 
   554         SDL_DestroyMutex(s_mutexJoyStickEnum);
   555         SDL_DestroyCond(s_condJoystickThread);
   556         s_condJoystickThread= NULL;
   557         s_mutexJoyStickEnum = NULL;
   558         s_threadJoystick = NULL;
   559     }
   560 
   561     SDL_DINPUT_JoystickQuit();
   562     SDL_XINPUT_JoystickQuit();
   563 
   564     s_bDeviceAdded = SDL_FALSE;
   565     s_bDeviceRemoved = SDL_FALSE;
   566 }
   567 
   568 static SDL_bool
   569 WINDOWS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
   570 {
   571     return SDL_FALSE;
   572 }
   573 
   574 SDL_JoystickDriver SDL_WINDOWS_JoystickDriver =
   575 {
   576     WINDOWS_JoystickInit,
   577     WINDOWS_JoystickGetCount,
   578     WINDOWS_JoystickDetect,
   579     WINDOWS_JoystickGetDeviceName,
   580     WINDOWS_JoystickGetDevicePlayerIndex,
   581     WINDOWS_JoystickSetDevicePlayerIndex,
   582     WINDOWS_JoystickGetDeviceGUID,
   583     WINDOWS_JoystickGetDeviceInstanceID,
   584     WINDOWS_JoystickOpen,
   585     WINDOWS_JoystickRumble,
   586     WINDOWS_JoystickUpdate,
   587     WINDOWS_JoystickClose,
   588     WINDOWS_JoystickQuit,
   589     WINDOWS_JoystickGetGamepadMapping
   590 };
   591 
   592 #endif /* SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT */
   593 
   594 /* vi: set ts=4 sw=4 expandtab: */