src/joystick/windows/SDL_xinputjoystick.c
author Ryan C. Gordon
Tue, 24 Jan 2017 16:18:25 -0500
changeset 10850 c9dc0068b0e7
parent 10737 3406a0f8b041
child 10872 414ace708a6a
permissions -rw-r--r--
configure: report libsamplerate support status.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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 #include "../SDL_sysjoystick.h"
    24 
    25 #if SDL_JOYSTICK_XINPUT
    26 
    27 #include "SDL_assert.h"
    28 #include "SDL_hints.h"
    29 #include "SDL_windowsjoystick_c.h"
    30 #include "SDL_xinputjoystick_c.h"
    31 
    32 /*
    33  * Internal stuff.
    34  */
    35 static SDL_bool s_bXInputEnabled = SDL_TRUE;
    36 
    37 
    38 static SDL_bool
    39 SDL_XInputUseOldJoystickMapping()
    40 {
    41 #ifdef __WINRT__
    42     /* TODO: remove this __WINRT__ block, but only after integrating with UWP/WinRT's HID API */
    43     return SDL_TRUE;
    44 #else
    45     static int s_XInputUseOldJoystickMapping = -1;
    46     if (s_XInputUseOldJoystickMapping < 0) {
    47         s_XInputUseOldJoystickMapping = SDL_GetHintBoolean(SDL_HINT_XINPUT_USE_OLD_JOYSTICK_MAPPING, SDL_FALSE);
    48     }
    49     return (s_XInputUseOldJoystickMapping > 0);
    50 #endif
    51 }
    52 
    53 SDL_bool SDL_XINPUT_Enabled(void)
    54 {
    55     return s_bXInputEnabled;
    56 }
    57 
    58 int
    59 SDL_XINPUT_JoystickInit(void)
    60 {
    61     s_bXInputEnabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, SDL_TRUE);
    62 
    63     if (s_bXInputEnabled && WIN_LoadXInputDLL() < 0) {
    64         s_bXInputEnabled = SDL_FALSE;  /* oh well. */
    65     }
    66     return 0;
    67 }
    68 
    69 static char *
    70 GetXInputName(const Uint8 userid, BYTE SubType)
    71 {
    72     char name[32];
    73 
    74     if (SDL_XInputUseOldJoystickMapping()) {
    75         SDL_snprintf(name, sizeof(name), "X360 Controller #%u", 1 + userid);
    76     } else {
    77         switch (SubType) {
    78         case XINPUT_DEVSUBTYPE_GAMEPAD:
    79             SDL_snprintf(name, sizeof(name), "XInput Controller #%u", 1 + userid);
    80             break;
    81         case XINPUT_DEVSUBTYPE_WHEEL:
    82             SDL_snprintf(name, sizeof(name), "XInput Wheel #%u", 1 + userid);
    83             break;
    84         case XINPUT_DEVSUBTYPE_ARCADE_STICK:
    85             SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%u", 1 + userid);
    86             break;
    87         case XINPUT_DEVSUBTYPE_FLIGHT_STICK:
    88             SDL_snprintf(name, sizeof(name), "XInput FlightStick #%u", 1 + userid);
    89             break;
    90         case XINPUT_DEVSUBTYPE_DANCE_PAD:
    91             SDL_snprintf(name, sizeof(name), "XInput DancePad #%u", 1 + userid);
    92             break;
    93         case XINPUT_DEVSUBTYPE_GUITAR:
    94         case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE:
    95         case XINPUT_DEVSUBTYPE_GUITAR_BASS:
    96             SDL_snprintf(name, sizeof(name), "XInput Guitar #%u", 1 + userid);
    97             break;
    98         case XINPUT_DEVSUBTYPE_DRUM_KIT:
    99             SDL_snprintf(name, sizeof(name), "XInput DrumKit #%u", 1 + userid);
   100             break;
   101         case XINPUT_DEVSUBTYPE_ARCADE_PAD:
   102             SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%u", 1 + userid);
   103             break;
   104         default:
   105             SDL_snprintf(name, sizeof(name), "XInput Device #%u", 1 + userid);
   106             break;
   107         }
   108     }
   109     return SDL_strdup(name);
   110 }
   111 
   112 /* We can't really tell what device is being used for XInput, but we can guess
   113    and we'll be correct for the case where only one device is connected.
   114  */
   115 static void
   116 GuessXInputDevice(UINT device_index, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion)
   117 {
   118 #ifndef __WINRT__   /* TODO: remove this ifndef __WINRT__ block, but only after integrating with UWP/WinRT's HID API */
   119 
   120     PRAWINPUTDEVICELIST devices = NULL;
   121     UINT i, found_count = 0, device_count = 0;
   122 
   123     if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!device_count)) {
   124         return;  /* oh well. */
   125     }
   126 
   127     devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * device_count);
   128     if (devices == NULL) {
   129         return;
   130     }
   131 
   132     if (GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) {
   133         SDL_free(devices);
   134         return;  /* oh well. */
   135     }
   136 
   137     for (i = 0; i < device_count; i++) {
   138         RID_DEVICE_INFO rdi;
   139         char devName[128];
   140         UINT rdiSize = sizeof(rdi);
   141         UINT nameSize = SDL_arraysize(devName);
   142 
   143         rdi.cbSize = sizeof(rdi);
   144         if ((devices[i].dwType == RIM_TYPEHID) &&
   145             (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != ((UINT)-1)) &&
   146             (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) != ((UINT)-1)) &&
   147             (SDL_strstr(devName, "IG_") != NULL)) {
   148             *pVID = (Uint16)rdi.hid.dwVendorId;
   149             *pPID = (Uint16)rdi.hid.dwProductId;
   150             *pVersion = (Uint16)rdi.hid.dwVersionNumber;
   151 
   152             if (found_count++ == device_index) {
   153                 /* We don't really know the order of the devices relative to XInput,
   154                    but we'll guess that this is the correct one
   155                  */
   156                 break;
   157             }
   158         }
   159     }
   160     SDL_free(devices);
   161 #endif  /* ifndef __WINRT__ */
   162 }
   163 
   164 static void
   165 AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext)
   166 {
   167     JoyStick_DeviceData *pPrevJoystick = NULL;
   168     JoyStick_DeviceData *pNewJoystick = *pContext;
   169 
   170     if (SDL_XInputUseOldJoystickMapping() && SubType != XINPUT_DEVSUBTYPE_GAMEPAD)
   171         return;
   172 
   173     if (SubType == XINPUT_DEVSUBTYPE_UNKNOWN)
   174         return;
   175 
   176     while (pNewJoystick) {
   177         if (pNewJoystick->bXInputDevice && (pNewJoystick->XInputUserId == userid) && (pNewJoystick->SubType == SubType)) {
   178             /* if we are replacing the front of the list then update it */
   179             if (pNewJoystick == *pContext) {
   180                 *pContext = pNewJoystick->pNext;
   181             } else if (pPrevJoystick) {
   182                 pPrevJoystick->pNext = pNewJoystick->pNext;
   183             }
   184 
   185             pNewJoystick->pNext = SYS_Joystick;
   186             SYS_Joystick = pNewJoystick;
   187             return;   /* already in the list. */
   188         }
   189 
   190         pPrevJoystick = pNewJoystick;
   191         pNewJoystick = pNewJoystick->pNext;
   192     }
   193 
   194     pNewJoystick = (JoyStick_DeviceData *)SDL_malloc(sizeof(JoyStick_DeviceData));
   195     if (!pNewJoystick) {
   196         return; /* better luck next time? */
   197     }
   198     SDL_zerop(pNewJoystick);
   199 
   200     pNewJoystick->joystickname = GetXInputName(userid, SubType);
   201     if (!pNewJoystick->joystickname) {
   202         SDL_free(pNewJoystick);
   203         return; /* better luck next time? */
   204     }
   205 
   206     pNewJoystick->bXInputDevice = SDL_TRUE;
   207     if (SDL_XInputUseOldJoystickMapping()) {
   208         SDL_zero(pNewJoystick->guid);
   209     } else {
   210         const Uint16 BUS_USB = 0x03;
   211         Uint16 vendor = 0;
   212         Uint16 product = 0;
   213         Uint16 version = 0;
   214         Uint16 *guid16 = (Uint16 *)pNewJoystick->guid.data;
   215 
   216         GuessXInputDevice(userid, &vendor, &product, &version);
   217 
   218         *guid16++ = SDL_SwapLE16(BUS_USB);
   219         *guid16++ = 0;
   220         *guid16++ = SDL_SwapLE16(vendor);
   221         *guid16++ = 0;
   222         *guid16++ = SDL_SwapLE16(product);
   223         *guid16++ = 0;
   224         *guid16++ = SDL_SwapLE16(version);
   225         *guid16++ = 0;
   226 
   227         /* Note that this is an XInput device and what subtype it is */
   228         pNewJoystick->guid.data[14] = 'x';
   229         pNewJoystick->guid.data[15] = SubType;
   230     }
   231     pNewJoystick->SubType = SubType;
   232     pNewJoystick->XInputUserId = userid;
   233     SDL_SYS_AddJoystickDevice(pNewJoystick);
   234 }
   235 
   236 void
   237 SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
   238 {
   239     int iuserid;
   240 
   241     if (!s_bXInputEnabled) {
   242         return;
   243     }
   244 
   245     /* iterate in reverse, so these are in the final list in ascending numeric order. */
   246     for (iuserid = XUSER_MAX_COUNT - 1; iuserid >= 0; iuserid--) {
   247         const Uint8 userid = (Uint8)iuserid;
   248         XINPUT_CAPABILITIES capabilities;
   249         if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) {
   250             AddXInputDevice(userid, capabilities.SubType, pContext);
   251         }
   252     }
   253 }
   254 
   255 int
   256 SDL_XINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickdevice)
   257 {
   258     const Uint8 userId = joystickdevice->XInputUserId;
   259     XINPUT_CAPABILITIES capabilities;
   260     XINPUT_VIBRATION state;
   261 
   262     SDL_assert(s_bXInputEnabled);
   263     SDL_assert(XINPUTGETCAPABILITIES);
   264     SDL_assert(XINPUTSETSTATE);
   265     SDL_assert(userId < XUSER_MAX_COUNT);
   266 
   267     joystick->hwdata->bXInputDevice = SDL_TRUE;
   268 
   269     if (XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities) != ERROR_SUCCESS) {
   270         SDL_free(joystick->hwdata);
   271         joystick->hwdata = NULL;
   272         return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?");
   273     }
   274     SDL_zero(state);
   275     joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS);
   276     joystick->hwdata->userid = userId;
   277 
   278     /* The XInput API has a hard coded button/axis mapping, so we just match it */
   279     if (SDL_XInputUseOldJoystickMapping()) {
   280         joystick->naxes = 6;
   281         joystick->nbuttons = 15;
   282     } else {
   283         joystick->naxes = 6;
   284         joystick->nbuttons = 11;
   285         joystick->nhats = 1;
   286     }
   287     return 0;
   288 }
   289 
   290 static void 
   291 UpdateXInputJoystickBatteryInformation(SDL_Joystick * joystick, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)
   292 {
   293     if ( pBatteryInformation->BatteryType != BATTERY_TYPE_UNKNOWN )
   294     {
   295         SDL_JoystickPowerLevel ePowerLevel = SDL_JOYSTICK_POWER_UNKNOWN;
   296         if (pBatteryInformation->BatteryType == BATTERY_TYPE_WIRED) {
   297             ePowerLevel = SDL_JOYSTICK_POWER_WIRED;
   298         } else {
   299             switch ( pBatteryInformation->BatteryLevel )
   300             {
   301             case BATTERY_LEVEL_EMPTY:
   302                 ePowerLevel = SDL_JOYSTICK_POWER_EMPTY;
   303                 break;
   304             case BATTERY_LEVEL_LOW:
   305                 ePowerLevel = SDL_JOYSTICK_POWER_LOW;
   306                 break;
   307             case BATTERY_LEVEL_MEDIUM:
   308                 ePowerLevel = SDL_JOYSTICK_POWER_MEDIUM;
   309                 break;
   310             default:
   311             case BATTERY_LEVEL_FULL:
   312                 ePowerLevel = SDL_JOYSTICK_POWER_FULL;
   313                 break;
   314             }
   315         }
   316 
   317         SDL_PrivateJoystickBatteryLevel( joystick, ePowerLevel );
   318     }
   319 }
   320 
   321 static void
   322 UpdateXInputJoystickState_OLD(SDL_Joystick * joystick, XINPUT_STATE_EX *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)
   323 {
   324     static WORD s_XInputButtons[] = {
   325         XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_DPAD_DOWN, XINPUT_GAMEPAD_DPAD_LEFT, XINPUT_GAMEPAD_DPAD_RIGHT,
   326         XINPUT_GAMEPAD_START, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB,
   327         XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER,
   328         XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y,
   329         XINPUT_GAMEPAD_GUIDE
   330     };
   331     WORD wButtons = pXInputState->Gamepad.wButtons;
   332     Uint8 button;
   333 
   334     SDL_PrivateJoystickAxis(joystick, 0, (Sint16)pXInputState->Gamepad.sThumbLX);
   335     SDL_PrivateJoystickAxis(joystick, 1, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbLY)));
   336     SDL_PrivateJoystickAxis(joystick, 2, (Sint16)pXInputState->Gamepad.sThumbRX);
   337     SDL_PrivateJoystickAxis(joystick, 3, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbRY)));
   338     SDL_PrivateJoystickAxis(joystick, 4, (Sint16)(((int)pXInputState->Gamepad.bLeftTrigger * 65535 / 255) - 32768));
   339     SDL_PrivateJoystickAxis(joystick, 5, (Sint16)(((int)pXInputState->Gamepad.bRightTrigger * 65535 / 255) - 32768));
   340 
   341     for (button = 0; button < SDL_arraysize(s_XInputButtons); ++button) {
   342         SDL_PrivateJoystickButton(joystick, button, (wButtons & s_XInputButtons[button]) ? SDL_PRESSED : SDL_RELEASED);
   343     }
   344 
   345     UpdateXInputJoystickBatteryInformation( joystick, pBatteryInformation );
   346 }
   347 
   348 static void
   349 UpdateXInputJoystickState(SDL_Joystick * joystick, XINPUT_STATE_EX *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)
   350 {
   351     static WORD s_XInputButtons[] = {
   352         XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y,
   353         XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START,
   354         XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB,
   355         XINPUT_GAMEPAD_GUIDE
   356     };
   357     WORD wButtons = pXInputState->Gamepad.wButtons;
   358     Uint8 button;
   359     Uint8 hat = SDL_HAT_CENTERED;
   360 
   361     SDL_PrivateJoystickAxis(joystick, 0, (Sint16)pXInputState->Gamepad.sThumbLX);
   362     SDL_PrivateJoystickAxis(joystick, 1, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbLY)));
   363     SDL_PrivateJoystickAxis(joystick, 2, (Sint16)(((int)pXInputState->Gamepad.bLeftTrigger * 65535 / 255) - 32768));
   364     SDL_PrivateJoystickAxis(joystick, 3, (Sint16)pXInputState->Gamepad.sThumbRX);
   365     SDL_PrivateJoystickAxis(joystick, 4, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbRY)));
   366     SDL_PrivateJoystickAxis(joystick, 5, (Sint16)(((int)pXInputState->Gamepad.bRightTrigger * 65535 / 255) - 32768));
   367 
   368     for (button = 0; button < SDL_arraysize(s_XInputButtons); ++button) {
   369         SDL_PrivateJoystickButton(joystick, button, (wButtons & s_XInputButtons[button]) ? SDL_PRESSED : SDL_RELEASED);
   370     }
   371 
   372     if (wButtons & XINPUT_GAMEPAD_DPAD_UP) {
   373         hat |= SDL_HAT_UP;
   374     }
   375     if (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) {
   376         hat |= SDL_HAT_DOWN;
   377     }
   378     if (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) {
   379         hat |= SDL_HAT_LEFT;
   380     }
   381     if (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) {
   382         hat |= SDL_HAT_RIGHT;
   383     }
   384     SDL_PrivateJoystickHat(joystick, 0, hat);
   385 
   386     UpdateXInputJoystickBatteryInformation( joystick, pBatteryInformation );
   387 }
   388 
   389 void
   390 SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick)
   391 {
   392     HRESULT result;
   393     XINPUT_STATE_EX XInputState;
   394     XINPUT_BATTERY_INFORMATION_EX XBatteryInformation;
   395 
   396     if (!XINPUTGETSTATE)
   397         return;
   398 
   399     result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState);
   400     if (result == ERROR_DEVICE_NOT_CONNECTED) {
   401         joystick->hwdata->send_remove_event = SDL_TRUE;
   402         joystick->hwdata->removed = SDL_TRUE;
   403         return;
   404     }
   405 
   406     SDL_zero( XBatteryInformation );
   407     if ( XINPUTGETBATTERYINFORMATION )
   408     {
   409         result = XINPUTGETBATTERYINFORMATION( joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation );
   410     }
   411 
   412     /* only fire events if the data changed from last time */
   413     if (XInputState.dwPacketNumber && XInputState.dwPacketNumber != joystick->hwdata->dwPacketNumber) {
   414         if (SDL_XInputUseOldJoystickMapping()) {
   415             UpdateXInputJoystickState_OLD(joystick, &XInputState, &XBatteryInformation);
   416         } else {
   417             UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation);
   418         }
   419         joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber;
   420     }
   421 }
   422 
   423 void
   424 SDL_XINPUT_JoystickClose(SDL_Joystick * joystick)
   425 {
   426 }
   427 
   428 void
   429 SDL_XINPUT_JoystickQuit(void)
   430 {
   431     if (s_bXInputEnabled) {
   432         WIN_UnloadXInputDLL();
   433     }
   434 }
   435 
   436 SDL_bool
   437 SDL_SYS_IsXInputGamepad_DeviceIndex(int device_index)
   438 {
   439     JoyStick_DeviceData *device = SYS_Joystick;
   440     int index;
   441 
   442     for (index = device_index; index > 0; index--)
   443         device = device->pNext;
   444 
   445     return device->bXInputDevice;
   446 }
   447 
   448 #else /* !SDL_JOYSTICK_XINPUT */
   449 
   450 typedef struct JoyStick_DeviceData JoyStick_DeviceData;
   451 
   452 SDL_bool SDL_XINPUT_Enabled(void)
   453 {
   454     return SDL_FALSE;
   455 }
   456 
   457 int
   458 SDL_XINPUT_JoystickInit(void)
   459 {
   460     return 0;
   461 }
   462 
   463 void
   464 SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
   465 {
   466 }
   467 
   468 int
   469 SDL_XINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickdevice)
   470 {
   471     return SDL_Unsupported();
   472 }
   473 
   474 void
   475 SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick)
   476 {
   477 }
   478 
   479 void
   480 SDL_XINPUT_JoystickClose(SDL_Joystick * joystick)
   481 {
   482 }
   483 
   484 void
   485 SDL_XINPUT_JoystickQuit(void)
   486 {
   487 }
   488 
   489 #endif /* SDL_JOYSTICK_XINPUT */
   490 
   491 /* vi: set ts=4 sw=4 expandtab: */