Atlas - SDL_xinputjoystick.c
Home / ext / SDL / src / joystick / windows Lines: 1 | Size: 15027 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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#ifdef SDL_JOYSTICK_XINPUT 26 27#include "SDL_windowsjoystick_c.h" 28#include "SDL_xinputjoystick_c.h" 29#include "SDL_rawinputjoystick_c.h" 30#include "../hidapi/SDL_hidapijoystick_c.h" 31 32// Set up for C function definitions, even when using C++ 33#ifdef __cplusplus 34extern "C" { 35#endif 36 37/* 38 * Internal stuff. 39 */ 40static bool s_bXInputEnabled = false; 41 42bool SDL_XINPUT_Enabled(void) 43{ 44 return s_bXInputEnabled; 45} 46 47bool SDL_XINPUT_JoystickInit(void) 48{ 49 bool enabled = SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, true); 50 51 if (enabled && !WIN_LoadXInputDLL()) { 52 enabled = false; // oh well. 53 } 54 s_bXInputEnabled = enabled; 55 56 return true; 57} 58 59static const char *GetXInputName(const Uint8 userid, BYTE SubType) 60{ 61 static char name[32]; 62 63 switch (SubType) { 64 case XINPUT_DEVSUBTYPE_GAMEPAD: 65 (void)SDL_snprintf(name, sizeof(name), "XInput Controller #%d", 1 + userid); 66 break; 67 case XINPUT_DEVSUBTYPE_WHEEL: 68 (void)SDL_snprintf(name, sizeof(name), "XInput Wheel #%d", 1 + userid); 69 break; 70 case XINPUT_DEVSUBTYPE_ARCADE_STICK: 71 (void)SDL_snprintf(name, sizeof(name), "XInput ArcadeStick #%d", 1 + userid); 72 break; 73 case XINPUT_DEVSUBTYPE_FLIGHT_STICK: 74 (void)SDL_snprintf(name, sizeof(name), "XInput FlightStick #%d", 1 + userid); 75 break; 76 case XINPUT_DEVSUBTYPE_DANCE_PAD: 77 (void)SDL_snprintf(name, sizeof(name), "XInput DancePad #%d", 1 + userid); 78 break; 79 case XINPUT_DEVSUBTYPE_GUITAR: 80 case XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE: 81 case XINPUT_DEVSUBTYPE_GUITAR_BASS: 82 (void)SDL_snprintf(name, sizeof(name), "XInput Guitar #%d", 1 + userid); 83 break; 84 case XINPUT_DEVSUBTYPE_DRUM_KIT: 85 (void)SDL_snprintf(name, sizeof(name), "XInput DrumKit #%d", 1 + userid); 86 break; 87 case XINPUT_DEVSUBTYPE_ARCADE_PAD: 88 (void)SDL_snprintf(name, sizeof(name), "XInput ArcadePad #%d", 1 + userid); 89 break; 90 default: 91 (void)SDL_snprintf(name, sizeof(name), "XInput Device #%d", 1 + userid); 92 break; 93 } 94 return name; 95} 96 97static bool GetXInputDeviceInfo(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion) 98{ 99 SDL_XINPUT_CAPABILITIES_EX capabilities; 100 101 if (!XINPUTGETCAPABILITIESEX || XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) != ERROR_SUCCESS) { 102 // Use a generic VID/PID representing an XInput controller 103 if (pVID) { 104 *pVID = USB_VENDOR_MICROSOFT; 105 } 106 if (pPID) { 107 *pPID = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; 108 } 109 return false; 110 } 111 112 // Fixup for Wireless Xbox 360 Controller 113 if (capabilities.ProductId == 0 && capabilities.Capabilities.Flags & XINPUT_CAPS_WIRELESS) { 114 capabilities.VendorId = USB_VENDOR_MICROSOFT; 115 capabilities.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; 116 } 117 118 if (pVID) { 119 *pVID = capabilities.VendorId; 120 } 121 if (pPID) { 122 *pPID = capabilities.ProductId; 123 } 124 if (pVersion) { 125 *pVersion = capabilities.ProductVersion; 126 } 127 return true; 128} 129 130int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid) 131{ 132 SDL_XINPUT_CAPABILITIES_EX capabilities; 133 134 if (XINPUTGETCAPABILITIESEX && 135 XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) == ERROR_SUCCESS && 136 capabilities.VendorId == USB_VENDOR_VALVE && 137 capabilities.ProductId == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { 138 return (int)capabilities.unk2; 139 } 140 return -1; 141} 142 143static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext) 144{ 145 const char *name = NULL; 146 Uint16 vendor = 0; 147 Uint16 product = 0; 148 Uint16 version = 0; 149 JoyStick_DeviceData *pPrevJoystick = NULL; 150 JoyStick_DeviceData *pNewJoystick = *pContext; 151 152#ifdef SDL_JOYSTICK_RAWINPUT 153 if (RAWINPUT_IsEnabled()) { 154 // The raw input driver handles more than 4 controllers, so prefer that when available 155 /* We do this check here rather than at the top of SDL_XINPUT_JoystickDetect() because 156 we need to check XInput state before RAWINPUT gets a hold of the device, otherwise 157 when a controller is connected via the wireless adapter, it will shut down at the 158 first subsequent XInput call. This seems like a driver stack bug? 159 160 Reference: https://github.com/libsdl-org/SDL/issues/3468 161 */ 162 return; 163 } 164#endif 165 166 if (SubType == XINPUT_DEVSUBTYPE_UNKNOWN) { 167 return; 168 } 169 170 while (pNewJoystick) { 171 if (pNewJoystick->bXInputDevice && (pNewJoystick->XInputUserId == userid) && (pNewJoystick->SubType == SubType)) { 172 // if we are replacing the front of the list then update it 173 if (pNewJoystick == *pContext) { 174 *pContext = pNewJoystick->pNext; 175 } else if (pPrevJoystick) { 176 pPrevJoystick->pNext = pNewJoystick->pNext; 177 } 178 179 pNewJoystick->pNext = SYS_Joystick; 180 SYS_Joystick = pNewJoystick; 181 return; // already in the list. 182 } 183 184 pPrevJoystick = pNewJoystick; 185 pNewJoystick = pNewJoystick->pNext; 186 } 187 188 name = GetXInputName(userid, SubType); 189 GetXInputDeviceInfo(userid, &vendor, &product, &version); 190 if (SDL_ShouldIgnoreJoystick(vendor, product, version, name) || 191 SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, name)) { 192 return; 193 } 194 195 pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData)); 196 if (!pNewJoystick) { 197 return; // better luck next time? 198 } 199 200 pNewJoystick->bXInputDevice = true; 201 pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, NULL, name); 202 if (!pNewJoystick->joystickname) { 203 SDL_free(pNewJoystick); 204 return; // better luck next time? 205 } 206 (void)SDL_snprintf(pNewJoystick->path, sizeof(pNewJoystick->path), "XInput#%u", userid); 207 pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, NULL, name, 'x', SubType); 208 pNewJoystick->SubType = SubType; 209 pNewJoystick->XInputUserId = userid; 210 211 WINDOWS_AddJoystickDevice(pNewJoystick); 212} 213 214void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) 215{ 216 int iuserid; 217 218 if (!s_bXInputEnabled) { 219 return; 220 } 221 222 // iterate in reverse, so these are in the final list in ascending numeric order. 223 for (iuserid = XUSER_MAX_COUNT - 1; iuserid >= 0; iuserid--) { 224 const Uint8 userid = (Uint8)iuserid; 225 XINPUT_CAPABILITIES capabilities; 226 if (XINPUTGETCAPABILITIES(userid, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) { 227 AddXInputDevice(userid, capabilities.SubType, pContext); 228 } 229 } 230} 231 232bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version) 233{ 234 int iuserid; 235 236 if (!s_bXInputEnabled) { 237 return false; 238 } 239 240 // iterate in reverse, so these are in the final list in ascending numeric order. 241 for (iuserid = 0; iuserid < XUSER_MAX_COUNT; ++iuserid) { 242 const Uint8 userid = (Uint8)iuserid; 243 Uint16 slot_vendor; 244 Uint16 slot_product; 245 Uint16 slot_version; 246 if (GetXInputDeviceInfo(userid, &slot_vendor, &slot_product, &slot_version)) { 247 if (vendor == slot_vendor && product == slot_product && version == slot_version) { 248 return true; 249 } 250 } 251 } 252 return false; 253} 254 255bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice) 256{ 257 const Uint8 userId = joystickdevice->XInputUserId; 258 XINPUT_CAPABILITIES capabilities; 259 XINPUT_VIBRATION state; 260 261 SDL_assert(s_bXInputEnabled); 262 SDL_assert(XINPUTGETCAPABILITIES); 263 SDL_assert(XINPUTSETSTATE); 264 SDL_assert(userId < XUSER_MAX_COUNT); 265 266 joystick->hwdata->bXInputDevice = true; 267 268 if (XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities) != ERROR_SUCCESS) { 269 SDL_free(joystick->hwdata); 270 joystick->hwdata = NULL; 271 return SDL_SetError("Failed to obtain XInput device capabilities. Device disconnected?"); 272 } 273 SDL_zero(state); 274 joystick->hwdata->bXInputHaptic = (XINPUTSETSTATE(userId, &state) == ERROR_SUCCESS); 275 joystick->hwdata->userid = userId; 276 277 // The XInput API has a hard coded button/axis mapping, so we just match it 278 joystick->naxes = 6; 279 joystick->nbuttons = 11; 280 joystick->nhats = 1; 281 282 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); 283 284 return true; 285} 286 287static void UpdateXInputJoystickBatteryInformation(SDL_Joystick *joystick, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) 288{ 289 SDL_PowerState state; 290 int percent; 291 switch (pBatteryInformation->BatteryType) { 292 case BATTERY_TYPE_WIRED: 293 state = SDL_POWERSTATE_CHARGING; 294 break; 295 case BATTERY_TYPE_UNKNOWN: 296 case BATTERY_TYPE_DISCONNECTED: 297 state = SDL_POWERSTATE_UNKNOWN; 298 break; 299 default: 300 state = SDL_POWERSTATE_ON_BATTERY; 301 break; 302 } 303 switch (pBatteryInformation->BatteryLevel) { 304 case BATTERY_LEVEL_EMPTY: 305 percent = 10; 306 break; 307 case BATTERY_LEVEL_LOW: 308 percent = 40; 309 break; 310 case BATTERY_LEVEL_MEDIUM: 311 percent = 70; 312 break; 313 default: 314 case BATTERY_LEVEL_FULL: 315 percent = 100; 316 break; 317 } 318 SDL_SendJoystickPowerInfo(joystick, state, percent); 319} 320 321static void UpdateXInputJoystickState(SDL_Joystick *joystick, XINPUT_STATE *pXInputState, XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation) 322{ 323 static WORD s_XInputButtons[] = { 324 XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y, 325 XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START, 326 XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB, 327 XINPUT_GAMEPAD_GUIDE 328 }; 329 WORD wButtons = pXInputState->Gamepad.wButtons; 330 Uint8 button; 331 Uint8 hat = SDL_HAT_CENTERED; 332 Uint64 timestamp = SDL_GetTicksNS(); 333 334 SDL_SendJoystickAxis(timestamp, joystick, 0, pXInputState->Gamepad.sThumbLX); 335 SDL_SendJoystickAxis(timestamp, joystick, 1, ~pXInputState->Gamepad.sThumbLY); 336 SDL_SendJoystickAxis(timestamp, joystick, 2, ((int)pXInputState->Gamepad.bLeftTrigger * 257) - 32768); 337 SDL_SendJoystickAxis(timestamp, joystick, 3, pXInputState->Gamepad.sThumbRX); 338 SDL_SendJoystickAxis(timestamp, joystick, 4, ~pXInputState->Gamepad.sThumbRY); 339 SDL_SendJoystickAxis(timestamp, joystick, 5, ((int)pXInputState->Gamepad.bRightTrigger * 257) - 32768); 340 341 for (button = 0; button < (Uint8)SDL_arraysize(s_XInputButtons); ++button) { 342 bool down = ((wButtons & s_XInputButtons[button]) != 0); 343 SDL_SendJoystickButton(timestamp, joystick, button, down); 344 } 345 346 if (wButtons & XINPUT_GAMEPAD_DPAD_UP) { 347 hat |= SDL_HAT_UP; 348 } 349 if (wButtons & XINPUT_GAMEPAD_DPAD_DOWN) { 350 hat |= SDL_HAT_DOWN; 351 } 352 if (wButtons & XINPUT_GAMEPAD_DPAD_LEFT) { 353 hat |= SDL_HAT_LEFT; 354 } 355 if (wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) { 356 hat |= SDL_HAT_RIGHT; 357 } 358 SDL_SendJoystickHat(timestamp, joystick, 0, hat); 359 360 UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation); 361} 362 363bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 364{ 365 XINPUT_VIBRATION XVibration; 366 367 if (!XINPUTSETSTATE) { 368 return SDL_Unsupported(); 369 } 370 371 XVibration.wLeftMotorSpeed = low_frequency_rumble; 372 XVibration.wRightMotorSpeed = high_frequency_rumble; 373 if (XINPUTSETSTATE(joystick->hwdata->userid, &XVibration) != ERROR_SUCCESS) { 374 return SDL_SetError("XInputSetState() failed"); 375 } 376 return true; 377} 378 379void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick) 380{ 381 DWORD result; 382 XINPUT_STATE XInputState; 383 XINPUT_BATTERY_INFORMATION_EX XBatteryInformation; 384 385 if (!XINPUTGETSTATE) { 386 return; 387 } 388 389 result = XINPUTGETSTATE(joystick->hwdata->userid, &XInputState); 390 if (result == ERROR_DEVICE_NOT_CONNECTED) { 391 return; 392 } 393 394 SDL_zero(XBatteryInformation); 395 if (XINPUTGETBATTERYINFORMATION) { 396 result = XINPUTGETBATTERYINFORMATION(joystick->hwdata->userid, BATTERY_DEVTYPE_GAMEPAD, &XBatteryInformation); 397 } 398 399#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) 400 // XInputOnGameInput doesn't ever change dwPacketNumber, so have to just update every frame 401 UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation); 402#else 403 // only fire events if the data changed from last time 404 if (XInputState.dwPacketNumber && XInputState.dwPacketNumber != joystick->hwdata->dwPacketNumber) { 405 UpdateXInputJoystickState(joystick, &XInputState, &XBatteryInformation); 406 joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber; 407 } 408#endif 409} 410 411void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick) 412{ 413} 414 415void SDL_XINPUT_JoystickQuit(void) 416{ 417 if (s_bXInputEnabled) { 418 s_bXInputEnabled = false; 419 WIN_UnloadXInputDLL(); 420 } 421} 422 423// Ends C function definitions when using C++ 424#ifdef __cplusplus 425} 426#endif 427 428#else // !SDL_JOYSTICK_XINPUT 429 430typedef struct JoyStick_DeviceData JoyStick_DeviceData; 431 432bool SDL_XINPUT_Enabled(void) 433{ 434 return false; 435} 436 437bool SDL_XINPUT_JoystickInit(void) 438{ 439 return true; 440} 441 442void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext) 443{ 444} 445 446bool SDL_XINPUT_JoystickPresent(Uint16 vendor, Uint16 product, Uint16 version) 447{ 448 return false; 449} 450 451bool SDL_XINPUT_JoystickOpen(SDL_Joystick *joystick, JoyStick_DeviceData *joystickdevice) 452{ 453 return SDL_Unsupported(); 454} 455 456bool SDL_XINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 457{ 458 return SDL_Unsupported(); 459} 460 461void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick) 462{ 463} 464 465void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick) 466{ 467} 468 469void SDL_XINPUT_JoystickQuit(void) 470{ 471} 472 473#endif // SDL_JOYSTICK_XINPUT 474[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.