| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "SDL_internal.h" |
| |
| #ifdef SDL_JOYSTICK_GAMEINPUT |
| |
| #include "../SDL_sysjoystick.h" |
| #include "../usb_ids.h" |
| #include "../../core/windows/SDL_windows.h" |
| #include "../../core/windows/SDL_gameinput.h" |
| |
| // Default value for SDL_HINT_JOYSTICK_GAMEINPUT |
| #if defined(SDL_PLATFORM_GDK) |
| #define SDL_GAMEINPUT_DEFAULT true |
| #else |
| #define SDL_GAMEINPUT_DEFAULT false |
| #endif |
| |
| // Enable sensor support in GameInput 2.0, once we have a device that can be used for testing |
| #if GAMEINPUT_API_VERSION >= 2 |
| //#define GAMEINPUT_SENSOR_SUPPORT |
| #endif |
| |
| enum |
| { |
| SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE = 11 |
| }; |
| |
| typedef struct GAMEINPUT_InternalDevice |
| { |
| IGameInputDevice *device; |
| char path[(APP_LOCAL_DEVICE_ID_SIZE * 2) + 1]; |
| char *name; |
| SDL_GUID guid; // generated by SDL |
| SDL_JoystickID device_instance; // generated by SDL |
| const GameInputDeviceInfo *info; |
| int steam_virtual_gamepad_slot; |
| bool isAdded; |
| bool isDeleteRequested; |
| } GAMEINPUT_InternalDevice; |
| |
| typedef struct GAMEINPUT_InternalList |
| { |
| GAMEINPUT_InternalDevice **devices; |
| int count; |
| } GAMEINPUT_InternalList; |
| |
| typedef struct joystick_hwdata |
| { |
| GAMEINPUT_InternalDevice *devref; |
| bool report_sensors; |
| GameInputRumbleParams rumbleParams; |
| GameInputCallbackToken system_button_callback_token; |
| } GAMEINPUT_InternalJoystickHwdata; |
| |
| static GAMEINPUT_InternalList g_GameInputList = { NULL }; |
| static IGameInput *g_pGameInput = NULL; |
| static GameInputCallbackToken g_GameInputCallbackToken = 0; |
| static Uint64 g_GameInputTimestampOffset; |
| |
| static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info) |
| { |
| if (info->supportedInput & GameInputKindGamepad) { |
| return true; |
| } |
| return false; |
| } |
| |
| #if GAMEINPUT_API_VERSION >= 1 |
| static int GetSteamVirtualGamepadSlot(const char *device_path) |
| { |
| int slot = -1; |
| |
| // The format for the raw input device path is documented here: |
| // https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices |
| (void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot); |
| return slot; |
| } |
| #endif // GAMEINPUT_API_VERSION >= 1 |
| |
| static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice) |
| { |
| GAMEINPUT_InternalDevice **devicelist = NULL; |
| GAMEINPUT_InternalDevice *elem = NULL; |
| const GameInputDeviceInfo *info = NULL; |
| Uint16 bus = SDL_HARDWARE_BUS_USB; |
| Uint16 vendor = 0; |
| Uint16 product = 0; |
| Uint16 version = 0; |
| const char *manufacturer_string = NULL; |
| const char *product_string = NULL; |
| char tmp[4]; |
| int idx = 0; |
| |
| SDL_AssertJoysticksLocked(); |
| |
| #if GAMEINPUT_API_VERSION >= 1 |
| HRESULT hr = pDevice->GetDeviceInfo(&info); |
| if (FAILED(hr)) { |
| return WIN_SetErrorFromHRESULT("IGameInputDevice::GetDeviceInfo", hr); |
| } |
| #else |
| info = pDevice->GetDeviceInfo(); |
| #endif |
| if (false /*info->capabilities & GameInputDeviceCapabilityWireless*/) { |
| bus = SDL_HARDWARE_BUS_BLUETOOTH; |
| } else { |
| bus = SDL_HARDWARE_BUS_USB; |
| } |
| vendor = info->vendorId; |
| product = info->productId; |
| //version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor; |
| |
| if (SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, "")) { |
| return true; |
| } |
| |
| for (idx = 0; idx < g_GameInputList.count; ++idx) { |
| elem = g_GameInputList.devices[idx]; |
| if (elem && elem->device == pDevice) { |
| // we're already added |
| elem->isDeleteRequested = false; |
| return true; |
| } |
| } |
| |
| elem = (GAMEINPUT_InternalDevice *)SDL_calloc(1, sizeof(*elem)); |
| if (!elem) { |
| return false; |
| } |
| |
| devicelist = (GAMEINPUT_InternalDevice **)SDL_realloc(g_GameInputList.devices, sizeof(elem) * (g_GameInputList.count + 1LL)); |
| if (!devicelist) { |
| SDL_free(elem); |
| return false; |
| } |
| |
| // Generate a device path |
| for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) { |
| SDL_snprintf(tmp, SDL_arraysize(tmp), "%02hhX", info->deviceId.value[idx]); |
| SDL_strlcat(elem->path, tmp, SDL_arraysize(elem->path)); |
| } |
| |
| #if GAMEINPUT_API_VERSION >= 1 |
| if (info->displayName) { |
| product_string = info->displayName; |
| } |
| #else |
| if (info->displayName) { |
| product_string = info->displayName->data; |
| } |
| #endif |
| |
| pDevice->AddRef(); |
| elem->device = pDevice; |
| elem->name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string); |
| elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, manufacturer_string, product_string, 'g', 0); |
| elem->device_instance = SDL_GetNextObjectID(); |
| elem->info = info; |
| #if GAMEINPUT_API_VERSION >= 1 |
| elem->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(info->pnpPath); |
| #else |
| elem->steam_virtual_gamepad_slot = -1; |
| #endif |
| |
| g_GameInputList.devices = devicelist; |
| g_GameInputList.devices[g_GameInputList.count++] = elem; |
| |
| return true; |
| } |
| |
| static bool GAMEINPUT_InternalRemoveByIndex(int idx) |
| { |
| GAMEINPUT_InternalDevice **devicelist = NULL; |
| GAMEINPUT_InternalDevice *elem; |
| int bytes = 0; |
| |
| SDL_AssertJoysticksLocked(); |
| |
| if (idx < 0 || idx >= g_GameInputList.count) { |
| return SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx); |
| } |
| |
| elem = g_GameInputList.devices[idx]; |
| if (elem) { |
| elem->device->Release(); |
| SDL_free(elem->name); |
| SDL_free(elem); |
| } |
| g_GameInputList.devices[idx] = NULL; |
| |
| if (g_GameInputList.count == 1) { |
| // last element in the list, free the entire list then |
| SDL_free(g_GameInputList.devices); |
| g_GameInputList.devices = NULL; |
| } else { |
| if (idx != g_GameInputList.count - 1) { |
| bytes = sizeof(*devicelist) * (g_GameInputList.count - idx); |
| SDL_memmove(&g_GameInputList.devices[idx], &g_GameInputList.devices[idx + 1], bytes); |
| } |
| } |
| |
| // decrement the count and return |
| --g_GameInputList.count; |
| return true; |
| } |
| |
| static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx) |
| { |
| // We're guaranteed that the index is in range when this is called |
| SDL_AssertJoysticksLocked(); |
| return g_GameInputList.devices[idx]; |
| } |
| |
| static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback( |
| _In_ GameInputCallbackToken callbackToken, |
| _In_ void *context, |
| _In_ IGameInputDevice *device, |
| _In_ uint64_t timestamp, |
| _In_ GameInputDeviceStatus currentStatus, |
| _In_ GameInputDeviceStatus previousStatus) |
| { |
| int idx = 0; |
| GAMEINPUT_InternalDevice *elem = NULL; |
| |
| if (!device) { |
| // This should never happen, but ignore it if it does |
| return; |
| } |
| |
| SDL_LockJoysticks(); |
| |
| if (currentStatus & GameInputDeviceConnected) { |
| GAMEINPUT_InternalAddOrFind(device); |
| } else { |
| for (idx = 0; idx < g_GameInputList.count; ++idx) { |
| elem = g_GameInputList.devices[idx]; |
| if (elem && elem->device == device) { |
| // will be deleted on the next Detect call |
| elem->isDeleteRequested = true; |
| break; |
| } |
| } |
| } |
| |
| SDL_UnlockJoysticks(); |
| } |
| |
| static void GAMEINPUT_JoystickDetect(void); |
| static void GAMEINPUT_JoystickQuit(void); |
| |
| static bool GAMEINPUT_JoystickInit(void) |
| { |
| HRESULT hr; |
| |
| if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) { |
| return true; |
| } |
| |
| if (!SDL_InitGameInput(&g_pGameInput)) { |
| return false; |
| } |
| |
| #if GAMEINPUT_API_VERSION >= 2 |
| // Allow background controller input |
| // SDL manages focus policy at a higher level, so we can set this unconditionally. |
| g_pGameInput->SetFocusPolicy(GameInputEnableBackgroundInput | GameInputEnableBackgroundGuideButton | GameInputEnableBackgroundShareButton); |
| #endif |
| |
| hr = g_pGameInput->RegisterDeviceCallback(NULL, |
| GameInputKindController, |
| GameInputDeviceConnected, |
| GameInputBlockingEnumeration, |
| NULL, |
| GAMEINPUT_InternalJoystickDeviceCallback, |
| &g_GameInputCallbackToken); |
| if (FAILED(hr)) { |
| GAMEINPUT_JoystickQuit(); |
| return WIN_SetErrorFromHRESULT("IGameInput::RegisterDeviceCallback", hr); |
| } |
| |
| // Calculate the relative offset between SDL timestamps and GameInput timestamps |
| Uint64 now = SDL_GetTicksNS(); |
| uint64_t timestampUS = g_pGameInput->GetCurrentTimestamp(); |
| g_GameInputTimestampOffset = (SDL_NS_TO_US(now) - timestampUS); |
| |
| GAMEINPUT_JoystickDetect(); |
| |
| return true; |
| } |
| |
| static int GAMEINPUT_JoystickGetCount(void) |
| { |
| SDL_AssertJoysticksLocked(); |
| |
| return g_GameInputList.count; |
| } |
| |
| static void GAMEINPUT_JoystickDetect(void) |
| { |
| int idx; |
| GAMEINPUT_InternalDevice *elem = NULL; |
| |
| SDL_AssertJoysticksLocked(); |
| |
| for (idx = 0; idx < g_GameInputList.count; ++idx) { |
| elem = g_GameInputList.devices[idx]; |
| if (!elem) { |
| continue; |
| } |
| |
| if (!elem->isAdded) { |
| SDL_PrivateJoystickAdded(elem->device_instance); |
| elem->isAdded = true; |
| } |
| |
| if (elem->isDeleteRequested || !(elem->device->GetDeviceStatus() & GameInputDeviceConnected)) { |
| SDL_PrivateJoystickRemoved(elem->device_instance); |
| GAMEINPUT_InternalRemoveByIndex(idx--); |
| } |
| } |
| } |
| |
| static bool GAMEINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) |
| { |
| SDL_AssertJoysticksLocked(); |
| |
| if (g_pGameInput) { |
| if (vendor_id == USB_VENDOR_MICROSOFT && |
| product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) { |
| // The Xbox One controller shows up as a hardcoded raw input VID/PID, which we definitely handle |
| return true; |
| } |
| |
| for (int i = 0; i < g_GameInputList.count; ++i) { |
| GAMEINPUT_InternalDevice *elem = g_GameInputList.devices[i]; |
| if (elem && vendor_id == elem->info->vendorId && product_id == elem->info->productId) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| static const char *GAMEINPUT_JoystickGetDeviceName(int device_index) |
| { |
| return GAMEINPUT_InternalFindByIndex(device_index)->name; |
| } |
| |
| static const char *GAMEINPUT_JoystickGetDevicePath(int device_index) |
| { |
| // APP_LOCAL_DEVICE_ID as a hex string, since it's required for some association callbacks |
| return GAMEINPUT_InternalFindByIndex(device_index)->path; |
| } |
| |
| static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) |
| { |
| return GAMEINPUT_InternalFindByIndex(device_index)->steam_virtual_gamepad_slot; |
| } |
| |
| static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index) |
| { |
| return -1; |
| } |
| |
| static void GAMEINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index) |
| { |
| } |
| |
| static SDL_GUID GAMEINPUT_JoystickGetDeviceGUID(int device_index) |
| { |
| return GAMEINPUT_InternalFindByIndex(device_index)->guid; |
| } |
| |
| static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index) |
| { |
| return GAMEINPUT_InternalFindByIndex(device_index)->device_instance; |
| } |
| |
| static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *device) |
| { |
| #if 0 |
| GameInputBatteryState battery_state; |
| SDL_PowerState state; |
| int percent = 0; |
| |
| SDL_zero(battery_state); |
| IGameInputDevice_GetBatteryState(device, &battery_state); |
| |
| switch (battery_state.status) { |
| case GameInputBatteryNotPresent: |
| state = SDL_POWERSTATE_NO_BATTERY; |
| break; |
| case GameInputBatteryDischarging: |
| state = SDL_POWERSTATE_ON_BATTERY; |
| break; |
| case GameInputBatteryIdle: |
| state = SDL_POWERSTATE_CHARGED; |
| break; |
| case GameInputBatteryCharging: |
| state = SDL_POWERSTATE_CHARGING; |
| break; |
| default: |
| state = SDL_POWERSTATE_UNKNOWN; |
| break; |
| } |
| if (battery_state.fullChargeCapacity > 0.0f) { |
| percent = (int)SDL_roundf((battery_state.remainingCapacity / battery_state.fullChargeCapacity) * 100.0f); |
| } |
| SDL_SendJoystickPowerInfo(joystick, state, percent); |
| #endif |
| } |
| |
| #if GAMEINPUT_API_VERSION >= 1 |
| static void CALLBACK GAMEINPUT_InternalSystemButtonCallback( |
| _In_ GameInputCallbackToken callbackToken, |
| _In_ void * context, |
| _In_ IGameInputDevice * device, |
| _In_ uint64_t timestampUS, |
| _In_ GameInputSystemButtons currentButtons, |
| _In_ GameInputSystemButtons previousButtons) |
| { |
| SDL_Joystick *joystick = (SDL_Joystick *)context; |
| |
| GameInputSystemButtons changedButtons = (previousButtons ^ currentButtons); |
| if (changedButtons) { |
| Uint64 timestamp = SDL_US_TO_NS(timestampUS + g_GameInputTimestampOffset); |
| |
| SDL_LockJoysticks(); |
| if (changedButtons & GameInputSystemButtonGuide) { |
| bool down = ((currentButtons & GameInputSystemButtonGuide) != 0); |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, down); |
| } |
| if (changedButtons & GameInputSystemButtonShare) { |
| bool down = ((currentButtons & GameInputSystemButtonShare) != 0); |
| SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE, down); |
| } |
| SDL_UnlockJoysticks(); |
| } |
| } |
| #endif // GAMEINPUT_API_VERSION >= 1 |
| |
| static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index) |
| { |
| GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index); |
| const GameInputDeviceInfo *info = elem->info; |
| GAMEINPUT_InternalJoystickHwdata *hwdata = NULL; |
| |
| if (!elem) { |
| return false; |
| } |
| |
| hwdata = (GAMEINPUT_InternalJoystickHwdata *)SDL_calloc(1, sizeof(*hwdata)); |
| if (!hwdata) { |
| return false; |
| } |
| |
| hwdata->devref = elem; |
| |
| joystick->hwdata = hwdata; |
| if (GAMEINPUT_InternalIsGamepad(info)) { |
| joystick->naxes = 6; |
| joystick->nbuttons = 11; |
| joystick->nhats = 1; |
| |
| #if GAMEINPUT_API_VERSION >= 1 |
| if (info->supportedSystemButtons != GameInputSystemButtonNone) { |
| if (info->supportedSystemButtons & GameInputSystemButtonShare) { |
| ++joystick->nbuttons; |
| } |
| |
| g_pGameInput->RegisterSystemButtonCallback(elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token); |
| } |
| #endif // GAMEINPUT_API_VERSION >= 1 |
| } else { |
| joystick->naxes = info->controllerAxisCount; |
| joystick->nbuttons = info->controllerButtonCount; |
| joystick->nhats = info->controllerSwitchCount; |
| } |
| |
| if (info->supportedRumbleMotors & (GameInputRumbleLowFrequency | GameInputRumbleHighFrequency)) { |
| SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); |
| } |
| if (info->supportedRumbleMotors & (GameInputRumbleLeftTrigger | GameInputRumbleRightTrigger)) { |
| SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true); |
| } |
| |
| #ifdef GAMEINPUT_SENSOR_SUPPORT |
| if (info->supportedInput & GameInputKindSensors) { |
| // FIXME: What's the sensor update rate? |
| if (info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) { |
| SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); |
| } |
| if (info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) { |
| SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); |
| } |
| } |
| #endif |
| return true; |
| } |
| |
| static bool GAMEINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) |
| { |
| // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it |
| GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; |
| GameInputRumbleParams *params = &hwdata->rumbleParams; |
| params->lowFrequency = (float)low_frequency_rumble / (float)SDL_MAX_UINT16; |
| params->highFrequency = (float)high_frequency_rumble / (float)SDL_MAX_UINT16; |
| hwdata->devref->device->SetRumbleState(params); |
| return true; |
| } |
| |
| static bool GAMEINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) |
| { |
| // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it |
| GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; |
| GameInputRumbleParams *params = &hwdata->rumbleParams; |
| params->leftTrigger = (float)left_rumble / (float)SDL_MAX_UINT16; |
| params->rightTrigger = (float)right_rumble / (float)SDL_MAX_UINT16; |
| hwdata->devref->device->SetRumbleState(params); |
| return true; |
| } |
| |
| static bool GAMEINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) |
| { |
| return SDL_Unsupported(); |
| } |
| |
| static bool GAMEINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) |
| { |
| return SDL_Unsupported(); |
| } |
| |
| static bool GAMEINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) |
| { |
| joystick->hwdata->report_sensors = enabled; |
| return true; |
| } |
| |
| static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick) |
| { |
| GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; |
| IGameInputDevice *device = hwdata->devref->device; |
| const GameInputDeviceInfo *info = hwdata->devref->info; |
| IGameInputReading *reading = NULL; |
| Uint64 timestamp; |
| GameInputGamepadState state; |
| HRESULT hr; |
| |
| hr = g_pGameInput->GetCurrentReading(info->supportedInput, device, &reading); |
| if (FAILED(hr)) { |
| // don't SetError here since there can be a legitimate case when there's no reading avail |
| return; |
| } |
| |
| timestamp = SDL_US_TO_NS(reading->GetTimestamp() + g_GameInputTimestampOffset); |
| |
| if (GAMEINPUT_InternalIsGamepad(info)) { |
| static WORD s_XInputButtons[] = { |
| GameInputGamepadA, // SDL_GAMEPAD_BUTTON_SOUTH |
| GameInputGamepadB, // SDL_GAMEPAD_BUTTON_EAST |
| GameInputGamepadX, // SDL_GAMEPAD_BUTTON_WEST |
| GameInputGamepadY, // SDL_GAMEPAD_BUTTON_NORTH |
| GameInputGamepadView, // SDL_GAMEPAD_BUTTON_BACK |
| 0, // The guide button is not available |
| GameInputGamepadMenu, // SDL_GAMEPAD_BUTTON_START |
| GameInputGamepadLeftThumbstick, // SDL_GAMEPAD_BUTTON_LEFT_STICK |
| GameInputGamepadRightThumbstick, // SDL_GAMEPAD_BUTTON_RIGHT_STICK |
| GameInputGamepadLeftShoulder, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER |
| GameInputGamepadRightShoulder, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER |
| }; |
| Uint8 btnidx = 0, hat = 0; |
| |
| if (reading->GetGamepadState(&state)) { |
| for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) { |
| WORD button_mask = s_XInputButtons[btnidx]; |
| if (!button_mask) { |
| continue; |
| } |
| bool down = ((state.buttons & button_mask) != 0); |
| SDL_SendJoystickButton(timestamp, joystick, btnidx, down); |
| } |
| |
| if (state.buttons & GameInputGamepadDPadUp) { |
| hat |= SDL_HAT_UP; |
| } |
| if (state.buttons & GameInputGamepadDPadDown) { |
| hat |= SDL_HAT_DOWN; |
| } |
| if (state.buttons & GameInputGamepadDPadLeft) { |
| hat |= SDL_HAT_LEFT; |
| } |
| if (state.buttons & GameInputGamepadDPadRight) { |
| hat |= SDL_HAT_RIGHT; |
| } |
| SDL_SendJoystickHat(timestamp, joystick, 0, hat); |
| |
| #define CONVERT_AXIS(v) (Sint16)(((v) < 0.0f) ? ((v)*32768.0f) : ((v)*32767.0f)) |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, CONVERT_AXIS(state.leftThumbstickX)); |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, CONVERT_AXIS(-state.leftThumbstickY)); |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, CONVERT_AXIS(state.rightThumbstickX)); |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, CONVERT_AXIS(-state.rightThumbstickY)); |
| #undef CONVERT_AXIS |
| #define CONVERT_TRIGGER(v) (Sint16)((v)*65535.0f - 32768.0f) |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, CONVERT_TRIGGER(state.leftTrigger)); |
| SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, CONVERT_TRIGGER(state.rightTrigger)); |
| #undef CONVERT_TRIGGER |
| } |
| } else { |
| bool *button_state = SDL_stack_alloc(bool, info->controllerButtonCount); |
| float *axis_state = SDL_stack_alloc(float, info->controllerAxisCount); |
| GameInputSwitchPosition *switch_state = SDL_stack_alloc(GameInputSwitchPosition, info->controllerSwitchCount); |
| |
| if (button_state) { |
| uint32_t i; |
| uint32_t button_count = reading->GetControllerButtonState(info->controllerButtonCount, button_state); |
| for (i = 0; i < button_count; ++i) { |
| SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]); |
| } |
| SDL_stack_free(button_state); |
| } |
| |
| #define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f) |
| if (axis_state) { |
| uint32_t i; |
| uint32_t axis_count = reading->GetControllerAxisState(info->controllerAxisCount, axis_state); |
| for (i = 0; i < axis_count; ++i) { |
| SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i])); |
| } |
| SDL_stack_free(axis_state); |
| } |
| #undef CONVERT_AXIS |
| |
| if (switch_state) { |
| uint32_t i; |
| uint32_t switch_count = reading->GetControllerSwitchState(info->controllerSwitchCount, switch_state); |
| for (i = 0; i < switch_count; ++i) { |
| Uint8 hat; |
| switch (switch_state[i]) { |
| case GameInputSwitchUp: |
| hat = SDL_HAT_UP; |
| break; |
| case GameInputSwitchUpRight: |
| hat = SDL_HAT_UP | SDL_HAT_RIGHT; |
| break; |
| case GameInputSwitchRight: |
| hat = SDL_HAT_RIGHT; |
| break; |
| case GameInputSwitchDownRight: |
| hat = SDL_HAT_DOWN | SDL_HAT_RIGHT; |
| break; |
| case GameInputSwitchDown: |
| hat = SDL_HAT_DOWN; |
| break; |
| case GameInputSwitchDownLeft: |
| hat = SDL_HAT_DOWN | SDL_HAT_LEFT; |
| break; |
| case GameInputSwitchLeft: |
| hat = SDL_HAT_LEFT; |
| break; |
| case GameInputSwitchUpLeft: |
| hat = SDL_HAT_UP | SDL_HAT_LEFT; |
| break; |
| case GameInputSwitchCenter: |
| default: |
| hat = SDL_HAT_CENTERED; |
| break; |
| } |
| SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat); |
| } |
| SDL_stack_free(switch_state); |
| } |
| } |
| |
| #ifdef GAMEINPUT_SENSOR_SUPPORT |
| if (hwdata->report_sensors) { |
| GameInputSensorsState sensor_state; |
| |
| if (reading->GetSensorsState(&sensor_state)) { |
| if ((info->sensorsInfo->supportedSensors & GameInputSensorsGyrometer) != 0) { |
| float data[3] = { |
| sensor_state.angularVelocityInRadPerSecX, |
| sensor_state.angularVelocityInRadPerSecY, |
| sensor_state.angularVelocityInRadPerSecZ |
| }; |
| SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, SDL_arraysize(data)); |
| } |
| if ((info->sensorsInfo->supportedSensors & GameInputSensorsAccelerometer) != 0) { |
| float data[3] = { |
| sensor_state.accelerationInGX * SDL_STANDARD_GRAVITY, |
| sensor_state.accelerationInGY * SDL_STANDARD_GRAVITY, |
| sensor_state.accelerationInGZ * SDL_STANDARD_GRAVITY |
| }; |
| SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, SDL_arraysize(data)); |
| } |
| } |
| } |
| #endif // GAMEINPUT_SENSOR_SUPPORT |
| |
| reading->Release(); |
| |
| // FIXME: We can poll this at a much lower rate |
| GAMEINPUT_UpdatePowerInfo(joystick, device); |
| } |
| |
| static void GAMEINPUT_JoystickClose(SDL_Joystick *joystick) |
| { |
| GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata; |
| |
| if (hwdata->system_button_callback_token) { |
| #if GAMEINPUT_API_VERSION >= 1 |
| g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token); |
| #else |
| g_pGameInput->UnregisterCallback(hwdata->system_button_callback_token, 10000); |
| #endif |
| } |
| SDL_free(hwdata); |
| |
| joystick->hwdata = NULL; |
| } |
| |
| static void GAMEINPUT_JoystickQuit(void) |
| { |
| if (g_pGameInput) { |
| // free the callback |
| if (g_GameInputCallbackToken) { |
| #if GAMEINPUT_API_VERSION >= 1 |
| g_pGameInput->UnregisterCallback(g_GameInputCallbackToken); |
| #else |
| g_pGameInput->UnregisterCallback(g_GameInputCallbackToken, 10000); |
| #endif |
| g_GameInputCallbackToken = 0; |
| } |
| |
| // free the list |
| while (g_GameInputList.count > 0) { |
| GAMEINPUT_InternalRemoveByIndex(0); |
| } |
| |
| SDL_QuitGameInput(); |
| g_pGameInput = NULL; |
| } |
| } |
| |
| static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) |
| { |
| GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index); |
| |
| if (!GAMEINPUT_InternalIsGamepad(elem->info)) { |
| return false; |
| } |
| |
| out->a.kind = EMappingKind_Button; |
| out->a.target = SDL_GAMEPAD_BUTTON_SOUTH; |
| |
| out->b.kind = EMappingKind_Button; |
| out->b.target = SDL_GAMEPAD_BUTTON_EAST; |
| |
| out->x.kind = EMappingKind_Button; |
| out->x.target = SDL_GAMEPAD_BUTTON_WEST; |
| |
| out->y.kind = EMappingKind_Button; |
| out->y.target = SDL_GAMEPAD_BUTTON_NORTH; |
| |
| out->back.kind = EMappingKind_Button; |
| out->back.target = SDL_GAMEPAD_BUTTON_BACK; |
| |
| #if GAMEINPUT_API_VERSION >= 1 |
| if (elem->info->supportedSystemButtons & GameInputSystemButtonGuide) { |
| out->guide.kind = EMappingKind_Button; |
| out->guide.target = SDL_GAMEPAD_BUTTON_GUIDE; |
| } |
| |
| if (elem->info->supportedSystemButtons & GameInputSystemButtonShare) { |
| out->misc1.kind = EMappingKind_Button; |
| out->misc1.target = SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE; |
| } |
| #endif // GAMEINPUT_API_VERSION >= 1 |
| |
| out->start.kind = EMappingKind_Button; |
| out->start.target = SDL_GAMEPAD_BUTTON_START; |
| |
| out->leftstick.kind = EMappingKind_Button; |
| out->leftstick.target = SDL_GAMEPAD_BUTTON_LEFT_STICK; |
| |
| out->rightstick.kind = EMappingKind_Button; |
| out->rightstick.target = SDL_GAMEPAD_BUTTON_RIGHT_STICK; |
| |
| out->leftshoulder.kind = EMappingKind_Button; |
| out->leftshoulder.target = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; |
| |
| out->rightshoulder.kind = EMappingKind_Button; |
| out->rightshoulder.target = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER; |
| |
| out->dpup.kind = EMappingKind_Hat; |
| out->dpup.target = SDL_HAT_UP; |
| |
| out->dpdown.kind = EMappingKind_Hat; |
| out->dpdown.target = SDL_HAT_DOWN; |
| |
| out->dpleft.kind = EMappingKind_Hat; |
| out->dpleft.target = SDL_HAT_LEFT; |
| |
| out->dpright.kind = EMappingKind_Hat; |
| out->dpright.target = SDL_HAT_RIGHT; |
| |
| out->leftx.kind = EMappingKind_Axis; |
| out->leftx.target = SDL_GAMEPAD_AXIS_LEFTX; |
| |
| out->lefty.kind = EMappingKind_Axis; |
| out->lefty.target = SDL_GAMEPAD_AXIS_LEFTY; |
| |
| out->rightx.kind = EMappingKind_Axis; |
| out->rightx.target = SDL_GAMEPAD_AXIS_RIGHTX; |
| |
| out->righty.kind = EMappingKind_Axis; |
| out->righty.target = SDL_GAMEPAD_AXIS_RIGHTY; |
| |
| out->lefttrigger.kind = EMappingKind_Axis; |
| out->lefttrigger.target = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; |
| |
| out->righttrigger.kind = EMappingKind_Axis; |
| out->righttrigger.target = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; |
| |
| return true; |
| } |
| |
| |
| SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver = |
| { |
| GAMEINPUT_JoystickInit, |
| GAMEINPUT_JoystickGetCount, |
| GAMEINPUT_JoystickDetect, |
| GAMEINPUT_JoystickIsDevicePresent, |
| GAMEINPUT_JoystickGetDeviceName, |
| GAMEINPUT_JoystickGetDevicePath, |
| GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot, |
| GAMEINPUT_JoystickGetDevicePlayerIndex, |
| GAMEINPUT_JoystickSetDevicePlayerIndex, |
| GAMEINPUT_JoystickGetDeviceGUID, |
| GAMEINPUT_JoystickGetDeviceInstanceID, |
| GAMEINPUT_JoystickOpen, |
| GAMEINPUT_JoystickRumble, |
| GAMEINPUT_JoystickRumbleTriggers, |
| GAMEINPUT_JoystickSetLED, |
| GAMEINPUT_JoystickSendEffect, |
| GAMEINPUT_JoystickSetSensorsEnabled, |
| GAMEINPUT_JoystickUpdate, |
| GAMEINPUT_JoystickClose, |
| GAMEINPUT_JoystickQuit, |
| GAMEINPUT_JoystickGetGamepadMapping |
| }; |
| |
| |
| #endif // SDL_JOYSTICK_GAMEINPUT |