src/joystick/windows/SDL_windowsjoystick.c
author Sam Lantinga
Thu, 03 Jul 2014 15:39:55 -0700
changeset 8972 dfc759d7486f
child 8988 1deb2998dbae
permissions -rw-r--r--
Split the XInput and DirectInput code so Windows RT can use the existing XInput support.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2014 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_thread.h"
    39 #include "SDL_timer.h"
    40 #include "SDL_mutex.h"
    41 #include "SDL_events.h"
    42 #include "SDL_hints.h"
    43 #include "SDL_joystick.h"
    44 #include "../SDL_sysjoystick.h"
    45 #if !SDL_EVENTS_DISABLED
    46 #include "../../events/SDL_events_c.h"
    47 #endif
    48 #include "../../core/windows/SDL_windows.h"
    49 #include <dbt.h>
    50 
    51 #define INITGUID /* Only set here, if set twice will cause mingw32 to break. */
    52 #include "SDL_windowsjoystick_c.h"
    53 #include "SDL_dinputjoystick_c.h"
    54 #include "SDL_xinputjoystick_c.h"
    55 
    56 #include "../../haptic/windows/SDL_dinputhaptic_c.h"    /* For haptic hot plugging */
    57 #include "../../haptic/windows/SDL_xinputhaptic_c.h"    /* For haptic hot plugging */
    58 
    59 
    60 /* local variables */
    61 static SDL_bool s_bDeviceAdded = SDL_FALSE;
    62 static SDL_bool s_bDeviceRemoved = SDL_FALSE;
    63 static SDL_JoystickID s_nInstanceID = -1;
    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 void
    92 SDL_CheckDeviceNotification(SDL_DeviceNotificationData *data)
    93 {
    94 }
    95 
    96 #else /* !__WINRT__ */
    97 
    98 typedef struct
    99 {
   100     HRESULT coinitialized;
   101     WNDCLASSEX wincl;
   102     HWND messageWindow;
   103     HDEVNOTIFY hNotify;
   104 } SDL_DeviceNotificationData;
   105 
   106 
   107 /* windowproc for our joystick detect thread message only window, to detect any USB device addition/removal */
   108 static LRESULT CALLBACK
   109 SDL_PrivateJoystickDetectProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
   110 {
   111     switch (message) {
   112     case WM_DEVICECHANGE:
   113         switch (wParam) {
   114         case DBT_DEVICEARRIVAL:
   115             if (((DEV_BROADCAST_HDR*)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
   116                 s_bWindowsDeviceChanged = SDL_TRUE;
   117             }
   118             break;
   119         case DBT_DEVICEREMOVECOMPLETE:
   120             if (((DEV_BROADCAST_HDR*)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
   121                 s_bWindowsDeviceChanged = SDL_TRUE;
   122             }
   123             break;
   124         }
   125         return 0;
   126     }
   127 
   128     return DefWindowProc (hwnd, message, wParam, lParam);
   129 }
   130 
   131 static void
   132 SDL_CleanupDeviceNotification(SDL_DeviceNotificationData *data)
   133 {
   134     if (data->hNotify)
   135         UnregisterDeviceNotification(data->hNotify);
   136 
   137     if (data->messageWindow)
   138         DestroyWindow(data->messageWindow);
   139 
   140     UnregisterClass(data->wincl.lpszClassName, data->wincl.hInstance);
   141 
   142     if (data->coinitialized == S_OK) {
   143         WIN_CoUninitialize();
   144     }
   145 }
   146 
   147 static int
   148 SDL_CreateDeviceNotification(SDL_DeviceNotificationData *data)
   149 {
   150     DEV_BROADCAST_DEVICEINTERFACE dbh;
   151     GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
   152 
   153     SDL_zerop(data);
   154 
   155     data->coinitialized = WIN_CoInitialize();
   156 
   157     data->wincl.hInstance = GetModuleHandle(NULL);
   158     data->wincl.lpszClassName = L"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 -1;
   166     }
   167 
   168     data->messageWindow = (HWND)CreateWindowEx(0,  L"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 -1;
   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 -1;
   185     }
   186     return 0;
   187 }
   188 
   189 static void
   190 SDL_CheckDeviceNotification(SDL_DeviceNotificationData *data)
   191 {
   192     MSG msg;
   193 
   194     if (!data->messageWindow) {
   195         return;
   196     }
   197 
   198     while (PeekMessage(&msg, data->messageWindow, 0, 0, PM_NOREMOVE)) {
   199         if (GetMessage(&msg, data->messageWindow, 0, 0) != 0)  {
   200             TranslateMessage(&msg);
   201             DispatchMessage(&msg);
   202         }
   203     }
   204 }
   205 
   206 #endif /* __WINRT__ */
   207 
   208 /* Function/thread to scan the system for joysticks. */
   209 static int
   210 SDL_JoystickThread(void *_data)
   211 {
   212     SDL_DeviceNotificationData notification_data;
   213 
   214 #if SDL_JOYSTICK_XINPUT
   215     SDL_bool bOpenedXInputDevices[XUSER_MAX_COUNT];
   216     SDL_zero(bOpenedXInputDevices);
   217 #endif
   218 
   219     if (SDL_CreateDeviceNotification(&notification_data) < 0) {
   220         return -1;
   221     }
   222 
   223     SDL_LockMutex(s_mutexJoyStickEnum);
   224     while (s_bJoystickThreadQuit == SDL_FALSE) {
   225         SDL_bool bXInputChanged = SDL_FALSE;
   226 
   227         SDL_CondWaitTimeout(s_condJoystickThread, s_mutexJoyStickEnum, 300);
   228 
   229         SDL_CheckDeviceNotification(&notification_data);
   230 
   231 #if SDL_JOYSTICK_XINPUT
   232         if (SDL_XINPUT_Enabled() && XINPUTGETCAPABILITIES) {
   233             /* scan for any change in XInput devices */
   234             Uint8 userId;
   235             for (userId = 0; userId < XUSER_MAX_COUNT; userId++) {
   236                 XINPUT_CAPABILITIES capabilities;
   237                 const DWORD result = XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities);
   238                 const SDL_bool available = (result == ERROR_SUCCESS);
   239                 if (bOpenedXInputDevices[userId] != available) {
   240                     bXInputChanged = SDL_TRUE;
   241                     bOpenedXInputDevices[userId] = available;
   242                 }
   243             }
   244         }
   245 #endif /* SDL_JOYSTICK_XINPUT */
   246 
   247         if (s_bWindowsDeviceChanged || bXInputChanged) {
   248             SDL_UnlockMutex(s_mutexJoyStickEnum);  /* let main thread go while we SDL_Delay(). */
   249             SDL_Delay(300); /* wait for direct input to find out about this device */
   250             SDL_LockMutex(s_mutexJoyStickEnum);
   251 
   252             s_bDeviceRemoved = SDL_TRUE;
   253             s_bDeviceAdded = SDL_TRUE;
   254             s_bWindowsDeviceChanged = SDL_FALSE;
   255         }
   256     }
   257     SDL_UnlockMutex(s_mutexJoyStickEnum);
   258 
   259     SDL_CleanupDeviceNotification(&notification_data);
   260 
   261     return 1;
   262 }
   263 
   264 void SDL_SYS_AddJoystickDevice(JoyStick_DeviceData *device)
   265 {
   266     device->send_add_event = SDL_TRUE;
   267     device->nInstanceID = ++s_nInstanceID;
   268     device->pNext = SYS_Joystick;
   269     SYS_Joystick = device;
   270 
   271     s_bDeviceAdded = SDL_TRUE;
   272 }
   273 
   274 /* Function to scan the system for joysticks.
   275  * This function should set SDL_numjoysticks to the number of available
   276  * joysticks.  Joystick 0 should be the system default joystick.
   277  * It should return 0, or -1 on an unrecoverable fatal error.
   278  */
   279 int
   280 SDL_SYS_JoystickInit(void)
   281 {
   282     if (SDL_DINPUT_JoystickInit() < 0) {
   283         SDL_SYS_JoystickQuit();
   284         return -1;
   285     }
   286 
   287     if (SDL_XINPUT_JoystickInit() < 0) {
   288         SDL_SYS_JoystickQuit();
   289         return -1;
   290     }
   291 
   292     s_mutexJoyStickEnum = SDL_CreateMutex();
   293     s_condJoystickThread = SDL_CreateCond();
   294     s_bDeviceAdded = SDL_TRUE; /* force a scan of the system for joysticks this first time */
   295 
   296     SDL_SYS_JoystickDetect();
   297 
   298     if (!s_threadJoystick) {
   299         s_bJoystickThreadQuit = SDL_FALSE;
   300         /* spin up the thread to detect hotplug of devices */
   301 #if defined(__WIN32__) && !defined(HAVE_LIBC)
   302 #undef SDL_CreateThread
   303 #if SDL_DYNAMIC_API
   304         s_threadJoystick= SDL_CreateThread_REAL(SDL_JoystickThread, "SDL_joystick", NULL, NULL, NULL);
   305 #else
   306         s_threadJoystick= SDL_CreateThread(SDL_JoystickThread, "SDL_joystick", NULL, NULL, NULL);
   307 #endif
   308 #else
   309         s_threadJoystick = SDL_CreateThread(SDL_JoystickThread, "SDL_joystick", NULL);
   310 #endif
   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()
   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()
   332 {
   333     JoyStick_DeviceData *pCurList = NULL;
   334 #if !SDL_EVENTS_DISABLED
   335     SDL_Event event;
   336 #endif
   337 
   338     /* only enum the devices if the joystick thread told us something changed */
   339     if (!s_bDeviceAdded && !s_bDeviceRemoved) {
   340         return;  /* thread hasn't signaled, nothing to do right now. */
   341     }
   342 
   343     SDL_LockMutex(s_mutexJoyStickEnum);
   344 
   345     s_bDeviceAdded = SDL_FALSE;
   346     s_bDeviceRemoved = SDL_FALSE;
   347 
   348     pCurList = SYS_Joystick;
   349     SYS_Joystick = NULL;
   350 
   351     /* Look for DirectInput joysticks, wheels, head trackers, gamepads, etc.. */
   352     SDL_DINPUT_JoystickDetect(&pCurList);
   353 
   354     /* Look for XInput devices. Do this last, so they're first in the final list. */
   355     SDL_XINPUT_JoystickDetect(&pCurList);
   356 
   357     SDL_UnlockMutex(s_mutexJoyStickEnum);
   358 
   359     while (pCurList) {
   360         JoyStick_DeviceData *pListNext = NULL;
   361 
   362         if (pCurList->bXInputDevice) {
   363             SDL_XINPUT_MaybeRemoveDevice(pCurList->XInputUserId);
   364         } else {
   365             SDL_DINPUT_MaybeRemoveDevice(&pCurList->dxdevice);
   366         }
   367 
   368 #if !SDL_EVENTS_DISABLED
   369         SDL_zero(event);
   370         event.type = SDL_JOYDEVICEREMOVED;
   371 
   372         if (SDL_GetEventState(event.type) == SDL_ENABLE) {
   373             event.jdevice.which = pCurList->nInstanceID;
   374             if ((!SDL_EventOK) || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
   375                 SDL_PushEvent(&event);
   376             }
   377         }
   378 #endif /* !SDL_EVENTS_DISABLED */
   379 
   380         pListNext = pCurList->pNext;
   381         SDL_free(pCurList->joystickname);
   382         SDL_free(pCurList);
   383         pCurList = pListNext;
   384     }
   385 
   386     if (s_bDeviceAdded) {
   387         JoyStick_DeviceData *pNewJoystick;
   388         int device_index = 0;
   389         s_bDeviceAdded = SDL_FALSE;
   390         pNewJoystick = SYS_Joystick;
   391         while (pNewJoystick) {
   392             if (pNewJoystick->send_add_event) {
   393                 if (pNewJoystick->bXInputDevice) {
   394                     SDL_XINPUT_MaybeAddDevice(pNewJoystick->XInputUserId);
   395                 } else {
   396                     SDL_DINPUT_MaybeAddDevice(&pNewJoystick->dxdevice);
   397                 }
   398 
   399 #if !SDL_EVENTS_DISABLED
   400                 SDL_zero(event);
   401                 event.type = SDL_JOYDEVICEADDED;
   402 
   403                 if (SDL_GetEventState(event.type) == SDL_ENABLE) {
   404                     event.jdevice.which = device_index;
   405                     if ((!SDL_EventOK) || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
   406                         SDL_PushEvent(&event);
   407                     }
   408                 }
   409 #endif /* !SDL_EVENTS_DISABLED */
   410                 pNewJoystick->send_add_event = SDL_FALSE;
   411             }
   412             device_index++;
   413             pNewJoystick = pNewJoystick->pNext;
   414         }
   415     }
   416 }
   417 
   418 /* Function to get the device-dependent name of a joystick */
   419 const char *
   420 SDL_SYS_JoystickNameForDeviceIndex(int device_index)
   421 {
   422     JoyStick_DeviceData *device = SYS_Joystick;
   423 
   424     for (; device_index > 0; device_index--)
   425         device = device->pNext;
   426 
   427     return device->joystickname;
   428 }
   429 
   430 /* Function to perform the mapping between current device instance and this joysticks instance id */
   431 SDL_JoystickID
   432 SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
   433 {
   434     JoyStick_DeviceData *device = SYS_Joystick;
   435     int index;
   436 
   437     for (index = device_index; index > 0; index--)
   438         device = device->pNext;
   439 
   440     return device->nInstanceID;
   441 }
   442 
   443 /* Function to open a joystick for use.
   444    The joystick to open is specified by the index field of the joystick.
   445    This should fill the nbuttons and naxes fields of the joystick structure.
   446    It returns 0, or -1 if there is an error.
   447  */
   448 int
   449 SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
   450 {
   451     JoyStick_DeviceData *joystickdevice = SYS_Joystick;
   452 
   453     for (; device_index > 0; device_index--)
   454         joystickdevice = joystickdevice->pNext;
   455 
   456     /* allocate memory for system specific hardware data */
   457     joystick->instance_id = joystickdevice->nInstanceID;
   458     joystick->closed = SDL_FALSE;
   459     joystick->hwdata =
   460         (struct joystick_hwdata *) SDL_malloc(sizeof(struct joystick_hwdata));
   461     if (joystick->hwdata == NULL) {
   462         return SDL_OutOfMemory();
   463     }
   464     SDL_zerop(joystick->hwdata);
   465     joystick->hwdata->guid = joystickdevice->guid;
   466 
   467     if (joystickdevice->bXInputDevice) {
   468         return SDL_XINPUT_JoystickOpen(joystick, joystickdevice);
   469     } else {
   470         return SDL_DINPUT_JoystickOpen(joystick, joystickdevice);
   471     }
   472 }
   473 
   474 /* return true if this joystick is plugged in right now */
   475 SDL_bool 
   476 SDL_SYS_JoystickAttached(SDL_Joystick * joystick)
   477 {
   478     return !joystick->closed && !joystick->hwdata->removed;
   479 }
   480 
   481 void
   482 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
   483 {
   484     if (joystick->closed || !joystick->hwdata) {
   485         return;
   486     }
   487 
   488     if (joystick->hwdata->bXInputDevice) {
   489         SDL_XINPUT_JoystickUpdate(joystick);
   490     } else {
   491         SDL_DINPUT_JoystickUpdate(joystick);
   492     }
   493 
   494     if (joystick->hwdata->removed) {
   495         joystick->closed = SDL_TRUE;
   496         joystick->uncentered = SDL_TRUE;
   497     }
   498 }
   499 
   500 /* Function to close a joystick after use */
   501 void
   502 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
   503 {
   504     if (joystick->hwdata->bXInputDevice) {
   505         SDL_XINPUT_JoystickClose(joystick);
   506     } else {
   507         SDL_DINPUT_JoystickClose(joystick);
   508     }
   509 
   510     /* free system specific hardware data */
   511     SDL_free(joystick->hwdata);
   512 
   513     joystick->closed = SDL_TRUE;
   514 }
   515 
   516 /* Function to perform any system-specific joystick related cleanup */
   517 void
   518 SDL_SYS_JoystickQuit(void)
   519 {
   520     JoyStick_DeviceData *device = SYS_Joystick;
   521 
   522     while (device) {
   523         JoyStick_DeviceData *device_next = device->pNext;
   524         SDL_free(device->joystickname);
   525         SDL_free(device);
   526         device = device_next;
   527     }
   528     SYS_Joystick = NULL;
   529 
   530     if (s_threadJoystick) {
   531         SDL_LockMutex(s_mutexJoyStickEnum);
   532         s_bJoystickThreadQuit = SDL_TRUE;
   533         SDL_CondBroadcast(s_condJoystickThread); /* signal the joystick thread to quit */
   534         SDL_UnlockMutex(s_mutexJoyStickEnum);
   535         SDL_WaitThread(s_threadJoystick, NULL); /* wait for it to bugger off */
   536 
   537         SDL_DestroyMutex(s_mutexJoyStickEnum);
   538         SDL_DestroyCond(s_condJoystickThread);
   539         s_condJoystickThread= NULL;
   540         s_mutexJoyStickEnum = NULL;
   541         s_threadJoystick = NULL;
   542     }
   543 
   544     SDL_DINPUT_JoystickQuit();
   545     SDL_XINPUT_JoystickQuit();
   546 }
   547 
   548 /* return the stable device guid for this device index */
   549 SDL_JoystickGUID
   550 SDL_SYS_JoystickGetDeviceGUID(int device_index)
   551 {
   552     JoyStick_DeviceData *device = SYS_Joystick;
   553     int index;
   554 
   555     for (index = device_index; index > 0; index--)
   556         device = device->pNext;
   557 
   558     return device->guid;
   559 }
   560 
   561 SDL_JoystickGUID
   562 SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
   563 {
   564     return joystick->hwdata->guid;
   565 }
   566 
   567 #endif /* SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT */
   568 
   569 /* vi: set ts=4 sw=4 expandtab: */