Atlas - SDL_hidapi_flydigi.c

Home / ext / SDL / src / joystick / hidapi Lines: 1 | Size: 36346 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2024 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_hidapi_flydigi.h" 29 30#ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI 31 32// Define this if you want to log all packets from the controller 33#if 0 34#define DEBUG_FLYDIGI_PROTOCOL 35#endif 36 37#ifndef DEG2RAD 38#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f)) 39#endif 40 41enum 42{ 43 SDL_GAMEPAD_BUTTON_FLYDIGI_M1 = 11, 44 SDL_GAMEPAD_BUTTON_FLYDIGI_M2, 45 SDL_GAMEPAD_BUTTON_FLYDIGI_M3, 46 SDL_GAMEPAD_BUTTON_FLYDIGI_M4, 47 SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS 48}; 49 50/* Rate of IMU Sensor Packets over wireless dongle observed in testcontroller at 1000hz */ 51#define SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ 1000 52#define SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ) 53/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 500hz */ 54#define SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ 500 55#define SENSOR_INTERVAL_VADER4_PRO_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ) 56 57/* Rate of IMU Sensor Packets over wireless dongle observed in testcontroller at 295hz */ 58#define SENSOR_INTERVAL_APEX5_DONGLE_RATE_HZ 295 59#define SENSOR_INTERVAL_APEX5_DONGLE_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_APEX5_DONGLE_RATE_HZ) 60/* Rate of IMU Sensor Packets over wired connection observed in testcontroller at 970hz */ 61#define SENSOR_INTERVAL_APEX5_WIRED_RATE_HZ 970 62#define SENSOR_INTERVAL_APEX5_WIRED_NS (SDL_NS_PER_SECOND / SENSOR_INTERVAL_APEX5_WIRED_RATE_HZ) 63 64#define FLYDIGI_ACQUIRE_CONTROLLER_HEARTBEAT_TIME 1000 * 30 65 66#define FLYDIGI_V1_CMD_REPORT_ID 0x05 67#define FLYDIGI_V1_HAPTIC_COMMAND 0x0F 68#define FLYDIGI_V1_GET_INFO_COMMAND 0xEC 69 70#define FLYDIGI_V2_CMD_REPORT_ID 0x03 71#define FLYDIGI_V2_MAGIC1 0x5A 72#define FLYDIGI_V2_MAGIC2 0xA5 73#define FLYDIGI_V2_GET_INFO_COMMAND 0x01 74#define FLYDIGI_V2_GET_STATUS_COMMAND 0x10 75#define FLYDIGI_V2_SET_STATUS_COMMAND 0x11 76#define FLYDIGI_V2_HAPTIC_COMMAND 0x12 77#define FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND 0x1C 78#define FLYDIGI_V2_INPUT_REPORT 0xEF 79 80#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8)) 81 82typedef struct 83{ 84 SDL_HIDAPI_Device *device; 85 Uint8 deviceID; 86 bool available; 87 bool has_cz; 88 bool has_lmrm; 89 bool wireless; 90 bool sensors_supported; 91 bool sensors_enabled; 92 Uint16 firmware_version; 93 Uint64 sensor_timestamp_ns; // Simulate onboard clock. Advance by known time step. Nanoseconds. 94 Uint64 sensor_timestamp_step_ns; // Based on observed rate of receipt of IMU sensor packets. 95 float accelScale; 96 float gyroScale; 97 Uint64 next_heartbeat; 98 Uint64 last_packet; 99 Uint8 last_state[USB_PACKET_LENGTH]; 100} SDL_DriverFlydigi_Context; 101 102 103static void HIDAPI_DriverFlydigi_RegisterHints(SDL_HintCallback callback, void *userdata) 104{ 105 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, callback, userdata); 106} 107 108static void HIDAPI_DriverFlydigi_UnregisterHints(SDL_HintCallback callback, void *userdata) 109{ 110 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, callback, userdata); 111} 112 113static bool HIDAPI_DriverFlydigi_IsEnabled(void) 114{ 115 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); 116} 117 118static bool HIDAPI_DriverFlydigi_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) 119{ 120 return SDL_IsJoystickFlydigiController(vendor_id, product_id) && interface_number == 2; 121} 122 123static bool HIDAPI_DriverFlydigi_InitControllerV1(SDL_HIDAPI_Device *device) 124{ 125 SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; 126 127 // Detecting the Vader 2 can take over 1000 read retries, so be generous here 128 for (int attempt = 0; ctx->deviceID == 0 && attempt < 30; ++attempt) { 129 const Uint8 request[] = { FLYDIGI_V1_CMD_REPORT_ID, FLYDIGI_V1_GET_INFO_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 130 // This write will occasionally return -1, so ignore failure here and try again 131 (void)SDL_hid_write(device->dev, request, sizeof(request)); 132 133 // Read the reply 134 for (int i = 0; i < 100; ++i) { 135 SDL_Delay(1); 136 137 Uint8 data[USB_PACKET_LENGTH]; 138 int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0); 139 if (size < 0) { 140 break; 141 } 142 if (size == 0) { 143 continue; 144 } 145 146#ifdef DEBUG_FLYDIGI_PROTOCOL 147 HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); 148#endif 149 if (size == 32 && data[15] == 236) { 150 ctx->deviceID = data[3]; 151 ctx->firmware_version = LOAD16(data[9], data[10]); 152 153 char serial[9]; 154 (void)SDL_snprintf(serial, sizeof(serial), "%.2x%.2x%.2x%.2x", data[5], data[6], data[7], data[8]); 155 HIDAPI_SetDeviceSerial(device, serial); 156 157 // The Vader 2 with firmware 6.0.4.9 doesn't report the connection state 158 if (ctx->firmware_version >= 0x6400) { 159 switch (data[13]) { 160 case 0: 161 // Wireless connection 162 ctx->wireless = true; 163 break; 164 case 1: 165 // Wired connection 166 ctx->wireless = false; 167 break; 168 default: 169 break; 170 } 171 } 172 173 // Done! 174 break; 175 } 176 } 177 } 178 ctx->available = true; 179 180 return true; 181} 182 183static void HIDAPI_DriverFlydigi_SetAvailable(SDL_HIDAPI_Device* device, bool available) 184{ 185 SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; 186 187 if (available == ctx->available) { 188 return; 189 } 190 191 if (available) { 192 if (device->num_joysticks == 0) { 193 HIDAPI_JoystickConnected(device, NULL); 194 } 195 } else { 196 if (device->num_joysticks > 0) { 197 HIDAPI_JoystickDisconnected(device, device->joysticks[0]); 198 } 199 } 200 ctx->available = available; 201} 202 203static bool GetReply(SDL_HIDAPI_Device* device, Uint8 command, Uint8* data, size_t length) 204{ 205 for (int i = 0; i < 100; ++i) { 206 SDL_Delay(1); 207 208 int size = SDL_hid_read_timeout(device->dev, data, length, 0); 209 if (size < 0) { 210 break; 211 } 212 if (size == 0) { 213 continue; 214 } 215 216#ifdef DEBUG_FLYDIGI_PROTOCOL 217 HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); 218#endif 219 220 if (size == 32 && data[1] == FLYDIGI_V2_MAGIC1 && data[2] == FLYDIGI_V2_MAGIC2 && data[3] == command) { 221 return true; 222 } 223 } 224 return false; 225} 226 227static bool SDL_HIDAPI_Flydigi_SendAcquireRequest(SDL_HIDAPI_Device *device, bool acquire) 228{ 229 const Uint8 acquireControllerCmd[32] = { 230 FLYDIGI_V2_CMD_REPORT_ID, 231 FLYDIGI_V2_MAGIC1, 232 FLYDIGI_V2_MAGIC2, 233 FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND, 234 23, 235 acquire ? 1 : 0, 236 'S', 'D', 'L', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 237 }; 238 239 if (SDL_hid_write(device->dev, acquireControllerCmd, sizeof(acquireControllerCmd)) < 0) { 240 return SDL_SetError("Couldn't send acquire command"); 241 } 242 return true; 243} 244 245static void HIDAPI_DriverFlydigi_HandleAcquireResponse(SDL_HIDAPI_Device *device, Uint8 *data, int size) 246{ 247 if (data[5] != 1 && data[6] == 0) { 248 // Controller acquiring failed or has been disabled 249 HIDAPI_DriverFlydigi_SetAvailable(device, false); 250 } 251} 252 253static bool HIDAPI_DriverFlydigi_InitControllerV2(SDL_HIDAPI_Device *device) 254{ 255 SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; 256 257 Uint8 data[USB_PACKET_LENGTH]; 258 const Uint8 query_info[] = { FLYDIGI_V2_CMD_REPORT_ID, FLYDIGI_V2_MAGIC1, FLYDIGI_V2_MAGIC2, FLYDIGI_V2_GET_INFO_COMMAND, 2, 0 }; 259 if (SDL_hid_write(device->dev, query_info, sizeof(query_info)) < 0) { 260 return SDL_SetError("Couldn't query controller info"); 261 } 262 if (!GetReply(device, FLYDIGI_V2_GET_INFO_COMMAND, data, sizeof(data))) { 263 return SDL_SetError("Couldn't get controller info"); 264 } 265 266 // Check the firmware version 267 ctx->firmware_version = LOAD16(data[17], data[16]); 268 if (ctx->firmware_version < 0x7031) { 269 return SDL_SetError("Unsupported firmware version"); 270 } 271 272 switch (data[7]) { 273 case 1: 274 // Wired connection 275 ctx->wireless = false; 276 break; 277 case 2: 278 // Wireless connection 279 ctx->wireless = true; 280 break; 281 default: 282 break; 283 } 284 ctx->deviceID = data[6]; 285 286 // See whether we can acquire the controller 287 const Uint8 query_status[] = { FLYDIGI_V2_CMD_REPORT_ID, FLYDIGI_V2_MAGIC1, FLYDIGI_V2_MAGIC2, FLYDIGI_V2_GET_STATUS_COMMAND }; 288 if (SDL_hid_write(device->dev, query_status, sizeof(query_status)) < 0) { 289 return SDL_SetError("Couldn't query controller status"); 290 } 291 if (!GetReply(device, FLYDIGI_V2_GET_STATUS_COMMAND, data, sizeof(data))) { 292 return SDL_SetError("Couldn't get controller status"); 293 } 294 if (data[10] == 1) { 295 ctx->available = true; 296 } else { 297 // Click "Allow third-party apps to take over mappings" in the FlyDigi Space Station app 298 } 299 return true; 300} 301 302static void HIDAPI_DriverFlydigi_UpdateDeviceIdentity(SDL_HIDAPI_Device *device) 303{ 304 SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; 305 306 Uint8 controller_type = SDL_FLYDIGI_UNKNOWN; 307 switch (ctx->deviceID) { 308 case 19: 309 controller_type = SDL_FLYDIGI_APEX2; 310 break; 311 case 24: 312 case 26: 313 case 29: 314 controller_type = SDL_FLYDIGI_APEX3; 315 break; 316 case 84: 317 controller_type = SDL_FLYDIGI_APEX4; 318 break; 319 case 20: 320 case 21: 321 case 23: 322 controller_type = SDL_FLYDIGI_VADER2; 323 break; 324 case 22: 325 controller_type = SDL_FLYDIGI_VADER2_PRO; 326 break; 327 case 28: 328 controller_type = SDL_FLYDIGI_VADER3; 329 break; 330 case 80: 331 case 81: 332 controller_type = SDL_FLYDIGI_VADER3_PRO; 333 break; 334 case 85: 335 case 91: 336 case 105: 337 controller_type = SDL_FLYDIGI_VADER4_PRO; 338 break; 339 case 128: 340 case 129: 341 case 133: 342 case 134: 343 controller_type = SDL_FLYDIGI_APEX5; 344 break; 345 default: 346 // Try to guess from the name of the controller 347 if (SDL_strstr(device->name, "VADER") != NULL) { 348 if (SDL_strstr(device->name, "VADER2") != NULL) { 349 controller_type = SDL_FLYDIGI_VADER2; 350 } else if (SDL_strstr(device->name, "VADER3") != NULL) { 351 controller_type = SDL_FLYDIGI_VADER3; 352 } else if (SDL_strstr(device->name, "VADER4") != NULL) { 353 controller_type = SDL_FLYDIGI_VADER4; 354 } 355 } else if (SDL_strstr(device->name, "APEX") != NULL) { 356 if (SDL_strstr(device->name, "APEX2") != NULL) { 357 controller_type = SDL_FLYDIGI_APEX2; 358 } else if (SDL_strstr(device->name, "APEX3") != NULL) { 359 controller_type = SDL_FLYDIGI_APEX3; 360 } else if (SDL_strstr(device->name, "APEX4") != NULL) { 361 controller_type = SDL_FLYDIGI_APEX4; 362 } else if (SDL_strstr(device->name, "APEX5") != NULL) { 363 controller_type = SDL_FLYDIGI_APEX5; 364 } 365 } 366 break; 367 } 368 device->guid.data[15] = controller_type; 369 370 // This is the previous sensor default of 125hz. 371 // Override this in the switch statement below based on observed sensor packet rate. 372 ctx->sensor_timestamp_step_ns = SDL_NS_PER_SECOND / 125; 373 374 switch (controller_type) { 375 case SDL_FLYDIGI_APEX2: 376 HIDAPI_SetDeviceName(device, "Flydigi Apex 2"); 377 break; 378 case SDL_FLYDIGI_APEX3: 379 HIDAPI_SetDeviceName(device, "Flydigi Apex 3"); 380 break; 381 case SDL_FLYDIGI_APEX4: 382 // The Apex 4 controller has sensors, but they're only reported when gyro mouse is enabled 383 HIDAPI_SetDeviceName(device, "Flydigi Apex 4"); 384 break; 385 case SDL_FLYDIGI_APEX5: 386 HIDAPI_SetDeviceName(device, "Flydigi Apex 5"); 387 ctx->has_lmrm = true; 388 ctx->sensors_supported = true; 389 ctx->accelScale = SDL_STANDARD_GRAVITY / 4096.0f; 390 ctx->gyroScale = DEG2RAD(2000.0f); 391 ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_APEX5_DONGLE_NS : SENSOR_INTERVAL_APEX5_WIRED_NS; 392 break; 393 case SDL_FLYDIGI_VADER2: 394 // The Vader 2 controller has sensors, but they're only reported when gyro mouse is enabled 395 HIDAPI_SetDeviceName(device, "Flydigi Vader 2"); 396 ctx->has_cz = true; 397 break; 398 case SDL_FLYDIGI_VADER2_PRO: 399 HIDAPI_SetDeviceName(device, "Flydigi Vader 2 Pro"); 400 ctx->has_cz = true; 401 break; 402 case SDL_FLYDIGI_VADER3: 403 HIDAPI_SetDeviceName(device, "Flydigi Vader 3"); 404 ctx->has_cz = true; 405 break; 406 case SDL_FLYDIGI_VADER3_PRO: 407 HIDAPI_SetDeviceName(device, "Flydigi Vader 3 Pro"); 408 ctx->has_cz = true; 409 ctx->sensors_supported = true; 410 ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f; 411 ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER4_PRO_WIRED_NS; 412 break; 413 case SDL_FLYDIGI_VADER4: 414 case SDL_FLYDIGI_VADER4_PRO: 415 HIDAPI_SetDeviceName(device, "Flydigi Vader 4 Pro"); 416 ctx->has_cz = true; 417 ctx->sensors_supported = true; 418 ctx->accelScale = SDL_STANDARD_GRAVITY / 256.0f; 419 ctx->sensor_timestamp_step_ns = ctx->wireless ? SENSOR_INTERVAL_VADER4_PRO_DONGLE_NS : SENSOR_INTERVAL_VADER4_PRO_WIRED_NS; 420 break; 421 default: 422 SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Unknown FlyDigi controller with ID %d, name '%s'", ctx->deviceID, device->name); 423 break; 424 } 425} 426 427static bool HIDAPI_DriverFlydigi_InitDevice(SDL_HIDAPI_Device *device) 428{ 429 SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)SDL_calloc(1, sizeof(*ctx)); 430 if (!ctx) { 431 return false; 432 } 433 ctx->device = device; 434 435 device->context = ctx; 436 437 bool initialized; 438 if (device->vendor_id == USB_VENDOR_FLYDIGI_V1) { 439 initialized = HIDAPI_DriverFlydigi_InitControllerV1(device); 440 } else { 441 initialized = HIDAPI_DriverFlydigi_InitControllerV2(device); 442 } 443 if (!initialized) { 444 return false; 445 } 446 447 HIDAPI_DriverFlydigi_UpdateDeviceIdentity(device); 448 449 if (ctx->available) { 450 return HIDAPI_JoystickConnected(device, NULL); 451 } else { 452 // We'll connect it once it becomes available 453 return true; 454 } 455} 456 457static int HIDAPI_DriverFlydigi_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) 458{ 459 return -1; 460} 461 462static void HIDAPI_DriverFlydigi_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) 463{ 464} 465 466static bool HIDAPI_DriverFlydigi_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 467{ 468 SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; 469 470 SDL_AssertJoysticksLocked(); 471 472 SDL_zeroa(ctx->last_state); 473 474 // Initialize the joystick capabilities 475 joystick->nbuttons = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS; 476 if (ctx->has_cz) { 477 joystick->nbuttons += 2; 478 } 479 if (ctx->has_lmrm) { 480 joystick->nbuttons += 2; 481 } 482 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; 483 joystick->nhats = 1; 484 485 if (ctx->wireless) { 486 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS; 487 } 488 489 if (ctx->sensors_supported) { 490 const float flSensorRate = ctx->wireless ? (float)SENSOR_INTERVAL_VADER4_PRO_DONGLE_RATE_HZ : (float)SENSOR_INTERVAL_VADER4_PRO_WIRED_RATE_HZ; 491 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, flSensorRate); 492 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, flSensorRate); 493 } 494 return true; 495} 496 497static bool HIDAPI_DriverFlydigi_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 498{ 499 if (device->vendor_id == USB_VENDOR_FLYDIGI_V1) { 500 Uint8 rumble_packet[] = { FLYDIGI_V1_CMD_REPORT_ID, FLYDIGI_V1_HAPTIC_COMMAND, 0x00, 0x00 }; 501 rumble_packet[2] = low_frequency_rumble >> 8; 502 rumble_packet[3] = high_frequency_rumble >> 8; 503 504 if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { 505 return SDL_SetError("Couldn't send rumble packet"); 506 } 507 } else { 508 Uint8 rumble_packet[] = { FLYDIGI_V2_CMD_REPORT_ID, FLYDIGI_V2_MAGIC1, FLYDIGI_V2_MAGIC2, FLYDIGI_V2_HAPTIC_COMMAND, 6, 0, 0, 0, 0, 0 }; 509 rumble_packet[5] = low_frequency_rumble >> 8; 510 rumble_packet[6] = high_frequency_rumble >> 8; 511 512 if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { 513 return SDL_SetError("Couldn't send rumble packet"); 514 } 515 } 516 return true; 517} 518 519static bool HIDAPI_DriverFlydigi_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) 520{ 521 return SDL_Unsupported(); 522} 523 524static Uint32 HIDAPI_DriverFlydigi_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 525{ 526 return SDL_JOYSTICK_CAP_RUMBLE; 527} 528 529static bool HIDAPI_DriverFlydigi_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) 530{ 531 return SDL_Unsupported(); 532} 533 534static bool HIDAPI_DriverFlydigi_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) 535{ 536 return SDL_Unsupported(); 537} 538 539static bool HIDAPI_DriverFlydigi_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) 540{ 541 SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; 542 if (ctx->sensors_supported) { 543 ctx->sensors_enabled = enabled; 544 return true; 545 } 546 return SDL_Unsupported(); 547} 548 549static void HIDAPI_DriverFlydigi_HandleStatePacketV1(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) 550{ 551 Sint16 axis; 552 Uint64 timestamp = SDL_GetTicksNS(); 553 554 Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS; 555 556 if (ctx->last_state[9] != data[9]) { 557 Uint8 hat; 558 559 switch (data[9] & 0x0F) { 560 case 0x01u: 561 hat = SDL_HAT_UP; 562 break; 563 case 0x02u | 0x01u: 564 hat = SDL_HAT_RIGHTUP; 565 break; 566 case 0x02u: 567 hat = SDL_HAT_RIGHT; 568 break; 569 case 0x02u | 0x04u: 570 hat = SDL_HAT_RIGHTDOWN; 571 break; 572 case 0x04u: 573 hat = SDL_HAT_DOWN; 574 break; 575 case 0x08u | 0x04u: 576 hat = SDL_HAT_LEFTDOWN; 577 break; 578 case 0x08u: 579 hat = SDL_HAT_LEFT; 580 break; 581 case 0x08u | 0x01u: 582 hat = SDL_HAT_LEFTUP; 583 break; 584 default: 585 hat = SDL_HAT_CENTERED; 586 break; 587 } 588 SDL_SendJoystickHat(timestamp, joystick, 0, hat); 589 590 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[9] & 0x10) != 0)); 591 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[9] & 0x20) != 0)); 592 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[9] & 0x40) != 0)); 593 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[9] & 0x80) != 0)); 594 } 595 596 if (ctx->last_state[10] != data[10]) { 597 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[10] & 0x01) != 0)); 598 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[10] & 0x02) != 0)); 599 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[10] & 0x04) != 0)); 600 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[10] & 0x08) != 0)); 601 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[10] & 0x40) != 0)); 602 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[10] & 0x80) != 0)); 603 } 604 605 if (ctx->last_state[7] != data[7]) { 606 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M1, ((data[7] & 0x04) != 0)); 607 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M2, ((data[7] & 0x08) != 0)); 608 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M3, ((data[7] & 0x10) != 0)); 609 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M4, ((data[7] & 0x20) != 0)); 610 if (ctx->has_cz) { 611 SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[7] & 0x01) != 0)); 612 SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[7] & 0x02) != 0)); 613 } 614 } 615 616 if (ctx->last_state[8] != data[8]) { 617 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[8] & 0x08) != 0)); 618 // The '+' button is used to toggle gyro mouse mode, so don't pass that to the application 619 // SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x01) != 0)); 620 // The '-' button is only available on the Vader 2, for simplicity let's ignore that 621 // SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x10) != 0)); 622 } 623 624#define READ_STICK_AXIS(offset) \ 625 (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) 626 { 627 axis = READ_STICK_AXIS(17); 628 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); 629 axis = READ_STICK_AXIS(19); 630 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); 631 axis = READ_STICK_AXIS(21); 632 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); 633 axis = READ_STICK_AXIS(22); 634 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); 635 } 636#undef READ_STICK_AXIS 637 638#define READ_TRIGGER_AXIS(offset) \ 639 (Sint16)(((int)data[offset] * 257) - 32768) 640 { 641 axis = READ_TRIGGER_AXIS(23); 642 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); 643 axis = READ_TRIGGER_AXIS(24); 644 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); 645 } 646#undef READ_TRIGGER_AXIS 647 648 if (ctx->sensors_enabled) { 649 Uint64 sensor_timestamp; 650 float values[3]; 651 652 // Advance the imu sensor time stamp based on the observed rate of receipt of packets in the testcontroller app. 653 // This varies between Product ID and connection type. 654 sensor_timestamp = ctx->sensor_timestamp_ns; 655 ctx->sensor_timestamp_ns += ctx->sensor_timestamp_step_ns; 656 657 // Pitch and yaw scales may be receiving extra filtering for the sake of bespoke direct mouse output. 658 // As result, roll has a different scaling factor than pitch and yaw. 659 // These values were estimated using the testcontroller tool in lieux of hard data sheet references. 660 const float flPitchAndYawScale = DEG2RAD(72000.0f); 661 const float flRollScale = DEG2RAD(1200.0f); 662 663 values[0] = HIDAPI_RemapVal(-1.0f * LOAD16(data[26], data[27]), INT16_MIN, INT16_MAX, -flPitchAndYawScale, flPitchAndYawScale); 664 values[1] = HIDAPI_RemapVal(-1.0f * LOAD16(data[18], data[20]), INT16_MIN, INT16_MAX, -flPitchAndYawScale, flPitchAndYawScale); 665 values[2] = HIDAPI_RemapVal(-1.0f * LOAD16(data[29], data[30]), INT16_MIN, INT16_MAX, -flRollScale, flRollScale); 666 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3); 667 668 const float flAccelScale = ctx->accelScale; 669 values[0] = -LOAD16(data[11], data[12]) * flAccelScale; // Acceleration along pitch axis 670 values[1] = LOAD16(data[15], data[16]) * flAccelScale; // Acceleration along yaw axis 671 values[2] = LOAD16(data[13], data[14]) * flAccelScale; // Acceleration along roll axis 672 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3); 673 } 674 675 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); 676} 677 678static void HIDAPI_DriverFlydigi_HandlePacketV1(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) 679{ 680 if (data[0] != 0x04 || data[1] != 0xFE) { 681 // We don't know how to handle this report, ignore it 682 return; 683 } 684 685 if (joystick) { 686 HIDAPI_DriverFlydigi_HandleStatePacketV1(joystick, ctx, data, size); 687 } 688} 689 690static void HIDAPI_DriverFlydigi_HandleStatePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) 691{ 692 Sint16 axis; 693 Uint64 timestamp = SDL_GetTicksNS(); 694 695 Uint8 extra_button_index = SDL_GAMEPAD_NUM_BASE_FLYDIGI_BUTTONS; 696 697 if (ctx->last_state[11] != data[11]) { 698 Uint8 hat; 699 700 switch (data[11] & 0x0F) { 701 case 0x01u: 702 hat = SDL_HAT_UP; 703 break; 704 case 0x02u | 0x01u: 705 hat = SDL_HAT_RIGHTUP; 706 break; 707 case 0x02u: 708 hat = SDL_HAT_RIGHT; 709 break; 710 case 0x02u | 0x04u: 711 hat = SDL_HAT_RIGHTDOWN; 712 break; 713 case 0x04u: 714 hat = SDL_HAT_DOWN; 715 break; 716 case 0x08u | 0x04u: 717 hat = SDL_HAT_LEFTDOWN; 718 break; 719 case 0x08u: 720 hat = SDL_HAT_LEFT; 721 break; 722 case 0x08u | 0x01u: 723 hat = SDL_HAT_LEFTUP; 724 break; 725 default: 726 hat = SDL_HAT_CENTERED; 727 break; 728 } 729 SDL_SendJoystickHat(timestamp, joystick, 0, hat); 730 731 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[11] & 0x10) != 0)); 732 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[11] & 0x20) != 0)); 733 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[11] & 0x40) != 0)); 734 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[11] & 0x80) != 0)); 735 } 736 737 if (ctx->last_state[12] != data[12]) { 738 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[12] & 0x01) != 0)); 739 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[12] & 0x02) != 0)); 740 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[12] & 0x04) != 0)); 741 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[12] & 0x08) != 0)); 742 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[12] & 0x40) != 0)); 743 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[12] & 0x80) != 0)); 744 } 745 746 if (ctx->last_state[13] != data[13]) { 747 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M1, ((data[13] & 0x04) != 0)); 748 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M2, ((data[13] & 0x08) != 0)); 749 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M3, ((data[13] & 0x10) != 0)); 750 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_FLYDIGI_M4, ((data[13] & 0x20) != 0)); 751 if (ctx->has_lmrm) { 752 SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x40) != 0)); 753 SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[13] & 0x80) != 0)); 754 } 755 } 756 757 if (ctx->last_state[14] != data[14]) { 758 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[14] & 0x08) != 0)); 759 SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[14] & 0x01) != 0)); 760 // The '-' button is only available on the Vader 2, for simplicity let's ignore that 761 SDL_SendJoystickButton(timestamp, joystick, extra_button_index++, ((data[8] & 0x10) != 0)); 762 } 763 764 axis = LOAD16(data[3], data[4]); 765 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); 766 axis = -LOAD16(data[5], data[6]); 767 if (axis <= -32768) { 768 axis = 32767; 769 } 770 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); 771 axis = LOAD16(data[7], data[8]); 772 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); 773 axis = -LOAD16(data[9], data[10]); 774 if (axis <= -32768) { 775 axis = 32767; 776 } 777 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); 778 779#define READ_TRIGGER_AXIS(offset) \ 780 (Sint16)(((int)data[offset] * 257) - 32768) 781 { 782 axis = READ_TRIGGER_AXIS(15); 783 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis); 784 axis = READ_TRIGGER_AXIS(16); 785 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis); 786 } 787#undef READ_TRIGGER_AXIS 788 789 if (ctx->sensors_enabled) { 790 Uint64 sensor_timestamp; 791 float values[3]; 792 793 // Advance the imu sensor time stamp based on the observed rate of receipt of packets in the testcontroller app. 794 // This varies between Product ID and connection type. 795 sensor_timestamp = ctx->sensor_timestamp_ns; 796 ctx->sensor_timestamp_ns += ctx->sensor_timestamp_step_ns; 797 798 const float flGyroScale = ctx->gyroScale; 799 values[0] = HIDAPI_RemapVal((float)LOAD16(data[17], data[18]), INT16_MIN, INT16_MAX, -flGyroScale, flGyroScale); 800 values[1] = HIDAPI_RemapVal((float)LOAD16(data[21], data[22]), INT16_MIN, INT16_MAX, -flGyroScale, flGyroScale); 801 values[2] = HIDAPI_RemapVal(-(float)LOAD16(data[19], data[20]), INT16_MIN, INT16_MAX, -flGyroScale, flGyroScale); 802 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3); 803 804 const float flAccelScale = ctx->accelScale; 805 values[0] = LOAD16(data[23], data[24]) * flAccelScale; // Acceleration along pitch axis 806 values[1] = LOAD16(data[27], data[28]) * flAccelScale; // Acceleration along yaw axis 807 values[2] = -LOAD16(data[25], data[26]) * flAccelScale; // Acceleration along roll axis 808 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3); 809 } 810 811 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); 812} 813 814static void HIDAPI_DriverFlydigi_HandleStatusUpdate(SDL_HIDAPI_Device *device, Uint8 *data, int size) 815{ 816 if (data[9] == 1) { 817 // We can now acquire the controller 818 HIDAPI_DriverFlydigi_SetAvailable(device, true); 819 } else { 820 // We can no longer acquire the controller 821 HIDAPI_DriverFlydigi_SetAvailable(device, false); 822 } 823} 824 825static void HIDAPI_DriverFlydigi_HandlePacketV2(SDL_Joystick *joystick, SDL_DriverFlydigi_Context *ctx, Uint8 *data, int size) 826{ 827 if (size > 0 && data[0] != 0x5A) { 828 // If first byte is not 0x5A, it must be REPORT_ID, we need to remove it. 829 ++data; 830 --size; 831 } 832 if (size < 31 || data[0] != FLYDIGI_V2_MAGIC1 || data[1] != FLYDIGI_V2_MAGIC2) { 833 // We don't know how to handle this report, ignore it 834 return; 835 } 836 837 switch (data[2]) { 838 case FLYDIGI_V2_ACQUIRE_CONTROLLER_COMMAND: 839 HIDAPI_DriverFlydigi_HandleAcquireResponse(ctx->device, data, size); 840 break; 841 case FLYDIGI_V2_INPUT_REPORT: 842 if (joystick) { 843 HIDAPI_DriverFlydigi_HandleStatePacketV2(joystick, ctx, data, size); 844 } 845 break; 846 case FLYDIGI_V2_SET_STATUS_COMMAND: 847 HIDAPI_DriverFlydigi_HandleStatusUpdate(ctx->device, data, size); 848 break; 849 default: 850 // We don't recognize this command, ignore it 851 break; 852 } 853} 854 855static bool HIDAPI_DriverFlydigi_UpdateDevice(SDL_HIDAPI_Device *device) 856{ 857 SDL_DriverFlydigi_Context *ctx = (SDL_DriverFlydigi_Context *)device->context; 858 SDL_Joystick *joystick = NULL; 859 Uint8 data[USB_PACKET_LENGTH]; 860 int size = 0; 861 Uint64 now = SDL_GetTicks(); 862 863 if (device->num_joysticks > 0) { 864 joystick = SDL_GetJoystickFromID(device->joysticks[0]); 865 } 866 867 if (device->vendor_id == USB_VENDOR_FLYDIGI_V2 && joystick) { 868 if (!ctx->next_heartbeat || now >= ctx->next_heartbeat) { 869 SDL_HIDAPI_Flydigi_SendAcquireRequest(device, true); 870 ctx->next_heartbeat = now + FLYDIGI_ACQUIRE_CONTROLLER_HEARTBEAT_TIME; 871 } 872 } 873 874 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { 875#ifdef DEBUG_FLYDIGI_PROTOCOL 876 HIDAPI_DumpPacket("Flydigi packet: size = %d", data, size); 877#endif 878 ctx->last_packet = now; 879 880 if (device->vendor_id == USB_VENDOR_FLYDIGI_V1) { 881 HIDAPI_DriverFlydigi_HandlePacketV1(joystick, ctx, data, size); 882 } else { 883 HIDAPI_DriverFlydigi_HandlePacketV2(joystick, ctx, data, size); 884 } 885 } 886 887 if (device->vendor_id == USB_VENDOR_FLYDIGI_V2) { 888 // If we haven't gotten a packet in a while, check to make sure we can still acquire it 889 const int INPUT_TIMEOUT_MS = 100; 890 if (now >= (ctx->last_packet + INPUT_TIMEOUT_MS)) { 891 ctx->next_heartbeat = now; 892 } 893 } 894 895 if (size < 0 && device->num_joysticks > 0) { 896 // Read error, device is disconnected 897 HIDAPI_JoystickDisconnected(device, device->joysticks[0]); 898 } 899 return (size >= 0); 900} 901 902static void HIDAPI_DriverFlydigi_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 903{ 904 // Don't unacquire the controller, someone else might be using it too. 905 // The controller will automatically unacquire itself after a little while 906 //SDL_HIDAPI_Flydigi_SendAcquireRequest(device, false); 907} 908 909static void HIDAPI_DriverFlydigi_FreeDevice(SDL_HIDAPI_Device *device) 910{ 911} 912 913SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi = { 914 SDL_HINT_JOYSTICK_HIDAPI_FLYDIGI, 915 true, 916 HIDAPI_DriverFlydigi_RegisterHints, 917 HIDAPI_DriverFlydigi_UnregisterHints, 918 HIDAPI_DriverFlydigi_IsEnabled, 919 HIDAPI_DriverFlydigi_IsSupportedDevice, 920 HIDAPI_DriverFlydigi_InitDevice, 921 HIDAPI_DriverFlydigi_GetDevicePlayerIndex, 922 HIDAPI_DriverFlydigi_SetDevicePlayerIndex, 923 HIDAPI_DriverFlydigi_UpdateDevice, 924 HIDAPI_DriverFlydigi_OpenJoystick, 925 HIDAPI_DriverFlydigi_RumbleJoystick, 926 HIDAPI_DriverFlydigi_RumbleJoystickTriggers, 927 HIDAPI_DriverFlydigi_GetJoystickCapabilities, 928 HIDAPI_DriverFlydigi_SetJoystickLED, 929 HIDAPI_DriverFlydigi_SendJoystickEffect, 930 HIDAPI_DriverFlydigi_SetJoystickSensorsEnabled, 931 HIDAPI_DriverFlydigi_CloseJoystick, 932 HIDAPI_DriverFlydigi_FreeDevice, 933}; 934 935#endif // SDL_JOYSTICK_HIDAPI_FLYDIGI 936 937#endif // SDL_JOYSTICK_HIDAPI 938
[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.