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