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