Atlas - SDL_gameinputjoystick.cpp
Home / ext / SDL / src / joystick / gdk Lines: 1 | Size: 38658 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 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#ifdef SDL_JOYSTICK_GAMEINPUT 24 25#include "../SDL_sysjoystick.h" 26#include "../usb_ids.h" 27#include "../../core/windows/SDL_windows.h" 28#include "../../core/windows/SDL_gameinput.h" 29 30// Default value for SDL_HINT_JOYSTICK_GAMEINPUT 31#if defined(SDL_PLATFORM_GDK) 32#define SDL_GAMEINPUT_DEFAULT true 33#else 34#define SDL_GAMEINPUT_DEFAULT false 35#endif 36 37// Enable sensor support in GameInput 2.0, once we have a device that can be used for testing 38#if GAMEINPUT_API_VERSION >= 2 39//#define GAMEINPUT_SENSOR_SUPPORT 40#endif 41 42enum 43{ 44 SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE = 11 45}; 46 47enum 48{ 49 SDL_GAMEINPUT_RAWTYPE_NONE, 50 SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR, 51 SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT, 52 SDL_GAMEINPUT_RAWTYPE_GUITAR_HERO_LIVE_GUITAR, 53 SDL_GAMEINPUT_RAWTYPE_LEGACY_ADAPTER, 54}; 55 56typedef struct GAMEINPUT_InternalDevice 57{ 58 IGameInputDevice *device; 59 char path[(APP_LOCAL_DEVICE_ID_SIZE * 2) + 1]; 60 char *name; 61 SDL_GUID guid; // generated by SDL 62 SDL_JoystickID device_instance; // generated by SDL 63 const GameInputDeviceInfo *info; 64 int raw_type; 65 int steam_virtual_gamepad_slot; 66 bool isAdded; 67 bool isDeleteRequested; 68} GAMEINPUT_InternalDevice; 69 70typedef struct GAMEINPUT_InternalList 71{ 72 GAMEINPUT_InternalDevice **devices; 73 int count; 74} GAMEINPUT_InternalList; 75 76typedef struct joystick_hwdata 77{ 78 GAMEINPUT_InternalDevice *devref; 79 bool report_sensors; 80 GameInputRumbleParams rumbleParams; 81 GameInputCallbackToken system_button_callback_token; 82} GAMEINPUT_InternalJoystickHwdata; 83 84static GAMEINPUT_InternalList g_GameInputList = { NULL }; 85static IGameInput *g_pGameInput = NULL; 86static GameInputCallbackToken g_GameInputCallbackToken = 0; 87static Uint64 g_GameInputTimestampOffset; 88 89extern "C" 90{ 91 extern bool SDL_XINPUT_Enabled(void); 92} 93 94static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) 95{ 96 if (info->supportedInput & GameInputKindGamepad) { 97 return true; 98 } 99 return false; 100} 101 102static Uint8 GAMEINPUT_GetDeviceRawType(const GameInputDeviceInfo *info) 103{ 104#if GAMEINPUT_API_VERSION >= 3 105 GameInputKind supportedInput = info->supportedInput; 106 if (supportedInput & GameInputKindRawDeviceReport) { 107 switch (info->vendorId) { 108 case USB_VENDOR_MADCATZ: 109 switch (info->productId) { 110 case USB_PRODUCT_MADCATZ_XB1_STRATOCASTER_GUITAR: 111 return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR; 112 case USB_PRODUCT_MADCATZ_XB1_DRUM_KIT: 113 return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT; 114 case USB_PRODUCT_MADCATZ_XB1_LEGACY_ADAPTER: 115 return SDL_GAMEINPUT_RAWTYPE_LEGACY_ADAPTER; 116 default: 117 break; 118 } 119 break; 120 case USB_VENDOR_PDP: 121 switch (info->productId) { 122 case USB_PRODUCT_PDP_XB1_JAGUAR_GUITAR: 123 case USB_PRODUCT_PDP_XB1_RIFFMASTER_GUITAR: 124 return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR; 125 case USB_PRODUCT_PDP_XB1_DRUM_KIT: 126 return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT; 127 default: 128 break; 129 } 130 break; 131 case USB_VENDOR_CRKD: 132 switch (info->productId) { 133 case USB_PRODUCT_PDP_XB1_JAGUAR_GUITAR: 134 return SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR; 135 default: 136 break; 137 } 138 break; 139 case USB_VENDOR_RED_OCTANE: 140 switch (info->productId) { 141 case USB_PRODUCT_RED_OCTANE_XB1_GUITAR_HERO_LIVE_GUITAR: 142 return SDL_GAMEINPUT_RAWTYPE_GUITAR_HERO_LIVE_GUITAR; 143 default: 144 break; 145 } 146 break; 147 } 148 } 149#endif // GAMEINPUT_API_VERSION >= 3 150 return SDL_GAMEINPUT_RAWTYPE_NONE; 151} 152static Uint8 GAMEINPUT_GetDeviceSubtype(const GameInputDeviceInfo *info) 153{ 154 GameInputKind supportedInput = info->supportedInput; 155 Uint8 rawType = GAMEINPUT_GetDeviceRawType(info); 156 if (rawType == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR || rawType == SDL_GAMEINPUT_RAWTYPE_GUITAR_HERO_LIVE_GUITAR) { 157 return SDL_JOYSTICK_TYPE_GUITAR; 158 } 159 if (rawType == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT) { 160 return SDL_JOYSTICK_TYPE_DRUM_KIT; 161 } 162 if (supportedInput & GameInputKindRacingWheel) { 163 return SDL_JOYSTICK_TYPE_WHEEL; 164 } 165 if (supportedInput & GameInputKindArcadeStick) { 166 return SDL_JOYSTICK_TYPE_ARCADE_STICK; 167 } 168 if (supportedInput & GameInputKindFlightStick) { 169 return SDL_JOYSTICK_TYPE_FLIGHT_STICK; 170 } 171 if (supportedInput & (GameInputKindGamepad | GameInputKindController)) { 172 return SDL_JOYSTICK_TYPE_GAMEPAD; 173 } 174 // Other device subtypes don't have their own GameInputKind enum entries. 175 return 0; 176} 177 178#if GAMEINPUT_API_VERSION >= 1 179static int GetSteamVirtualGamepadSlot(const char *device_path) 180{ 181 int slot = -1; 182 183 // The format for the raw input device path is documented here: 184 // https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices 185 (void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot); 186 return slot; 187} 188#endif // GAMEINPUT_API_VERSION >= 1 189 190static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) 191{ 192 GAMEINPUT_InternalDevice **devicelist = NULL; 193 GAMEINPUT_InternalDevice *elem = NULL; 194 const GameInputDeviceInfo *info = NULL; 195 Uint16 bus = SDL_HARDWARE_BUS_USB; 196 Uint16 vendor = 0; 197 Uint16 product = 0; 198 Uint16 version = 0; 199 const char *product_string = NULL; 200 Uint8 driver_signature = 'g'; 201 Uint8 subtype = 0; 202 int raw_type = SDL_GAMEINPUT_RAWTYPE_NONE; 203 char tmp[4]; 204 int idx = 0; 205 206 SDL_AssertJoysticksLocked(); 207 208#if GAMEINPUT_API_VERSION >= 1 209 HRESULT hr = pDevice->GetDeviceInfo(&info); 210 if (FAILED(hr)) { 211 return WIN_SetErrorFromHRESULT("IGameInputDevice::GetDeviceInfo", hr); 212 } 213#else 214 info = pDevice->GetDeviceInfo(); 215#endif 216 if (false /*info->capabilities & GameInputDeviceCapabilityWireless*/) { 217 bus = SDL_HARDWARE_BUS_BLUETOOTH; 218 } else { 219 bus = SDL_HARDWARE_BUS_USB; 220 } 221 vendor = info->vendorId; 222 product = info->productId; 223 //version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor; 224 subtype = GAMEINPUT_GetDeviceSubtype(info); 225 raw_type = GAMEINPUT_GetDeviceRawType(info); 226 227#if GAMEINPUT_API_VERSION >= 1 228 if (info->displayName) { 229 product_string = info->displayName; 230 } 231#else 232 if (info->displayName) { 233 product_string = info->displayName->data; 234 } 235#endif 236 237 if (SDL_ShouldIgnoreJoystick(vendor, product, version, product_string) || 238 SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, product_string)) { 239 return true; 240 } 241 242#if defined(SDL_JOYSTICK_DINPUT) && defined(SDL_HAPTIC_DINPUT) 243 // This joystick backend currently doesn't provide a haptic backend, so fallback to DirectInput for haptic-capable devices. 244 if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true) && info->forceFeedbackMotorCount > 0 && pDevice->IsForceFeedbackMotorPoweredOn(0)) { 245 return true; 246 } 247#endif 248 249 if (!GAMEINPUT_InternalIsGamepad(info) && raw_type == SDL_GAMEINPUT_RAWTYPE_NONE) { 250#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT) 251 if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true) || SDL_XINPUT_Enabled()) { 252 // Let other backends handle non-gamepad controllers to possibly avoid bugs and/or regressions. 253 return true; 254 } 255#endif 256 if (info->supportedInput & GameInputKindController) { 257 // Maintain GUID compatibility with DirectInput controller mappings. 258 driver_signature = 0; 259 subtype = 0; 260 } else { 261 // This joystick backend currently doesn't provide proper reading of other joystick types. 262 return true; 263 } 264 } 265 266 for (idx = 0; idx < g_GameInputList.count; ++idx) { 267 elem = g_GameInputList.devices[idx]; 268 if (elem && elem->device == pDevice) { 269 // we're already added 270 elem->isDeleteRequested = false; 271 return true; 272 } 273 } 274 275 elem = (GAMEINPUT_InternalDevice *)SDL_calloc(1, sizeof(*elem)); 276 if (!elem) { 277 return false; 278 } 279 280 devicelist = (GAMEINPUT_InternalDevice **)SDL_realloc(g_GameInputList.devices, sizeof(elem) * (g_GameInputList.count + 1LL)); 281 if (!devicelist) { 282 SDL_free(elem); 283 return false; 284 } 285 286 // Generate a device path 287 for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) { 288 SDL_snprintf(tmp, SDL_arraysize(tmp), "%02hhX", info->deviceId.value[idx]); 289 SDL_strlcat(elem->path, tmp, SDL_arraysize(elem->path)); 290 } 291 292 pDevice->AddRef(); 293 elem->device = pDevice; 294 elem->name = SDL_CreateJoystickName(vendor, product, NULL, product_string); 295 elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, NULL, product_string, driver_signature, subtype); 296 elem->device_instance = SDL_GetNextObjectID(); 297 elem->info = info; 298 elem->raw_type = raw_type; 299#if GAMEINPUT_API_VERSION >= 1 300 elem->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(info->pnpPath); 301#else 302 elem->steam_virtual_gamepad_slot = -1; 303#endif 304 305 g_GameInputList.devices = devicelist; 306 g_GameInputList.devices[g_GameInputList.count++] = elem; 307 308 return true; 309} 310 311static bool GAMEINPUT_InternalRemoveByIndex(int idx) 312{ 313 GAMEINPUT_InternalDevice **devicelist = NULL; 314 GAMEINPUT_InternalDevice *elem; 315 int bytes = 0; 316 317 SDL_AssertJoysticksLocked(); 318 319 if (idx < 0 || idx >= g_GameInputList.count) { 320 return SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx); 321 } 322 323 elem = g_GameInputList.devices[idx]; 324 if (elem) { 325 elem->device->Release(); 326 SDL_free(elem->name); 327 SDL_free(elem); 328 } 329 g_GameInputList.devices[idx] = NULL; 330 331 if (g_GameInputList.count == 1) { 332 // last element in the list, free the entire list then 333 SDL_free(g_GameInputList.devices); 334 g_GameInputList.devices = NULL; 335 } else { 336 if (idx != g_GameInputList.count - 1) { 337 bytes = sizeof(*devicelist) * (g_GameInputList.count - idx); 338 SDL_memmove(&g_GameInputList.devices[idx], &g_GameInputList.devices[idx + 1], bytes); 339 } 340 } 341 342 // decrement the count and return 343 --g_GameInputList.count; 344 return true; 345} 346 347static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx) 348{ 349 // We're guaranteed that the index is in range when this is called 350 SDL_AssertJoysticksLocked(); 351 return g_GameInputList.devices[idx]; 352} 353 354static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback( 355 _In_ GameInputCallbackToken callbackToken, 356 _In_ void *context, 357 _In_ IGameInputDevice *device, 358 _In_ uint64_t timestamp, 359 _In_ GameInputDeviceStatus currentStatus, 360 _In_ GameInputDeviceStatus previousStatus) 361{ 362 int idx = 0; 363 GAMEINPUT_InternalDevice *elem = NULL; 364 365 if (!device) { 366 // This should never happen, but ignore it if it does 367 return; 368 } 369 370 SDL_LockJoysticks(); 371 372 if (currentStatus & GameInputDeviceConnected) { 373 GAMEINPUT_InternalAddOrFind(device); 374 } else { 375 for (idx = 0; idx < g_GameInputList.count; ++idx) { 376 elem = g_GameInputList.devices[idx]; 377 if (elem && elem->device == device) { 378 // will be deleted on the next Detect call 379 elem->isDeleteRequested = true; 380 break; 381 } 382 } 383 } 384 385 SDL_UnlockJoysticks(); 386} 387 388static void GAMEINPUT_JoystickDetect(void); 389static void GAMEINPUT_JoystickQuit(void); 390 391static bool GAMEINPUT_IsRawGameInputEnabled(void) 392{ 393#if GAMEINPUT_API_VERSION >= 3 394 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT_RAW, true); 395#else 396 return false; 397#endif 398} 399 400static bool GAMEINPUT_JoystickInit(void) 401{ 402 HRESULT hr; 403 404 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT) && !GAMEINPUT_IsRawGameInputEnabled()) { 405 return true; 406 } 407 408 if (!SDL_InitGameInput(&g_pGameInput)) { 409 return false; 410 } 411 412#if GAMEINPUT_API_VERSION >= 2 413 // Allow background controller input 414 // SDL manages focus policy at a higher level, so we can set this unconditionally. 415 g_pGameInput->SetFocusPolicy(GameInputEnableBackgroundInput | GameInputEnableBackgroundGuideButton | GameInputEnableBackgroundShareButton); 416#endif 417 418 GameInputKind kind = GameInputKindUnknown; 419 if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) { 420 kind |= GameInputKindController; 421 } 422#if GAMEINPUT_API_VERSION >= 3 423 if (GAMEINPUT_IsRawGameInputEnabled()) { 424 kind |= GameInputKindRawDeviceReport; 425 } 426#endif 427 428 hr = g_pGameInput->RegisterDeviceCallback(NULL, 429 kind, 430 GameInputDeviceConnected, 431 GameInputBlockingEnumeration, 432 NULL, 433 GAMEINPUT_InternalJoystickDeviceCallback, 434 &g_GameInputCallbackToken); 435 if (FAILED(hr)) { 436 GAMEINPUT_JoystickQuit(); 437 return WIN_SetErrorFromHRESULT("IGameInput::RegisterDeviceCallback", hr); 438 } 439 440 // Calculate the relative offset between SDL timestamps and GameInput timestamps 441 Uint64 now = SDL_GetTicksNS(); 442 uint64_t timestampUS = g_pGameInput->GetCurrentTimestamp(); 443 g_GameInputTimestampOffset = (SDL_NS_TO_US(now) - timestampUS); 444 445 GAMEINPUT_JoystickDetect(); 446 447 return true; 448} 449 450static int GAMEINPUT_JoystickGetCount(void) 451{ 452 SDL_AssertJoysticksLocked(); 453 454 return g_GameInputList.count; 455} 456 457static void GAMEINPUT_JoystickDetect(void) 458{ 459 int idx; 460 GAMEINPUT_InternalDevice *elem = NULL; 461 462 SDL_AssertJoysticksLocked(); 463 464 for (idx = 0; idx < g_GameInputList.count; ++idx) { 465 elem = g_GameInputList.devices[idx]; 466 if (!elem) { 467 continue; 468 } 469 470 if (!elem->isAdded) { 471 SDL_PrivateJoystickAdded(elem->device_instance); 472 elem->isAdded = true; 473 } 474 475 if (elem->isDeleteRequested || !(elem->device->GetDeviceStatus() & GameInputDeviceConnected)) { 476 SDL_PrivateJoystickRemoved(elem->device_instance); 477 GAMEINPUT_InternalRemoveByIndex(idx--); 478 } 479 } 480} 481 482static bool GAMEINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) 483{ 484 SDL_AssertJoysticksLocked(); 485 486 if (g_pGameInput) { 487 if (vendor_id == USB_VENDOR_MICROSOFT && 488 product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) { 489 // The Xbox One controller shows up as a hardcoded raw input VID/PID, which we definitely handle 490 return true; 491 } 492 493 for (int i = 0; i < g_GameInputList.count; ++i) { 494 GAMEINPUT_InternalDevice *elem = g_GameInputList.devices[i]; 495 if (elem && vendor_id == elem->info->vendorId && product_id == elem->info->productId) { 496 return true; 497 } 498 } 499 } 500 return false; 501} 502 503static const char *GAMEINPUT_JoystickGetDeviceName(int device_index) 504{ 505 return GAMEINPUT_InternalFindByIndex(device_index)->name; 506} 507 508static const char *GAMEINPUT_JoystickGetDevicePath(int device_index) 509{ 510 // APP_LOCAL_DEVICE_ID as a hex string, since it's required for some association callbacks 511 return GAMEINPUT_InternalFindByIndex(device_index)->path; 512} 513 514static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) 515{ 516 return GAMEINPUT_InternalFindByIndex(device_index)->steam_virtual_gamepad_slot; 517} 518 519static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index) 520{ 521 return -1; 522} 523 524static void GAMEINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index) 525{ 526} 527 528static SDL_GUID GAMEINPUT_JoystickGetDeviceGUID(int device_index) 529{ 530 return GAMEINPUT_InternalFindByIndex(device_index)->guid; 531} 532 533static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index) 534{ 535 return GAMEINPUT_InternalFindByIndex(device_index)->device_instance; 536} 537 538static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *device) 539{ 540#if 0 541 GameInputBatteryState battery_state; 542 SDL_PowerState state; 543 int percent = 0; 544 545 SDL_zero(battery_state); 546 IGameInputDevice_GetBatteryState(device, &battery_state); 547 548 switch (battery_state.status) { 549 case GameInputBatteryNotPresent: 550 state = SDL_POWERSTATE_NO_BATTERY; 551 break; 552 case GameInputBatteryDischarging: 553 state = SDL_POWERSTATE_ON_BATTERY; 554 break; 555 case GameInputBatteryIdle: 556 state = SDL_POWERSTATE_CHARGED; 557 break; 558 case GameInputBatteryCharging: 559 state = SDL_POWERSTATE_CHARGING; 560 break; 561 default: 562 state = SDL_POWERSTATE_UNKNOWN; 563 break; 564 } 565 if (battery_state.fullChargeCapacity > 0.0f) { 566 percent = (int)SDL_roundf((battery_state.remainingCapacity / battery_state.fullChargeCapacity) * 100.0f); 567 } 568 SDL_SendJoystickPowerInfo(joystick, state, percent); 569#endif 570} 571 572#if GAMEINPUT_API_VERSION >= 1 573static void CALLBACK GAMEINPUT_InternalSystemButtonCallback( 574 _In_ GameInputCallbackToken callbackToken, 575 _In_ void * context, 576 _In_ IGameInputDevice * device, 577 _In_ uint64_t timestampUS, 578 _In_ GameInputSystemButtons currentButtons, 579 _In_ GameInputSystemButtons previousButtons) 580{ 581 SDL_Joystick *joystick = (SDL_Joystick *)context; 582 583 GameInputSystemButtons changedButtons = (previousButtons ^ currentButtons); 584 if (changedButtons) { 585 Uint64 timestamp = SDL_US_TO_NS(timestampUS + g_GameInputTimestampOffset); 586 587 SDL_LockJoysticks(); 588 if (changedButtons & GameInputSystemButtonGuide) { 589 bool down = ((currentButtons & GameInputSystemButtonGuide) != 0); 590 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, down); 591 } 592 if (changedButtons & GameInputSystemButtonShare) { 593 bool down = ((currentButtons & GameInputSystemButtonShare) != 0); 594 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE, down); 595 } 596 SDL_UnlockJoysticks(); 597 } 598} 599#endif // GAMEINPUT_API_VERSION >= 1 600 601static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) 602{ 603 GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index); 604 const GameInputDeviceInfo *info = elem->info; 605 GAMEINPUT_InternalJoystickHwdata *hwdata = NULL; 606 607 if (!elem) { 608 return false; 609 } 610 611 hwdata = (GAMEINPUT_InternalJoystickHwdata *)SDL_calloc(1, sizeof(*hwdata)); 612 if (!hwdata) { 613 return false; 614 } 615 616 hwdata->devref = elem; 617 618 joystick->hwdata = hwdata; 619 if (GAMEINPUT_InternalIsGamepad(info) || GAMEINPUT_GetDeviceRawType(info) != SDL_GAMEINPUT_RAWTYPE_NONE) { 620 joystick->naxes = 6; 621 joystick->nbuttons = 11; 622 joystick->nhats = 1; 623 624#if GAMEINPUT_API_VERSION >= 1 625 if (info->supportedSystemButtons != GameInputSystemButtonNone) { 626 if (info->supportedSystemButtons & GameInputSystemButtonShare) { 627 ++joystick->nbuttons; 628 } 629 630 g_pGameInput->RegisterSystemButtonCallback(elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token); 631 } 632#endif // GAMEINPUT_API_VERSION >= 1 633 } else { 634#if GAMEINPUT_API_VERSION >= 3 635 joystick->naxes = info->controllerInfo->controllerAxisCount; 636 joystick->nbuttons = info->controllerInfo->controllerButtonCount; 637 joystick->nhats = info->controllerInfo->controllerSwitchCount; 638#else 639 joystick->naxes = info->controllerAxisCount; 640 joystick->nbuttons = info->controllerButtonCount; 641 joystick->nhats = info->controllerSwitchCount; 642#endif // GAMEINPUT_API_VERSION >= 3 643 } 644 645 if (info->supportedRumbleMotors & (GameInputRumbleLowFrequency | GameInputRumbleHighFrequency)) { 646 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); 647 } 648 if (info->supportedRumbleMotors & (GameInputRumbleLeftTrigger | GameInputRumbleRightTrigger)) { 649 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); 650 } 651 652#ifdef GAMEINPUT_SENSOR_SUPPORT 653 if (info->supportedInput & GameInputKindSensors) { 654 // FIXME: What's the sensor update rate? 655 if (info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) { 656 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); 657 } 658 if (info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) { 659 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); 660 } 661 } 662#endif 663 return true; 664} 665 666static bool GAMEINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 667{ 668 // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it 669 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; 670 GameInputRumbleParams *params = &hwdata->rumbleParams; 671 params->lowFrequency = (float)low_frequency_rumble / (float)SDL_MAX_UINT16; 672 params->highFrequency = (float)high_frequency_rumble / (float)SDL_MAX_UINT16; 673 hwdata->devref->device->SetRumbleState(params); 674 return true; 675} 676 677static bool GAMEINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) 678{ 679 // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it 680 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; 681 GameInputRumbleParams *params = &hwdata->rumbleParams; 682 params->leftTrigger = (float)left_rumble / (float)SDL_MAX_UINT16; 683 params->rightTrigger = (float)right_rumble / (float)SDL_MAX_UINT16; 684 hwdata->devref->device->SetRumbleState(params); 685 return true; 686} 687 688static bool GAMEINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) 689{ 690 return SDL_Unsupported(); 691} 692 693static bool GAMEINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) 694{ 695 return SDL_Unsupported(); 696} 697 698static bool GAMEINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) 699{ 700 joystick->hwdata->report_sensors = enabled; 701 return true; 702} 703 704static void GAMEINPUT_GuitarUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp) 705{ 706#if GAMEINPUT_API_VERSION >= 3 707 IGameInputRawDeviceReport *rawState; 708 if (reading->GetRawReport(&rawState)) { 709 static WORD s_GuitarButtons[] = { 710 0x0010, // SDL_GAMEPAD_BUTTON_SOUTH 711 0x0020, // SDL_GAMEPAD_BUTTON_EAST 712 0x0040, // SDL_GAMEPAD_BUTTON_WEST 713 0x0080, // SDL_GAMEPAD_BUTTON_NORTH 714 0x0008, // SDL_GAMEPAD_BUTTON_BACK 715 0, // The guide button is not available 716 0x0004, // SDL_GAMEPAD_BUTTON_START 717 0, // right joystick click unavailable 718 0x4000, // SDL_GAMEPAD_BUTTON_RIGHT_STICK 719 0x1000, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER 720 0, // right shoulder unavailable 721 }; 722 Uint8 btnidx = 0, hat = 0; 723 uint8_t rawData[40]; 724 SDL_memset(rawData, 0, sizeof(rawData)); 725 size_t len = rawState->GetRawData(sizeof(rawData), rawData); 726 uint16_t buttons = rawData[0] | rawData[1] << 8; 727 if (len >= 10) { 728 for (btnidx = 0; btnidx < SDL_arraysize(s_GuitarButtons); ++btnidx) { 729 WORD button_mask = s_GuitarButtons[btnidx]; 730 if (!button_mask) { 731 continue; 732 } 733 bool down = ((buttons & button_mask) != 0); 734 SDL_SendJoystickButton(timestamp, joystick, btnidx, down); 735 } 736 if (buttons & 0x0100) { 737 hat |= SDL_HAT_UP; 738 } 739 if (buttons & 0x0200) { 740 hat |= SDL_HAT_DOWN; 741 } 742 if (buttons & 0x0400) { 743 hat |= SDL_HAT_LEFT; 744 } 745 if (buttons & 0x0800) { 746 hat |= SDL_HAT_RIGHT; 747 } 748 SDL_SendJoystickHat(timestamp, joystick, 0, hat); 749 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, (rawData[3] * 257) - 32768); 750 // PS3 RB guitars had tilt on right shoulder 751 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, rawData[2] >= 0xD0); 752 // PS3 RB guitars send L2 when using solo buttons 753 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (rawData[6]) ? 32767 : -32768); 754 // Align pickup selector mappings with PS3 instruments 755 static const Sint16 effects_mappings[] = {-26880, -13568, -1792, 11008, 24576}; 756 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, effects_mappings[rawData[4] >> 4]); 757 } 758 } 759#endif // GAMEINPUT_API_VERSION >= 3 760} 761 762static void GAMEINPUT_GamepadUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp) { 763 GameInputGamepadState state; 764 static WORD s_XInputButtons[] = { 765 GameInputGamepadA, // SDL_GAMEPAD_BUTTON_SOUTH 766 GameInputGamepadB, // SDL_GAMEPAD_BUTTON_EAST 767 GameInputGamepadX, // SDL_GAMEPAD_BUTTON_WEST 768 GameInputGamepadY, // SDL_GAMEPAD_BUTTON_NORTH 769 GameInputGamepadView, // SDL_GAMEPAD_BUTTON_BACK 770 0, // The guide button is not available 771 GameInputGamepadMenu, // SDL_GAMEPAD_BUTTON_START 772 GameInputGamepadLeftThumbstick, // SDL_GAMEPAD_BUTTON_LEFT_STICK 773 GameInputGamepadRightThumbstick, // SDL_GAMEPAD_BUTTON_RIGHT_STICK 774 GameInputGamepadLeftShoulder, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER 775 GameInputGamepadRightShoulder, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER 776 }; 777 Uint8 btnidx = 0, hat = 0; 778 779 if (reading->GetGamepadState(&state)) { 780 for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) { 781 WORD button_mask = s_XInputButtons[btnidx]; 782 if (!button_mask) { 783 continue; 784 } 785 bool down = ((state.buttons & button_mask) != 0); 786 SDL_SendJoystickButton(timestamp, joystick, btnidx, down); 787 } 788 789 if (state.buttons & GameInputGamepadDPadUp) { 790 hat |= SDL_HAT_UP; 791 } 792 if (state.buttons & GameInputGamepadDPadDown) { 793 hat |= SDL_HAT_DOWN; 794 } 795 if (state.buttons & GameInputGamepadDPadLeft) { 796 hat |= SDL_HAT_LEFT; 797 } 798 if (state.buttons & GameInputGamepadDPadRight) { 799 hat |= SDL_HAT_RIGHT; 800 } 801 SDL_SendJoystickHat(timestamp, joystick, 0, hat); 802 803#define CONVERT_AXIS(v) (Sint16)(((v) < 0.0f) ? ((v)*32768.0f) : ((v)*32767.0f)) 804 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, CONVERT_AXIS(state.leftThumbstickX)); 805 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, CONVERT_AXIS(-state.leftThumbstickY)); 806 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, CONVERT_AXIS(state.rightThumbstickX)); 807 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, CONVERT_AXIS(-state.rightThumbstickY)); 808#undef CONVERT_AXIS 809#define CONVERT_TRIGGER(v) (Sint16)((v)*65535.0f - 32768.0f) 810 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, CONVERT_TRIGGER(state.leftTrigger)); 811 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, CONVERT_TRIGGER(state.rightTrigger)); 812#undef CONVERT_TRIGGER 813 } 814} 815 816static void GAMEINPUT_ControllerUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp) 817{ 818 bool *button_state = SDL_stack_alloc(bool, joystick->nbuttons); 819 float *axis_state = SDL_stack_alloc(float, joystick->naxes); 820 GameInputSwitchPosition *switch_state = SDL_stack_alloc(GameInputSwitchPosition, joystick->nhats); 821 822 if (button_state) { 823 uint32_t i; 824 uint32_t button_count = reading->GetControllerButtonState(joystick->nbuttons, button_state); 825 for (i = 0; i < button_count; ++i) { 826 SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]); 827 } 828 SDL_stack_free(button_state); 829 } 830 831#define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f) 832 if (axis_state) { 833 uint32_t i; 834 uint32_t axis_count = reading->GetControllerAxisState(joystick->naxes, axis_state); 835 for (i = 0; i < axis_count; ++i) { 836 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i])); 837 } 838 SDL_stack_free(axis_state); 839 } 840#undef CONVERT_AXIS 841 842 if (switch_state) { 843 uint32_t i; 844 uint32_t switch_count = reading->GetControllerSwitchState(joystick->nhats, switch_state); 845 for (i = 0; i < switch_count; ++i) { 846 Uint8 hat; 847 switch (switch_state[i]) { 848 case GameInputSwitchUp: 849 hat = SDL_HAT_UP; 850 break; 851 case GameInputSwitchUpRight: 852 hat = SDL_HAT_UP | SDL_HAT_RIGHT; 853 break; 854 case GameInputSwitchRight: 855 hat = SDL_HAT_RIGHT; 856 break; 857 case GameInputSwitchDownRight: 858 hat = SDL_HAT_DOWN | SDL_HAT_RIGHT; 859 break; 860 case GameInputSwitchDown: 861 hat = SDL_HAT_DOWN; 862 break; 863 case GameInputSwitchDownLeft: 864 hat = SDL_HAT_DOWN | SDL_HAT_LEFT; 865 break; 866 case GameInputSwitchLeft: 867 hat = SDL_HAT_LEFT; 868 break; 869 case GameInputSwitchUpLeft: 870 hat = SDL_HAT_UP | SDL_HAT_LEFT; 871 break; 872 case GameInputSwitchCenter: 873 default: 874 hat = SDL_HAT_CENTERED; 875 break; 876 } 877 SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat); 878 } 879 SDL_stack_free(switch_state); 880 } 881} 882 883static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) 884{ 885 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; 886 GAMEINPUT_InternalDevice *internal_device = hwdata->devref; 887 IGameInputDevice *device = hwdata->devref->device; 888 const GameInputDeviceInfo *info = hwdata->devref->info; 889 IGameInputReading *reading = NULL; 890 Uint64 timestamp; 891 HRESULT hr; 892 893 hr = g_pGameInput->GetCurrentReading(info->supportedInput, device, &reading); 894 if (FAILED(hr)) { 895 // don't SetError here since there can be a legitimate case when there's no reading avail 896 return; 897 } 898 899 timestamp = SDL_US_TO_NS(reading->GetTimestamp() + g_GameInputTimestampOffset); 900 if (internal_device->raw_type == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR) { 901 GAMEINPUT_GuitarUpdate(joystick, reading, timestamp); 902 } else if (GAMEINPUT_InternalIsGamepad(info)) { 903 GAMEINPUT_GamepadUpdate(joystick, reading, timestamp); 904 } else { 905 GAMEINPUT_ControllerUpdate(joystick, reading, timestamp); 906 } 907 908#ifdef GAMEINPUT_SENSOR_SUPPORT 909 if (hwdata->report_sensors) { 910 GameInputSensorsState sensor_state; 911 912 if (reading->GetSensorsState(&sensor_state)) { 913 if ((info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) != 0) { 914 float data[3] = { 915 sensor_state.angularVelocityInRadPerSecX, 916 sensor_state.angularVelocityInRadPerSecY, 917 sensor_state.angularVelocityInRadPerSecZ 918 }; 919 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, SDL_arraysize(data)); 920 } 921 if ((info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) != 0) { 922 float data[3] = { 923 sensor_state.accelerationInGX * SDL_STANDARD_GRAVITY, 924 sensor_state.accelerationInGY * SDL_STANDARD_GRAVITY, 925 sensor_state.accelerationInGZ * SDL_STANDARD_GRAVITY 926 }; 927 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, SDL_arraysize(data)); 928 } 929 } 930 } 931#endif // GAMEINPUT_SENSOR_SUPPORT 932 933 reading->Release(); 934 935 // FIXME: We can poll this at a much lower rate 936 GAMEINPUT_UpdatePowerInfo(joystick, device); 937} 938 939static void GAMEINPUT_JoystickClose(SDL_Joystick *joystick) 940{ 941 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; 942 943 if (hwdata->system_button_callback_token) { 944#if GAMEINPUT_API_VERSION >= 1 945 g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token); 946#else 947 g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token, 10000); 948#endif 949 } 950 SDL_free(hwdata); 951 952 joystick->hwdata = NULL; 953} 954 955static void GAMEINPUT_JoystickQuit(void) 956{ 957 if (g_pGameInput) { 958 // free the callback 959 if (g_GameInputCallbackToken) { 960#if GAMEINPUT_API_VERSION >= 1 961 g_pGameInput->UnregisterCallback(g_GameInputCallbackToken); 962#else 963 g_pGameInput->UnregisterCallback(g_GameInputCallbackToken, 10000); 964#endif 965 g_GameInputCallbackToken = 0; 966 } 967 968 // free the list 969 while (g_GameInputList.count > 0) { 970 GAMEINPUT_InternalRemoveByIndex(0); 971 } 972 973 SDL_QuitGameInput(); 974 g_pGameInput = NULL; 975 } 976} 977 978static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) 979{ 980 GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index); 981 982 if (!GAMEINPUT_InternalIsGamepad(elem->info) && elem->raw_type == SDL_GAMEINPUT_RAWTYPE_NONE) { 983 return false; 984 } 985 986 out->a.kind = EMappingKind_Button; 987 out->a.target = SDL_GAMEPAD_BUTTON_SOUTH; 988 989 out->b.kind = EMappingKind_Button; 990 out->b.target = SDL_GAMEPAD_BUTTON_EAST; 991 992 out->x.kind = EMappingKind_Button; 993 out->x.target = SDL_GAMEPAD_BUTTON_WEST; 994 995 out->y.kind = EMappingKind_Button; 996 out->y.target = SDL_GAMEPAD_BUTTON_NORTH; 997 998 out->back.kind = EMappingKind_Button; 999 out->back.target = SDL_GAMEPAD_BUTTON_BACK; 1000 1001#if GAMEINPUT_API_VERSION >= 1 1002 if (elem->info->supportedSystemButtons & GameInputSystemButtonGuide) { 1003 out->guide.kind = EMappingKind_Button; 1004 out->guide.target = SDL_GAMEPAD_BUTTON_GUIDE; 1005 } 1006 1007 if (elem->info->supportedSystemButtons & GameInputSystemButtonShare) { 1008 out->misc1.kind = EMappingKind_Button; 1009 out->misc1.target = SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE; 1010 } 1011#endif // GAMEINPUT_API_VERSION >= 1 1012 1013 out->start.kind = EMappingKind_Button; 1014 out->start.target = SDL_GAMEPAD_BUTTON_START; 1015 1016 out->leftstick.kind = EMappingKind_Button; 1017 out->leftstick.target = SDL_GAMEPAD_BUTTON_LEFT_STICK; 1018 1019 out->rightstick.kind = EMappingKind_Button; 1020 out->rightstick.target = SDL_GAMEPAD_BUTTON_RIGHT_STICK; 1021 1022 out->leftshoulder.kind = EMappingKind_Button; 1023 out->leftshoulder.target = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; 1024 1025 out->rightshoulder.kind = EMappingKind_Button; 1026 out->rightshoulder.target = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER; 1027 1028 out->dpup.kind = EMappingKind_Hat; 1029 out->dpup.target = SDL_HAT_UP; 1030 1031 out->dpdown.kind = EMappingKind_Hat; 1032 out->dpdown.target = SDL_HAT_DOWN; 1033 1034 out->dpleft.kind = EMappingKind_Hat; 1035 out->dpleft.target = SDL_HAT_LEFT; 1036 1037 out->dpright.kind = EMappingKind_Hat; 1038 out->dpright.target = SDL_HAT_RIGHT; 1039 1040 out->leftx.kind = EMappingKind_Axis; 1041 out->leftx.target = SDL_GAMEPAD_AXIS_LEFTX; 1042 1043 out->lefty.kind = EMappingKind_Axis; 1044 out->lefty.target = SDL_GAMEPAD_AXIS_LEFTY; 1045 1046 out->rightx.kind = EMappingKind_Axis; 1047 out->rightx.target = SDL_GAMEPAD_AXIS_RIGHTX; 1048 1049 out->righty.kind = EMappingKind_Axis; 1050 out->righty.target = SDL_GAMEPAD_AXIS_RIGHTY; 1051 1052 out->lefttrigger.kind = EMappingKind_Axis; 1053 out->lefttrigger.target = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; 1054 1055 out->righttrigger.kind = EMappingKind_Axis; 1056 out->righttrigger.target = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; 1057 1058 return true; 1059} 1060 1061 1062SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver = 1063{ 1064 GAMEINPUT_JoystickInit, 1065 GAMEINPUT_JoystickGetCount, 1066 GAMEINPUT_JoystickDetect, 1067 GAMEINPUT_JoystickIsDevicePresent, 1068 GAMEINPUT_JoystickGetDeviceName, 1069 GAMEINPUT_JoystickGetDevicePath, 1070 GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot, 1071 GAMEINPUT_JoystickGetDevicePlayerIndex, 1072 GAMEINPUT_JoystickSetDevicePlayerIndex, 1073 GAMEINPUT_JoystickGetDeviceGUID, 1074 GAMEINPUT_JoystickGetDeviceInstanceID, 1075 GAMEINPUT_JoystickOpen, 1076 GAMEINPUT_JoystickRumble, 1077 GAMEINPUT_JoystickRumbleTriggers, 1078 GAMEINPUT_JoystickSetLED, 1079 GAMEINPUT_JoystickSendEffect, 1080 GAMEINPUT_JoystickSetSensorsEnabled, 1081 GAMEINPUT_JoystickUpdate, 1082 GAMEINPUT_JoystickClose, 1083 GAMEINPUT_JoystickQuit, 1084 GAMEINPUT_JoystickGetGamepadMapping 1085}; 1086 1087 1088#endif // SDL_JOYSTICK_GAMEINPUT 1089[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.