src/joystick/windows/SDL_xinputjoystick.c
author Sam Lantinga <slouken@libsdl.org>
Wed, 30 Sep 2015 15:39:30 -0700
changeset 9884 26b595dea221
parent 9660 b7188b6a6f2a
child 9918 31b7adf67756
permissions -rw-r--r--
SDL - added new SDL_JoystickCurrentPowerLevel() API that returns the battery level of the selected joystick. Currently only implemented for XInput devices, other platforms are a TODO.

CR: Sam
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2015 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     static int s_XInputUseOldJoystickMapping = -1;
    42     if (s_XInputUseOldJoystickMapping < 0) {
    43         const char *hint = SDL_GetHint(SDL_HINT_XINPUT_USE_OLD_JOYSTICK_MAPPING);
    44         s_XInputUseOldJoystickMapping = (hint && *hint == '1') ? 1 : 0;
    45     }
    46     return (s_XInputUseOldJoystickMapping > 0);
    47 }
    48 
    49 SDL_bool SDL_XINPUT_Enabled(void)
    50 {
    51     return s_bXInputEnabled;
    52 }
    53 
    54 int
    55 SDL_XINPUT_JoystickInit(void)
    56 {
    57     const char *env = SDL_GetHint(SDL_HINT_XINPUT_ENABLED);
    58     if (env && !SDL_atoi(env)) {
    59         s_bXInputEnabled = SDL_FALSE;
    60     }
    61 
    62     if (s_bXInputEnabled && WIN_LoadXInputDLL() < 0) {
    63         s_bXInputEnabled = SDL_FALSE;  /* oh well. */
    64     }
    65     return 0;
    66 }
    67 
    68 static char *
    69 GetXInputName(const Uint8 userid, BYTE SubType)
    70 {
    71     char name[32];
    72 
    73     if (SDL_XInputUseOldJoystickMapping()) {
    74         SDL_snprintf(name, sizeof(name), "X360 Controller #%u", 1 + userid);
    75     } else {
    76         switch (SubType) {
    77         case XINPUT_DEVSUBTYPE_GAMEPAD:
    78             SDL_snprintf(name, sizeof(name), "XInput Controller #%u", 1 + userid);
    79             break;
    80         case XINPUT_DEVSUBTYPE_WHEEL:
    81             SDL_snprintf(name, sizeof(name), "XInput Wheel #%u", 1 + userid);
    82             break;
    83         case XINPUT_DEVSUBTYPE_ARCADE_STICK:
    84             SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%u", 1 + userid);
    85             break;
    86         case XINPUT_DEVSUBTYPE_FLIGHT_STICK:
    87             SDL_snprintf(name, sizeof(name), "XInput FlightStick #%u", 1 + userid);
    88             break;
    89         case XINPUT_DEVSUBTYPE_DANCE_PAD:
    90             SDL_snprintf(name, sizeof(name), "XInput DancePad #%u", 1 + userid);
    91             break;
    92         case XINPUT_DEVSUBTYPE_GUITAR:
    93         case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE:
    94         case XINPUT_DEVSUBTYPE_GUITAR_BASS:
    95             SDL_snprintf(name, sizeof(name), "XInput Guitar #%u", 1 + userid);
    96             break;
    97         case XINPUT_DEVSUBTYPE_DRUM_KIT:
    98             SDL_snprintf(name, sizeof(name), "XInput DrumKit #%u", 1 + userid);
    99             break;
   100         case XINPUT_DEVSUBTYPE_ARCADE_PAD:
   101             SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%u", 1 + userid);
   102             break;
   103         default:
   104             SDL_snprintf(name, sizeof(name), "XInput Device #%u", 1 + userid);
   105             break;
   106         }
   107     }
   108     return SDL_strdup(name);
   109 }
   110 
   111 static void
   112 AddXInputDevice(const Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext)
   113 {
   114     JoyStick_DeviceData *pPrevJoystick = NULL;
   115     JoyStick_DeviceData *pNewJoystick = *pContext;
   116 
   117     if (SDL_XInputUseOldJoystickMapping() && SubType != XINPUT_DEVSUBTYPE_GAMEPAD)
   118         return;
   119 
   120     if (SubType == XINPUT_DEVSUBTYPE_UNKNOWN)
   121         return;
   122 
   123     while (pNewJoystick) {
   124         if (pNewJoystick->bXInputDevice && (pNewJoystick->XInputUserId == userid) && (pNewJoystick->SubType == SubType)) {
   125             /* if we are replacing the front of the list then update it */
   126             if (pNewJoystick == *pContext) {
   127                 *pContext = pNewJoystick->pNext;
   128             } else if (pPrevJoystick) {
   129                 pPrevJoystick->pNext = pNewJoystick->pNext;
   130             }
   131 
   132             pNewJoystick->pNext = SYS_Joystick;
   133             SYS_Joystick = pNewJoystick;
   134             return;   /* already in the list. */
   135         }
   136 
   137         pPrevJoystick = pNewJoystick;
   138         pNewJoystick = pNewJoystick->pNext;
   139     }
   140 
   141     pNewJoystick = (JoyStick_DeviceData *)SDL_malloc(sizeof(JoyStick_DeviceData));
   142     if (!pNewJoystick) {
   143         return; /* better luck next time? */
   144     }
   145     SDL_zerop(pNewJoystick);
   146 
   147     pNewJoystick->joystickname = GetXInputName(userid, SubType);
   148     if (!pNewJoystick->joystickname) {
   149         SDL_free(pNewJoystick);
   150         return; /* better luck next time? */
   151     }
   152 
   153     pNewJoystick->bXInputDevice = SDL_TRUE;
   154     if (SDL_XInputUseOldJoystickMapping()) {
   155         SDL_zero(pNewJoystick->guid);
   156     } else {
   157         pNewJoystick->guid.data[0] = 'x';
   158         pNewJoystick->guid.data[1] = 'i';
   159         pNewJoystick->guid.data[2] = 'n';
   160         pNewJoystick->guid.data[3] = 'p';
   161         pNewJoystick->guid.data[4] = 'u';
   162         pNewJoystick->guid.data[5] = 't';
   163         pNewJoystick->guid.data[6] = SubType;
   164     }
   165     pNewJoystick->SubType = SubType;
   166     pNewJoystick->XInputUserId = userid;
   167     SDL_SYS_AddJoystickDevice(pNewJoystick);
   168 }
   169 
   170 void
   171 SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
   172 {
   173     int iuserid;
   174 
   175     if (!s_bXInputEnabled) {
   176         return;
   177     }
   178 
   179     /* iterate in reverse, so these are in the final list in ascending numeric order. */
   180     for (iuserid = XUSER_MAX_COUNT - 1; iuserid >= 0; iuserid--) {
   181         const Uint8 userid = (Uint8)iuserid;
   182         XINPUT_CAPABILITIES capabilities;
   183         if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) {
   184             AddXInputDevice(userid, capabilities.SubType, pContext);
   185         }
   186     }
   187 }
   188 
   189 int
   190 SDL_XINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickdevice)
   191 {
   192     const Uint8 userId = joystickdevice->XInputUserId;
   193     XINPUT_CAPABILITIES capabilities;
   194     XINPUT_VIBRATION state;
   195 
   196     SDL_assert(s_bXInputEnabled);
   197     SDL_assert(XINPUTGETCAPABILITIES);
   198     SDL_assert(XINPUTSETSTATE);
   199     SDL_assert(userId < XUSER_MAX_COUNT);
   200 
   201     joystick->hwdata->bXInputDevice = SDL_TRUE;
   202 
   203     if (XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities) != ERROR_SUCCESS) {
   204         SDL_free(joystick->hwdata);
   205         joystick->hwdata = NULL;
   206         return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?");
   207     }
   208     SDL_zero(state);
   209     joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS);
   210     joystick->hwdata->userid = userId;
   211 
   212     /* The XInput API has a hard coded button/axis mapping, so we just match it */
   213     if (SDL_XInputUseOldJoystickMapping()) {
   214         joystick->naxes = 6;
   215         joystick->nbuttons = 15;
   216     } else {
   217         joystick->naxes = 6;
   218         joystick->nbuttons = 11;
   219         joystick->nhats = 1;
   220     }
   221     return 0;
   222 }
   223 
   224 static void 
   225 UpdateXInputJoystickBatteryInformation(SDL_Joystick * joystick, XINPUT_BATTERY_INFORMATION *pBatteryInformation)
   226 {
   227     if ( pBatteryInformation->BatteryType != BATTERY_TYPE_UNKNOWN )
   228     {
   229         SDL_JoystickPowerLevel ePowerLevel = SDL_JOYSTICK_POWER_UNKNOWN;
   230         if (pBatteryInformation->BatteryType == BATTERY_TYPE_WIRED) {
   231             ePowerLevel = SDL_JOYSTICK_POWER_WIRED;
   232         } else {
   233             switch ( pBatteryInformation->BatteryLevel )
   234             {
   235             case BATTERY_LEVEL_EMPTY:
   236                 ePowerLevel = SDL_JOYSTICK_POWER_EMPTY;
   237                 break;
   238             case BATTERY_LEVEL_LOW:
   239                 ePowerLevel = SDL_JOYSTICK_POWER_LOW;
   240                 break;
   241             case BATTERY_LEVEL_MEDIUM:
   242                 ePowerLevel = SDL_JOYSTICK_POWER_MEDIUM;
   243                 break;
   244             default:
   245             case BATTERY_LEVEL_FULL:
   246                 ePowerLevel = SDL_JOYSTICK_POWER_FULL;
   247                 break;
   248             }
   249         }
   250 
   251         SDL_PrivateJoystickBatteryLevel( joystick, ePowerLevel );
   252     }
   253 }
   254 
   255 static void
   256 UpdateXInputJoystickState_OLD(SDL_Joystick * joystick, XINPUT_STATE_EX *pXInputState, XINPUT_BATTERY_INFORMATION *pBatteryInformation)
   257 {
   258     static WORD s_XInputButtons[] = {
   259         XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_DPAD_DOWN, XINPUT_GAMEPAD_DPAD_LEFT, XINPUT_GAMEPAD_DPAD_RIGHT,
   260         XINPUT_GAMEPAD_START, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB,
   261         XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER,
   262         XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y,
   263         XINPUT_GAMEPAD_GUIDE
   264     };
   265     WORD wButtons = pXInputState->Gamepad.wButtons;
   266     Uint8 button;
   267 
   268     SDL_PrivateJoystickAxis(joystick, 0, (Sint16)pXInputState->Gamepad.sThumbLX);
   269     SDL_PrivateJoystickAxis(joystick, 1, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbLY)));
   270     SDL_PrivateJoystickAxis(joystick, 2, (Sint16)pXInputState->Gamepad.sThumbRX);
   271     SDL_PrivateJoystickAxis(joystick, 3, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbRY)));
   272     SDL_PrivateJoystickAxis(joystick, 4, (Sint16)(((int)pXInputState->Gamepad.bLeftTrigger * 65535 / 255) - 32768));
   273     SDL_PrivateJoystickAxis(joystick, 5, (Sint16)(((int)pXInputState->Gamepad.bRightTrigger * 65535 / 255) - 32768));
   274 
   275     for (button = 0; button < SDL_arraysize(s_XInputButtons); ++button) {
   276         SDL_PrivateJoystickButton(joystick, button, (wButtons & s_XInputButtons[button]) ? SDL_PRESSED : SDL_RELEASED);
   277     }
   278 
   279     UpdateXInputJoystickBatteryInformation( joystick, pBatteryInformation );
   280 }
   281 
   282 static void
   283 UpdateXInputJoystickState(SDL_Joystick * joystick, XINPUT_STATE_EX *pXInputState, XINPUT_BATTERY_INFORMATION *pBatteryInformation)
   284 {
   285     static WORD s_XInputButtons[] = {
   286         XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y,
   287         XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START,
   288         XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB,
   289         XINPUT_GAMEPAD_GUIDE
   290     };
   291     WORD wButtons = pXInputState->Gamepad.wButtons;
   292     Uint8 button;
   293     Uint8 hat = SDL_HAT_CENTERED;
   294 
   295     SDL_PrivateJoystickAxis(joystick, 0, (Sint16)pXInputState->Gamepad.sThumbLX);
   296     SDL_PrivateJoystickAxis(joystick, 1, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbLY)));
   297     SDL_PrivateJoystickAxis(joystick, 2, (Sint16)(((int)pXInputState->Gamepad.bLeftTrigger * 65535 / 255) - 32768));
   298     SDL_PrivateJoystickAxis(joystick, 3, (Sint16)pXInputState->Gamepad.sThumbRX);
   299     SDL_PrivateJoystickAxis(joystick, 4, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbRY)));
   300     SDL_PrivateJoystickAxis(joystick, 5, (Sint16)(((int)pXInputState->Gamepad.bRightTrigger * 65535 / 255) - 32768));
   301 
   302     for (button = 0; button < SDL_arraysize(s_XInputButtons); ++button) {
   303         SDL_PrivateJoystickButton(joystick, button, (wButtons & s_XInputButtons[button]) ? SDL_PRESSED : SDL_RELEASED);
   304     }
   305 
   306     if (wButtons & XINPUT_GAMEPAD_DPAD_UP) {
   307         hat |= SDL_HAT_UP;
   308     }
   309     if (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) {
   310         hat |= SDL_HAT_DOWN;
   311     }
   312     if (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) {
   313         hat |= SDL_HAT_LEFT;
   314     }
   315     if (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) {
   316         hat |= SDL_HAT_RIGHT;
   317     }
   318     SDL_PrivateJoystickHat(joystick, 0, hat);
   319 
   320     UpdateXInputJoystickBatteryInformation( joystick, pBatteryInformation );
   321 }
   322 
   323 void
   324 SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick)
   325 {
   326     HRESULT result;
   327     XINPUT_STATE_EX XInputState;
   328     XINPUT_BATTERY_INFORMATION XBatteryInformation;
   329 
   330     if (!XINPUTGETSTATE)
   331         return;
   332 
   333     result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState);
   334     if (result == ERROR_DEVICE_NOT_CONNECTED) {
   335         joystick->hwdata->send_remove_event = SDL_TRUE;
   336         joystick->hwdata->removed = SDL_TRUE;
   337         return;
   338     }
   339 
   340     SDL_zero( XBatteryInformation );
   341     if ( XINPUTGETBATTERYINFORMATION )
   342     {
   343         result = XINPUTGETBATTERYINFORMATION( joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation );
   344     }
   345 
   346     /* only fire events if the data changed from last time */
   347     if (XInputState.dwPacketNumber && XInputState.dwPacketNumber != joystick->hwdata->dwPacketNumber) {
   348         if (SDL_XInputUseOldJoystickMapping()) {
   349             UpdateXInputJoystickState_OLD(joystick, &XInputState, &XBatteryInformation);
   350         } else {
   351             UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation);
   352         }
   353         joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber;
   354     }
   355 }
   356 
   357 void
   358 SDL_XINPUT_JoystickClose(SDL_Joystick * joystick)
   359 {
   360 }
   361 
   362 void
   363 SDL_XINPUT_JoystickQuit(void)
   364 {
   365     if (s_bXInputEnabled) {
   366         WIN_UnloadXInputDLL();
   367     }
   368 }
   369 
   370 SDL_bool
   371 SDL_SYS_IsXInputGamepad_DeviceIndex(int device_index)
   372 {
   373     JoyStick_DeviceData *device = SYS_Joystick;
   374     int index;
   375 
   376     for (index = device_index; index > 0; index--)
   377         device = device->pNext;
   378 
   379     return (device->SubType == XINPUT_DEVSUBTYPE_GAMEPAD);
   380 }
   381 
   382 #else /* !SDL_JOYSTICK_XINPUT */
   383 
   384 typedef struct JoyStick_DeviceData JoyStick_DeviceData;
   385 
   386 SDL_bool SDL_XINPUT_Enabled(void)
   387 {
   388     return SDL_FALSE;
   389 }
   390 
   391 int
   392 SDL_XINPUT_JoystickInit(void)
   393 {
   394     return 0;
   395 }
   396 
   397 void
   398 SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)
   399 {
   400 }
   401 
   402 int
   403 SDL_XINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickdevice)
   404 {
   405     return SDL_Unsupported();
   406 }
   407 
   408 void
   409 SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick)
   410 {
   411 }
   412 
   413 void
   414 SDL_XINPUT_JoystickClose(SDL_Joystick * joystick)
   415 {
   416 }
   417 
   418 void
   419 SDL_XINPUT_JoystickQuit(void)
   420 {
   421 }
   422 
   423 #endif /* SDL_JOYSTICK_XINPUT */
   424 
   425 /* vi: set ts=4 sw=4 expandtab: */