Path: blob/master/thirdparty/sdl/joystick/windows/SDL_xinputjoystick.c
9905 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#include "../SDL_sysjoystick.h"2324#ifdef SDL_JOYSTICK_XINPUT2526#include "SDL_windowsjoystick_c.h"27#include "SDL_xinputjoystick_c.h"28#include "SDL_rawinputjoystick_c.h"29#include "../hidapi/SDL_hidapijoystick_c.h"3031// Set up for C function definitions, even when using C++32#ifdef __cplusplus33extern "C" {34#endif3536/*37* Internal stuff.38*/39static bool s_bXInputEnabled = false;4041bool SDL_XINPUT_Enabled(void)42{43return s_bXInputEnabled;44}4546bool SDL_XINPUT_JoystickInit(void)47{48bool enabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, true);4950if (enabled && !WIN_LoadXInputDLL()) {51enabled = false; // oh well.52}53s_bXInputEnabled = enabled;5455return true;56}5758static const char *GetXInputName(const Uint8 userid, BYTE SubType)59{60static char name[32];6162switch (SubType) {63case XINPUT_DEVSUBTYPE_GAMEPAD:64(void)SDL_snprintf(name, sizeof(name), "XInput Controller #%d", 1 + userid);65break;66case XINPUT_DEVSUBTYPE_WHEEL:67(void)SDL_snprintf(name, sizeof(name), "XInput Wheel #%d", 1 + userid);68break;69case XINPUT_DEVSUBTYPE_ARCADE_STICK:70(void)SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%d", 1 + userid);71break;72case XINPUT_DEVSUBTYPE_FLIGHT_STICK:73(void)SDL_snprintf(name, sizeof(name), "XInput FlightStick #%d", 1 + userid);74break;75case XINPUT_DEVSUBTYPE_DANCE_PAD:76(void)SDL_snprintf(name, sizeof(name), "XInput DancePad #%d", 1 + userid);77break;78case XINPUT_DEVSUBTYPE_GUITAR:79case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE:80case XINPUT_DEVSUBTYPE_GUITAR_BASS:81(void)SDL_snprintf(name, sizeof(name), "XInput Guitar #%d", 1 + userid);82break;83case XINPUT_DEVSUBTYPE_DRUM_KIT:84(void)SDL_snprintf(name, sizeof(name), "XInput DrumKit #%d", 1 + userid);85break;86case XINPUT_DEVSUBTYPE_ARCADE_PAD:87(void)SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%d", 1 + userid);88break;89default:90(void)SDL_snprintf(name, sizeof(name), "XInput Device #%d", 1 + userid);91break;92}93return name;94}9596static bool GetXInputDeviceInfo(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion)97{98SDL_XINPUT_CAPABILITIES_EX capabilities;99100if (!XINPUTGETCAPABILITIESEX || XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) != ERROR_SUCCESS) {101// Use a generic VID/PID representing an XInput controller102if (pVID) {103*pVID = USB_VENDOR_MICROSOFT;104}105if (pPID) {106*pPID = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;107}108return false;109}110111// Fixup for Wireless Xbox 360 Controller112if (capabilities.ProductId == 0 && capabilities.Capabilities.Flags & XINPUT_CAPS_WIRELESS) {113capabilities.VendorId = USB_VENDOR_MICROSOFT;114capabilities.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;115}116117if (pVID) {118*pVID = capabilities.VendorId;119}120if (pPID) {121*pPID = capabilities.ProductId;122}123if (pVersion) {124*pVersion = capabilities.ProductVersion;125}126return true;127}128129int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid)130{131SDL_XINPUT_CAPABILITIES_EX capabilities;132133if (XINPUTGETCAPABILITIESEX &&134XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) == ERROR_SUCCESS &&135capabilities.VendorId == USB_VENDOR_VALVE &&136capabilities.ProductId == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {137return (int)capabilities.unk2;138}139return -1;140}141142static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext)143{144const char *name = NULL;145Uint16 vendor = 0;146Uint16 product = 0;147Uint16 version = 0;148JoyStick_DeviceData *pPrevJoystick = NULL;149JoyStick_DeviceData *pNewJoystick = *pContext;150151#ifdef SDL_JOYSTICK_RAWINPUT152if (RAWINPUT_IsEnabled()) {153// The raw input driver handles more than 4 controllers, so prefer that when available154/* We do this check here rather than at the top of SDL_XINPUT_JoystickDetect() because155we need to check XInput state before RAWINPUT gets a hold of the device, otherwise156when a controller is connected via the wireless adapter, it will shut down at the157first subsequent XInput call. This seems like a driver stack bug?158159Reference: https://github.com/libsdl-org/SDL/issues/3468160*/161return;162}163#endif164165if (SubType == XINPUT_DEVSUBTYPE_UNKNOWN) {166return;167}168169while (pNewJoystick) {170if (pNewJoystick->bXInputDevice && (pNewJoystick->XInputUserId == userid) && (pNewJoystick->SubType == SubType)) {171// if we are replacing the front of the list then update it172if (pNewJoystick == *pContext) {173*pContext = pNewJoystick->pNext;174} else if (pPrevJoystick) {175pPrevJoystick->pNext = pNewJoystick->pNext;176}177178pNewJoystick->pNext = SYS_Joystick;179SYS_Joystick = pNewJoystick;180return; // already in the list.181}182183pPrevJoystick = pNewJoystick;184pNewJoystick = pNewJoystick->pNext;185}186187name = GetXInputName(userid, SubType);188GetXInputDeviceInfo(userid, &vendor, &product, &version);189if (SDL_ShouldIgnoreJoystick(vendor, product, version, name) ||190SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, name)) {191return;192}193194pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData));195if (!pNewJoystick) {196return; // better luck next time?197}198199pNewJoystick->bXInputDevice = true;200pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, NULL, name);201if (!pNewJoystick->joystickname) {202SDL_free(pNewJoystick);203return; // better luck next time?204}205(void)SDL_snprintf(pNewJoystick->path, sizeof(pNewJoystick->path), "XInput#%u", userid);206pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, NULL, name, 'x', SubType);207pNewJoystick->SubType = SubType;208pNewJoystick->XInputUserId = userid;209210WINDOWS_AddJoystickDevice(pNewJoystick);211}212213void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)214{215int iuserid;216217if (!s_bXInputEnabled) {218return;219}220221// iterate in reverse, so these are in the final list in ascending numeric order.222for (iuserid = XUSER_MAX_COUNT - 1; iuserid >= 0; iuserid--) {223const Uint8 userid = (Uint8)iuserid;224XINPUT_CAPABILITIES capabilities;225if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) {226AddXInputDevice(userid, capabilities.SubType, pContext);227}228}229}230231bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)232{233int iuserid;234235if (!s_bXInputEnabled) {236return false;237}238239// iterate in reverse, so these are in the final list in ascending numeric order.240for (iuserid = 0; iuserid < XUSER_MAX_COUNT; ++iuserid) {241const Uint8 userid = (Uint8)iuserid;242Uint16 slot_vendor;243Uint16 slot_product;244Uint16 slot_version;245if (GetXInputDeviceInfo(userid, &slot_vendor, &slot_product, &slot_version)) {246if (vendor == slot_vendor && product == slot_product && version == slot_version) {247return true;248}249}250}251return false;252}253254bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)255{256const Uint8 userId = joystickdevice->XInputUserId;257XINPUT_CAPABILITIES capabilities;258XINPUT_VIBRATION state;259260SDL_assert(s_bXInputEnabled);261SDL_assert(XINPUTGETCAPABILITIES);262SDL_assert(XINPUTSETSTATE);263SDL_assert(userId < XUSER_MAX_COUNT);264265joystick->hwdata->bXInputDevice = true;266267if (XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities) != ERROR_SUCCESS) {268SDL_free(joystick->hwdata);269joystick->hwdata = NULL;270return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?");271}272SDL_zero(state);273joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS);274joystick->hwdata->userid = userId;275276// The XInput API has a hard coded button/axis mapping, so we just match it277joystick->naxes = 6;278joystick->nbuttons = 11;279joystick->nhats = 1;280281SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);282283return true;284}285286static void UpdateXInputJoystickBatteryInformation(SDL_Joystick *joystick, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)287{288SDL_PowerState state;289int percent;290switch (pBatteryInformation->BatteryType) {291case BATTERY_TYPE_WIRED:292state = SDL_POWERSTATE_CHARGING;293break;294case BATTERY_TYPE_UNKNOWN:295case BATTERY_TYPE_DISCONNECTED:296state = SDL_POWERSTATE_UNKNOWN;297break;298default:299state = SDL_POWERSTATE_ON_BATTERY;300break;301}302switch (pBatteryInformation->BatteryLevel) {303case BATTERY_LEVEL_EMPTY:304percent = 10;305break;306case BATTERY_LEVEL_LOW:307percent = 40;308break;309case BATTERY_LEVEL_MEDIUM:310percent = 70;311break;312default:313case BATTERY_LEVEL_FULL:314percent = 100;315break;316}317SDL_SendJoystickPowerInfo(joystick, state, percent);318}319320static void UpdateXInputJoystickState(SDL_Joystick *joystick, XINPUT_STATE *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation)321{322static WORD s_XInputButtons[] = {323XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y,324XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START,325XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB,326XINPUT_GAMEPAD_GUIDE327};328WORD wButtons = pXInputState->Gamepad.wButtons;329Uint8 button;330Uint8 hat = SDL_HAT_CENTERED;331Uint64 timestamp = SDL_GetTicksNS();332333SDL_SendJoystickAxis(timestamp, joystick, 0, pXInputState->Gamepad.sThumbLX);334SDL_SendJoystickAxis(timestamp, joystick, 1, ~pXInputState->Gamepad.sThumbLY);335SDL_SendJoystickAxis(timestamp, joystick, 2, ((int)pXInputState->Gamepad.bLeftTrigger * 257) - 32768);336SDL_SendJoystickAxis(timestamp, joystick, 3, pXInputState->Gamepad.sThumbRX);337SDL_SendJoystickAxis(timestamp, joystick, 4, ~pXInputState->Gamepad.sThumbRY);338SDL_SendJoystickAxis(timestamp, joystick, 5, ((int)pXInputState->Gamepad.bRightTrigger * 257) - 32768);339340for (button = 0; button < (Uint8)SDL_arraysize(s_XInputButtons); ++button) {341bool down = ((wButtons & s_XInputButtons[button]) != 0);342SDL_SendJoystickButton(timestamp, joystick, button, down);343}344345if (wButtons & XINPUT_GAMEPAD_DPAD_UP) {346hat |= SDL_HAT_UP;347}348if (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) {349hat |= SDL_HAT_DOWN;350}351if (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) {352hat |= SDL_HAT_LEFT;353}354if (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) {355hat |= SDL_HAT_RIGHT;356}357SDL_SendJoystickHat(timestamp, joystick, 0, hat);358359UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation);360}361362bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)363{364XINPUT_VIBRATION XVibration;365366if (!XINPUTSETSTATE) {367return SDL_Unsupported();368}369370XVibration.wLeftMotorSpeed = low_frequency_rumble;371XVibration.wRightMotorSpeed = high_frequency_rumble;372if (XINPUTSETSTATE(joystick->hwdata->userid, &XVibration) != ERROR_SUCCESS) {373return SDL_SetError("XInputSetState() failed");374}375return true;376}377378void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick)379{380DWORD result;381XINPUT_STATE XInputState;382XINPUT_BATTERY_INFORMATION_EX XBatteryInformation;383384if (!XINPUTGETSTATE) {385return;386}387388result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState);389if (result == ERROR_DEVICE_NOT_CONNECTED) {390return;391}392393SDL_zero(XBatteryInformation);394if (XINPUTGETBATTERYINFORMATION) {395result = XINPUTGETBATTERYINFORMATION(joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation);396}397398#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)399// XInputOnGameInput doesn't ever change dwPacketNumber, so have to just update every frame400UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation);401#else402// only fire events if the data changed from last time403if (XInputState.dwPacketNumber && XInputState.dwPacketNumber != joystick->hwdata->dwPacketNumber) {404UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation);405joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber;406}407#endif408}409410void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick)411{412}413414void SDL_XINPUT_JoystickQuit(void)415{416if (s_bXInputEnabled) {417s_bXInputEnabled = false;418WIN_UnloadXInputDLL();419}420}421422// Ends C function definitions when using C++423#ifdef __cplusplus424}425#endif426427#else // !SDL_JOYSTICK_XINPUT428429typedef struct JoyStick_DeviceData JoyStick_DeviceData;430431bool SDL_XINPUT_Enabled(void)432{433return false;434}435436bool SDL_XINPUT_JoystickInit(void)437{438return true;439}440441void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext)442{443}444445bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version)446{447return false;448}449450bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice)451{452return SDL_Unsupported();453}454455bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)456{457return SDL_Unsupported();458}459460void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick)461{462}463464void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick)465{466}467468void SDL_XINPUT_JoystickQuit(void)469{470}471472#endif // SDL_JOYSTICK_XINPUT473474475