Atlas - SDL_hidapi_lg4ff.c

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