Atlas - SDL_hidapi_steam_hori.c

Home / ext / SDL / src / joystick / hidapi Lines: 1 | Size: 15228 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#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 35#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) 36 37enum 38{ 39 SDL_GAMEPAD_BUTTON_HORI_QAM = 11, 40 SDL_GAMEPAD_BUTTON_HORI_FR, 41 SDL_GAMEPAD_BUTTON_HORI_FL, 42 SDL_GAMEPAD_BUTTON_HORI_M1, 43 SDL_GAMEPAD_BUTTON_HORI_M2, 44 SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, 45 SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, 46 SDL_GAMEPAD_NUM_HORI_BUTTONS 47}; 48 49typedef struct 50{ 51 Uint8 last_state[USB_PACKET_LENGTH]; 52 Uint64 sensor_ticks; 53 Uint32 last_tick; 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 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); 130 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); 131 132 return true; 133} 134 135static bool HIDAPI_DriverSteamHori_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 136{ 137 // Device doesn't support rumble 138 return SDL_Unsupported(); 139} 140 141static bool HIDAPI_DriverSteamHori_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) 142{ 143 return SDL_Unsupported(); 144} 145 146static Uint32 HIDAPI_DriverSteamHori_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 147{ 148 return 0; 149} 150 151static bool HIDAPI_DriverSteamHori_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) 152{ 153 return SDL_Unsupported(); 154} 155 156static bool HIDAPI_DriverSteamHori_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) 157{ 158 return SDL_Unsupported(); 159} 160 161static bool HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) 162{ 163 return true; 164} 165 166#undef clamp 167#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) 168 169#ifndef DEG2RAD 170#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) 171#endif 172 173//--------------------------------------------------------------------------- 174// Scale and clamp values to a range 175//--------------------------------------------------------------------------- 176static float RemapValClamped(float val, float A, float B, float C, float D) 177{ 178 if (A == B) { 179 return (val - B) >= 0.0f ? D : C; 180 } else { 181 float cVal = (val - A) / (B - A); 182 cVal = clamp(cVal, 0.0f, 1.0f); 183 184 return C + (D - C) * cVal; 185 } 186} 187 188#define REPORT_HEADER_USB 0x07 189#define REPORT_HEADER_BT 0x00 190 191static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSteamHori_Context *ctx, Uint8 *data, int size) 192{ 193 Sint16 axis; 194 Uint64 timestamp = SDL_GetTicksNS(); 195 196 // Make sure it's gamepad state and not OTA FW update info 197 if (data[0] != REPORT_HEADER_USB && data[0] != REPORT_HEADER_BT) { 198 /* We don't know how to handle this report */ 199 return; 200 } 201 202 #define READ_STICK_AXIS(offset) \ 203 (data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), -0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16)) 204 { 205 axis = READ_STICK_AXIS(1); 206 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); 207 axis = READ_STICK_AXIS(2); 208 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); 209 axis = READ_STICK_AXIS(3); 210 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); 211 axis = READ_STICK_AXIS(4); 212 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); 213 } 214#undef READ_STICK_AXIS 215 216 if (ctx->last_state[5] != data[5]) { 217 Uint8 hat; 218 219 switch (data[5] & 0xF) { 220 case 0: 221 hat = SDL_HAT_UP; 222 break; 223 case 1: 224 hat = SDL_HAT_RIGHTUP; 225 break; 226 case 2: 227 hat = SDL_HAT_RIGHT; 228 break; 229 case 3: 230 hat = SDL_HAT_RIGHTDOWN; 231 break; 232 case 4: 233 hat = SDL_HAT_DOWN; 234 break; 235 case 5: 236 hat = SDL_HAT_LEFTDOWN; 237 break; 238 case 6: 239 hat = SDL_HAT_LEFT; 240 break; 241 case 7: 242 hat = SDL_HAT_LEFTUP; 243 break; 244 default: 245 hat = SDL_HAT_CENTERED; 246 break; 247 } 248 SDL_SendJoystickHat(timestamp, joystick, 0, hat); 249 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x10) != 0)); 250 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x20) != 0)); 251 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_QAM, ((data[5] & 0x40) != 0)); 252 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x80) != 0)); 253 254 } 255 256 if (ctx->last_state[6] != data[6]) { 257 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[6] & 0x01) != 0)); 258 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M1 /* M1 */, ((data[6] & 0x02) != 0)); 259 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[6] & 0x04) != 0)); 260 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[6] & 0x08) != 0)); 261 262 // TODO: can we handle the digital trigger mode? The data seems to come through analog regardless of the trigger state 263 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x40) != 0)); 264 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x80) != 0)); 265 } 266 267 if (ctx->last_state[7] != data[7]) { 268 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[7] & 0x01) != 0)); 269 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[7] & 0x02) != 0)); 270 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[7] & 0x04) != 0)); 271 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M2, ((data[7] & 0x08) != 0)); 272 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, ((data[7] & 0x10) != 0)); 273 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, ((data[7] & 0x20) != 0)); 274 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FR, ((data[7] & 0x40) != 0)); 275 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0)); 276 } 277 278 if (!ctx->wireless && ctx->serial_needs_init) { 279 char serial[18]; 280 (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", 281 data[38], data[39], data[40], data[41], data[42], data[43]); 282 283 SDL_AssertJoysticksLocked(); 284 joystick->serial = SDL_strdup(serial); 285 ctx->serial_needs_init = false; 286 } 287 288#define READ_TRIGGER_AXIS(offset) \ 289 (Sint16)(((int)data[offset] * 257) - 32768) 290 { 291 axis = READ_TRIGGER_AXIS(8); 292 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); 293 axis = READ_TRIGGER_AXIS(9); 294 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); 295 } 296#undef READ_TRIGGER_AXIS 297 298 if (1) { 299 Uint64 sensor_timestamp; 300 float imu_data[3]; 301 302 /* 16-bit timestamp */ 303 Uint32 delta; 304 Uint16 tick = LOAD16(data[10], 305 data[11]); 306 if (ctx->last_tick < tick) { 307 delta = (tick - ctx->last_tick); 308 } else { 309 delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1); 310 } 311 312 ctx->last_tick = tick; 313 ctx->sensor_ticks += delta; 314 315 /* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */ 316 sensor_timestamp = timestamp; // if the values were good we would call SDL_US_TO_NS(ctx->sensor_ticks); 317 318 const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f; 319 const float gyroScale = DEG2RAD(2048); 320 321 imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); 322 imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); 323 imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale); 324 325 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3); 326 327 // SDL_Log("%u %f, %f, %f ", data[0], imu_data[0], imu_data[1], imu_data[2] ); 328 imu_data[2] = LOAD16(data[18], data[19]) * accelScale; 329 imu_data[1] = -1 * LOAD16(data[20], data[21]) * accelScale; 330 imu_data[0] = LOAD16(data[22], data[23]) * accelScale; 331 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, imu_data, 3); 332 } 333 334 if (ctx->last_state[24] != data[24]) { 335 bool bCharging = (data[24] & 0x10) != 0; 336 int percent = (data[24] & 0xF) * 10; 337 SDL_PowerState state; 338 339 if (bCharging) { 340 state = SDL_POWERSTATE_CHARGING; 341 } else if (ctx->wireless) { 342 state = SDL_POWERSTATE_ON_BATTERY; 343 } else { 344 state = SDL_POWERSTATE_CHARGED; 345 } 346 347 SDL_SendJoystickPowerInfo(joystick, state, percent); 348 } 349 350 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); 351} 352 353static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device) 354{ 355 SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context; 356 SDL_Joystick *joystick = NULL; 357 Uint8 data[USB_PACKET_LENGTH]; 358 int size = 0; 359 360 if (device->num_joysticks > 0) { 361 joystick = SDL_GetJoystickFromID(device->joysticks[0]); 362 } else { 363 return false; 364 } 365 366 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { 367#ifdef DEBUG_HORI_PROTOCOL 368 HIDAPI_DumpPacket("Google Hori packet: size = %d", data, size); 369#endif 370 if (!joystick) { 371 continue; 372 } 373 374 HIDAPI_DriverSteamHori_HandleStatePacket(joystick, ctx, data, size); 375 } 376 377 if (size < 0) { 378 /* Read error, device is disconnected */ 379 HIDAPI_JoystickDisconnected(device, device->joysticks[0]); 380 } 381 return (size >= 0); 382} 383 384static void HIDAPI_DriverSteamHori_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 385{ 386} 387 388static void HIDAPI_DriverSteamHori_FreeDevice(SDL_HIDAPI_Device *device) 389{ 390} 391 392SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori = { 393 SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, 394 true, 395 HIDAPI_DriverSteamHori_RegisterHints, 396 HIDAPI_DriverSteamHori_UnregisterHints, 397 HIDAPI_DriverSteamHori_IsEnabled, 398 HIDAPI_DriverSteamHori_IsSupportedDevice, 399 HIDAPI_DriverSteamHori_InitDevice, 400 HIDAPI_DriverSteamHori_GetDevicePlayerIndex, 401 HIDAPI_DriverSteamHori_SetDevicePlayerIndex, 402 HIDAPI_DriverSteamHori_UpdateDevice, 403 HIDAPI_DriverSteamHori_OpenJoystick, 404 HIDAPI_DriverSteamHori_RumbleJoystick, 405 HIDAPI_DriverSteamHori_RumbleJoystickTriggers, 406 HIDAPI_DriverSteamHori_GetJoystickCapabilities, 407 HIDAPI_DriverSteamHori_SetJoystickLED, 408 HIDAPI_DriverSteamHori_SendJoystickEffect, 409 HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled, 410 HIDAPI_DriverSteamHori_CloseJoystick, 411 HIDAPI_DriverSteamHori_FreeDevice, 412}; 413 414#endif /* SDL_JOYSTICK_HIDAPI_STEAM_HORI */ 415 416#endif /* SDL_JOYSTICK_HIDAPI */ 417
[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.