Atlas - SDL_hidapi_lg4ff.c

Home / ext / SDL / src / joystick / hidapi Lines: 1 | Size: 32533 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 2025 Simon Wood <[email protected]> 4 Copyright (C) 2025 Michal Malý <[email protected]> 5 Copyright (C) 2025 Katharine Chui <[email protected]> 6 7 This software is provided 'as-is', without any express or implied 8 warranty. In no event will the authors be held liable for any damages 9 arising from the use of this software. 10 11 Permission is granted to anyone to use this software for any purpose, 12 including commercial applications, and to alter it and redistribute it 13 freely, subject to the following restrictions: 14 15 1. The origin of this software must not be misrepresented; you must not 16 claim that you wrote the original software. If you use this software 17 in a product, an acknowledgment in the product documentation would be 18 appreciated but is not required. 19 2. Altered source versions must be plainly marked as such, and must not be 20 misrepresented as being the original software. 21 3. This notice may not be removed or altered from any source distribution. 22*/ 23 24#include "SDL_internal.h" 25 26#ifdef SDL_JOYSTICK_HIDAPI 27 28#include "../SDL_sysjoystick.h" 29#include "SDL3/SDL_events.h" 30#include "SDL_hidapijoystick_c.h" 31 32#ifdef SDL_JOYSTICK_HIDAPI_LG4FF 33 34#define USB_VENDOR_ID_LOGITECH 0x046d 35#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f 36#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b 37#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299 38#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a 39#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298 40#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294 41 42static Uint32 supported_device_ids[] = { 43 USB_DEVICE_ID_LOGITECH_G29_WHEEL, 44 USB_DEVICE_ID_LOGITECH_G27_WHEEL, 45 USB_DEVICE_ID_LOGITECH_G25_WHEEL, 46 USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, 47 USB_DEVICE_ID_LOGITECH_DFP_WHEEL, 48 USB_DEVICE_ID_LOGITECH_WHEEL 49}; 50 51// keep the same order as the supported_ids array 52static const char *supported_device_names[] = { 53 "Logitech G29", 54 "Logitech G27", 55 "Logitech G25", 56 "Logitech Driving Force GT", 57 "Logitech Driving Force Pro", 58 "Driving Force EX" 59}; 60 61static const char *HIDAPI_DriverLg4ff_GetDeviceName(Uint32 device_id) 62{ 63 for (int i = 0;i < (sizeof supported_device_ids) / sizeof(Uint32);i++) { 64 if (supported_device_ids[i] == device_id) { 65 return supported_device_names[i]; 66 } 67 } 68 SDL_assert(0); 69 return ""; 70} 71 72static int HIDAPI_DriverLg4ff_GetNumberOfButtons(Uint32 device_id) 73{ 74 switch (device_id) { 75 case USB_DEVICE_ID_LOGITECH_G29_WHEEL: 76 return 25; 77 case USB_DEVICE_ID_LOGITECH_G27_WHEEL: 78 return 23; 79 case USB_DEVICE_ID_LOGITECH_G25_WHEEL: 80 return 19; 81 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: 82 return 21; 83 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: 84 return 14; 85 case USB_DEVICE_ID_LOGITECH_WHEEL: 86 return 13; 87 default: 88 SDL_assert(0); 89 return 0; 90 } 91} 92 93typedef struct 94{ 95 Uint8 last_report_buf[32]; 96 bool initialized; 97 bool is_ffex; 98 Uint16 range; 99} SDL_DriverLg4ff_Context; 100 101static void HIDAPI_DriverLg4ff_RegisterHints(SDL_HintCallback callback, void *userdata) 102{ 103 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, callback, userdata); 104} 105 106static void HIDAPI_DriverLg4ff_UnregisterHints(SDL_HintCallback callback, void *userdata) 107{ 108 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, callback, userdata); 109} 110 111static bool HIDAPI_DriverLg4ff_IsEnabled(void) 112{ 113 #if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) 114 /* 115 * hid.dll simply cannot send 7 bytes reports unlike other platforms 116 * it enforces full length repots of 17 from the device's descriptor, which does not work on the device 117 * this breaks ffb and led control, so we disable this by default 118 */ 119 bool hint_default = false; 120 #else 121 bool hint_default = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT); 122 #endif 123 bool enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, hint_default); 124 125 return enabled; 126} 127 128/* 129 Wheel id information by: 130 Michal Malý <[email protected]> <[email protected]> 131 Simon Wood <[email protected]> 132 `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git 133*/ 134static Uint16 HIDAPI_DriverLg4ff_IdentifyWheel(Uint16 device_id, Uint16 release_number) 135{ 136 #define is_device(ret, m, r) { \ 137 if ((release_number & m) == r) { \ 138 return ret; \ 139 } \ 140 } 141 #define is_dfp { \ 142 is_device(USB_DEVICE_ID_LOGITECH_DFP_WHEEL, 0xf000, 0x1000); \ 143 } 144 #define is_dfgt { \ 145 is_device(USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, 0xff00, 0x1300); \ 146 } 147 #define is_g25 { \ 148 is_device(USB_DEVICE_ID_LOGITECH_G25_WHEEL, 0xff00, 0x1200); \ 149 } 150 #define is_g27 { \ 151 is_device(USB_DEVICE_ID_LOGITECH_G27_WHEEL, 0xfff0, 0x1230); \ 152 } 153 #define is_g29 { \ 154 is_device(USB_DEVICE_ID_LOGITECH_G29_WHEEL, 0xfff8, 0x1350); \ 155 is_device(USB_DEVICE_ID_LOGITECH_G29_WHEEL, 0xff00, 0x8900); \ 156 } 157 switch(device_id){ 158 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: 159 case USB_DEVICE_ID_LOGITECH_WHEEL: 160 is_g29; 161 is_g27; 162 is_g25; 163 is_dfgt; 164 is_dfp; 165 break; 166 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: 167 is_g29; 168 is_dfgt; 169 break; 170 case USB_DEVICE_ID_LOGITECH_G25_WHEEL: 171 is_g29; 172 is_g27; 173 is_g25; 174 break; 175 case USB_DEVICE_ID_LOGITECH_G27_WHEEL: 176 is_g29; 177 is_g27; 178 break; 179 case USB_DEVICE_ID_LOGITECH_G29_WHEEL: 180 is_g29; 181 break; 182 } 183 return 0; 184 #undef is_device 185 #undef is_dfp 186 #undef is_dfgt 187 #undef is_g25 188 #undef is_g27 189 #undef is_g29 190} 191 192static int SDL_HIDAPI_DriverLg4ff_GetEnvInt(const char *env_name, int min, int max, int def) 193{ 194 const char *env = SDL_getenv(env_name); 195 int value = 0; 196 if(env == NULL) { 197 return def; 198 } 199 value = SDL_atoi(env); 200 if (value < min) { 201 value = min; 202 } 203 if (value > max) { 204 value = max; 205 } 206 return value; 207} 208 209/* 210 Commands by: 211 Michal Malý <[email protected]> <[email protected]> 212 Simon Wood <[email protected]> 213 `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git 214*/ 215static bool HIDAPI_DriverLg4ff_SwitchMode(SDL_HIDAPI_Device *device, Uint16 target_product_id){ 216 int ret = 0; 217 218 switch(target_product_id){ 219 case USB_DEVICE_ID_LOGITECH_G29_WHEEL:{ 220 Uint8 cmd[] = {0xf8, 0x09, 0x05, 0x01, 0x01, 0x00, 0x00}; 221 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 222 break; 223 } 224 case USB_DEVICE_ID_LOGITECH_G27_WHEEL:{ 225 Uint8 cmd[] = {0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00}; 226 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 227 break; 228 } 229 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:{ 230 Uint8 cmd[] = {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}; 231 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 232 break; 233 } 234 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ 235 Uint8 cmd[] = {0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00}; 236 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 237 break; 238 } 239 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ 240 Uint8 cmd[] = {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; 241 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 242 break; 243 } 244 case USB_DEVICE_ID_LOGITECH_WHEEL:{ 245 Uint8 cmd[] = {0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00}; 246 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 247 break; 248 } 249 default:{ 250 SDL_assert(0); 251 } 252 } 253 if(ret == -1){ 254 return false; 255 } 256 return true; 257} 258 259static bool HIDAPI_DriverLg4ff_IsSupportedDevice( 260 SDL_HIDAPI_Device *device, 261 const char *name, 262 SDL_GamepadType type, 263 Uint16 vendor_id, 264 Uint16 product_id, 265 Uint16 version, 266 int interface_number, 267 int interface_class, 268 int interface_subclass, 269 int interface_protocol) 270{ 271 int i; 272 if (vendor_id != USB_VENDOR_ID_LOGITECH) { 273 return false; 274 } 275 for (i = 0;i < SDL_arraysize(supported_device_ids);i++) { 276 if (supported_device_ids[i] == product_id) { 277 break; 278 } 279 } 280 if (i == SDL_arraysize(supported_device_ids)) { 281 return false; 282 } 283 Uint16 real_id = HIDAPI_DriverLg4ff_IdentifyWheel(product_id, version); 284 if (real_id == product_id || real_id == 0) { 285 // either it is already in native mode, or we don't know what the native mode is 286 return true; 287 } 288 // a supported native mode is found, send mode change command, then still state that we support the device 289 if (device && SDL_HIDAPI_DriverLg4ff_GetEnvInt("SDL_HIDAPI_LG4FF_NO_MODE_SWITCH", 0, 1, 0) == 0) { 290 HIDAPI_DriverLg4ff_SwitchMode(device, real_id); 291 } 292 return true; 293} 294 295/* 296 *Ported* 297 Original functions by: 298 Michal Malý <[email protected]> <[email protected]> 299 lg4ff_set_range_g25 lg4ff_set_range_dfp 300 `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git 301*/ 302static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) 303{ 304 Uint8 cmd[7] = {0}; 305 int ret = 0; 306 SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; 307 308 if (range < 40) { 309 range = 40; 310 } 311 if (range > 900) { 312 range = 900; 313 } 314 315 ctx->range = (Uint16)range; 316 switch (device->product_id) { 317 case USB_DEVICE_ID_LOGITECH_G29_WHEEL: 318 case USB_DEVICE_ID_LOGITECH_G27_WHEEL: 319 case USB_DEVICE_ID_LOGITECH_G25_WHEEL: 320 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ 321 cmd[0] = 0xf8; 322 cmd[1] = 0x81; 323 cmd[2] = range & 0x00ff; 324 cmd[3] = (range & 0xff00) >> 8; 325 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 326 if (ret == -1) { 327 return false; 328 } 329 break; 330 } 331 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ 332 int start_left, start_right, full_range; 333 334 /* Prepare "coarse" limit command */ 335 cmd[0] = 0xf8; 336 cmd[1] = 0x00; /* Set later */ 337 cmd[2] = 0x00; 338 cmd[3] = 0x00; 339 cmd[4] = 0x00; 340 cmd[5] = 0x00; 341 cmd[6] = 0x00; 342 343 if (range > 200) { 344 cmd[1] = 0x03; 345 full_range = 900; 346 } else { 347 cmd[1] = 0x02; 348 full_range = 200; 349 } 350 ret = SDL_hid_write(device->dev, cmd, 7); 351 if(ret == -1){ 352 return false; 353 } 354 355 /* Prepare "fine" limit command */ 356 cmd[0] = 0x81; 357 cmd[1] = 0x0b; 358 cmd[2] = 0x00; 359 cmd[3] = 0x00; 360 cmd[4] = 0x00; 361 cmd[5] = 0x00; 362 cmd[6] = 0x00; 363 364 if (range != 200 && range != 900) { 365 /* Construct fine limit command */ 366 start_left = (((full_range - range + 1) * 2047) / full_range); 367 start_right = 0xfff - start_left; 368 369 cmd[2] = (Uint8)(start_left >> 4); 370 cmd[3] = (Uint8)(start_right >> 4); 371 cmd[4] = 0xff; 372 cmd[5] = (start_right & 0xe) << 4 | (start_left & 0xe); 373 cmd[6] = 0xff; 374 } 375 376 ret = SDL_hid_write(device->dev, cmd, 7); 377 if (ret == -1) { 378 return false; 379 } 380 break; 381 } 382 case USB_DEVICE_ID_LOGITECH_WHEEL: 383 // no range setting for ffex/dfex 384 break; 385 default: 386 SDL_assert(0); 387 } 388 389 return true; 390} 391 392/* 393 *Ported* 394 Original functions by: 395 Simon Wood <[email protected]> 396 Michal Malý <[email protected]> <[email protected]> 397 lg4ff_set_autocenter_default lg4ff_set_autocenter_ffex 398 `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git 399*/ 400static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int magnitude) 401{ 402 SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; 403 Uint8 cmd[7] = {0}; 404 int ret; 405 406 if (magnitude < 0) { 407 magnitude = 0; 408 } 409 if (magnitude > 65535) { 410 magnitude = 65535; 411 } 412 413 if (ctx->is_ffex) { 414 magnitude = magnitude * 90 / 65535; 415 416 cmd[0] = 0xfe; 417 cmd[1] = 0x03; 418 cmd[2] = (Uint8)((Uint16)magnitude >> 14); 419 cmd[3] = (Uint8)((Uint16)magnitude >> 14); 420 cmd[4] = (Uint8)magnitude; 421 422 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 423 if(ret == -1){ 424 return false; 425 } 426 } else { 427 Uint32 expand_a; 428 Uint32 expand_b; 429 // first disable 430 cmd[0] = 0xf5; 431 432 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 433 if (ret == -1) { 434 return false; 435 } 436 437 if (magnitude == 0) { 438 return true; 439 } 440 441 // set strength 442 443 if (magnitude <= 0xaaaa) { 444 expand_a = 0x0c * magnitude; 445 expand_b = 0x80 * magnitude; 446 } else { 447 expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa); 448 expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa); 449 } 450 // TODO do not adjust for MOMO wheels, when support is added 451 expand_a = expand_a >> 1; 452 453 SDL_zeroa(cmd); 454 cmd[0] = 0xfe; 455 cmd[1] = 0x0d; 456 cmd[2] = (Uint8)(expand_a / 0xaaaa); 457 cmd[3] = (Uint8)(expand_a / 0xaaaa); 458 cmd[4] = (Uint8)(expand_b / 0xaaaa); 459 460 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 461 if (ret == -1) { 462 return false; 463 } 464 465 // enable 466 SDL_zeroa(cmd); 467 cmd[0] = 0x14; 468 469 ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); 470 if (ret == -1) { 471 return false; 472 } 473 } 474 return true; 475} 476 477/* 478 ffex identification method by: 479 Simon Wood <[email protected]> 480 Michal Malý <[email protected]> <[email protected]> 481 lg4ff_init 482 `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git 483*/ 484static bool HIDAPI_DriverLg4ff_InitDevice(SDL_HIDAPI_Device *device) 485{ 486 SDL_DriverLg4ff_Context *ctx; 487 488 ctx = (SDL_DriverLg4ff_Context *)SDL_malloc(sizeof(SDL_DriverLg4ff_Context)); 489 if (ctx == NULL) { 490 SDL_OutOfMemory(); 491 return false; 492 } 493 SDL_zerop(ctx); 494 495 device->context = ctx; 496 device->joystick_type = SDL_JOYSTICK_TYPE_WHEEL; 497 498 HIDAPI_SetDeviceName(device, HIDAPI_DriverLg4ff_GetDeviceName(device->product_id)); 499 500 if (SDL_hid_set_nonblocking(device->dev, 1) != 0) { 501 return false; 502 } 503 504 if (!HIDAPI_DriverLg4ff_SetAutoCenter(device, 0)) { 505 return false; 506 } 507 508 if (device->product_id == USB_DEVICE_ID_LOGITECH_WHEEL && 509 (device->version >> 8) == 0x21 && 510 (device->version & 0xff) == 0x00) { 511 ctx->is_ffex = true; 512 } else { 513 ctx->is_ffex = false; 514 } 515 516 ctx->range = 900; 517 518 return HIDAPI_JoystickConnected(device, NULL); 519} 520 521static int HIDAPI_DriverLg4ff_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) 522{ 523 return -1; 524} 525 526static void HIDAPI_DriverLg4ff_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) 527{ 528} 529 530 531static bool HIDAPI_DriverLg4ff_GetBit(const Uint8 *buf, int bit_num, size_t buf_len) 532{ 533 int byte_offset = bit_num / 8; 534 int local_bit = bit_num % 8; 535 Uint8 mask = 1 << local_bit; 536 if ((size_t)byte_offset >= buf_len) { 537 SDL_assert(0); 538 } 539 return (buf[byte_offset] & mask) ? true : false; 540} 541 542/* 543 *Ported* 544 Original functions by: 545 Michal Malý <[email protected]> <[email protected]> 546 lg4ff_adjust_dfp_x_axis 547 `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git 548*/ 549static Uint16 lg4ff_adjust_dfp_x_axis(Uint16 value, Uint16 range) 550{ 551 Uint16 max_range; 552 Sint32 new_value; 553 554 if (range == 900) 555 return value; 556 else if (range == 200) 557 return value; 558 else if (range < 200) 559 max_range = 200; 560 else 561 max_range = 900; 562 563 new_value = 8192 + ((value - 8192) * max_range / range); 564 if (new_value < 0) 565 return 0; 566 else if (new_value > 16383) 567 return 16383; 568 else 569 return (Uint16)new_value; 570} 571 572static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, 573 SDL_Joystick *joystick, 574 Uint8 *report_buf, 575 size_t report_size) 576{ 577 SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; 578 Uint8 hat = 0; 579 Uint8 last_hat = 0; 580 int num_buttons = HIDAPI_DriverLg4ff_GetNumberOfButtons(device->product_id); 581 int bit_offset = 0; 582 Uint64 timestamp = SDL_GetTicksNS(); 583 584 bool state_changed = false; 585 586 switch (device->product_id) { 587 case USB_DEVICE_ID_LOGITECH_G29_WHEEL: 588 case USB_DEVICE_ID_LOGITECH_G27_WHEEL: 589 case USB_DEVICE_ID_LOGITECH_G25_WHEEL: 590 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: 591 hat = report_buf[0] & 0x0f; 592 last_hat = ctx->last_report_buf[0] & 0x0f; 593 break; 594 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: 595 hat = report_buf[3] >> 4; 596 last_hat = ctx->last_report_buf[3] >> 4; 597 break; 598 case USB_DEVICE_ID_LOGITECH_WHEEL: 599 hat = report_buf[2] & 0x0F; 600 last_hat = ctx->last_report_buf[2] & 0x0F; 601 break; 602 default: 603 SDL_assert(0); 604 } 605 606 if (hat != last_hat) { 607 Uint8 sdl_hat = 0; 608 state_changed = true; 609 switch (hat) { 610 case 0: 611 sdl_hat = SDL_HAT_UP; 612 break; 613 case 1: 614 sdl_hat = SDL_HAT_RIGHTUP; 615 break; 616 case 2: 617 sdl_hat = SDL_HAT_RIGHT; 618 break; 619 case 3: 620 sdl_hat = SDL_HAT_RIGHTDOWN; 621 break; 622 case 4: 623 sdl_hat = SDL_HAT_DOWN; 624 break; 625 case 5: 626 sdl_hat = SDL_HAT_LEFTDOWN; 627 break; 628 case 6: 629 sdl_hat = SDL_HAT_LEFT; 630 break; 631 case 7: 632 sdl_hat = SDL_HAT_LEFTUP; 633 break; 634 case 8: 635 sdl_hat = SDL_HAT_CENTERED; 636 break; 637 // do not assert out, in case hardware can report weird hat values 638 } 639 SDL_SendJoystickHat(timestamp, joystick, 0, sdl_hat); 640 } 641 642 switch (device->product_id) { 643 case USB_DEVICE_ID_LOGITECH_G29_WHEEL: 644 case USB_DEVICE_ID_LOGITECH_G27_WHEEL: 645 case USB_DEVICE_ID_LOGITECH_G25_WHEEL: 646 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: 647 bit_offset = 4; 648 break; 649 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: 650 bit_offset = 14; 651 break; 652 case USB_DEVICE_ID_LOGITECH_WHEEL: 653 bit_offset = 0; 654 break; 655 default: 656 SDL_assert(0); 657 } 658 659 if (device->product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL) { 660 // ref https://github.com/sonik-br/lgff_wheel_adapter/blob/d97f7823154818e1b3edff6d51498a122c302728/pico_lgff_wheel_adapter/reports.h#L265-L310 661 // shifter_r is outside of the main button bit field for this particular wheel 662 num_buttons--; 663 664 bool button_on = HIDAPI_DriverLg4ff_GetBit(report_buf, 80, report_size); 665 bool button_was_on = HIDAPI_DriverLg4ff_GetBit(ctx->last_report_buf, 80, report_size); 666 if (button_on != button_was_on) { 667 state_changed = true; 668 SDL_SendJoystickButton(timestamp, joystick, (Uint8)(SDL_GAMEPAD_BUTTON_SOUTH + num_buttons), button_on); 669 } 670 } 671 672 for (int i = 0;i < num_buttons;i++) { 673 int bit_num = bit_offset + i; 674 bool button_on = HIDAPI_DriverLg4ff_GetBit(report_buf, bit_num, report_size); 675 bool button_was_on = HIDAPI_DriverLg4ff_GetBit(ctx->last_report_buf, bit_num, report_size); 676 if(button_on != button_was_on){ 677 state_changed = true; 678 SDL_SendJoystickButton(timestamp, joystick, (Uint8)(SDL_GAMEPAD_BUTTON_SOUTH + i), button_on); 679 } 680 } 681 682 switch (device->product_id) { 683 case USB_DEVICE_ID_LOGITECH_G29_WHEEL:{ 684 Uint16 x = *(Uint16 *)&report_buf[4]; 685 Uint16 last_x = *(Uint16 *)&ctx->last_report_buf[4]; 686 if (x != last_x) { 687 state_changed = true; 688 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x - 32768); 689 } 690 if (report_buf[6] != ctx->last_report_buf[6]) { 691 state_changed = true; 692 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[6] * 257 - 32768); 693 } 694 if (report_buf[7] != ctx->last_report_buf[7]) { 695 state_changed = true; 696 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[7] * 257 - 32768); 697 } 698 if (report_buf[8] != ctx->last_report_buf[8]) { 699 state_changed = true; 700 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[8] * 257 - 32768); 701 } 702 break; 703 } 704 case USB_DEVICE_ID_LOGITECH_G27_WHEEL: 705 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:{ 706 Uint16 x = report_buf[4] << 6; 707 Uint16 last_x = ctx->last_report_buf[4] << 6; 708 x = x | report_buf[3] >> 2; 709 last_x = last_x | ctx->last_report_buf[3] >> 2; 710 if (x != last_x) { 711 state_changed = true; 712 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x * 4 - 32768); 713 } 714 if (report_buf[5] != ctx->last_report_buf[5]) { 715 state_changed = true; 716 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[5] * 257 - 32768); 717 } 718 if (report_buf[6] != ctx->last_report_buf[6]) { 719 state_changed = true; 720 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[6] * 257 - 32768); 721 } 722 if (report_buf[7] != ctx->last_report_buf[7]) { 723 state_changed = true; 724 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[7] * 257 - 32768); 725 } 726 break; 727 } 728 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ 729 Uint16 x = report_buf[4]; 730 Uint16 last_x = ctx->last_report_buf[4]; 731 x = x | (report_buf[5] & 0x3F) << 8; 732 last_x = last_x | (ctx->last_report_buf[5] & 0x3F) << 8; 733 if (x != last_x) { 734 state_changed = true; 735 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x * 4 - 32768); 736 } 737 if (report_buf[6] != ctx->last_report_buf[6]) { 738 state_changed = true; 739 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[6] * 257 - 32768); 740 } 741 if (report_buf[7] != ctx->last_report_buf[7]) { 742 state_changed = true; 743 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[7] * 257 - 32768); 744 } 745 break; 746 } 747 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ 748 Uint16 x = report_buf[0]; 749 Uint16 last_x = ctx->last_report_buf[0]; 750 x = x | (report_buf[1] & 0x3F) << 8; 751 last_x = last_x | (ctx->last_report_buf[1] & 0x3F) << 8; 752 if (x != last_x) { 753 state_changed = true; 754 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, lg4ff_adjust_dfp_x_axis(x, ctx->range) * 4 - 32768); 755 } 756 if (report_buf[5] != ctx->last_report_buf[5]) { 757 state_changed = true; 758 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[5] * 257 - 32768); 759 } 760 if (report_buf[6] != ctx->last_report_buf[6]) { 761 state_changed = true; 762 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[6] * 257 - 32768); 763 } 764 break; 765 } 766 case USB_DEVICE_ID_LOGITECH_WHEEL:{ 767 if (report_buf[3] != ctx->last_report_buf[3]) { 768 state_changed = true; 769 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, report_buf[3] * 257 - 32768); 770 } 771 if (report_buf[4] != ctx->last_report_buf[4]) { 772 state_changed = true; 773 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[4] * 257 - 32768); 774 } 775 if (report_buf[5] != ctx->last_report_buf[5]) { 776 state_changed = true; 777 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[5] * 257 - 32768); 778 } 779 if (report_buf[6] != ctx->last_report_buf[6]) { 780 state_changed = true; 781 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[7] * 257 - 32768); 782 } 783 break; 784 } 785 default: 786 SDL_assert(0); 787 } 788 789 SDL_memcpy(ctx->last_report_buf, report_buf, report_size); 790 return state_changed; 791} 792 793static bool HIDAPI_DriverLg4ff_UpdateDevice(SDL_HIDAPI_Device *device) 794{ 795 SDL_Joystick *joystick = NULL; 796 int r; 797 Uint8 report_buf[32] = {0}; 798 size_t report_size = 0; 799 SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; 800 801 if (device->num_joysticks > 0) { 802 joystick = SDL_GetJoystickFromID(device->joysticks[0]); 803 if (joystick == NULL) { 804 return false; 805 } 806 } else { 807 return false; 808 } 809 810 switch (device->product_id) { 811 case USB_DEVICE_ID_LOGITECH_G29_WHEEL: 812 report_size = 12; 813 break; 814 case USB_DEVICE_ID_LOGITECH_G27_WHEEL: 815 case USB_DEVICE_ID_LOGITECH_G25_WHEEL: 816 report_size = 11; 817 break; 818 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: 819 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: 820 report_size = 8; 821 break; 822 case USB_DEVICE_ID_LOGITECH_WHEEL: 823 report_size = 27; 824 break; 825 default: 826 SDL_assert(0); 827 } 828 829 do { 830 r = SDL_hid_read(device->dev, report_buf, report_size); 831 if (r < 0) { 832 /* Failed to read from controller */ 833 HIDAPI_JoystickDisconnected(device, device->joysticks[0]); 834 return false; 835 } else if ((size_t)r == report_size) { 836 bool state_changed = HIDAPI_DriverLg4ff_HandleState(device, joystick, report_buf, report_size); 837 if(state_changed && !ctx->initialized) { 838 ctx->initialized = true; 839 HIDAPI_DriverLg4ff_SetRange(device, SDL_HIDAPI_DriverLg4ff_GetEnvInt("SDL_HIDAPI_LG4FF_RANGE", 40, 900, 900)); 840 HIDAPI_DriverLg4ff_SetAutoCenter(device, 0); 841 } 842 } 843 } while (r > 0); 844 845 return true; 846} 847 848static bool HIDAPI_DriverLg4ff_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 849{ 850 SDL_AssertJoysticksLocked(); 851 852 // Initialize the joystick capabilities 853 joystick->nhats = 1; 854 joystick->nbuttons = HIDAPI_DriverLg4ff_GetNumberOfButtons(device->product_id); 855 switch(device->product_id){ 856 case USB_DEVICE_ID_LOGITECH_G29_WHEEL: 857 case USB_DEVICE_ID_LOGITECH_G27_WHEEL: 858 case USB_DEVICE_ID_LOGITECH_G25_WHEEL: 859 case USB_DEVICE_ID_LOGITECH_WHEEL: 860 joystick->naxes = 4; 861 break; 862 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: 863 joystick->naxes = 3; 864 break; 865 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: 866 joystick->naxes = 3; 867 break; 868 default: 869 SDL_assert(0); 870 } 871 872 return true; 873} 874 875static bool HIDAPI_DriverLg4ff_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 876{ 877 return SDL_Unsupported(); 878} 879 880static bool HIDAPI_DriverLg4ff_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) 881{ 882 return SDL_Unsupported(); 883} 884 885static Uint32 HIDAPI_DriverLg4ff_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 886{ 887 switch(device->product_id) { 888 case USB_DEVICE_ID_LOGITECH_G29_WHEEL: 889 case USB_DEVICE_ID_LOGITECH_G27_WHEEL: 890 return SDL_JOYSTICK_CAP_MONO_LED; 891 default: 892 return 0; 893 } 894} 895 896/* 897 Commands by: 898 Michal Malý <[email protected]> <[email protected]> 899 Simon Wood <[email protected]> 900 lg4ff_led_set_brightness lg4ff_set_leds 901 `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git 902*/ 903static bool HIDAPI_DriverLg4ff_SendLedCommand(SDL_HIDAPI_Device *device, Uint8 state) 904{ 905 Uint8 cmd[7]; 906 Uint8 led_state = 0; 907 908 switch (state) { 909 case 0: 910 led_state = 0; 911 break; 912 case 1: 913 led_state = 1; 914 break; 915 case 2: 916 led_state = 3; 917 break; 918 case 3: 919 led_state = 7; 920 break; 921 case 4: 922 led_state = 15; 923 break; 924 case 5: 925 led_state = 31; 926 break; 927 default: 928 SDL_assert(0); 929 } 930 931 cmd[0] = 0xf8; 932 cmd[1] = 0x12; 933 cmd[2] = led_state; 934 cmd[3] = 0x00; 935 cmd[4] = 0x00; 936 cmd[5] = 0x00; 937 cmd[6] = 0x00; 938 939 return SDL_hid_write(device->dev, cmd, sizeof(cmd)) == sizeof(cmd); 940} 941 942static bool HIDAPI_DriverLg4ff_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) 943{ 944 int max_led = red; 945 946 // only g27/g29, and g923 when supported is added 947 if (device->product_id != USB_DEVICE_ID_LOGITECH_G29_WHEEL && 948 device->product_id != USB_DEVICE_ID_LOGITECH_G27_WHEEL) { 949 return SDL_Unsupported(); 950 } 951 952 if (green > max_led) { 953 max_led = green; 954 } 955 if (blue > max_led) { 956 max_led = blue; 957 } 958 959 return HIDAPI_DriverLg4ff_SendLedCommand(device, (Uint8)((5 * max_led) / 255)); 960} 961 962static bool HIDAPI_DriverLg4ff_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) 963{ 964 // allow programs to send raw commands 965 return SDL_hid_write(device->dev, data, size) == size; 966} 967 968static bool HIDAPI_DriverLg4ff_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) 969{ 970 // On steam deck, sensors are enabled by default. Nothing to do here. 971 return SDL_Unsupported(); 972} 973 974static void HIDAPI_DriverLg4ff_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) 975{ 976 // remember to stop effects on haptics close, when implemented 977 HIDAPI_DriverLg4ff_SetJoystickLED(device, joystick, 0, 0, 0); 978} 979 980static void HIDAPI_DriverLg4ff_FreeDevice(SDL_HIDAPI_Device *device) 981{ 982 // device context is freed in SDL_hidapijoystick.c 983} 984 985 986SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff = { 987 SDL_HINT_JOYSTICK_HIDAPI_LG4FF, 988 true, 989 HIDAPI_DriverLg4ff_RegisterHints, 990 HIDAPI_DriverLg4ff_UnregisterHints, 991 HIDAPI_DriverLg4ff_IsEnabled, 992 HIDAPI_DriverLg4ff_IsSupportedDevice, 993 HIDAPI_DriverLg4ff_InitDevice, 994 HIDAPI_DriverLg4ff_GetDevicePlayerIndex, 995 HIDAPI_DriverLg4ff_SetDevicePlayerIndex, 996 HIDAPI_DriverLg4ff_UpdateDevice, 997 HIDAPI_DriverLg4ff_OpenJoystick, 998 HIDAPI_DriverLg4ff_RumbleJoystick, 999 HIDAPI_DriverLg4ff_RumbleJoystickTriggers, 1000 HIDAPI_DriverLg4ff_GetJoystickCapabilities, 1001 HIDAPI_DriverLg4ff_SetJoystickLED, 1002 HIDAPI_DriverLg4ff_SendJoystickEffect, 1003 HIDAPI_DriverLg4ff_SetSensorsEnabled, 1004 HIDAPI_DriverLg4ff_CloseJoystick, 1005 HIDAPI_DriverLg4ff_FreeDevice, 1006}; 1007 1008 1009#endif /* SDL_JOYSTICK_HIDAPI_LG4FF */ 1010 1011#endif /* SDL_JOYSTICK_HIDAPI */ 1012
[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.