src/joystick/winrt/SDL_xinputjoystick.c
changeset 8552 abd934eac415
parent 8502 78d341895e9f
child 8562 f1aac2d56c76
equal deleted inserted replaced
8551:666f05b079ea 8552:abd934eac415
    36 #include "SDL_joystick.h"
    36 #include "SDL_joystick.h"
    37 #include "../SDL_sysjoystick.h"
    37 #include "../SDL_sysjoystick.h"
    38 #include "../SDL_joystick_c.h"
    38 #include "../SDL_joystick_c.h"
    39 #include "SDL_events.h"
    39 #include "SDL_events.h"
    40 #include "../../events/SDL_events_c.h"
    40 #include "../../events/SDL_events_c.h"
       
    41 #include "SDL_timer.h"
    41 
    42 
    42 #include <Windows.h>
    43 #include <Windows.h>
    43 #include <Xinput.h>
    44 #include <Xinput.h>
    44 
    45 
    45 struct joystick_hwdata {
    46 struct joystick_hwdata {
    46     //Uint8 bXInputHaptic; // Supports force feedback via XInput.
    47     //Uint8 bXInputHaptic; // Supports force feedback via XInput.
    47     DWORD userIndex;    // The XInput device index, in the range [0, XUSER_MAX_COUNT-1] (probably [0,3]).
    48     DWORD userIndex;    // The XInput device index, in the range [0, XUSER_MAX_COUNT-1] (probably [0,3]).
    48     XINPUT_STATE XInputState;   // the last-read in XInputState, kept around to compare old and new values
    49     XINPUT_STATE XInputState;   // the last-read in XInputState, kept around to compare old and new values
    49     SDL_bool isDeviceConnected; // was the device connected (on the last polling, or during backend-initialization)?
    50     SDL_bool isDeviceConnected; // was the device connected (on the last detection-polling, or during backend-initialization)?
    50     SDL_bool isDeviceRemovalEventPending;   // was the device removed, and is the associated removal event pending?
    51     SDL_bool isDeviceConnectionEventPending;    // was a device added, and is the associated add-event pending?
       
    52     SDL_bool isDeviceRemovalEventPending;   // was the device removed, and is the associated remove-event pending?
    51 };
    53 };
    52 
    54 
    53 /* Keep track of data on all XInput devices, regardless of whether or not
    55 /* Keep track of data on all XInput devices, regardless of whether or not
    54    they've been opened (via SDL_JoystickOpen).
    56    they've been opened (via SDL_JoystickOpen).
    55  */
    57  */
    56 static struct joystick_hwdata g_XInputData[XUSER_MAX_COUNT];
    58 static struct joystick_hwdata g_XInputData[XUSER_MAX_COUNT];
       
    59 
       
    60 /* Device detection can be extremely costly performance-wise, in some cases.
       
    61    In particular, if no devices are connected, calls to detect a single device,
       
    62    via either XInputGetState() or XInputGetCapabilities(), can take upwards of
       
    63    20 ms on a 1st generation Surface RT, more if devices are detected across
       
    64    all of of XInput's four device slots.  WinRT and XInput do not appear to
       
    65    have callback-based APIs to notify an app when a device is connected, at
       
    66    least as of Windows 8.1.  The synchronous XInput calls must be used.
       
    67 
       
    68    Once a device is connected, calling XInputGetState() is a much less costly
       
    69    operation, with individual calls costing well under 1 ms, and often under
       
    70    0.1 ms [on a 1st gen Surface RT].
       
    71 
       
    72    With XInput's performance limitations in mind, a separate device-detection
       
    73    thread will be utilized (by SDL) to try to move costly XInput calls off the
       
    74    main thread.  Polling of active devices still, however, occurs on the main
       
    75    thread.
       
    76  */
       
    77 static SDL_Thread * g_DeviceDetectionThread = NULL;
       
    78 static SDL_mutex * g_DeviceInfoLock = NULL;
       
    79 static SDL_bool g_DeviceDetectionQuit = SDL_FALSE;
       
    80 
       
    81 /* Main function for the device-detection thread.
       
    82  */
       
    83 static int
       
    84 DeviceDetectionThreadMain(void * _data)
       
    85 {
       
    86     DWORD result;
       
    87     XINPUT_CAPABILITIES tempXInputCaps;
       
    88     int i;
       
    89 
       
    90     while (1) {
       
    91         /* See if the device-detection thread is being asked to shutdown.
       
    92          */
       
    93         SDL_LockMutex(g_DeviceInfoLock);
       
    94         if (g_DeviceDetectionQuit) {
       
    95             SDL_UnlockMutex(g_DeviceInfoLock);
       
    96             break;
       
    97         }
       
    98         SDL_UnlockMutex(g_DeviceInfoLock);
       
    99 
       
   100         /* Add a short delay to prevent the device-detection thread from eating
       
   101            up too much CPU time:
       
   102          */
       
   103         SDL_Delay(300);
       
   104 
       
   105         /* TODO, WinRT: try making the device-detection thread wakeup sooner from its CPU-preserving SDL_Delay, if the thread was asked to quit.
       
   106          */
       
   107 
       
   108         /* See if any new devices are connected. */
       
   109         for (i = 0; i < XUSER_MAX_COUNT; ++i) {
       
   110             if (!g_XInputData[i].isDeviceConnected &&
       
   111                 !g_XInputData[i].isDeviceConnectionEventPending &&
       
   112                 !g_XInputData[i].isDeviceRemovalEventPending)
       
   113             {
       
   114                 result = XInputGetCapabilities(i, 0, &tempXInputCaps);
       
   115                 if (result == ERROR_SUCCESS) {
       
   116                     /* Yes, a device is connected.  Mark it as such.
       
   117                        Others will be told about this (via an
       
   118                        SDL_JOYDEVICEADDED event) in the next call to
       
   119                        SDL_SYS_JoystickDetect.
       
   120                      */
       
   121                     SDL_LockMutex(g_DeviceInfoLock);
       
   122                     g_XInputData[i].isDeviceConnected = SDL_TRUE;
       
   123                     g_XInputData[i].isDeviceConnectionEventPending = SDL_TRUE;
       
   124                     SDL_UnlockMutex(g_DeviceInfoLock);
       
   125                 }
       
   126             }
       
   127         }
       
   128     }
       
   129 
       
   130     return 0;
       
   131 }
    57 
   132 
    58 /* Function to scan the system for joysticks.
   133 /* Function to scan the system for joysticks.
    59  * It should return 0, or -1 on an unrecoverable fatal error.
   134  * It should return 0, or -1 on an unrecoverable fatal error.
    60  */
   135  */
    61 int
   136 int
    74         if (result == ERROR_SUCCESS) {
   149         if (result == ERROR_SUCCESS) {
    75             g_XInputData[i].isDeviceConnected = SDL_TRUE;
   150             g_XInputData[i].isDeviceConnected = SDL_TRUE;
    76         }
   151         }
    77     }
   152     }
    78 
   153 
       
   154     /* Start up the device-detection thread.
       
   155      */
       
   156     g_DeviceDetectionQuit = SDL_FALSE;
       
   157     g_DeviceInfoLock = SDL_CreateMutex();
       
   158     g_DeviceDetectionThread = SDL_CreateThread(DeviceDetectionThreadMain, "SDL_joystick", NULL);
       
   159 
    79     return (0);
   160     return (0);
    80 }
   161 }
    81 
   162 
    82 int SDL_SYS_NumJoysticks()
   163 int SDL_SYS_NumJoysticks()
    83 {
   164 {
    85     DWORD i;
   166     DWORD i;
    86 
   167 
    87     /* Iterate through each possible XInput device and see if something
   168     /* Iterate through each possible XInput device and see if something
    88        was connected (at joystick init, or during the last polling).
   169        was connected (at joystick init, or during the last polling).
    89      */
   170      */
       
   171     SDL_LockMutex(g_DeviceInfoLock);
    90     for (i = 0; i < XUSER_MAX_COUNT; ++i) {
   172     for (i = 0; i < XUSER_MAX_COUNT; ++i) {
    91         if (g_XInputData[i].isDeviceConnected) {
   173         if (g_XInputData[i].isDeviceConnected) {
    92             ++joystickCount;
   174             ++joystickCount;
    93         }
   175         }
    94     }
   176     }
       
   177     SDL_UnlockMutex(g_DeviceInfoLock);
    95 
   178 
    96     return joystickCount;
   179     return joystickCount;
    97 }
   180 }
    98 
   181 
    99 void SDL_SYS_JoystickDetect()
   182 void SDL_SYS_JoystickDetect()
   100 {
   183 {
   101     DWORD i;
   184     DWORD i;
   102     XINPUT_STATE tempXInputState;
       
   103     HRESULT result;
       
   104     SDL_Event event;
   185     SDL_Event event;
   105 
   186 
   106     /* Iterate through each possible XInput device, seeing if any devices
   187     /* Iterate through each possible XInput device, seeing if any devices
   107        have been connected, or if they were removed.
   188        have been connected, or if they were removed.
   108      */
   189      */
       
   190     SDL_LockMutex(g_DeviceInfoLock);
   109     for (i = 0; i < XUSER_MAX_COUNT; ++i) {
   191     for (i = 0; i < XUSER_MAX_COUNT; ++i) {
   110         /* See if any new devices are connected. */
   192         /* See if any new devices are connected. */
   111         if (!g_XInputData[i].isDeviceConnected && !g_XInputData[i].isDeviceRemovalEventPending) {
   193         if (g_XInputData[i].isDeviceConnectionEventPending) {
   112             result = XInputGetState(i, &tempXInputState);
       
   113             if (result == ERROR_SUCCESS) {
       
   114                 /* Yup, a device is connected.  Mark the device as connected,
       
   115                    then tell others about it (via an SDL_JOYDEVICEADDED event.)
       
   116                  */
       
   117                 g_XInputData[i].isDeviceConnected = SDL_TRUE;
       
   118 
       
   119 #if !SDL_EVENTS_DISABLED
   194 #if !SDL_EVENTS_DISABLED
   120                 SDL_zero(event);
   195             SDL_zero(event);
   121                 event.type = SDL_JOYDEVICEADDED;
   196             event.type = SDL_JOYDEVICEADDED;
   122                 
   197                 
   123                 if (SDL_GetEventState(event.type) == SDL_ENABLE) {
   198             if (SDL_GetEventState(event.type) == SDL_ENABLE) {
   124                     event.jdevice.which = i;
   199                 event.jdevice.which = i;
   125                     if ((SDL_EventOK == NULL)
   200                 if ((SDL_EventOK == NULL)
   126                         || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
   201                     || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
   127                         SDL_PushEvent(&event);
   202                     SDL_PushEvent(&event);
   128                     }
       
   129                 }
   203                 }
       
   204             }
   130 #endif
   205 #endif
   131             }
   206             g_XInputData[i].isDeviceConnectionEventPending = SDL_FALSE;
   132         } else if (g_XInputData[i].isDeviceRemovalEventPending) {
   207         } else if (g_XInputData[i].isDeviceRemovalEventPending) {
   133             /* A device was previously marked as removed (by
   208             /* A device was previously marked as removed (by
   134                SDL_SYS_JoystickUpdate).  Tell others about the device removal.
   209                SDL_SYS_JoystickUpdate).  Tell others about the device removal.
   135             */
   210             */
   136 
   211 
   148                 }
   223                 }
   149             }
   224             }
   150 #endif
   225 #endif
   151         }
   226         }
   152     }
   227     }
       
   228     SDL_UnlockMutex(g_DeviceInfoLock);
   153 }
   229 }
   154 
   230 
   155 SDL_bool SDL_SYS_JoystickNeedsPolling()
   231 SDL_bool SDL_SYS_JoystickNeedsPolling()
   156 {
   232 {
   157     /* Since XInput, or WinRT, provides any events to indicate when a game
   233     /* Since XInput, or WinRT, provides any events to indicate when a game
   291 }
   367 }
   292 
   368 
   293 /* Function to determine is this joystick is attached to the system right now */
   369 /* Function to determine is this joystick is attached to the system right now */
   294 SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
   370 SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
   295 {
   371 {
   296     return joystick->hwdata->isDeviceConnected;
   372     SDL_bool isDeviceConnected;
       
   373     SDL_LockMutex(g_DeviceInfoLock);
       
   374     isDeviceConnected = joystick->hwdata->isDeviceConnected;
       
   375     SDL_UnlockMutex(g_DeviceInfoLock);
       
   376     return isDeviceConnected;
   297 }
   377 }
   298 
   378 
   299 /* Function to return > 0 if a bit array of buttons differs after applying a mask
   379 /* Function to return > 0 if a bit array of buttons differs after applying a mask
   300 */
   380 */
   301 static int ButtonChanged( int ButtonsNow, int ButtonsPrev, int ButtonMask )
   381 static int ButtonChanged( int ButtonsNow, int ButtonsPrev, int ButtonMask )
   310  */
   390  */
   311 void
   391 void
   312 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
   392 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
   313 {
   393 {
   314     HRESULT result;
   394     HRESULT result;
       
   395     XINPUT_STATE prevXInputState;
       
   396 
       
   397     SDL_LockMutex(g_DeviceInfoLock);
   315 
   398 
   316     /* Before polling for new data, make note of the old data */
   399     /* Before polling for new data, make note of the old data */
   317     XINPUT_STATE prevXInputState = joystick->hwdata->XInputState;
   400     prevXInputState = joystick->hwdata->XInputState;
   318 
   401 
   319     /* Poll for new data */
   402     /* Poll for new data */
   320     result = XInputGetState(joystick->hwdata->userIndex, &joystick->hwdata->XInputState);
   403     result = XInputGetState(joystick->hwdata->userIndex, &joystick->hwdata->XInputState);
   321     if (result == ERROR_DEVICE_NOT_CONNECTED) {
   404     if (result == ERROR_DEVICE_NOT_CONNECTED) {
   322         if (joystick->hwdata->isDeviceConnected) {
   405         if (joystick->hwdata->isDeviceConnected) {
   323             joystick->hwdata->isDeviceConnected = SDL_FALSE;
   406             joystick->hwdata->isDeviceConnected = SDL_FALSE;
   324             joystick->hwdata->isDeviceRemovalEventPending = SDL_TRUE;
   407             joystick->hwdata->isDeviceRemovalEventPending = SDL_TRUE;
   325             /* TODO, WinRT: make sure isDeviceRemovalEventPending gets cleared as appropriate, and that quick re-plugs don't cause trouble */
   408             /* TODO, WinRT: make sure isDeviceRemovalEventPending gets cleared as appropriate, and that quick re-plugs don't cause trouble */
   326         }
   409         }
       
   410         SDL_UnlockMutex(g_DeviceInfoLock);
   327         return;
   411         return;
   328     }
   412     }
   329 
   413 
   330     /* Make sure the device is marked as connected */
   414     /* Make sure the device is marked as connected */
   331     joystick->hwdata->isDeviceConnected = SDL_TRUE;
   415     joystick->hwdata->isDeviceConnected = SDL_TRUE;
   373         if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_Y ) )
   457         if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons, XINPUT_GAMEPAD_Y ) )
   374             SDL_PrivateJoystickButton(joystick, 13, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_Y ? SDL_PRESSED :	SDL_RELEASED );
   458             SDL_PrivateJoystickButton(joystick, 13, pXInputState->Gamepad.wButtons & XINPUT_GAMEPAD_Y ? SDL_PRESSED :	SDL_RELEASED );
   375         if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons,  0x400 ) )
   459         if ( ButtonChanged( pXInputState->Gamepad.wButtons, pXInputStatePrev->Gamepad.wButtons,  0x400 ) )
   376             SDL_PrivateJoystickButton(joystick, 14, pXInputState->Gamepad.wButtons & 0x400 ? SDL_PRESSED :	SDL_RELEASED ); // 0x400 is the undocumented code for the guide button
   460             SDL_PrivateJoystickButton(joystick, 14, pXInputState->Gamepad.wButtons & 0x400 ? SDL_PRESSED :	SDL_RELEASED ); // 0x400 is the undocumented code for the guide button
   377     }
   461     }
       
   462 
       
   463     SDL_UnlockMutex(g_DeviceInfoLock);
   378 }
   464 }
   379 
   465 
   380 /* Function to close a joystick after use */
   466 /* Function to close a joystick after use */
   381 void
   467 void
   382 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
   468 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
   383 {
   469 {
   384     /* Clear cached button data on the joystick */
   470     /* Clear cached button data on the joystick */
       
   471     SDL_LockMutex(g_DeviceInfoLock);
   385     SDL_zero(joystick->hwdata->XInputState);
   472     SDL_zero(joystick->hwdata->XInputState);
       
   473     SDL_UnlockMutex(g_DeviceInfoLock);
   386 
   474 
   387     /* There's need to free 'hwdata', as it's a pointer to a global array.
   475     /* There's need to free 'hwdata', as it's a pointer to a global array.
   388        The field will be cleared anyways, just to indicate that it's not
   476        The field will be cleared anyways, just to indicate that it's not
   389        currently needed.
   477        currently needed.
   390      */
   478      */
   393 
   481 
   394 /* Function to perform any system-specific joystick related cleanup */
   482 /* Function to perform any system-specific joystick related cleanup */
   395 void
   483 void
   396 SDL_SYS_JoystickQuit(void)
   484 SDL_SYS_JoystickQuit(void)
   397 {
   485 {
       
   486     /* Tell the joystick detection thread to stop, then wait for it to finish */
       
   487     SDL_LockMutex(g_DeviceInfoLock);
       
   488     g_DeviceDetectionQuit = SDL_TRUE;
       
   489     SDL_UnlockMutex(g_DeviceInfoLock);
       
   490     SDL_WaitThread(g_DeviceDetectionThread, NULL);
       
   491 
       
   492     /* Clean up device-detection stuff */
       
   493     SDL_DestroyMutex(g_DeviceInfoLock);
       
   494     g_DeviceInfoLock = NULL;
       
   495     g_DeviceDetectionThread = NULL;
       
   496     g_DeviceDetectionQuit = SDL_FALSE;
       
   497 
   398     return;
   498     return;
   399 }
   499 }
   400 
   500 
   401 SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
   501 SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
   402 {
   502 {