Atlas - SDL_hidapi_steam_hori.c
Home / ext / SDL / src / joystick / hidapi Lines: 1 | Size: 15785 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_HIDAPI 24 25#include "../SDL_sysjoystick.h" 26#include "SDL_hidapijoystick_c.h" 27#include "SDL_hidapi_rumble.h" 28#include "../SDL_joystick_c.h" 29 30#ifdef SDL_JOYSTICK_HIDAPI_STEAM_HORI 31 32/* Define this if you want to log all packets from the controller */ 33/*#define DEBUG_HORI_PROTOCOL*/ 34 35enum 36{ 37 SDL_GAMEPAD_BUTTON_HORI_QAM = 11, 38 SDL_GAMEPAD_BUTTON_HORI_FR, 39 SDL_GAMEPAD_BUTTON_HORI_FL, 40 SDL_GAMEPAD_BUTTON_HORI_M1, 41 SDL_GAMEPAD_BUTTON_HORI_M2, 42 SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, 43 SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, 44 SDL_GAMEPAD_NUM_HORI_BUTTONS 45}; 46 47typedef struct 48{ 49 Uint8 last_state[USB_PACKET_LENGTH]; 50 Uint64 sensor_ticks; 51 Uint32 last_tick; 52 Uint64 simulated_sensor_step_ns; 53 Uint64 simulated_sensor_time_stamp; 54 bool wireless; 55 bool serial_needs_init; 56} SDL_DriverSteamHori_Context; 57 58static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device); 59 60static void HIDAPI_DriverSteamHori_RegisterHints(SDL_HintCallback callback, void *userdata) 61{ 62 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata); 63} 64 65static void HIDAPI_DriverSteamHori_UnregisterHints(SDL_HintCallback callback, void *userdata) 66{ 67 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata); 68} 69 70static bool HIDAPI_DriverSteamHori_IsEnabled(void) 71{ 72 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); 73} 74 75static bool HIDAPI_DriverSteamHori_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) 76{ 77 return SDL_IsJoystickHoriSteamController(vendor_id, product_id); 78} 79 80static bool HIDAPI_DriverSteamHori_InitDevice(SDL_HIDAPI_Device *device) 81{ 82 SDL_DriverSteamHori_Context *ctx; 83 84 ctx = (SDL_DriverSteamHori_Context *)SDL_calloc(1, sizeof(*ctx)); 85 if (!ctx) { 86 return false; 87 } 88 89 device->context = ctx; 90 ctx->serial_needs_init = true; 91 92 HIDAPI_SetDeviceName(device, "Wireless HORIPAD For Steam"); 93 94 return HIDAPI_JoystickConnected(device, NULL); 95} 96 97static int HIDAPI_DriverSteamHori_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) 98{ 99 return -1; 100} 101 102static void HIDAPI_DriverSteamHori_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) 103{ 104} 105 106static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 107{ 108 SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context; 109 110 SDL_AssertJoysticksLocked(); 111 112 SDL_zeroa(ctx->last_state); 113 114 /* Initialize the joystick capabilities */ 115 joystick->nbuttons = SDL_GAMEPAD_NUM_HORI_BUTTONS; 116 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; 117 joystick->nhats = 1; 118 119 ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT; 120 121 if (ctx->wireless && device->serial) { 122 joystick->serial = SDL_strdup(device->serial); 123 ctx->serial_needs_init = false; 124 } else if (!ctx->wireless) { 125 // Need to actual read from the device to init the serial 126 HIDAPI_DriverSteamHori_UpdateDevice(device); 127 } 128 129 const float sensorupdaterate = ctx->wireless ? 120.0f : 250.0f; 130 131 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, sensorupdaterate); 132 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, sensorupdaterate); 133 134 const Uint64 sensorupdatestep_ms = ctx->wireless ? 8333 : 4000; // Equivalent to 120hz / 250hz respectively 135 ctx->simulated_sensor_step_ns = SDL_US_TO_NS(sensorupdatestep_ms); 136 return true; 137} 138 139static bool HIDAPI_DriverSteamHori_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 140{ 141 // Device doesn't support rumble 142 return SDL_Unsupported(); 143} 144 145static bool HIDAPI_DriverSteamHori_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) 146{ 147 return SDL_Unsupported(); 148} 149 150static Uint32 HIDAPI_DriverSteamHori_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 151{ 152 return 0; 153} 154 155static bool HIDAPI_DriverSteamHori_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) 156{ 157 return SDL_Unsupported(); 158} 159 160static bool HIDAPI_DriverSteamHori_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) 161{ 162 return SDL_Unsupported(); 163} 164 165static bool HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) 166{ 167 return true; 168} 169 170#undef clamp 171#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) 172 173#ifndef DEG2RAD 174#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) 175#endif 176 177//--------------------------------------------------------------------------- 178// Scale and clamp values to a range 179//--------------------------------------------------------------------------- 180static float RemapValClamped(float val, float A, float B, float C, float D) 181{ 182 if (A == B) { 183 return (val - B) >= 0.0f ? D : C; 184 } else { 185 float cVal = (val - A) / (B - A); 186 cVal = clamp(cVal, 0.0f, 1.0f); 187 188 return C + (D - C) * cVal; 189 } 190} 191 192#define REPORT_HEADER_USB 0x07 193#define REPORT_HEADER_BT 0x00 194 195static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSteamHori_Context *ctx, Uint8 *data, int size) 196{ 197 Sint16 axis; 198 Uint64 timestamp = SDL_GetTicksNS(); 199 200 // Make sure it's gamepad state and not OTA FW update info 201 if (data[0] != REPORT_HEADER_USB && data[0] != REPORT_HEADER_BT) { 202 /* We don't know how to handle this report */ 203 return; 204 } 205 206#define READ_STICK_AXIS(offset) \ 207 (data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), -0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16)) 208 { 209 axis = READ_STICK_AXIS(1); 210 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); 211 axis = READ_STICK_AXIS(2); 212 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); 213 axis = READ_STICK_AXIS(3); 214 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); 215 axis = READ_STICK_AXIS(4); 216 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); 217 } 218#undef READ_STICK_AXIS 219 220 if (ctx->last_state[5] != data[5]) { 221 Uint8 hat; 222 223 switch (data[5] & 0xF) { 224 case 0: 225 hat = SDL_HAT_UP; 226 break; 227 case 1: 228 hat = SDL_HAT_RIGHTUP; 229 break; 230 case 2: 231 hat = SDL_HAT_RIGHT; 232 break; 233 case 3: 234 hat = SDL_HAT_RIGHTDOWN; 235 break; 236 case 4: 237 hat = SDL_HAT_DOWN; 238 break; 239 case 5: 240 hat = SDL_HAT_LEFTDOWN; 241 break; 242 case 6: 243 hat = SDL_HAT_LEFT; 244 break; 245 case 7: 246 hat = SDL_HAT_LEFTUP; 247 break; 248 default: 249 hat = SDL_HAT_CENTERED; 250 break; 251 } 252 SDL_SendJoystickHat(timestamp, joystick, 0, hat); 253 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x10) != 0)); 254 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x20) != 0)); 255 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_QAM, ((data[5] & 0x40) != 0)); 256 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x80) != 0)); 257 258 } 259 260 if (ctx->last_state[6] != data[6]) { 261 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[6] & 0x01) != 0)); 262 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M1 /* M1 */, ((data[6] & 0x02) != 0)); 263 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[6] & 0x04) != 0)); 264 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[6] & 0x08) != 0)); 265 266 // TODO: can we handle the digital trigger mode? The data seems to come through analog regardless of the trigger state 267 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x40) != 0)); 268 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x80) != 0)); 269 } 270 271 if (ctx->last_state[7] != data[7]) { 272 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[7] & 0x01) != 0)); 273 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[7] & 0x02) != 0)); 274 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[7] & 0x04) != 0)); 275 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M2, ((data[7] & 0x08) != 0)); 276 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, ((data[7] & 0x10) != 0)); 277 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, ((data[7] & 0x20) != 0)); 278 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FR, ((data[7] & 0x40) != 0)); 279 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0)); 280 } 281 282 if (!ctx->wireless && ctx->serial_needs_init) { 283 char serial[18]; 284 (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", 285 data[38], data[39], data[40], data[41], data[42], data[43]); 286 287 SDL_AssertJoysticksLocked(); 288 joystick->serial = SDL_strdup(serial); 289 ctx->serial_needs_init = false; 290 } 291 292#define READ_TRIGGER_AXIS(offset) \ 293 (Sint16)(((int)data[offset] * 257) - 32768) 294 { 295 axis = READ_TRIGGER_AXIS(8); 296 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); 297 axis = READ_TRIGGER_AXIS(9); 298 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); 299 } 300#undef READ_TRIGGER_AXIS 301 302 if (1) { 303 Uint64 sensor_timestamp; 304 float imu_data[3]; 305 306 /* 16-bit timestamp */ 307 Uint32 delta; 308 Uint16 tick = LOAD16(data[10], 309 data[11]); 310 if (ctx->last_tick < tick) { 311 delta = (tick - ctx->last_tick); 312 } else { 313 delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); 314 } 315 316 ctx->last_tick = tick; 317 ctx->sensor_ticks += delta; 318 319 /* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */ 320 // sensor_timestamp = timestamp; // if the values were good we would call SDL_US_TO_NS(ctx->sensor_ticks); 321 322 /* New approach - simulate a fixed rate of 250hz (from observation). This reduces stutter from dropped/racing bluetooth packets.*/ 323 ctx->simulated_sensor_time_stamp += ctx->simulated_sensor_step_ns; 324 sensor_timestamp = ctx->simulated_sensor_time_stamp; 325 326 const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f; 327 const float gyroScale = DEG2RAD(2048); 328 329 imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); 330 imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); 331 imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); 332 333 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3); 334 335 // SDL_Log("%u %f, %f, %f ", data[0], imu_data[0], imu_data[1], imu_data[2] ); 336 imu_data[2] = LOAD16(data[18], data[19]) * accelScale; 337 imu_data[1] = -1 * LOAD16(data[20], data[21]) * accelScale; 338 imu_data[0] = LOAD16(data[22], data[23]) * accelScale; 339 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, imu_data, 3); 340 } 341 342 if (ctx->last_state[24] != data[24]) { 343 bool bCharging = (data[24] & 0x10) != 0; 344 int percent = (data[24] & 0xF) * 10; 345 SDL_PowerState state; 346 347 if (bCharging) { 348 state = SDL_POWERSTATE_CHARGING; 349 } else if (ctx->wireless) { 350 state = SDL_POWERSTATE_ON_BATTERY; 351 } else { 352 state = SDL_POWERSTATE_CHARGED; 353 } 354 355 SDL_SendJoystickPowerInfo(joystick, state, percent); 356 } 357 358 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); 359} 360 361static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device) 362{ 363 SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context; 364 SDL_Joystick *joystick = NULL; 365 Uint8 data[USB_PACKET_LENGTH]; 366 int size = 0; 367 368 if (device->num_joysticks > 0) { 369 joystick = SDL_GetJoystickFromID(device->joysticks[0]); 370 } else { 371 return false; 372 } 373 374 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { 375#ifdef DEBUG_HORI_PROTOCOL 376 HIDAPI_DumpPacket("Google Hori packet: size = %d", data, size); 377#endif 378 if (!joystick) { 379 continue; 380 } 381 382 HIDAPI_DriverSteamHori_HandleStatePacket(joystick, ctx, data, size); 383 } 384 385 if (size < 0) { 386 /* Read error, device is disconnected */ 387 HIDAPI_JoystickDisconnected(device, device->joysticks[0]); 388 } 389 return (size >= 0); 390} 391 392static void HIDAPI_DriverSteamHori_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 393{ 394} 395 396static void HIDAPI_DriverSteamHori_FreeDevice(SDL_HIDAPI_Device *device) 397{ 398} 399 400SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori = { 401 SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, 402 true, 403 HIDAPI_DriverSteamHori_RegisterHints, 404 HIDAPI_DriverSteamHori_UnregisterHints, 405 HIDAPI_DriverSteamHori_IsEnabled, 406 HIDAPI_DriverSteamHori_IsSupportedDevice, 407 HIDAPI_DriverSteamHori_InitDevice, 408 HIDAPI_DriverSteamHori_GetDevicePlayerIndex, 409 HIDAPI_DriverSteamHori_SetDevicePlayerIndex, 410 HIDAPI_DriverSteamHori_UpdateDevice, 411 HIDAPI_DriverSteamHori_OpenJoystick, 412 HIDAPI_DriverSteamHori_RumbleJoystick, 413 HIDAPI_DriverSteamHori_RumbleJoystickTriggers, 414 HIDAPI_DriverSteamHori_GetJoystickCapabilities, 415 HIDAPI_DriverSteamHori_SetJoystickLED, 416 HIDAPI_DriverSteamHori_SendJoystickEffect, 417 HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled, 418 HIDAPI_DriverSteamHori_CloseJoystick, 419 HIDAPI_DriverSteamHori_FreeDevice, 420}; 421 422#endif /* SDL_JOYSTICK_HIDAPI_STEAM_HORI */ 423 424#endif /* SDL_JOYSTICK_HIDAPI */ 425[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.