From f215aa5038c014c2ea11953aecc63b550878e2e5 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Wed, 27 Nov 2024 22:52:08 +0100 Subject: [PATCH 01/26] Initial logitech wheel SDL_HIDAPI_DriverLg4ff implementation Current implementation does not include SDL_Haptic integration for force feedback Supported devices: G29, G27, G25, DFGT, DFP Features: Joystick button, hat and axis input 5 level rev light control on SDL_JoystickSetLED raw hid command sending on SDL_JoystickSendEffect all command sending are based on https://github.com/berarma/new-lg4ff tested on G29 in various compat modes shifter input is not tested because I don't have one --- Xcode/SDL/SDL.xcodeproj/project.pbxproj | 6 +- include/SDL3/SDL_hints.h | 13 + src/joystick/hidapi/SDL_hidapi_lg4ff.c | 806 +++++++++++++++++++++ src/joystick/hidapi/SDL_hidapijoystick.c | 3 + src/joystick/hidapi/SDL_hidapijoystick_c.h | 2 + 5 files changed, 829 insertions(+), 1 deletion(-) create mode 100644 src/joystick/hidapi/SDL_hidapi_lg4ff.c diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index d9339b38eb3c2..df3c648006f54 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 55; objects = { /* Begin PBXAggregateTarget section */ @@ -71,6 +71,7 @@ 63134A262A7902FD0021E9A6 /* SDL_pen.c in Sources */ = {isa = PBXBuildFile; fileRef = 63134A242A7902FD0021E9A6 /* SDL_pen.c */; }; 75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */; }; 75E09163241EA924004729E1 /* SDL_virtualjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */; }; + 89E5801E2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c in Sources */ = {isa = PBXBuildFile; fileRef = 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */; }; 9846B07C287A9020000C35C8 /* SDL_hidapi_shield.c in Sources */ = {isa = PBXBuildFile; fileRef = 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */; }; A1626A3E2617006A003F1973 /* SDL_triangle.c in Sources */ = {isa = PBXBuildFile; fileRef = A1626A3D2617006A003F1973 /* SDL_triangle.c */; }; A1626A522617008D003F1973 /* SDL_triangle.h in Headers */ = {isa = PBXBuildFile; fileRef = A1626A512617008C003F1973 /* SDL_triangle.h */; }; @@ -608,6 +609,7 @@ 63134A242A7902FD0021E9A6 /* SDL_pen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_pen.c; sourceTree = ""; }; 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_virtualjoystick.c; sourceTree = ""; }; 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_virtualjoystick_c.h; sourceTree = ""; }; + 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_lg4ff.c; sourceTree = ""; }; 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = ""; }; A1626A3D2617006A003F1973 /* SDL_triangle.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_triangle.c; sourceTree = ""; }; A1626A512617008C003F1973 /* SDL_triangle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_triangle.h; sourceTree = ""; }; @@ -1904,6 +1906,7 @@ A7D8A7BE23E2513E00DCD162 /* hidapi */ = { isa = PBXGroup; children = ( + 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */, F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */, A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */, F3F07D59269640160074468B /* SDL_hidapi_luna.c */, @@ -2966,6 +2969,7 @@ A7D8B76423E2514300DCD162 /* SDL_mixer.c in Sources */, A7D8BB5723E2514500DCD162 /* SDL_events.c in Sources */, A7D8ADE623E2514100DCD162 /* SDL_blit_0.c in Sources */, + 89E5801E2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c in Sources */, A7D8B8A823E2514400DCD162 /* SDL_diskaudio.c in Sources */, 56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, F3A9AE9A2C8A13C100AAC390 /* SDL_shaders_gpu.c in Sources */, diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 9c8ad3f828a5a..95963720d6705 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -1723,6 +1723,19 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI "SDL_JOYSTICK_HIDAPI_STEAM_HORI" +/** + * A variable controlling whether the HIDAPI driver for some Logitech wheels + * should be used. + * + * This variable can be set to the following values: + * + * - "0": HIDAPI driver is not used + * - "1": HIDAPI driver is used + * + * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + */ +#define SDL_HINT_JOYSTICK_HIDAPI_LG4FF "SDL_JOYSTICK_HIDAPI_LG4FF" + /** * A variable controlling whether the HIDAPI driver for Nintendo Switch * controllers should be used. diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c new file mode 100644 index 0000000000000..0a5b404e923ba --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -0,0 +1,806 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2024 Katharine Chui + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + All hid command sent are based on https://github.com/berarma/new-lg4ff +*/ + +#include "../../SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "../SDL_sysjoystick.h" +#include "SDL3/SDL_events.h" +#include "SDL_hidapijoystick_c.h" + +#ifdef SDL_JOYSTICK_HIDAPI_LG4FF + +#define USB_VENDOR_ID_LOGITECH 0x046d +#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f +#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b +#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299 +#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a +#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298 +#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294 + +static Uint32 supported_device_ids[] = { + USB_DEVICE_ID_LOGITECH_G29_WHEEL, + USB_DEVICE_ID_LOGITECH_G27_WHEEL, + USB_DEVICE_ID_LOGITECH_G25_WHEEL, + USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, + USB_DEVICE_ID_LOGITECH_DFP_WHEEL, + USB_DEVICE_ID_LOGITECH_WHEEL +}; + +// keep the same order as the supported_ids array +static const char *supported_device_names[] = { + "Logitech G29", + "Logitech G27", + "Logitech G25", + "Logitech Driving Force GT", + "Logitech Driving Force Pro", + "Driving Force EX" +}; + +static const char *HIDAPI_DriverLg4ff_GetDeviceName(Uint32 device_id) +{ + for (int i = 0;i < (sizeof supported_device_ids) / sizeof(Uint32);i++) { + if (supported_device_ids[i] == device_id) { + return supported_device_names[i]; + } + } + SDL_assert(0); + return ""; +} + +static int HIDAPI_DriverLg4ff_GetNumberOfButtons(Uint32 device_id) +{ + switch (device_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + return 25; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + return 22; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + return 19; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + return 21; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + return 14; + case USB_DEVICE_ID_LOGITECH_WHEEL: + return 13; + default: + SDL_assert(0); + return 0; + } +} + +typedef struct +{ + Uint8 last_report_buf[32]; + bool initialized; +} SDL_DriverLg4ff_Context; + +static void HIDAPI_DriverLg4ff_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, callback, userdata); +} + +static void HIDAPI_DriverLg4ff_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, callback, userdata); +} + +static bool HIDAPI_DriverLg4ff_IsEnabled(void) +{ + bool enabled = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, + SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); + + return enabled; +} + +static bool HIDAPI_DriverLg4ff_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) +{ + /* + TODO + + Is it possible to identify native mode from hid? On the Linux kernel + driver that is done by checking with the usb stack, more specifically + bcdDevice on the usb descriptor + + If a way is found to probe for native mode on the HID layer, this function + should trigger mode switch, then return false, so the next probe cycle + would take the device on a supported mode + */ + if (vendor_id != USB_VENDOR_ID_LOGITECH) { + return false; + } + for (int i = 0;i < sizeof(supported_device_ids) / sizeof(Uint32);i++) { + if (supported_device_ids[i] == product_id) { + return true; + } + } + return false; +} + +static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) +{ + Uint8 cmd[7] = {0}; + int ret = 0; + + if (range < 40) { + range = 40; + } + if (range > 900) { + range = 900; + } + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ + cmd[0] = 0xf8; + cmd[1] = 0x81; + cmd[2] = range & 0x00ff; + cmd[3] = (range & 0xff00) >> 8; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + break; + } + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ + int start_left, start_right, full_range; + + /* Prepare "coarse" limit command */ + cmd[0] = 0xf8; + cmd[1] = 0x00; /* Set later */ + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + if (range > 200) { + cmd[1] = 0x03; + full_range = 900; + } else { + cmd[1] = 0x02; + full_range = 200; + } + ret = SDL_hid_write(device->dev, cmd, 7); + if(ret == -1){ + return false; + } + + /* Prepare "fine" limit command */ + cmd[0] = 0x81; + cmd[1] = 0x0b; + cmd[2] = 0x00; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + if (range != 200 && range != 900) { + /* Construct fine limit command */ + start_left = (((full_range - range + 1) * 2047) / full_range); + start_right = 0xfff - start_left; + + cmd[2] = start_left >> 4; + cmd[3] = start_right >> 4; + cmd[4] = 0xff; + cmd[5] = (start_right & 0xe) << 4 | (start_left & 0xe); + cmd[6] = 0xff; + } + + ret = SDL_hid_write(device->dev, cmd, 7); + if (ret == -1) { + return false; + } + break; + } + case USB_DEVICE_ID_LOGITECH_WHEEL: + // no range setting for ffex/dfex + break; + default: + SDL_assert(0); + } + + return true; +} + +static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int gain) +{ + /* + XXX + + Once again the Linux driver checks between ffex and dfex on the usb + stack, not sure how one can check for that on hid. + */ + Uint8 cmd[7] = {0}; + int ret; + + if (gain < 0) { + gain = 0; + } + if (gain > 65535) { + gain = 65535; + } + +#if 0 + if (is_ffex) { + gain = gain * 90 / 65535; + + cmd[0] = 0xfe; + cmd[1] = 0x03; + cmd[2] = (uint16_t)gain >> 14; + cmd[3] = (uint16_t)gain >> 14; + cmd[4] = (uint16_t)gain; + cmd[5] = 0x00; + cmd[6] = 0x00; + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if(ret == -1){ + return false; + } + }else +#endif + { + Uint32 expand_a; + Uint32 expand_b; + // first disable + cmd[0] = 0xf5; + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + + if (gain == 0) { + return true; + } + + // set strength + + if (gain <= 0xaaaa) { + expand_a = 0x0c * gain; + expand_b = 0x80 * gain; + } else { + expand_a = (0x0c * 0xaaaa) + 0x06 * (gain - 0xaaaa); + expand_b = (0x80 * 0xaaaa) + 0xff * (gain - 0xaaaa); + } + expand_a = expand_a >> 1; + + SDL_memset(cmd, 0x00, 7); + cmd[0] = 0xfe; + cmd[1] = 0x0d; + cmd[2] = expand_a / 0xaaaa; + cmd[3] = expand_a / 0xaaaa; + cmd[4] = expand_b / 0xaaaa; + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + + // enable + SDL_memset(cmd, 0x00, 7); + cmd[0] = 0x14; + + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + if (ret == -1) { + return false; + } + } + return true; +} + +static bool HIDAPI_DriverLg4ff_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_DriverLg4ff_Context *ctx; + + ctx = (SDL_DriverLg4ff_Context *)SDL_calloc(1, sizeof(SDL_DriverLg4ff_Context)); + if (ctx == NULL) { + SDL_OutOfMemory(); + return false; + } + SDL_memset(ctx, 0, sizeof(SDL_DriverLg4ff_Context)); + + device->context = ctx; + device->joystick_type = SDL_JOYSTICK_TYPE_WHEEL; + + HIDAPI_SetDeviceName(device, HIDAPI_DriverLg4ff_GetDeviceName(device->product_id)); + + if (SDL_hid_set_nonblocking(device->dev, 1) != 0) { + return false; + } + + if (!HIDAPI_DriverLg4ff_SetAutoCenter(device, 0)) { + return false; + } + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_DriverLg4ff_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_DriverLg4ff_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + + +static bool HIDAPI_DriverLg4ff_GetBit(const Uint8 *buf, int bit_num, int buf_len) +{ + int byte_offset = bit_num / 8; + int local_bit = bit_num % 8; + Uint8 mask = 1 << local_bit; + if (byte_offset >= buf_len) { + SDL_assert(0); + } + return (buf[byte_offset] & mask) ? true : false; +} + +static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, + SDL_Joystick *joystick, + Uint8 *report_buf, + size_t report_size) +{ + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; + Uint8 hat = 0; + Uint8 last_hat = 0; + int num_buttons = HIDAPI_DriverLg4ff_GetNumberOfButtons(device->product_id); + int bit_offset = 0; + Uint64 timestamp = SDL_GetTicksNS(); + + bool state_changed; + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + hat = report_buf[0] & 0x0f; + last_hat = ctx->last_report_buf[0] & 0x0f; + break; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + hat = report_buf[3] >> 4; + last_hat = ctx->last_report_buf[3] >> 4; + break; + case USB_DEVICE_ID_LOGITECH_WHEEL: + hat = report_buf[2] & 0x0F; + last_hat = ctx->last_report_buf[2] & 0x0F; + break; + default: + SDL_assert(0); + } + + if (hat != last_hat) { + Uint8 sdl_hat = 0; + state_changed = true; + switch (hat) { + case 0: + sdl_hat = SDL_HAT_UP; + break; + case 1: + sdl_hat = SDL_HAT_RIGHTUP; + break; + case 2: + sdl_hat = SDL_HAT_RIGHT; + break; + case 3: + sdl_hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + sdl_hat = SDL_HAT_DOWN; + break; + case 5: + sdl_hat = SDL_HAT_LEFTDOWN; + break; + case 6: + sdl_hat = SDL_HAT_LEFT; + break; + case 7: + sdl_hat = SDL_HAT_LEFTUP; + break; + case 8: + sdl_hat = SDL_HAT_CENTERED; + break; + // do not assert out, in case hardware can report weird hat values + } + SDL_SendJoystickHat(timestamp, joystick, 0, sdl_hat); + } + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + bit_offset = 4; + break; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + bit_offset = 14; + break; + case USB_DEVICE_ID_LOGITECH_WHEEL: + bit_offset = 0; + break; + default: + SDL_assert(0); + } + + for (int i = 0;i < num_buttons;i++) { + int bit_num = bit_offset + i; + bool button_on = HIDAPI_DriverLg4ff_GetBit(report_buf, bit_num, report_size); + bool button_was_on = HIDAPI_DriverLg4ff_GetBit(ctx->last_report_buf, bit_num, report_size); + if(button_on != button_was_on){ + state_changed = true; + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH + i, button_on); + } + } + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL:{ + Uint16 x = *(Uint16 *)&report_buf[4]; + Uint16 last_x = *(Uint16 *)&ctx->last_report_buf[4]; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[6] * 257 - 32768); + } + if (report_buf[7] != ctx->last_report_buf[7]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[7] * 257 - 32768); + } + if (report_buf[8] != ctx->last_report_buf[8]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[8] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL:{ + Uint16 x = report_buf[4] << 6; + Uint16 last_x = ctx->last_report_buf[4] << 6; + x = x | report_buf[3] >> 2; + last_x = last_x | ctx->last_report_buf[3] >> 2; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x * 4 - 32768); + } + if (report_buf[5] != ctx->last_report_buf[5]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[5] * 257 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[6] * 257 - 32768); + } + if (report_buf[7] != ctx->last_report_buf[7]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[7] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ + Uint16 x = report_buf[4]; + Uint16 last_x = ctx->last_report_buf[4]; + x = x | (report_buf[5] & 0x3F) << 8; + last_x = last_x | (ctx->last_report_buf[5] & 0x3F) << 8; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x * 4 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[6] * 257 - 32768); + } + if (report_buf[7] != ctx->last_report_buf[7]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[7] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ + Uint16 x = report_buf[0]; + Uint16 last_x = ctx->last_report_buf[0]; + x = x | (report_buf[1] & 0x3F) << 8; + last_x = last_x | (ctx->last_report_buf[1] & 0x3F) << 8; + if (x != last_x) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x * 4 - 32768); + } + if (report_buf[5] != ctx->last_report_buf[5]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[5] * 257 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[6] * 257 - 32768); + } + break; + } + case USB_DEVICE_ID_LOGITECH_WHEEL:{ + if (report_buf[3] != ctx->last_report_buf[3]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, report_buf[3] * 257 - 32768); + } + if (report_buf[4] != ctx->last_report_buf[4]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, report_buf[4] * 257 - 32768); + } + if (report_buf[5] != ctx->last_report_buf[5]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, report_buf[5] * 257 - 32768); + } + if (report_buf[6] != ctx->last_report_buf[6]) { + state_changed = true; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, report_buf[7] * 257 - 32768); + } + break; + } + default: + SDL_assert(0); + } + + SDL_memcpy(ctx->last_report_buf, report_buf, report_size); + return state_changed; +} + +static int SDL_HIDAPI_DriverLg4ff_GetEnvInt(const char *env_name, int min, int max, int def) +{ + const char *env = SDL_getenv(env_name); + int value = 0; + if(env == NULL) { + return def; + } + value = SDL_atoi(env); + if (value < min) { + value = min; + } + if (value > max) { + value = max; + } + return value; +} + +static bool HIDAPI_DriverLg4ff_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_Joystick *joystick = NULL; + size_t r; + Uint8 report_buf[32] = {0}; + size_t report_size = 0; + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; + + if (device->num_joysticks > 0) { + joystick = SDL_GetJoystickFromID(device->joysticks[0]); + if (joystick == NULL) { + return false; + } + } else { + return false; + } + + switch (device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + report_size = 12; + break; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + report_size = 11; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + report_size = 8; + break; + case USB_DEVICE_ID_LOGITECH_WHEEL: + report_size = 27; + break; + default: + SDL_assert(0); + } + + do { + r = SDL_hid_read(device->dev, report_buf, report_size); + + if (r < 0) { + /* Failed to read from controller */ + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + return false; + } else if (r == report_size) { + bool state_changed = HIDAPI_DriverLg4ff_HandleState(device, joystick, report_buf, report_size); + if(state_changed && !ctx->initialized) { + ctx->initialized = true; + HIDAPI_DriverLg4ff_SetRange(device, SDL_HIDAPI_DriverLg4ff_GetEnvInt("SDL_HIDAPI_LG4FF_RANGE", 40, 900, 900)); + HIDAPI_DriverLg4ff_SetAutoCenter(device, 0); + } + } + } while (r > 0); + + return true; +} + +static bool HIDAPI_DriverLg4ff_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_AssertJoysticksLocked(); + + // Initialize the joystick capabilities + joystick->nhats = 1; + joystick->nbuttons = HIDAPI_DriverLg4ff_GetNumberOfButtons(device->product_id); + switch(device->product_id){ + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_WHEEL: + joystick->naxes = 4; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + joystick->naxes = 3; + break; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + joystick->naxes = 3; + break; + default: + SDL_assert(0); + } + + return true; +} + +static bool HIDAPI_DriverLg4ff_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + +static bool HIDAPI_DriverLg4ff_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_DriverLg4ff_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + return 0; +} + +static bool HIDAPI_DriverLg4ff_SendLedCommand(SDL_HIDAPI_Device *device, Uint8 state) +{ + Uint8 cmd[7]; + Uint8 led_state = 0; + + switch (state) { + case 0: + led_state = 0; + break; + case 1: + led_state = 1; + break; + case 2: + led_state = 3; + break; + case 3: + led_state = 7; + break; + case 4: + led_state = 15; + break; + case 5: + led_state = 31; + break; + default: + SDL_assert(0); + } + + cmd[0] = 0xf8; + cmd[1] = 0x12; + cmd[2] = led_state; + cmd[3] = 0x00; + cmd[4] = 0x00; + cmd[5] = 0x00; + cmd[6] = 0x00; + + return SDL_hid_write(device->dev, cmd, sizeof(cmd)) == sizeof(cmd); +} + +static bool HIDAPI_DriverLg4ff_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + int max_led = red; + + // only g27/g29, and g923 when supported is added + if (device->product_id != USB_DEVICE_ID_LOGITECH_G29_WHEEL && + device->product_id != USB_DEVICE_ID_LOGITECH_G27_WHEEL) { + return SDL_Unsupported(); + } + + if (green > max_led) { + max_led = green; + } + if (blue > max_led) { + max_led = blue; + } + + return HIDAPI_DriverLg4ff_SendLedCommand(device, (5 * max_led) / 255); +} + +static bool HIDAPI_DriverLg4ff_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + // allow programs to send raw commands + return SDL_hid_write(device->dev, data, size) == size; +} + +static bool HIDAPI_DriverLg4ff_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) +{ + // On steam deck, sensors are enabled by default. Nothing to do here. + return SDL_Unsupported(); +} + +static void HIDAPI_DriverLg4ff_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + // remember to stop effects on haptics close, when implemented + HIDAPI_DriverLg4ff_SetJoystickLED(device, joystick, 0, 0, 0); +} + +static void HIDAPI_DriverLg4ff_FreeDevice(SDL_HIDAPI_Device *device) +{ + // device context is freed in SDL_hidapijoystick.c +} + + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff = { + SDL_HINT_JOYSTICK_HIDAPI_LG4FF, + true, + HIDAPI_DriverLg4ff_RegisterHints, + HIDAPI_DriverLg4ff_UnregisterHints, + HIDAPI_DriverLg4ff_IsEnabled, + HIDAPI_DriverLg4ff_IsSupportedDevice, + HIDAPI_DriverLg4ff_InitDevice, + HIDAPI_DriverLg4ff_GetDevicePlayerIndex, + HIDAPI_DriverLg4ff_SetDevicePlayerIndex, + HIDAPI_DriverLg4ff_UpdateDevice, + HIDAPI_DriverLg4ff_OpenJoystick, + HIDAPI_DriverLg4ff_RumbleJoystick, + HIDAPI_DriverLg4ff_RumbleJoystickTriggers, + HIDAPI_DriverLg4ff_GetJoystickCapabilities, + HIDAPI_DriverLg4ff_SetJoystickLED, + HIDAPI_DriverLg4ff_SendJoystickEffect, + HIDAPI_DriverLg4ff_SetSensorsEnabled, + HIDAPI_DriverLg4ff_CloseJoystick, + HIDAPI_DriverLg4ff_FreeDevice, +}; + + +#endif /* SDL_JOYSTICK_HIDAPI_LG4FF */ + +#endif /* SDL_JOYSTICK_HIDAPI */ diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index aec6463347a9c..15699802f21df 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -85,6 +85,9 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { #ifdef SDL_JOYSTICK_HIDAPI_XBOXONE &SDL_HIDAPI_DriverXboxOne, #endif +#ifdef SDL_JOYSTICK_HIDAPI_LG4FF + &SDL_HIDAPI_DriverLg4ff, +#endif }; static int SDL_HIDAPI_numdrivers = 0; static SDL_AtomicInt SDL_HIDAPI_updating_devices; diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index 9cd9f40065142..d00d9cc04f7fd 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -40,6 +40,7 @@ #define SDL_JOYSTICK_HIDAPI_XBOXONE #define SDL_JOYSTICK_HIDAPI_SHIELD #define SDL_JOYSTICK_HIDAPI_STEAM_HORI +#define SDL_JOYSTICK_HIDAPI_LG4FF // Joystick capability definitions #define SDL_JOYSTICK_CAP_MONO_LED 0x00000001 @@ -157,6 +158,7 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori; +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff; // Return true if a HID device is present and supported as a joystick of the given type extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type); From 72d50cc89f620c72a0396cbbb337b5c343c31f12 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Sun, 1 Dec 2024 20:48:53 +0100 Subject: [PATCH 02/26] Add hidapi SDL_Haptic carrier and SDL_Haptic lg4ff implementation effect rendering is ported from https://github.com/berarma/new-lg4ff --- Xcode/SDL/SDL.xcodeproj/project.pbxproj | 20 + cmake/sdlchecks.cmake | 1 + src/haptic/SDL_haptic.c | 132 ++- src/haptic/SDL_hidapihaptic.h | 48 + src/haptic/hidapi/SDL_hidapihaptic.c | 301 +++++ src/haptic/hidapi/SDL_hidapihaptic_c.h | 72 ++ src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 1179 ++++++++++++++++++++ src/joystick/hidapi/SDL_hidapi_lg4ff.c | 2 +- 8 files changed, 1737 insertions(+), 18 deletions(-) create mode 100644 src/haptic/SDL_hidapihaptic.h create mode 100644 src/haptic/hidapi/SDL_hidapihaptic.c create mode 100644 src/haptic/hidapi/SDL_hidapihaptic_c.h create mode 100644 src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index df3c648006f54..0be01d0b58dcd 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -72,6 +72,9 @@ 75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */; }; 75E09163241EA924004729E1 /* SDL_virtualjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */; }; 89E5801E2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c in Sources */ = {isa = PBXBuildFile; fileRef = 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */; }; + 89E580232D03606400DAF6D3 /* SDL_hidapihaptic.c in Sources */ = {isa = PBXBuildFile; fileRef = 89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */; }; + 89E580242D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c in Sources */ = {isa = PBXBuildFile; fileRef = 89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */; }; + 89E580252D03606400DAF6D3 /* SDL_hidapihaptic_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */; }; 9846B07C287A9020000C35C8 /* SDL_hidapi_shield.c in Sources */ = {isa = PBXBuildFile; fileRef = 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */; }; A1626A3E2617006A003F1973 /* SDL_triangle.c in Sources */ = {isa = PBXBuildFile; fileRef = A1626A3D2617006A003F1973 /* SDL_triangle.c */; }; A1626A522617008D003F1973 /* SDL_triangle.h in Headers */ = {isa = PBXBuildFile; fileRef = A1626A512617008C003F1973 /* SDL_triangle.h */; }; @@ -610,6 +613,9 @@ 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_virtualjoystick.c; sourceTree = ""; }; 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_virtualjoystick_c.h; sourceTree = ""; }; 89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_lg4ff.c; sourceTree = ""; }; + 89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapihaptic.c; sourceTree = ""; }; + 89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapihaptic_c.h; sourceTree = ""; }; + 89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapihaptic_lg4ff.c; sourceTree = ""; }; 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = ""; }; A1626A3D2617006A003F1973 /* SDL_triangle.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_triangle.c; sourceTree = ""; }; A1626A512617008C003F1973 /* SDL_triangle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_triangle.h; sourceTree = ""; }; @@ -1479,6 +1485,16 @@ path = virtual; sourceTree = ""; }; + 89E580222D03606400DAF6D3 /* hidapi */ = { + isa = PBXGroup; + children = ( + 89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */, + 89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */, + 89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */, + ); + path = hidapi; + sourceTree = ""; + }; A75FDAA423E2790500529352 /* ios */ = { isa = PBXGroup; children = ( @@ -1537,6 +1553,7 @@ A7D8A5C223E2513D00DCD162 /* haptic */ = { isa = PBXGroup; children = ( + 89E580222D03606400DAF6D3 /* hidapi */, A7D8A5CD23E2513D00DCD162 /* darwin */, A7D8A5C323E2513D00DCD162 /* dummy */, A7D8A5C623E2513D00DCD162 /* SDL_haptic_c.h */, @@ -2620,6 +2637,7 @@ F37E18642BAA40670098C111 /* SDL_time_c.h in Headers */, F31013C82C24E98200FBE946 /* SDL_keymap_c.h in Headers */, 63134A252A7902FD0021E9A6 /* SDL_pen_c.h in Headers */, + 89E580252D03606400DAF6D3 /* SDL_hidapihaptic_c.h in Headers */, F36C34312C0F876500991150 /* SDL_offscreenvulkan.h in Headers */, A7D8B2C023E2514200DCD162 /* SDL_pixels_c.h in Headers */, F37E18622BAA40090098C111 /* SDL_sysfilesystem.h in Headers */, @@ -2908,6 +2926,8 @@ A7D8BBDD23E2574800DCD162 /* SDL_uikitmodes.m in Sources */, A7D8BA3723E2514400DCD162 /* SDL_d3dmath.c in Sources */, F3A9AE9C2C8A13C100AAC390 /* SDL_pipeline_gpu.c in Sources */, + 89E580232D03606400DAF6D3 /* SDL_hidapihaptic.c in Sources */, + 89E580242D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c in Sources */, 75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */, F338A11A2D1B37E4007CDFDF /* SDL_tray.c in Sources */, A7D8ABEB23E2514100DCD162 /* SDL_nullvideo.c in Sources */, diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake index e5670305fd5fc..7d516f8b04794 100644 --- a/cmake/sdlchecks.cmake +++ b/cmake/sdlchecks.cmake @@ -1136,6 +1136,7 @@ macro(CheckHIDAPI) set(HAVE_SDL_JOYSTICK TRUE) set(HAVE_HIDAPI_JOYSTICK TRUE) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/joystick/hidapi/*.c") + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/haptic/hidapi/*.c") endif() else() set(SDL_HIDAPI_DISABLED 1) diff --git a/src/haptic/SDL_haptic.c b/src/haptic/SDL_haptic.c index 1c11db686431f..20dd5c07592ac 100644 --- a/src/haptic/SDL_haptic.c +++ b/src/haptic/SDL_haptic.c @@ -21,6 +21,9 @@ #include "SDL_internal.h" #include "SDL_syshaptic.h" +#ifdef SDL_JOYSTICK_HIDAPI +#include "SDL_hidapihaptic.h" +#endif //SDL_JOYSTICK_HIDAPI #include "SDL_haptic_c.h" #include "../joystick/SDL_joystick_c.h" // For SDL_IsJoystickValid #include "../SDL_hints_c.h" @@ -112,7 +115,17 @@ static SDL_Haptic *SDL_haptics = NULL; bool SDL_InitHaptics(void) { - return SDL_SYS_HapticInit(); + if (!SDL_SYS_HapticInit()) { + return false; + } + #ifdef SDL_JOYSTICK_HIDAPI + if (!SDL_HIDAPI_HapticInit()) { + SDL_SYS_HapticQuit(); + return false; + } + #endif //SDL_JOYSTICK_HIDAPI + + return true; } static bool SDL_GetHapticIndex(SDL_HapticID instance_id, int *driver_index) @@ -205,7 +218,6 @@ SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id) } // Initialize the haptic device - SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); haptic->instance_id = instance_id; haptic->rumble_id = -1; if (!SDL_SYS_HapticOpen(haptic)) { @@ -235,6 +247,7 @@ SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id) SDL_SetHapticAutocenter(haptic, 0); } + SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); return haptic; } @@ -295,7 +308,11 @@ bool SDL_IsJoystickHaptic(SDL_Joystick *joystick) // Must be a valid joystick if (SDL_IsJoystickValid(joystick) && !SDL_IsGamepad(SDL_GetJoystickID(joystick))) { + #ifdef SDL_JOYSTICK_HIDAPI + result = SDL_SYS_JoystickIsHaptic(joystick) || SDL_HIDAPI_JoystickIsHaptic(joystick); + #else //SDL_JOYSTICK_HIDAPI result = SDL_SYS_JoystickIsHaptic(joystick); + #endif //SDL_JOYSTICK_HIDAPI } } SDL_UnlockJoysticks(); @@ -310,16 +327,8 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) SDL_LockJoysticks(); { - // Must be a valid joystick - if (!SDL_IsJoystickValid(joystick)) { - SDL_SetError("Haptic: Joystick isn't valid."); - SDL_UnlockJoysticks(); - return NULL; - } - - // Joystick must be haptic - if (SDL_IsGamepad(SDL_GetJoystickID(joystick)) || - !SDL_SYS_JoystickIsHaptic(joystick)) { + // Joystick must be valid and haptic + if (!SDL_IsJoystickHaptic(joystick)) { SDL_SetError("Haptic: Joystick isn't a haptic device."); SDL_UnlockJoysticks(); return NULL; @@ -328,7 +337,11 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) hapticlist = SDL_haptics; // Check to see if joystick's haptic is already open while (hapticlist) { + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick) || SDL_HIDAPI_JoystickSameHaptic(hapticlist, joystick)) { + #else if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick)) { + #endif //SDL_JOYSTICK_HIDAPI haptic = hapticlist; ++haptic->ref_count; SDL_UnlockJoysticks(); @@ -349,6 +362,16 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) */ SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); haptic->rumble_id = -1; + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_JoystickIsHaptic(joystick)) { + if (!SDL_HIDAPI_HapticOpenFromJoystick(haptic, joystick)) { + SDL_SetError("Haptic: SDL_HIDAPI_HapticOpenFromJoystick failed."); + SDL_free(haptic); + SDL_UnlockJoysticks(); + return NULL; + } + } else + #endif //SDL_JOYSTICK_HIDAPI if (!SDL_SYS_HapticOpenFromJoystick(haptic, joystick)) { SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed."); SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false); @@ -379,6 +402,7 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) haptic->next = SDL_haptics; SDL_haptics = haptic; + SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); return haptic; } @@ -395,13 +419,20 @@ void SDL_CloseHaptic(SDL_Haptic *haptic) return; } - // Close it, properly removing effects if needed - for (i = 0; i < haptic->neffects; i++) { - if (haptic->effects[i].hweffect != NULL) { - SDL_DestroyHapticEffect(haptic, i); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + SDL_HIDAPI_HapticClose(haptic); + } else + #endif //SDL_JOYSTICK_HIDAPI + { + // Close it, properly removing effects if needed + for (i = 0; i < haptic->neffects; i++) { + if (haptic->effects[i].hweffect != NULL) { + SDL_DestroyHapticEffect(haptic, i); + } } + SDL_SYS_HapticClose(haptic); } - SDL_SYS_HapticClose(haptic); SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false); // Remove from the list @@ -433,6 +464,7 @@ void SDL_QuitHaptics(void) SDL_CloseHaptic(SDL_haptics); } + SDL_HIDAPI_HapticQuit(); SDL_SYS_HapticQuit(); } @@ -495,6 +527,12 @@ int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect) return -1; } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticNewEffect(haptic, effect); + } + #endif //SDL_JOYSTICK_HIDAPI + // See if there's a free slot for (i = 0; i < haptic->neffects; i++) { if (haptic->effects[i].hweffect == NULL) { @@ -527,6 +565,12 @@ bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffe { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticUpdateEffect(haptic, effect, data); + } + #endif //SDL_JOYSTICK_HIDAPI + if (!ValidEffect(haptic, effect)) { return false; } @@ -554,6 +598,12 @@ bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticRunEffect(haptic, effect, iterations); + } + #endif //SDL_JOYSTICK_HIDAPI + if (!ValidEffect(haptic, effect)) { return false; } @@ -570,6 +620,12 @@ bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticStopEffect(haptic, effect); + } + #endif //SDL_JOYSTICK_HIDAPI + if (!ValidEffect(haptic, effect)) { return false; } @@ -586,6 +642,12 @@ void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect) { CHECK_HAPTIC_MAGIC(haptic,); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticDestroyEffect(haptic, effect); + } + #endif //SDL_JOYSTICK_HIDAPI + if (!ValidEffect(haptic, effect)) { return; } @@ -602,6 +664,12 @@ bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticGetEffectStatus(haptic, effect); + } + #endif //SDL_JOYSTICK_HIDAPI + if (!ValidEffect(haptic, effect)) { return false; } @@ -648,6 +716,12 @@ bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain) real_gain = gain; } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticSetGain(haptic, real_gain); + } + #endif //SDL_JOYSTICK_HIDAPI + return SDL_SYS_HapticSetGain(haptic, real_gain); } @@ -663,6 +737,12 @@ bool SDL_SetHapticAutocenter(SDL_Haptic *haptic, int autocenter) return SDL_SetError("Haptic: Autocenter must be between 0 and 100."); } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticSetAutocenter(haptic, autocenter); + } + #endif //SDL_JOYSTICK_HIDAPI + return SDL_SYS_HapticSetAutocenter(haptic, autocenter); } @@ -674,6 +754,12 @@ bool SDL_PauseHaptic(SDL_Haptic *haptic) return SDL_SetError("Haptic: Device does not support setting pausing."); } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticPause(haptic); + } + #endif //SDL_JOYSTICK_HIDAPI + return SDL_SYS_HapticPause(haptic); } @@ -685,6 +771,12 @@ bool SDL_ResumeHaptic(SDL_Haptic *haptic) return true; // Not going to be paused, so we pretend it's unpaused. } + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticResume(haptic); + } + #endif //SDL_JOYSTICK_HIDAPI + return SDL_SYS_HapticResume(haptic); } @@ -692,6 +784,12 @@ bool SDL_StopHapticEffects(SDL_Haptic *haptic) { CHECK_HAPTIC_MAGIC(haptic, false); + #ifdef SDL_JOYSTICK_HIDAPI + if (SDL_HIDAPI_HapticIsHidapi(haptic)) { + return SDL_HIDAPI_HapticStopAll(haptic); + } + #endif //SDL_JOYSTICK_HIDAPI + return SDL_SYS_HapticStopAll(haptic); } diff --git a/src/haptic/SDL_hidapihaptic.h b/src/haptic/SDL_hidapihaptic.h new file mode 100644 index 0000000000000..3d14617f44145 --- /dev/null +++ b/src/haptic/SDL_hidapihaptic.h @@ -0,0 +1,48 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2024 Katharine Chui + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + All hid command sent and effect rendering are ported from https://github.com/berarma/new-lg4ff +*/ + +#ifndef SDL_hidapihaptic_h_ +#define SDL_hidapihaptic_h_ + +bool SDL_HIDAPI_HapticInit(); +bool SDL_HIDAPI_HapticIsHidapi(SDL_Haptic *haptic); +bool SDL_HIDAPI_JoystickIsHaptic(SDL_Joystick *joystick); +bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick); +bool SDL_HIDAPI_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick); +void SDL_HIDAPI_HapticClose(SDL_Haptic *haptic); +void SDL_HIDAPI_HapticQuit(void); +int SDL_HIDAPI_HapticNewEffect(SDL_Haptic *haptic, const SDL_HapticEffect *base); +bool SDL_HIDAPI_HapticUpdateEffect(SDL_Haptic *haptic, int id, const SDL_HapticEffect *data); +bool SDL_HIDAPI_HapticRunEffect(SDL_Haptic *haptic, int id, Uint32 iterations); +bool SDL_HIDAPI_HapticStopEffect(SDL_Haptic *haptic, int id); +void SDL_HIDAPI_HapticDestroyEffect(SDL_Haptic *haptic, int id); +bool SDL_HIDAPI_HapticGetEffectStatus(SDL_Haptic *haptic, int id); +bool SDL_HIDAPI_HapticSetGain(SDL_Haptic *haptic, int gain); +bool SDL_HIDAPI_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter); +bool SDL_HIDAPI_HapticPause(SDL_Haptic *haptic); +bool SDL_HIDAPI_HapticResume(SDL_Haptic *haptic); +bool SDL_HIDAPI_HapticStopAll(SDL_Haptic *haptic); + +#endif //SDL_hidapihaptic_h_ \ No newline at end of file diff --git a/src/haptic/hidapi/SDL_hidapihaptic.c b/src/haptic/hidapi/SDL_hidapihaptic.c new file mode 100644 index 0000000000000..819e1605f1d32 --- /dev/null +++ b/src/haptic/hidapi/SDL_hidapihaptic.c @@ -0,0 +1,301 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2024 Katharine Chui + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "SDL_hidapihaptic_c.h" +#include "SDL3/SDL_mutex.h" +#include "SDL3/SDL_error.h" + +extern struct SDL_JoystickDriver SDL_HIDAPI_JoystickDriver; + +typedef struct haptic_list_node +{ + SDL_Haptic *haptic; + struct haptic_list_node *next; +} haptic_list_node; + +static haptic_list_node *haptic_list_head = NULL; +static SDL_Mutex *haptic_list_mutex = NULL; + +SDL_HIDAPI_HapticDriver *drivers[] = { + #ifdef SDL_HAPTIC_HIDAPI_LG4FF + &SDL_HIDAPI_HapticDriverLg4ff, + #endif //SDL_HAPTIC_HIDAPI_LG4FF +}; + +bool SDL_HIDAPI_HapticInit() +{ + haptic_list_head = NULL; + haptic_list_mutex = SDL_CreateMutex(); + if (haptic_list_mutex == NULL) { + return SDL_OutOfMemory(); + } + return true; +} + +bool SDL_HIDAPI_HapticIsHidapi(SDL_Haptic *haptic) +{ + haptic_list_node *cur; + bool ret = false; + + SDL_LockMutex(haptic_list_mutex); + cur = haptic_list_head; + while (cur != NULL) { + if (cur->haptic == haptic) { + ret = true; + break; + } + cur = cur->next; + } + + SDL_UnlockMutex(haptic_list_mutex); + + return ret; +} + + +bool SDL_HIDAPI_JoystickIsHaptic(SDL_Joystick *joystick) +{ + int i; + + SDL_AssertJoysticksLocked(); + + if (joystick->driver != &SDL_HIDAPI_JoystickDriver){ + return false; + } + + for (i = 0; i < SDL_arraysize(drivers); ++i) { + if(drivers[i]->JoystickSupported(joystick)) { + return true; + } + } + return false; +} + +bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick) +{ + int i; + SDL_AssertJoysticksLocked(); + + if (joystick->driver != &SDL_HIDAPI_JoystickDriver){ + return SDL_SetError("Cannot open hidapi haptic from non hidapi joystick"); + } + + for (i = 0;i < SDL_arraysize(drivers);++i) { + if(drivers[i]->JoystickSupported(joystick)) { + SDL_HIDAPI_HapticDevice *device; + haptic_list_node *list_node; + // the driver is responsible for calling SDL_SetError + void *ctx = drivers[i]->Open(joystick); + if (ctx == NULL) { + return false; + } + + device = SDL_malloc(sizeof(SDL_HIDAPI_HapticDevice)); + if (device == NULL) { + SDL_HIDAPI_HapticDevice temp; + temp.ctx = ctx; + temp.driver = drivers[i]; + temp.joystick = joystick; + temp.driver->Close(&temp); + return SDL_OutOfMemory(); + } + + device->driver = drivers[i]; + device->haptic = haptic; + device->joystick = joystick; + device->ctx = ctx; + + list_node = SDL_malloc(sizeof(haptic_list_node)); + if(list_node == NULL) { + device->driver->Close(device); + SDL_free(device); + return SDL_OutOfMemory(); + } + + haptic->hwdata = (struct haptic_hwdata *)device; + + // this is outside of the syshaptic driver + + haptic->neffects = device->driver->NumEffects(device); + haptic->nplaying = device->driver->NumEffectsPlaying(device); + haptic->supported = device->driver->GetFeatures(device); + haptic->naxes = device->driver->NumAxes(device); + + // outside of SYS_HAPTIC + haptic->instance_id = 255; + + list_node->haptic = haptic; + list_node->next = NULL; + + // grab a joystick ref so that it doesn't get fully destroyed before the haptic is closed + joystick->ref_count++; + + SDL_LockMutex(haptic_list_mutex); + if (haptic_list_head == NULL) { + haptic_list_head = list_node; + } else { + haptic_list_node *cur = haptic_list_head; + while(cur->next != NULL) { + cur = cur->next; + } + cur->next = list_node; + } + SDL_UnlockMutex(haptic_list_mutex); + + return true; + } + } + + return SDL_SetError("No supported HIDAPI haptic driver found for joystick"); +} + +bool SDL_HIDAPI_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick) +{ + SDL_HIDAPI_HapticDevice *device; + + SDL_AssertJoysticksLocked(); + if (joystick->driver != &SDL_HIDAPI_JoystickDriver){ + return false; + } + + device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + + if (joystick == device->joystick) { + return true; + } + return false; +} + +void SDL_HIDAPI_HapticClose(SDL_Haptic *haptic) +{ + haptic_list_node *cur, *last; + + SDL_LockMutex(haptic_list_mutex); + + cur = haptic_list_head; + last = NULL; + while (cur != NULL) { + if (cur->haptic == haptic) { + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + + device->driver->Close(device); + + // a reference was grabbed during open, now release it + SDL_CloseJoystick(device->joystick); + + if (cur == haptic_list_head) { + haptic_list_head = cur->next; + } else { + last->next = cur->next; + } + + SDL_free(device->ctx); + SDL_free(device); + SDL_free(cur); + SDL_UnlockMutex(haptic_list_mutex); + return; + } + last = cur; + cur = cur->next; + } + + SDL_UnlockMutex(haptic_list_mutex); +} + +void SDL_HIDAPI_HapticQuit(void) +{ + // the list is cleared in SDL_haptic.c + if (haptic_list_mutex != NULL) { + SDL_DestroyMutex(haptic_list_mutex); + haptic_list_mutex = NULL; + } +} + +int SDL_HIDAPI_HapticNewEffect(SDL_Haptic *haptic, const SDL_HapticEffect *base) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->CreateEffect(device, base); +} + +bool SDL_HIDAPI_HapticUpdateEffect(SDL_Haptic *haptic, int id, const SDL_HapticEffect *data) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->UpdateEffect(device, id, data); +} + +bool SDL_HIDAPI_HapticRunEffect(SDL_Haptic *haptic, int id, Uint32 iterations) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->RunEffect(device, id, iterations); +} + +bool SDL_HIDAPI_HapticStopEffect(SDL_Haptic *haptic, int id) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->StopEffect(device, id); +} + +void SDL_HIDAPI_HapticDestroyEffect(SDL_Haptic *haptic, int id) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->DestroyEffect(device, id); +} + +bool SDL_HIDAPI_HapticGetEffectStatus(SDL_Haptic *haptic, int id) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->GetEffectStatus(device, id); +} + +bool SDL_HIDAPI_HapticSetGain(SDL_Haptic *haptic, int gain) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->SetGain(device, gain); +} + +bool SDL_HIDAPI_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->SetAutocenter(device, autocenter); +} + +bool SDL_HIDAPI_HapticPause(SDL_Haptic *haptic) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->Pause(device); +} + +bool SDL_HIDAPI_HapticResume(SDL_Haptic *haptic) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->Resume(device); +} + +bool SDL_HIDAPI_HapticStopAll(SDL_Haptic *haptic) +{ + SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; + return device->driver->StopEffects(device); +} + +#endif //SDL_JOYSTICK_HIDAPI \ No newline at end of file diff --git a/src/haptic/hidapi/SDL_hidapihaptic_c.h b/src/haptic/hidapi/SDL_hidapihaptic_c.h new file mode 100644 index 0000000000000..9159cc02f3c75 --- /dev/null +++ b/src/haptic/hidapi/SDL_hidapihaptic_c.h @@ -0,0 +1,72 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2024 Katharine Chui + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "../../SDL_internal.h" + +#ifndef SDL_hidapihaptic_c_h_ +#define SDL_hidapihaptic_c_h_ + +#include "SDL3/SDL_haptic.h" +#include "SDL3/SDL_joystick.h" +#include "../SDL_syshaptic.h" +#include "../../joystick/SDL_joystick_c.h" // accessing _SDL_JoystickDriver +#include "../../joystick/SDL_sysjoystick.h" // accessing _SDL_Joystick + +#define SDL_HAPTIC_HIDAPI_LG4FF + +typedef struct SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriver; +typedef struct SDL_HIDAPI_HapticDevice +{ + SDL_Haptic *haptic; /* related haptic ref */ + SDL_Joystick *joystick; /* related hidapi joystick */ + SDL_HIDAPI_HapticDriver *driver; /* driver to use */ + void *ctx; /* driver specific context */ +} SDL_HIDAPI_HapticDevice; + +struct SDL_HIDAPI_HapticDriver +{ + bool (*JoystickSupported)(SDL_Joystick *joystick); /* return SDL_TRUE if haptic can be opened from the joystick */ + void *(*Open)(SDL_Joystick *joystick); /* returns a driver context allocated with SDL_malloc, or null if it cannot be allocated */ + + /* functions below need to handle the possibility of a null joystick instance, indicating the absence of the joystick */ + void (*Close)(SDL_HIDAPI_HapticDevice *device); /* cleanup resources allocated during Open, do NOT free driver context created in Open */ + + /* below mirror SDL_haptic.h effect interfaces */ + int (*NumEffects)(SDL_HIDAPI_HapticDevice *device); /* returns supported number of effects the device can store */ + int (*NumEffectsPlaying)(SDL_HIDAPI_HapticDevice *device); /* returns supported number of effects the device can play concurrently */ + Uint32 (*GetFeatures)(SDL_HIDAPI_HapticDevice *device); /* returns supported effects in a bitmask */ + int (*NumAxes)(SDL_HIDAPI_HapticDevice *device); /* returns the number of haptic axes */ + int (*CreateEffect)(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *data); /* returns effect id if created correctly, negative number on error */ + bool (*UpdateEffect)(SDL_HIDAPI_HapticDevice *device, int id, const SDL_HapticEffect *data); /* returns 0 on success, negative number on error */ + bool (*RunEffect)(SDL_HIDAPI_HapticDevice *device, int id, Uint32 iterations); /* returns 0 on success, negative number on error */ + bool (*StopEffect)(SDL_HIDAPI_HapticDevice *device, int id); /* returns 0 on success, negative number on error */ + void (*DestroyEffect)(SDL_HIDAPI_HapticDevice *device, int id); /* returns 0 on success, negative number on error */ + bool (*GetEffectStatus)(SDL_HIDAPI_HapticDevice *device, int id); /* returns 0 if not playing, 1 if playing, negative number on error */ + bool (*SetGain)(SDL_HIDAPI_HapticDevice *device, int gain); /* gain 0 - 100, returns 0 on success, negative number on error */ + bool (*SetAutocenter)(SDL_HIDAPI_HapticDevice *device, int autocenter); /* gain 0 - 100, returns 0 on success, negative number on error */ + bool (*Pause)(SDL_HIDAPI_HapticDevice *device); /* returns 0 on success, negative number on error */ + bool (*Resume)(SDL_HIDAPI_HapticDevice *device); /* returns 0 on success, negative number on error */ + bool (*StopEffects)(SDL_HIDAPI_HapticDevice *device); /* returns 0 on success, negative number on error */ +}; + +extern SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriverLg4ff; + +#endif //SDL_joystick_c_h_ \ No newline at end of file diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c new file mode 100644 index 0000000000000..52ce124735db0 --- /dev/null +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -0,0 +1,1179 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2024 Katharine Chui + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + All hid command sent and effect rendering are ported from https://github.com/berarma/new-lg4ff +*/ + +#include "../../SDL_internal.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +#include "SDL_hidapihaptic_c.h" + +#ifdef SDL_HAPTIC_HIDAPI_LG4FF + +#include "SDL3/SDL_thread.h" +#include "SDL3/SDL_mutex.h" +#include "SDL3/SDL_timer.h" + +#include + +#define USB_VENDOR_ID_LOGITECH 0x046d +#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f +#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b +#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299 +#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a +#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298 +#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294 + +static Uint32 supported_device_ids[] = { + USB_DEVICE_ID_LOGITECH_G29_WHEEL, + USB_DEVICE_ID_LOGITECH_G27_WHEEL, + USB_DEVICE_ID_LOGITECH_G25_WHEEL, + USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, + USB_DEVICE_ID_LOGITECH_DFP_WHEEL, + USB_DEVICE_ID_LOGITECH_WHEEL +}; + + + +#define LG4FF_MAX_EFFECTS 16 + +#define FF_EFFECT_STARTED 0 +#define FF_EFFECT_ALLSET 1 +#define FF_EFFECT_PLAYING 2 +#define FF_EFFECT_UPDATING 3 + +struct lg4ff_effect_state{ + SDL_HapticEffect effect; + Uint64 start_at; + Uint64 play_at; + Uint64 stop_at; + Uint32 flags; + Uint64 time_playing; + Uint64 updated_at; + Uint32 phase; + Uint32 phase_adj; + Uint32 count; + + double direction_gain; + Sint32 slope; + +bool allocated; +}; + +struct lg4ff_effect_parameters{ + Sint32 level; + Sint32 d1; + Sint32 d2; + Sint32 k1; + Sint32 k2; + Uint32 clip; +}; + +struct lg4ff_slot{ + Sint32 id; + struct lg4ff_effect_parameters parameters; + Uint8 current_cmd[7]; + Uint32 cmd_op; + bool is_updated; + Uint32 effect_type; +}; + +typedef struct lg4ff_device{ + Uint16 product_id; + struct lg4ff_effect_state states[LG4FF_MAX_EFFECTS]; + struct lg4ff_slot slots[4]; + Sint32 effects_used; + + Sint32 gain; + Sint32 app_gain; + + Sint32 spring_level; + Sint32 damper_level; + Sint32 friction_level; + + Sint32 peak_ffb_level; + + SDL_Joystick *hid_handle; + + bool stop_thread; + SDL_Thread *thread; + char thread_name_buf[256]; + + SDL_Mutex *mutex; + int cmd_errors; +} lg4ff_device; + +Uint64 get_time_ms(){ + return SDL_GetTicks(); +} + +#define test_bit(bit, field) (*(field) & (1 << bit)) +#define __set_bit(bit, field) {*(field) = *(field) | (1 << bit);} +#define __clear_bit(bit, field) {*(field) = *(field) & ~(1 << bit);} +#define sin_deg(in) (double)(sin((double)(in) * (double)M_PI / 180.0)) + +#define time_after_eq(a, b) (a >= b) +#define time_before(a, b) (a < b) +#define time_diff(a, b) (a - b) + +#define STOP_EFFECT(state) ((state)->flags = 0) + +#define CLAMP_VALUE_U16(x) ((Uint16)((x) > 0xffff ? 0xffff : (x))) +#define SCALE_VALUE_U16(x, bits) (CLAMP_VALUE_U16(x) >> (16 - bits)) +#define CLAMP_VALUE_S16(x) ((Uint16)((x) <= -0x8000 ? -0x8000 : ((x) > 0x7fff ? 0x7fff : (x)))) +#define TRANSLATE_FORCE(x) ((CLAMP_VALUE_S16(x) + 0x8000) >> 8) +#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(abs(x) * 2, bits) + +bool effect_is_periodic(const SDL_HapticEffect *effect) +{ + +return effect->type == SDL_HAPTIC_SINE || + effect->type == SDL_HAPTIC_TRIANGLE || + effect->type == SDL_HAPTIC_SAWTOOTHUP || + effect->type == SDL_HAPTIC_SAWTOOTHDOWN || + effect->type == SDL_HAPTIC_SQUARE; +} + +bool effect_is_condition(const SDL_HapticEffect *effect) +{ +return effect->type == SDL_HAPTIC_SPRING || + effect->type == SDL_HAPTIC_DAMPER || + effect->type == SDL_HAPTIC_FRICTION; +} + +// linux SDL_syshaptic.c SDL_SYS_ToDirection +static Uint16 to_linux_direction(SDL_HapticDirection *src) +{ + Uint32 tmp; + + switch (src->type) { + case SDL_HAPTIC_POLAR: + tmp = ((src->dir[0] % 36000) * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ + return (Uint16)tmp; + + case SDL_HAPTIC_SPHERICAL: + /* + We convert to polar, because that's the only supported direction on Linux. + The first value of a spherical direction is practically the same as a + Polar direction, except that we have to add 90 degrees. It is the angle + from EAST {1,0} towards SOUTH {0,1}. + --> add 9000 + --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR. + */ + tmp = ((src->dir[0]) + 9000) % 36000; /* Convert to polars */ + tmp = (tmp * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ + return (Uint16)tmp; + + case SDL_HAPTIC_CARTESIAN: + if (!src->dir[1]) { + return (Uint16) (src->dir[0] >= 0 ? 0x4000 : 0xC000); + } else if (!src->dir[0]) { + return (Uint16) (src->dir[1] >= 0 ? 0x8000 : 0); + } else { + float f = SDL_atan2(src->dir[1], src->dir[0]); /* Ideally we'd use fixed point math instead of floats... */ + /* + SDL_atan2 takes the parameters: Y-axis-value and X-axis-value (in that order) + - Y-axis-value is the second coordinate (from center to SOUTH) + - X-axis-value is the first coordinate (from center to EAST) + We add 36000, because SDL_atan2 also returns negative values. Then we practically + have the first spherical value. Therefore we proceed as in case + SDL_HAPTIC_SPHERICAL and add another 9000 to get the polar value. + --> add 45000 in total + --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR. + */ + tmp = (((Sint32) (f * 18000. / M_PI)) + 45000) % 36000; + tmp = (tmp * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ + return (Uint16)tmp; + } + break; + case SDL_HAPTIC_STEERING_AXIS: + return 0x4000; + default: + SDL_assert(0); + } + + return 0; +} + +static Uint16 get_effect_direction(SDL_HapticEffect *effect) +{ + Uint16 direction = 0; + if (effect_is_periodic(effect)) { + direction = to_linux_direction(&effect->periodic.direction); + } else if (effect_is_condition(effect)) { + direction = to_linux_direction(&effect->condition.direction); + } else { + switch(effect->type) { + case SDL_HAPTIC_CONSTANT: + direction = to_linux_direction(&effect->constant.direction); + break; + case SDL_HAPTIC_RAMP: + direction = to_linux_direction(&effect->ramp.direction); + break; + default: + SDL_assert(0); + } + } + + return direction; +} + +static Uint32 get_effect_replay_length(SDL_HapticEffect *effect) +{ + int length = 0; + if (effect_is_periodic(effect)) { + length = effect->periodic.length; + } else if (effect_is_condition(effect)) { + length = effect->condition.length; + } else { + switch(effect->type) { + case SDL_HAPTIC_CONSTANT: + length = effect->constant.length; + break; + case SDL_HAPTIC_RAMP: + length = effect->ramp.length; + break; + default: + SDL_assert(0); + } + } + + if (length == SDL_HAPTIC_INFINITY) { + length = 0; + } + + return length; +} + +static Uint16 get_effect_replay_delay(SDL_HapticEffect *effect) +{ + int delay = 0; + if (effect_is_periodic(effect)) { + delay = effect->periodic.delay; + } else if (effect_is_condition(effect)) { + delay = effect->condition.delay; + } else { + switch(effect->type) { + case SDL_HAPTIC_CONSTANT: + delay = effect->constant.delay; + break; + case SDL_HAPTIC_RAMP: + delay = effect->ramp.delay; + break; + default: + SDL_assert(0); + } + } + + return delay; +} + +static int lg4ff_play_effect(struct lg4ff_device *device, int effect_id, int value) +{ + struct lg4ff_effect_state *state; + Uint64 now = get_time_ms(); + + state = &device->states[effect_id]; + + if (value > 0) { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + } else { + device->effects_used++; + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = now; + state->count = value; + } else { + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + STOP_EFFECT(state); + device->effects_used--; + } + } + + return 0; +} + +static int lg4ff_upload_effect(struct lg4ff_device *device, const SDL_HapticEffect *effect, int id) +{ + struct lg4ff_effect_state *state; + Uint64 now = get_time_ms(); + + if (effect_is_periodic(effect) && effect->periodic.period == 0) { + return -1; + } + + state = &device->states[id]; + + if (test_bit(FF_EFFECT_STARTED, &state->flags) && effect->type != state->effect.type) { + return -1; + } + + state->effect = *effect; + + if (test_bit(FF_EFFECT_STARTED, &state->flags)) { + __set_bit(FF_EFFECT_UPDATING, &state->flags); + state->updated_at = now; + } + + return 0; +} + +static void lg4ff_update_state(struct lg4ff_effect_state *state, const Uint64 now) +{ + SDL_HapticEffect *effect = &state->effect; + Uint64 phase_time; + Uint16 effect_direction = get_effect_direction(effect); + + if (!test_bit(FF_EFFECT_ALLSET, &state->flags)) { + state->play_at = state->start_at + get_effect_replay_delay(effect); + if (!test_bit(FF_EFFECT_UPDATING, &state->flags)) { + state->updated_at = state->play_at; + } + state->direction_gain = sin_deg(effect_direction * 360 / 0x10000); + if (effect_is_periodic(effect)) { + state->phase_adj = effect->periodic.phase * 360 / effect->periodic.period; + } + if (get_effect_replay_length(effect)) { + state->stop_at = state->play_at + get_effect_replay_length(effect); + } + } + __set_bit(FF_EFFECT_ALLSET, &state->flags); + + if (test_bit(FF_EFFECT_UPDATING, &state->flags)) { + __clear_bit(FF_EFFECT_PLAYING, &state->flags); + state->play_at = state->updated_at + get_effect_replay_delay(effect); + state->direction_gain = sin_deg(effect_direction * 360 / 0x10000); + if (get_effect_replay_length(effect)) { + state->stop_at = state->updated_at + get_effect_replay_length(effect); + } + if (effect_is_periodic(effect)) { + state->phase_adj = state->phase; + } + } + __clear_bit(FF_EFFECT_UPDATING, &state->flags); + + state->slope = 0; + if (effect->type == SDL_HAPTIC_RAMP && effect->ramp.length && (effect->ramp.length - effect->ramp.attack_length - effect->ramp.fade_length) != 0) { + state->slope = ((effect->ramp.end - effect->ramp.start) << 16) / (effect->ramp.length - effect->ramp.attack_length - effect->ramp.fade_length); + } + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags) && time_after_eq(now, + state->play_at) && (get_effect_replay_length(effect) == 0 || + time_before(now, state->stop_at))) { + __set_bit(FF_EFFECT_PLAYING, &state->flags); + } + + if (test_bit(FF_EFFECT_PLAYING, &state->flags)) { + state->time_playing = time_diff(now, state->play_at); + if (effect_is_periodic(effect)) { + phase_time = time_diff(now, state->updated_at); + state->phase = (phase_time % effect->periodic.period) * 360 / effect->periodic.period; + state->phase += state->phase_adj % 360; + } + } +} + +static Sint32 lg4ff_calculate_constant(struct lg4ff_effect_state *state) +{ + SDL_HapticConstant *constant = (SDL_HapticConstant *)&state->effect; + Sint32 level_sign; + Sint32 level = constant->level; + Sint32 d, t; + + if (state->time_playing < constant->attack_length) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * constant->attack_level; + level = level_sign * constant->attack_level + d * state->time_playing / constant->attack_length; + } else if (constant->length && constant->fade_length) { + t = state->time_playing - constant->length + constant->fade_length; + if (t > 0) { + level_sign = level < 0 ? -1 : 1; + d = level - level_sign * constant->fade_level; + level = level - d * t / constant->fade_length; + } + } + + return (Sint32)(state->direction_gain * level); +} + +static Sint32 lg4ff_calculate_ramp(struct lg4ff_effect_state *state) +{ + SDL_HapticRamp *ramp = (SDL_HapticRamp *)&state->effect; + Sint32 level_sign; + Sint32 level; + Sint32 d, t; + + if (state->time_playing < ramp->attack_length) { + level = ramp->start; + level_sign = level < 0 ? -1 : 1; + t = ramp->attack_length - state->time_playing; + d = level - level_sign * ramp->attack_level; + level = level_sign * ramp->attack_level + d * t / ramp->attack_length; + } else if (ramp->length && state->time_playing >= ramp->length - ramp->fade_length && ramp->fade_length) { + level = ramp->end; + level_sign = level < 0 ? -1 : 1; + t = state->time_playing - ramp->length + ramp->fade_length; + d = level_sign * ramp->fade_level - level; + level = level - d * t / ramp->fade_length; + } else { + t = state->time_playing - ramp->attack_length; + level = ramp->start + ((t * state->slope) >> 16); + } + + return (Sint32)(state->direction_gain * level); +} + +static Sint32 lg4ff_calculate_periodic(struct lg4ff_effect_state *state) +{ + SDL_HapticPeriodic *periodic = (SDL_HapticPeriodic *)&state->effect; + Sint32 magnitude = periodic->magnitude; + Sint32 magnitude_sign = magnitude < 0 ? -1 : 1; + Sint32 level = periodic->offset; + Sint32 d, t; + + if (state->time_playing < periodic->attack_length) { + d = magnitude - magnitude_sign * periodic->attack_level; + magnitude = magnitude_sign * periodic->attack_level + d * state->time_playing / periodic->attack_length; + } else if (periodic->length && periodic->fade_length) { + t = state->time_playing - get_effect_replay_length(&state->effect) + periodic->fade_length; + if (t > 0) { + d = magnitude - magnitude_sign * periodic->fade_level; + magnitude = magnitude - d * t / periodic->fade_length; + } + } + + switch (periodic->type) { + case SDL_HAPTIC_SINE: + level += (Sint32)(sin_deg(state->phase) * magnitude); + break; + case SDL_HAPTIC_SQUARE: + level += (state->phase < 180 ? 1 : -1) * magnitude; + break; + case SDL_HAPTIC_TRIANGLE: + level += llabs((Sint64)state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude; + break; + case SDL_HAPTIC_SAWTOOTHUP: + level += state->phase * magnitude * 2 / 360 - magnitude; + break; + case SDL_HAPTIC_SAWTOOTHDOWN: + level += magnitude - state->phase * magnitude * 2 / 360; + break; + default: + SDL_assert(0); + } + + return (Sint32)(state->direction_gain * level); +} + +static void lg4ff_calculate_spring(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) +{ + SDL_HapticCondition *condition = (SDL_HapticCondition *)&state->effect; + + parameters->d1 = ((Sint32)condition->center[0]) - condition->deadband[0] / 2; + parameters->d2 = ((Sint32)condition->center[0]) + condition->deadband[0] / 2; + parameters->k1 = condition->left_coeff[0]; + parameters->k2 = condition->right_coeff[0]; + parameters->clip = (Uint16)condition->right_sat[0]; +} + +static void lg4ff_calculate_resistance(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) +{ + SDL_HapticCondition *condition = (SDL_HapticCondition *)&state->effect; + + parameters->k1 = condition->left_coeff[0]; + parameters->k2 = condition->right_coeff[0]; + parameters->clip = (Uint16)condition->right_sat[0]; +} + +static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_parameters *parameters) +{ + Uint8 original_cmd[7]; + Sint32 d1; + Sint32 d2; + Sint32 k1; + Sint32 k2; + Sint32 s1; + Sint32 s2; + + SDL_memcpy(original_cmd, slot->current_cmd, sizeof(original_cmd)); + + if ((original_cmd[0] & 0xf) == 1) { + original_cmd[0] = (original_cmd[0] & 0xf0) + 0xc; + } + + if (slot->effect_type == SDL_HAPTIC_CONSTANT) { + if (slot->cmd_op == 0) { + slot->cmd_op = 1; + } else { + slot->cmd_op = 0xc; + } + } else { + if (parameters->clip == 0) { + slot->cmd_op = 3; + } else if (slot->cmd_op == 3) { + slot->cmd_op = 1; + } else { + slot->cmd_op = 0xc; + } + } + + slot->current_cmd[0] = (0x10 << slot->id) + slot->cmd_op; + + if (slot->cmd_op == 3) { + slot->current_cmd[1] = 0; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + } else { + switch (slot->effect_type) { + case SDL_HAPTIC_CONSTANT: + slot->current_cmd[1] = 0x00; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + slot->current_cmd[2 + slot->id] = TRANSLATE_FORCE(parameters->level); + break; + case SDL_HAPTIC_SPRING: + d1 = SCALE_VALUE_U16(((parameters->d1) + 0x8000) & 0xffff, 11); + d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11); + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + k1 = abs(parameters->k1); + k2 = abs(parameters->k2); + if (k1 < 2048) { + d1 = 0; + } else { + k1 -= 2048; + } + if (k2 < 2048) { + d2 = 2047; + } else { + k2 -= 2048; + } + slot->current_cmd[1] = 0x0b; + slot->current_cmd[2] = d1 >> 3; + slot->current_cmd[3] = d2 >> 3; + slot->current_cmd[4] = (SCALE_COEFF(k2, 4) << 4) + SCALE_COEFF(k1, 4); + slot->current_cmd[5] = ((d2 & 7) << 5) + ((d1 & 7) << 1) + (s2 << 4) + s1; + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; + case SDL_HAPTIC_DAMPER: + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0c; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 4); + slot->current_cmd[3] = s1; + slot->current_cmd[4] = SCALE_COEFF(parameters->k2, 4); + slot->current_cmd[5] = s2; + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; + case SDL_HAPTIC_FRICTION: + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0e; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 8); + slot->current_cmd[3] = SCALE_COEFF(parameters->k2, 8); + slot->current_cmd[4] = SCALE_VALUE_U16(parameters->clip, 8); + slot->current_cmd[5] = (s2 << 4) + s1; + slot->current_cmd[6] = 0; + break; + } + } + + if (SDL_memcmp(original_cmd, slot->current_cmd, sizeof(original_cmd))) { + slot->is_updated = 1; + } +} + +static int lg4ff_init_slots(struct lg4ff_device *device) +{ + struct lg4ff_effect_parameters parameters; + Uint8 cmd[7] = {0}; + int i; + bool ret; + + // Set/unset fixed loop mode + cmd[0] = 0x0d; + //cmd[1] = fixed_loop ? 1 : 0; + cmd[1] = 0; + ret = SDL_SendJoystickEffect(device->hid_handle, cmd, 7); + if(!ret){ + return -1; + } + + SDL_memset(&device->states, 0, sizeof(device->states)); + SDL_memset(&device->slots, 0, sizeof(device->slots)); + SDL_memset(¶meters, 0, sizeof(parameters)); + + device->slots[0].effect_type = SDL_HAPTIC_CONSTANT; + device->slots[1].effect_type = SDL_HAPTIC_SPRING; + device->slots[2].effect_type = SDL_HAPTIC_DAMPER; + device->slots[3].effect_type = SDL_HAPTIC_FRICTION; + + for (i = 0; i < 4; i++) { + device->slots[i].id = i; + lg4ff_update_slot(&device->slots[i], ¶meters); + ret = SDL_SendJoystickEffect(device->hid_handle, cmd, 7); + if(!ret){ + return -1; + } + device->slots[i].is_updated = 0; + } + + return 0; +} + +static int lg4ff_timer(struct lg4ff_device *device) +{ + struct lg4ff_slot *slot; + struct lg4ff_effect_state *state; + struct lg4ff_effect_parameters parameters[4]; + Uint64 now = get_time_ms(); + Uint16 gain; + Sint32 count; + Sint32 effect_id; + int i; + Sint32 ffb_level; + int status = 0; + + // XXX how to detect stacked up effects here? + + SDL_memset(parameters, 0, sizeof(parameters)); + + gain = (Uint32)device->gain * device->app_gain / 0xffff; + + count = device->effects_used; + + for (effect_id = 0; effect_id < LG4FF_MAX_EFFECTS; effect_id++) { + + if (!count) { + break; + } + + state = &device->states[effect_id]; + + if (!test_bit(FF_EFFECT_STARTED, &state->flags)) { + continue; + } + + count--; + + if (test_bit(FF_EFFECT_ALLSET, &state->flags)) { + if (get_effect_replay_length(&state->effect) && time_after_eq(now, state->stop_at)) { + STOP_EFFECT(state); + if (!--state->count) { + device->effects_used--; + continue; + } + __set_bit(FF_EFFECT_STARTED, &state->flags); + state->start_at = state->stop_at; + } + } + + lg4ff_update_state(state, now); + + if (!test_bit(FF_EFFECT_PLAYING, &state->flags)) { + continue; + } + + if (effect_is_periodic(&state->effect)) { + parameters[0].level += lg4ff_calculate_periodic(state); + } else { + switch (state->effect.type) { + case SDL_HAPTIC_CONSTANT: + parameters[0].level += lg4ff_calculate_constant(state); + break; + case SDL_HAPTIC_RAMP: + parameters[0].level += lg4ff_calculate_ramp(state); + break; + case SDL_HAPTIC_SPRING: + lg4ff_calculate_spring(state, ¶meters[1]); + break; + case SDL_HAPTIC_DAMPER: + lg4ff_calculate_resistance(state, ¶meters[2]); + break; + case SDL_HAPTIC_FRICTION: + lg4ff_calculate_resistance(state, ¶meters[3]); + break; + } + } + } + + parameters[0].level = (Sint64)parameters[0].level * gain / 0xffff; + parameters[1].clip = parameters[1].clip * device->spring_level / 100; + parameters[2].clip = parameters[2].clip * device->damper_level / 100; + parameters[3].clip = parameters[3].clip * device->friction_level / 100; + + ffb_level = abs(parameters[0].level); + for (i = 1; i < 4; i++) { + parameters[i].k1 = (Sint64)parameters[i].k1 * gain / 0xffff; + parameters[i].k2 = (Sint64)parameters[i].k2 * gain / 0xffff; + parameters[i].clip = parameters[i].clip * gain / 0xffff; + ffb_level += parameters[i].clip * 0x7fff / 0xffff; + } + if (ffb_level > device->peak_ffb_level) { + device->peak_ffb_level = ffb_level; + } + + for (i = 0; i < 4; i++) { + slot = &device->slots[i]; + lg4ff_update_slot(slot, ¶meters[i]); + if (slot->is_updated) { + bool ret = SDL_SendJoystickEffect(device->hid_handle, slot->current_cmd, 7); + if(!ret){ + status = -1; + } + slot->is_updated = 0; + } + } + + return status; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported(SDL_Joystick *joystick) +{ + Uint16 vendor_id = SDL_GetJoystickVendor(joystick); + Uint16 product_id = SDL_GetJoystickProduct(joystick); + if (vendor_id != USB_VENDOR_ID_LOGITECH) { + return false; + } + for (int i = 0;i < sizeof(supported_device_ids) / sizeof(Uint32);i++) { + if (supported_device_ids[i] == product_id) { + return true; + } + } + return false; +} + +static int SDLCALL SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction(void *ctx_in) +{ + lg4ff_device *ctx = (lg4ff_device *)ctx_in; + while (true) { + if (ctx->stop_thread) { + return 0; + } + SDL_LockMutex(ctx->mutex); + lg4ff_timer(ctx); + SDL_UnlockMutex(ctx->mutex); + SDL_Delay(2); + } +} + +static int SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt(const char *env_name, int min, int max, int def) +{ + const char *env = SDL_getenv(env_name); + int value = 0; + if(env == NULL) { + return def; + } + value = SDL_atoi(env); + if (value < min) { + value = min; + } + if (value > max) { + value = max; + } + return value; +} + +static void *SDL_HIDAPI_HapticDriverLg4ff_Open(SDL_Joystick *joystick) +{ + lg4ff_device *ctx; + if (!SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported(joystick)) { + SDL_SetError("Device not supported by the lg4ff hidapi haptic driver"); + return NULL; + } + + ctx = SDL_malloc(sizeof(lg4ff_device)); + if (ctx == NULL) { + SDL_OutOfMemory(); + return NULL; + } + SDL_memset(ctx, 0, sizeof(lg4ff_device)); + + ctx->hid_handle = joystick; + if (lg4ff_init_slots(ctx) != 0) { + SDL_SetError("lg4ff hidapi driver failed initializing effect slots"); + SDL_free(ctx); + return NULL; + } + + ctx->mutex = SDL_CreateMutex(); + if (ctx->mutex == NULL) { + SDL_free(ctx); + return NULL; + } + + ctx->spring_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_SPRING", 0, 100, 30); + ctx->damper_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_SPRING", 0, 100, 30); + ctx->friction_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_FRICTION", 0, 100, 30); + ctx->gain = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_GAIN", 0, 65535, 65535); + ctx->app_gain = 65535; + + ctx->product_id = SDL_GetJoystickProduct(joystick); + + sprintf(ctx->thread_name_buf, "SDL_hidapihaptic_lg4ff 0x%16llx %04x:%04x", (Uint64)joystick, USB_VENDOR_ID_LOGITECH, ctx->product_id); + ctx->stop_thread = false; + ctx->thread = SDL_CreateThread(SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction, ctx->thread_name_buf, ctx); + + return ctx; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_StopEffects(SDL_HIDAPI_HapticDevice *device) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + int i; + + SDL_LockMutex(ctx->mutex); + for (i = 0;i < LG4FF_MAX_EFFECTS;i++) { + struct lg4ff_effect_state *state = &ctx->states[i]; + STOP_EFFECT(state); + } + SDL_UnlockMutex(ctx->mutex); + + return true; +} + +static void SDL_HIDAPI_HapticDriverLg4ff_Close(SDL_HIDAPI_HapticDevice *device) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + + SDL_HIDAPI_HapticDriverLg4ff_StopEffects(device); + + // let effects finish in lg4ff_timer + SDL_Delay(50); + + ctx->stop_thread = true; + SDL_WaitThread(ctx->thread, NULL); +} + +static int SDL_HIDAPI_HapticDriverLg4ff_NumEffects(SDL_HIDAPI_HapticDevice *device) +{ + return LG4FF_MAX_EFFECTS; +} + +static Uint32 SDL_HIDAPI_HapticDriverLg4ff_GetFeatures(SDL_HIDAPI_HapticDevice *device) +{ + return SDL_HAPTIC_CONSTANT | + SDL_HAPTIC_SPRING | + SDL_HAPTIC_DAMPER | + SDL_HAPTIC_AUTOCENTER | + SDL_HAPTIC_SINE | + SDL_HAPTIC_SQUARE | + SDL_HAPTIC_TRIANGLE | + SDL_HAPTIC_SAWTOOTHUP | + SDL_HAPTIC_SAWTOOTHDOWN | + SDL_HAPTIC_RAMP | + SDL_HAPTIC_FRICTION | + SDL_HAPTIC_STATUS | + SDL_HAPTIC_GAIN; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_EffectSupported(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *effect) { + Uint32 features = SDL_HIDAPI_HapticDriverLg4ff_GetFeatures(device); + return (features & effect->type)? true : false; +} + +static int SDL_HIDAPI_HapticDriverLg4ff_NumAxes(SDL_HIDAPI_HapticDevice *device) +{ + return 1; +} + +static int SDL_HIDAPI_HapticDriverLg4ff_CreateEffect(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *data) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + int i; + int state_slot = -1; + int ret; + if (!SDL_HIDAPI_HapticDriverLg4ff_EffectSupported(device, data)) { + SDL_SetError("Unsupported effect"); + return -1; + } + + SDL_LockMutex(ctx->mutex); + for (i = 0;i < LG4FF_MAX_EFFECTS;i++) { + if (!ctx->states[i].allocated) { + state_slot = i; + break; + } + } + if (state_slot == -1) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("All effect slots in-use"); + return -1; + } + + ret = lg4ff_upload_effect(ctx, data, state_slot); + SDL_UnlockMutex(ctx->mutex); + if (ret == 0) { + ctx->states[state_slot].allocated = true; + return state_slot; + } else { + SDL_SetError("Bad effect parameters"); + return -1; + } + + return 0; +} + +// assumes ctx->mutex locked +static bool lg4ff_effect_slot_valid_active(lg4ff_device *ctx, int id) +{ + if (id >= LG4FF_MAX_EFFECTS || id < 0) { + return false; + } + if (!ctx->states[id].allocated) { + return false; + } + return true; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_UpdateEffect(SDL_HIDAPI_HapticDevice *device, int id, const SDL_HapticEffect *data) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + int ret; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Bad effect id"); + return false; + } + + ret = lg4ff_upload_effect(ctx, data, id); + SDL_UnlockMutex(ctx->mutex); + + return ret == 0; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_RunEffect(SDL_HIDAPI_HapticDevice *device, int id, Uint32 iterations) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + int ret; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Bad effect id"); + return false; + } + + ret = lg4ff_play_effect(ctx, id, iterations); + SDL_UnlockMutex(ctx->mutex); + + return ret == 0; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_StopEffect(SDL_HIDAPI_HapticDevice *device, int id) +{ + return SDL_HIDAPI_HapticDriverLg4ff_RunEffect(device, id, 0); +} + +static void SDL_HIDAPI_HapticDriverLg4ff_DestroyEffect(SDL_HIDAPI_HapticDevice *device, int id) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + struct lg4ff_effect_state *state; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + return; + } + + state = &ctx->states[id]; + STOP_EFFECT(state); + state->allocated = false; + + SDL_UnlockMutex(ctx->mutex); +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus(SDL_HIDAPI_HapticDevice *device, int id) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + bool ret = false; + + SDL_LockMutex(ctx->mutex); + if (!lg4ff_effect_slot_valid_active(ctx, id)) { + SDL_UnlockMutex(ctx->mutex); + return false; + } + + if(test_bit(FF_EFFECT_STARTED, &ctx->states[id].flags)){ + ret = true; + } + SDL_UnlockMutex(ctx->mutex); + + return ret; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_SetGain(SDL_HIDAPI_HapticDevice *device, int gain) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + if (gain > 100) { + gain = 100; + } + if (gain < 0) { + gain = 0; + } + ctx->app_gain = (65535 * gain) / 100; + return true; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice *device, int autocenter) +{ + lg4ff_device *ctx = (lg4ff_device *)device->ctx; + /* + XXX + + Once again the Linux driver checks between ffex and dfex on the usb + stack, not sure how one can check for that on hid. + */ + Uint8 cmd[7] = {0}; + bool ret; + + if (autocenter < 0) { + autocenter = 0; + } + if (autocenter > 100) { + autocenter = 100; + } + + SDL_LockMutex(ctx->mutex); + #if 0 + if (is_ffex) { + int magnitude = (90 * autocenter) / 100; + + cmd[0] = 0xfe; + cmd[1] = 0x03; + cmd[2] = (uint16_t)magnitude >> 14; + cmd[3] = (uint16_t)magnitude >> 14; + cmd[4] = (uint16_t)magnitude; + cmd[5] = 0x00; + cmd[6] = 0x00; + + ret = SDL_hid_write(ctx->dev, cmd, sizeof(cmd)); + if(ret == -1){ + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter command"); + return -1; + } + }else + #endif + { + Uint32 expand_a; + Uint32 expand_b; + int magnitude = (65535 * autocenter) / 100; + + // first disable + cmd[0] = 0xf5; + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter disable command"); + return false; + } + + if (magnitude == 0) { + SDL_UnlockMutex(ctx->mutex); + return true; + } + + // set strength + if (magnitude <= 0xaaaa) { + expand_a = 0x0c * magnitude; + expand_b = 0x80 * magnitude; + } else { + expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa); + expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa); + } + expand_a = expand_a >> 1; + + SDL_memset(cmd, 0x00, 7); + cmd[0] = 0xfe; + cmd[1] = 0x0d; + cmd[2] = expand_a / 0xaaaa; + cmd[3] = expand_a / 0xaaaa; + cmd[4] = expand_b / 0xaaaa; + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter magnitude command"); + return false; + } + + // enable + SDL_memset(cmd, 0x00, 7); + cmd[0] = 0x14; + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if (!ret) { + SDL_UnlockMutex(ctx->mutex); + SDL_SetError("Failed sending autocenter enable command"); + return false; + } + } + SDL_UnlockMutex(ctx->mutex); + return true; +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_Pause(SDL_HIDAPI_HapticDevice *device) +{ + return SDL_Unsupported(); +} + +static bool SDL_HIDAPI_HapticDriverLg4ff_Resume(SDL_HIDAPI_HapticDevice *device) +{ + return SDL_Unsupported(); +} + +SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriverLg4ff = { + SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported, + SDL_HIDAPI_HapticDriverLg4ff_Open, + SDL_HIDAPI_HapticDriverLg4ff_Close, + SDL_HIDAPI_HapticDriverLg4ff_NumEffects, + SDL_HIDAPI_HapticDriverLg4ff_NumEffects, + SDL_HIDAPI_HapticDriverLg4ff_GetFeatures, + SDL_HIDAPI_HapticDriverLg4ff_NumAxes, + SDL_HIDAPI_HapticDriverLg4ff_CreateEffect, + SDL_HIDAPI_HapticDriverLg4ff_UpdateEffect, + SDL_HIDAPI_HapticDriverLg4ff_RunEffect, + SDL_HIDAPI_HapticDriverLg4ff_StopEffect, + SDL_HIDAPI_HapticDriverLg4ff_DestroyEffect, + SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus, + SDL_HIDAPI_HapticDriverLg4ff_SetGain, + SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter, + SDL_HIDAPI_HapticDriverLg4ff_Pause, + SDL_HIDAPI_HapticDriverLg4ff_Resume, + SDL_HIDAPI_HapticDriverLg4ff_StopEffects, +}; + +#endif //SDL_HAPTIC_HIDAPI_LG4FF +#endif //SDL_JOYSTICK_HIDAPI \ No newline at end of file diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index 0a5b404e923ba..a0e27e2ac371d 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -328,7 +328,7 @@ static bool HIDAPI_DriverLg4ff_InitDevice(SDL_HIDAPI_Device *device) { SDL_DriverLg4ff_Context *ctx; - ctx = (SDL_DriverLg4ff_Context *)SDL_calloc(1, sizeof(SDL_DriverLg4ff_Context)); + ctx = (SDL_DriverLg4ff_Context *)SDL_malloc(sizeof(SDL_DriverLg4ff_Context)); if (ctx == NULL) { SDL_OutOfMemory(); return false; From 74b48ab3780067d4b74284ecd721903afc9513a5 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Fri, 6 Dec 2024 17:43:51 +0100 Subject: [PATCH 03/26] Do not spawn SDL_Gamepad mapping on known hidapi devices If a SDL_Joystick internal lookup manages to define a joystick type that is not gamepad, it should stay as a joystick --- src/joystick/SDL_gamepad.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index 8486865bffc15..690028cb8fa50 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -2630,7 +2630,12 @@ bool SDL_IsGamepad(SDL_JoystickID instance_id) SDL_LockJoysticks(); { const void *value; - if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) { + SDL_JoystickType js_type = SDL_GetJoystickTypeForID(instance_id); + if (js_type != SDL_JOYSTICK_TYPE_GAMEPAD && js_type != SDL_JOYSTICK_TYPE_UNKNOWN) + { + // avoid creating HIDAPI mapping if type is SDL_Joystick knows it is not a game pad + result = false; + } else if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) { result = (bool)(uintptr_t)value; } else { if (SDL_PrivateGetGamepadMapping(instance_id, true) != NULL) { From 5cf5f6279f65d841e5b04a331936ce2aa575ea56 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Fri, 6 Dec 2024 20:01:19 +0100 Subject: [PATCH 04/26] rewind objectVersion in xcode project file --- Xcode/SDL/SDL.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index 0be01d0b58dcd..426e80d45357d 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 55; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ From 5c1cdf3c314b6de17efa03e4c17d338d7a440d33 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Fri, 6 Dec 2024 21:42:35 +0100 Subject: [PATCH 05/26] fix haiku building --- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index 52ce124735db0..2d5adf3649575 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -191,7 +191,7 @@ static Uint16 to_linux_direction(SDL_HapticDirection *src) } else if (!src->dir[0]) { return (Uint16) (src->dir[1] >= 0 ? 0x8000 : 0); } else { - float f = SDL_atan2(src->dir[1], src->dir[0]); /* Ideally we'd use fixed point math instead of floats... */ + float f = (float)SDL_atan2(src->dir[1], src->dir[0]); /* Ideally we'd use fixed point math instead of floats... */ /* SDL_atan2 takes the parameters: Y-axis-value and X-axis-value (in that order) - Y-axis-value is the second coordinate (from center to SOUTH) @@ -838,7 +838,7 @@ static void *SDL_HIDAPI_HapticDriverLg4ff_Open(SDL_Joystick *joystick) ctx->product_id = SDL_GetJoystickProduct(joystick); - sprintf(ctx->thread_name_buf, "SDL_hidapihaptic_lg4ff 0x%16llx %04x:%04x", (Uint64)joystick, USB_VENDOR_ID_LOGITECH, ctx->product_id); + sprintf(ctx->thread_name_buf, "SDL_hidapihaptic_lg4ff %d %04x:%04x", SDL_GetJoystickID(joystick), USB_VENDOR_ID_LOGITECH, ctx->product_id); ctx->stop_thread = false; ctx->thread = SDL_CreateThread(SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction, ctx->thread_name_buf, ctx); From f3a143a1ef87d0bbb2c49db45eb53295272d5a5c Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Fri, 6 Dec 2024 23:51:42 +0100 Subject: [PATCH 06/26] fix build on non-hidapi platforms --- src/haptic/SDL_haptic.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/haptic/SDL_haptic.c b/src/haptic/SDL_haptic.c index 20dd5c07592ac..2bee9a55e03b3 100644 --- a/src/haptic/SDL_haptic.c +++ b/src/haptic/SDL_haptic.c @@ -464,7 +464,9 @@ void SDL_QuitHaptics(void) SDL_CloseHaptic(SDL_haptics); } + #ifdef SDL_JOYSTICK_HIDAPI SDL_HIDAPI_HapticQuit(); + #endif //SDL_JOYSTICK_HIDAPI SDL_SYS_HapticQuit(); } From 145ea0a1bab77111d70079e99b81e80c180ac332 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Fri, 6 Dec 2024 23:54:27 +0100 Subject: [PATCH 07/26] fix build on platforms without MPI --- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index 2d5adf3649575..9ac58eb9ea2f3 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -131,7 +131,7 @@ Uint64 get_time_ms(){ #define test_bit(bit, field) (*(field) & (1 << bit)) #define __set_bit(bit, field) {*(field) = *(field) | (1 << bit);} #define __clear_bit(bit, field) {*(field) = *(field) & ~(1 << bit);} -#define sin_deg(in) (double)(sin((double)(in) * (double)M_PI / 180.0)) +#define sin_deg(in) (double)(sin((double)(in) * SDL_PI_D / 180.0)) #define time_after_eq(a, b) (a >= b) #define time_before(a, b) (a < b) @@ -202,7 +202,7 @@ static Uint16 to_linux_direction(SDL_HapticDirection *src) --> add 45000 in total --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR. */ - tmp = (((Sint32) (f * 18000. / M_PI)) + 45000) % 36000; + tmp = (((Sint32) (f * 18000. / SDL_PI_D)) + 45000) % 36000; tmp = (tmp * 0x8000) / 18000; /* convert to range [0,0xFFFF] */ return (Uint16)tmp; } From 1ffb6225d47d9897875eef60e191ed7ec6c3ce5e Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Fri, 6 Dec 2024 23:58:10 +0100 Subject: [PATCH 08/26] fix build on android --- Android.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/Android.mk b/Android.mk index b5fe8955f9a1c..3ecc631d100d7 100644 --- a/Android.mk +++ b/Android.mk @@ -42,6 +42,7 @@ LOCAL_SRC_FILES := \ $(wildcard $(LOCAL_PATH)/src/haptic/*.c) \ $(wildcard $(LOCAL_PATH)/src/haptic/android/*.c) \ $(wildcard $(LOCAL_PATH)/src/haptic/dummy/*.c) \ + $(wildcard $(LOCAL_PATH)/src/haptic/hidapi/*.c) \ $(wildcard $(LOCAL_PATH)/src/hidapi/*.c) \ $(wildcard $(LOCAL_PATH)/src/hidapi/android/*.cpp) \ $(wildcard $(LOCAL_PATH)/src/joystick/*.c) \ From 31556bb76691b949beab929ce816afab23746369 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Sat, 7 Dec 2024 08:57:34 +0100 Subject: [PATCH 09/26] fix cmake msvc build --- src/haptic/SDL_haptic.c | 3 ++- src/haptic/hidapi/SDL_hidapihaptic.c | 2 +- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 16 ++++++++-------- src/joystick/hidapi/SDL_hidapi_lg4ff.c | 4 ++-- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/haptic/SDL_haptic.c b/src/haptic/SDL_haptic.c index 2bee9a55e03b3..38c6edef2c383 100644 --- a/src/haptic/SDL_haptic.c +++ b/src/haptic/SDL_haptic.c @@ -646,7 +646,8 @@ void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect) #ifdef SDL_JOYSTICK_HIDAPI if (SDL_HIDAPI_HapticIsHidapi(haptic)) { - return SDL_HIDAPI_HapticDestroyEffect(haptic, effect); + SDL_HIDAPI_HapticDestroyEffect(haptic, effect); + return; } #endif //SDL_JOYSTICK_HIDAPI diff --git a/src/haptic/hidapi/SDL_hidapihaptic.c b/src/haptic/hidapi/SDL_hidapihaptic.c index 819e1605f1d32..2ff4572d3d5d0 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic.c +++ b/src/haptic/hidapi/SDL_hidapihaptic.c @@ -259,7 +259,7 @@ bool SDL_HIDAPI_HapticStopEffect(SDL_Haptic *haptic, int id) void SDL_HIDAPI_HapticDestroyEffect(SDL_Haptic *haptic, int id) { SDL_HIDAPI_HapticDevice *device = (SDL_HIDAPI_HapticDevice *)haptic->hwdata; - return device->driver->DestroyEffect(device, id); + device->driver->DestroyEffect(device, id); } bool SDL_HIDAPI_HapticGetEffectStatus(SDL_Haptic *haptic, int id) diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index 9ac58eb9ea2f3..8758e67ac3d00 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -405,9 +405,9 @@ static Sint32 lg4ff_calculate_constant(struct lg4ff_effect_state *state) if (state->time_playing < constant->attack_length) { level_sign = level < 0 ? -1 : 1; d = level - level_sign * constant->attack_level; - level = level_sign * constant->attack_level + d * state->time_playing / constant->attack_length; + level = (Sint32) (level_sign * constant->attack_level + d * state->time_playing / constant->attack_length); } else if (constant->length && constant->fade_length) { - t = state->time_playing - constant->length + constant->fade_length; + t = (Sint32) (state->time_playing - constant->length + constant->fade_length); if (t > 0) { level_sign = level < 0 ? -1 : 1; d = level - level_sign * constant->fade_level; @@ -428,17 +428,17 @@ static Sint32 lg4ff_calculate_ramp(struct lg4ff_effect_state *state) if (state->time_playing < ramp->attack_length) { level = ramp->start; level_sign = level < 0 ? -1 : 1; - t = ramp->attack_length - state->time_playing; + t = (Sint32) (ramp->attack_length - state->time_playing); d = level - level_sign * ramp->attack_level; level = level_sign * ramp->attack_level + d * t / ramp->attack_length; } else if (ramp->length && state->time_playing >= ramp->length - ramp->fade_length && ramp->fade_length) { level = ramp->end; level_sign = level < 0 ? -1 : 1; - t = state->time_playing - ramp->length + ramp->fade_length; + t = (Sint32) (state->time_playing - ramp->length + ramp->fade_length); d = level_sign * ramp->fade_level - level; level = level - d * t / ramp->fade_length; } else { - t = state->time_playing - ramp->attack_length; + t = (Sint32) (state->time_playing - ramp->attack_length); level = ramp->start + ((t * state->slope) >> 16); } @@ -455,9 +455,9 @@ static Sint32 lg4ff_calculate_periodic(struct lg4ff_effect_state *state) if (state->time_playing < periodic->attack_length) { d = magnitude - magnitude_sign * periodic->attack_level; - magnitude = magnitude_sign * periodic->attack_level + d * state->time_playing / periodic->attack_length; + magnitude = (Sint32) (magnitude_sign * periodic->attack_level + d * state->time_playing / periodic->attack_length); } else if (periodic->length && periodic->fade_length) { - t = state->time_playing - get_effect_replay_length(&state->effect) + periodic->fade_length; + t = (Sint32) (state->time_playing - get_effect_replay_length(&state->effect) + periodic->fade_length); if (t > 0) { d = magnitude - magnitude_sign * periodic->fade_level; magnitude = magnitude - d * t / periodic->fade_length; @@ -472,7 +472,7 @@ static Sint32 lg4ff_calculate_periodic(struct lg4ff_effect_state *state) level += (state->phase < 180 ? 1 : -1) * magnitude; break; case SDL_HAPTIC_TRIANGLE: - level += llabs((Sint64)state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude; + level += (Sint32) (llabs((Sint64)state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude); break; case SDL_HAPTIC_SAWTOOTHUP: level += state->phase * magnitude * 2 / 360 - magnitude; diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index a0e27e2ac371d..56b0908c74534 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -361,12 +361,12 @@ static void HIDAPI_DriverLg4ff_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, S } -static bool HIDAPI_DriverLg4ff_GetBit(const Uint8 *buf, int bit_num, int buf_len) +static bool HIDAPI_DriverLg4ff_GetBit(const Uint8 *buf, int bit_num, size_t buf_len) { int byte_offset = bit_num / 8; int local_bit = bit_num % 8; Uint8 mask = 1 << local_bit; - if (byte_offset >= buf_len) { + if ((size_t)byte_offset >= buf_len) { SDL_assert(0); } return (buf[byte_offset] & mask) ? true : false; From 641774201b7faa049c637bf2047ef03ee55b7307 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Sat, 7 Dec 2024 09:30:47 +0100 Subject: [PATCH 10/26] fix SDL_OpenHaptic and SDL_OpenHapticFromJoystick's SDL_SetObjectValid, SDL_SetHapticGain and SDL_SetHapticAutocenter sequence --- src/haptic/SDL_haptic.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/haptic/SDL_haptic.c b/src/haptic/SDL_haptic.c index 38c6edef2c383..6037c1e405415 100644 --- a/src/haptic/SDL_haptic.c +++ b/src/haptic/SDL_haptic.c @@ -239,6 +239,8 @@ SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id) haptic->next = SDL_haptics; SDL_haptics = haptic; + SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); + // Disable autocenter and set gain to max. if (haptic->supported & SDL_HAPTIC_GAIN) { SDL_SetHapticGain(haptic, 100); @@ -247,7 +249,6 @@ SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id) SDL_SetHapticAutocenter(haptic, 0); } - SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); return haptic; } @@ -403,6 +404,15 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) SDL_haptics = haptic; SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true); + + // Disable autocenter and set gain to max. + if (haptic->supported & SDL_HAPTIC_GAIN) { + SDL_SetHapticGain(haptic, 100); + } + if (haptic->supported & SDL_HAPTIC_AUTOCENTER) { + SDL_SetHapticAutocenter(haptic, 0); + } + return haptic; } From 4cf000b63f59debd767805be62fde07fa8b02be5 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Sat, 7 Dec 2024 10:27:55 +0100 Subject: [PATCH 11/26] hand fixing VC project files, more build fixes --- VisualC-GDK/SDL/SDL.vcxproj | 5 +++++ VisualC-GDK/SDL/SDL.vcxproj.filters | 5 +++++ VisualC/SDL/SDL.vcxproj | 5 +++++ VisualC/SDL/SDL.vcxproj.filters | 18 ++++++++++++++++++ src/haptic/hidapi/SDL_hidapihaptic.c | 2 +- src/haptic/hidapi/SDL_hidapihaptic_c.h | 2 -- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 4 ++-- src/joystick/hidapi/SDL_hidapi_lg4ff.c | 2 +- 8 files changed, 37 insertions(+), 6 deletions(-) diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 5bf000436fb93..52fe361a0a086 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -454,8 +454,10 @@ + + @@ -703,6 +705,8 @@ stdcpp17 stdcpp17 + + @@ -725,6 +729,7 @@ + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index f760877e214e9..bbbf658450138 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -56,6 +56,8 @@ + + @@ -78,6 +80,7 @@ + @@ -343,8 +346,10 @@ + + diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index a15978a29cd71..7d4c3a201b275 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -367,8 +367,10 @@ + + @@ -573,6 +575,8 @@ + + @@ -595,6 +599,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index d653ee05f1d3d..614eafc2beee1 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -82,6 +82,9 @@ {ebc2fca3-3c26-45e3-815e-3e0581d5e226} + + {06DB01C0-65B5-4DE7-8ADC-C0B0CA3A1E69} + {47c445a2-7014-4e15-9660-7c89a27dddcf} @@ -564,6 +567,9 @@ haptic + + haptic + haptic @@ -621,6 +627,9 @@ haptic\windows + + haptic\hidapi + joystick\hidapi @@ -1163,6 +1172,12 @@ haptic\windows + + haptic\hidapi + + + haptic\hidapi + haptic\dummy @@ -1223,6 +1238,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi diff --git a/src/haptic/hidapi/SDL_hidapihaptic.c b/src/haptic/hidapi/SDL_hidapihaptic.c index 2ff4572d3d5d0..5b1ce6fb31f56 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic.c +++ b/src/haptic/hidapi/SDL_hidapihaptic.c @@ -18,7 +18,7 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ -#include "../../SDL_internal.h" +#include "SDL_internal.h" #ifdef SDL_JOYSTICK_HIDAPI diff --git a/src/haptic/hidapi/SDL_hidapihaptic_c.h b/src/haptic/hidapi/SDL_hidapihaptic_c.h index 9159cc02f3c75..04270c4aa512e 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_c.h +++ b/src/haptic/hidapi/SDL_hidapihaptic_c.h @@ -19,8 +19,6 @@ 3. This notice may not be removed or altered from any source distribution. */ -#include "../../SDL_internal.h" - #ifndef SDL_hidapihaptic_c_h_ #define SDL_hidapihaptic_c_h_ diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index 8758e67ac3d00..b617172fce16a 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -23,7 +23,7 @@ All hid command sent and effect rendering are ported from https://github.com/berarma/new-lg4ff */ -#include "../../SDL_internal.h" +#include "SDL_internal.h" #ifdef SDL_JOYSTICK_HIDAPI @@ -838,7 +838,7 @@ static void *SDL_HIDAPI_HapticDriverLg4ff_Open(SDL_Joystick *joystick) ctx->product_id = SDL_GetJoystickProduct(joystick); - sprintf(ctx->thread_name_buf, "SDL_hidapihaptic_lg4ff %d %04x:%04x", SDL_GetJoystickID(joystick), USB_VENDOR_ID_LOGITECH, ctx->product_id); + SDL_snprintf(ctx->thread_name_buf, sizeof(ctx->thread_name_buf), "SDL_hidapihaptic_lg4ff %d %04x:%04x", SDL_GetJoystickID(joystick), USB_VENDOR_ID_LOGITECH, ctx->product_id); ctx->stop_thread = false; ctx->thread = SDL_CreateThread(SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction, ctx->thread_name_buf, ctx); diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index 56b0908c74534..665d289c23fe0 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -23,7 +23,7 @@ All hid command sent are based on https://github.com/berarma/new-lg4ff */ -#include "../../SDL_internal.h" +#include "SDL_internal.h" #ifdef SDL_JOYSTICK_HIDAPI From 928b822bcddb0b2b9c964acb7644348aad14ccfd Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Mon, 23 Dec 2024 18:56:29 +0100 Subject: [PATCH 12/26] cleanup, label ported code on a per function basis --- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 181 ++++++++++++++------- src/joystick/hidapi/SDL_hidapi_lg4ff.c | 82 ++++++---- 2 files changed, 177 insertions(+), 86 deletions(-) diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index b617172fce16a..eed0a4f3d0057 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -19,10 +19,6 @@ 3. This notice may not be removed or altered from any source distribution. */ -/* - All hid command sent and effect rendering are ported from https://github.com/berarma/new-lg4ff -*/ - #include "SDL_internal.h" #ifdef SDL_JOYSTICK_HIDAPI @@ -289,6 +285,12 @@ static Uint16 get_effect_replay_delay(SDL_HapticEffect *effect) return delay; } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static int lg4ff_play_effect(struct lg4ff_device *device, int effect_id, int value) { struct lg4ff_effect_state *state; @@ -315,6 +317,12 @@ static int lg4ff_play_effect(struct lg4ff_device *device, int effect_id, int val return 0; } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static int lg4ff_upload_effect(struct lg4ff_device *device, const SDL_HapticEffect *effect, int id) { struct lg4ff_effect_state *state; @@ -340,6 +348,12 @@ static int lg4ff_upload_effect(struct lg4ff_device *device, const SDL_HapticEffe return 0; } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static void lg4ff_update_state(struct lg4ff_effect_state *state, const Uint64 now) { SDL_HapticEffect *effect = &state->effect; @@ -395,6 +409,12 @@ static void lg4ff_update_state(struct lg4ff_effect_state *state, const Uint64 no } } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static Sint32 lg4ff_calculate_constant(struct lg4ff_effect_state *state) { SDL_HapticConstant *constant = (SDL_HapticConstant *)&state->effect; @@ -418,6 +438,12 @@ static Sint32 lg4ff_calculate_constant(struct lg4ff_effect_state *state) return (Sint32)(state->direction_gain * level); } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static Sint32 lg4ff_calculate_ramp(struct lg4ff_effect_state *state) { SDL_HapticRamp *ramp = (SDL_HapticRamp *)&state->effect; @@ -445,6 +471,12 @@ static Sint32 lg4ff_calculate_ramp(struct lg4ff_effect_state *state) return (Sint32)(state->direction_gain * level); } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static Sint32 lg4ff_calculate_periodic(struct lg4ff_effect_state *state) { SDL_HapticPeriodic *periodic = (SDL_HapticPeriodic *)&state->effect; @@ -487,6 +519,12 @@ static Sint32 lg4ff_calculate_periodic(struct lg4ff_effect_state *state) return (Sint32)(state->direction_gain * level); } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static void lg4ff_calculate_spring(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) { SDL_HapticCondition *condition = (SDL_HapticCondition *)&state->effect; @@ -498,6 +536,12 @@ static void lg4ff_calculate_spring(struct lg4ff_effect_state *state, struct lg4f parameters->clip = (Uint16)condition->right_sat[0]; } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static void lg4ff_calculate_resistance(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters) { SDL_HapticCondition *condition = (SDL_HapticCondition *)&state->effect; @@ -507,6 +551,12 @@ static void lg4ff_calculate_resistance(struct lg4ff_effect_state *state, struct parameters->clip = (Uint16)condition->right_sat[0]; } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_parameters *parameters) { Uint8 original_cmd[7]; @@ -551,58 +601,58 @@ static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_param } else { switch (slot->effect_type) { case SDL_HAPTIC_CONSTANT: - slot->current_cmd[1] = 0x00; - slot->current_cmd[2] = 0; - slot->current_cmd[3] = 0; - slot->current_cmd[4] = 0; - slot->current_cmd[5] = 0; - slot->current_cmd[6] = 0; - slot->current_cmd[2 + slot->id] = TRANSLATE_FORCE(parameters->level); - break; + slot->current_cmd[1] = 0x00; + slot->current_cmd[2] = 0; + slot->current_cmd[3] = 0; + slot->current_cmd[4] = 0; + slot->current_cmd[5] = 0; + slot->current_cmd[6] = 0; + slot->current_cmd[2 + slot->id] = TRANSLATE_FORCE(parameters->level); + break; case SDL_HAPTIC_SPRING: - d1 = SCALE_VALUE_U16(((parameters->d1) + 0x8000) & 0xffff, 11); - d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11); - s1 = parameters->k1 < 0; - s2 = parameters->k2 < 0; - k1 = abs(parameters->k1); - k2 = abs(parameters->k2); - if (k1 < 2048) { - d1 = 0; - } else { - k1 -= 2048; - } - if (k2 < 2048) { - d2 = 2047; - } else { - k2 -= 2048; - } - slot->current_cmd[1] = 0x0b; - slot->current_cmd[2] = d1 >> 3; - slot->current_cmd[3] = d2 >> 3; - slot->current_cmd[4] = (SCALE_COEFF(k2, 4) << 4) + SCALE_COEFF(k1, 4); - slot->current_cmd[5] = ((d2 & 7) << 5) + ((d1 & 7) << 1) + (s2 << 4) + s1; - slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); - break; + d1 = SCALE_VALUE_U16(((parameters->d1) + 0x8000) & 0xffff, 11); + d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11); + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + k1 = abs(parameters->k1); + k2 = abs(parameters->k2); + if (k1 < 2048) { + d1 = 0; + } else { + k1 -= 2048; + } + if (k2 < 2048) { + d2 = 2047; + } else { + k2 -= 2048; + } + slot->current_cmd[1] = 0x0b; + slot->current_cmd[2] = d1 >> 3; + slot->current_cmd[3] = d2 >> 3; + slot->current_cmd[4] = (SCALE_COEFF(k2, 4) << 4) + SCALE_COEFF(k1, 4); + slot->current_cmd[5] = ((d2 & 7) << 5) + ((d1 & 7) << 1) + (s2 << 4) + s1; + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; case SDL_HAPTIC_DAMPER: - s1 = parameters->k1 < 0; - s2 = parameters->k2 < 0; - slot->current_cmd[1] = 0x0c; - slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 4); - slot->current_cmd[3] = s1; - slot->current_cmd[4] = SCALE_COEFF(parameters->k2, 4); - slot->current_cmd[5] = s2; - slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); - break; + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0c; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 4); + slot->current_cmd[3] = s1; + slot->current_cmd[4] = SCALE_COEFF(parameters->k2, 4); + slot->current_cmd[5] = s2; + slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); + break; case SDL_HAPTIC_FRICTION: - s1 = parameters->k1 < 0; - s2 = parameters->k2 < 0; - slot->current_cmd[1] = 0x0e; - slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 8); - slot->current_cmd[3] = SCALE_COEFF(parameters->k2, 8); - slot->current_cmd[4] = SCALE_VALUE_U16(parameters->clip, 8); - slot->current_cmd[5] = (s2 << 4) + s1; - slot->current_cmd[6] = 0; - break; + s1 = parameters->k1 < 0; + s2 = parameters->k2 < 0; + slot->current_cmd[1] = 0x0e; + slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 8); + slot->current_cmd[3] = SCALE_COEFF(parameters->k2, 8); + slot->current_cmd[4] = SCALE_VALUE_U16(parameters->clip, 8); + slot->current_cmd[5] = (s2 << 4) + s1; + slot->current_cmd[6] = 0; + break; } } @@ -611,6 +661,12 @@ static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_param } } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static int lg4ff_init_slots(struct lg4ff_device *device) { struct lg4ff_effect_parameters parameters; @@ -649,6 +705,12 @@ static int lg4ff_init_slots(struct lg4ff_device *device) return 0; } +/* + *Ported* + Original function by: + Bernat Arlandis + `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git +*/ static int lg4ff_timer(struct lg4ff_device *device) { struct lg4ff_slot *slot; @@ -1045,14 +1107,21 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_SetGain(SDL_HIDAPI_HapticDevice *device return true; } +/* + *Ported* + Original functions by: + Simon Wood + Michal Malý + lg4ff_set_autocenter_default lg4ff_set_autocenter_ffex + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice *device, int autocenter) { lg4ff_device *ctx = (lg4ff_device *)device->ctx; /* - XXX + TODO - Once again the Linux driver checks between ffex and dfex on the usb - stack, not sure how one can check for that on hid. + Check joystick instance to see if device is ffex or not */ Uint8 cmd[7] = {0}; bool ret; diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index 665d289c23fe0..6176025ec2500 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -19,10 +19,6 @@ 3. This notice may not be removed or altered from any source distribution. */ -/* - All hid command sent are based on https://github.com/berarma/new-lg4ff -*/ - #include "SDL_internal.h" #ifdef SDL_JOYSTICK_HIDAPI @@ -131,13 +127,14 @@ static bool HIDAPI_DriverLg4ff_IsSupportedDevice( /* TODO - Is it possible to identify native mode from hid? On the Linux kernel - driver that is done by checking with the usb stack, more specifically - bcdDevice on the usb descriptor + Review hidapi code to see if SDL_hid_device_info.release_number is reliable - If a way is found to probe for native mode on the HID layer, this function - should trigger mode switch, then return false, so the next probe cycle - would take the device on a supported mode + Use SDL_hid_get_device_info(device) to grab SDL_hid_device_info.release_number, + to find the native device model + + When a native device model is found and the device is not in it's native mode, + trigger mode switch, then return false, so the next probe cycle would take + the device in the new mode */ if (vendor_id != USB_VENDOR_ID_LOGITECH) { return false; @@ -150,6 +147,13 @@ static bool HIDAPI_DriverLg4ff_IsSupportedDevice( return false; } +/* + *Ported* + Original functions by: + Michal Malý + lg4ff_set_range_g25 lg4ff_set_range_dfp + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) { Uint8 cmd[7] = {0}; @@ -238,33 +242,43 @@ static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) return true; } -static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int gain) +/* + *Ported* + Original functions by: + Simon Wood + Michal Malý + lg4ff_set_autocenter_default lg4ff_set_autocenter_ffex + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int magnitude) { /* - XXX + TODO - Once again the Linux driver checks between ffex and dfex on the usb - stack, not sure how one can check for that on hid. + Review hidapi code to see if SDL_hid_device_info.release_number is reliable + + Use SDL_hid_get_device_info(device) to grab SDL_hid_device_info.release_number, + to tell between ffex and dfex */ Uint8 cmd[7] = {0}; int ret; - if (gain < 0) { - gain = 0; + if (magnitude < 0) { + magnitude = 0; } - if (gain > 65535) { - gain = 65535; + if (magnitude > 65535) { + magnitude = 65535; } #if 0 if (is_ffex) { - gain = gain * 90 / 65535; + magnitude = magnitude * 90 / 65535; cmd[0] = 0xfe; cmd[1] = 0x03; - cmd[2] = (uint16_t)gain >> 14; - cmd[3] = (uint16_t)gain >> 14; - cmd[4] = (uint16_t)gain; + cmd[2] = (uint16_t)magnitude >> 14; + cmd[3] = (uint16_t)magnitude >> 14; + cmd[4] = (uint16_t)magnitude; cmd[5] = 0x00; cmd[6] = 0x00; @@ -285,22 +299,23 @@ static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int gain return false; } - if (gain == 0) { + if (magnitude == 0) { return true; } // set strength - if (gain <= 0xaaaa) { - expand_a = 0x0c * gain; - expand_b = 0x80 * gain; + if (magnitude <= 0xaaaa) { + expand_a = 0x0c * magnitude; + expand_b = 0x80 * magnitude; } else { - expand_a = (0x0c * 0xaaaa) + 0x06 * (gain - 0xaaaa); - expand_b = (0x80 * 0xaaaa) + 0xff * (gain - 0xaaaa); + expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa); + expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa); } + // TODO do not adjust for MOMO wheels, when support is added expand_a = expand_a >> 1; - SDL_memset(cmd, 0x00, 7); + SDL_memset(cmd, 0x00, sizeof(cmd)); cmd[0] = 0xfe; cmd[1] = 0x0d; cmd[2] = expand_a / 0xaaaa; @@ -313,7 +328,7 @@ static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int gain } // enable - SDL_memset(cmd, 0x00, 7); + SDL_memset(cmd, 0x00, sizeof(cmd)); cmd[0] = 0x14; ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); @@ -695,6 +710,13 @@ static Uint32 HIDAPI_DriverLg4ff_GetJoystickCapabilities(SDL_HIDAPI_Device *devi return 0; } +/* + Commands by: + Michal Malý + Simon Wood + lg4ff_led_set_brightness lg4ff_set_leds + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ static bool HIDAPI_DriverLg4ff_SendLedCommand(SDL_HIDAPI_Device *device, Uint8 state) { Uint8 cmd[7]; From 2f71e957c1afe1fbd365f43ef395fb2b6fa6be93 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Wed, 25 Dec 2024 18:27:50 +0100 Subject: [PATCH 13/26] report led cap --- src/joystick/hidapi/SDL_hidapi_lg4ff.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index 6176025ec2500..5bcbb9b0f1d9d 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -707,7 +707,13 @@ static bool HIDAPI_DriverLg4ff_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, static Uint32 HIDAPI_DriverLg4ff_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) { - return 0; + switch(device->product_id) { + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + return SDL_JOYSTICK_CAP_MONO_LED; + default: + return 0; + } } /* From b63612d5af5a539a3d21f2e96f2499373a519f7a Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Wed, 25 Dec 2024 19:09:20 +0100 Subject: [PATCH 14/26] native mode switch --- src/joystick/hidapi/SDL_hidapi_lg4ff.c | 179 ++++++++++++++++++++----- 1 file changed, 147 insertions(+), 32 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index 5bcbb9b0f1d9d..6d3b77d576bbc 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -112,6 +112,137 @@ static bool HIDAPI_DriverLg4ff_IsEnabled(void) return enabled; } +/* + Wheel id information by: + Michal Malý + Simon Wood + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static Uint16 HIDAPI_DriverLg4ff_IdentifyWheel(Uint16 device_id, Uint16 release_number) +{ + #define is_device(ret, m, r) { \ + if ((release_number & m) == r) { \ + return ret; \ + } \ + } + #define is_dfp { \ + is_device(USB_DEVICE_ID_LOGITECH_DFP_WHEEL, 0xf000, 0x1000); \ + } + #define is_dfgt { \ + is_device(USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, 0xff00, 0x1300); \ + } + #define is_g25 { \ + is_device(USB_DEVICE_ID_LOGITECH_G25_WHEEL, 0xff00, 0x1200); \ + } + #define is_g27 { \ + is_device(USB_DEVICE_ID_LOGITECH_G27_WHEEL, 0xfff0, 0x1230); \ + } + #define is_g29 { \ + is_device(USB_DEVICE_ID_LOGITECH_G29_WHEEL, 0xfff8, 0x1350); \ + is_device(USB_DEVICE_ID_LOGITECH_G29_WHEEL, 0xff00, 0x8900); \ + } + switch(device_id){ + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + case USB_DEVICE_ID_LOGITECH_WHEEL: + is_g29; + is_g27; + is_g25; + is_dfgt; + is_dfp; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + is_g29; + is_dfgt; + break; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + is_g29; + is_g27; + is_g25; + break; + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + is_g29; + is_g27; + break; + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + is_g29; + break; + } + return 0; + #undef is_device + #undef is_dfp + #undef is_dfgt + #undef is_g25 + #undef is_g27 + #undef is_g29 +} + +static int SDL_HIDAPI_DriverLg4ff_GetEnvInt(const char *env_name, int min, int max, int def) +{ + const char *env = SDL_getenv(env_name); + int value = 0; + if(env == NULL) { + return def; + } + value = SDL_atoi(env); + if (value < min) { + value = min; + } + if (value > max) { + value = max; + } + return value; +} + +/* + Commands by: + Michal Malý + Simon Wood + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static bool HIDAPI_DriverLg4ff_SwitchMode(SDL_HIDAPI_Device *device, Uint16 target_product_id){ + int ret = 0; + + switch(target_product_id){ + case USB_DEVICE_ID_LOGITECH_G29_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x05, 0x01, 0x01, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_G27_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_G25_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + case USB_DEVICE_ID_LOGITECH_WHEEL:{ + Uint8 cmd[] = {0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00}; + ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); + break; + } + default:{ + SDL_assert(0); + } + } + if(ret == -1){ + return false; + } + return true; +} + static bool HIDAPI_DriverLg4ff_IsSupportedDevice( SDL_HIDAPI_Device *device, const char *name, @@ -124,27 +255,28 @@ static bool HIDAPI_DriverLg4ff_IsSupportedDevice( int interface_subclass, int interface_protocol) { - /* - TODO - - Review hidapi code to see if SDL_hid_device_info.release_number is reliable - - Use SDL_hid_get_device_info(device) to grab SDL_hid_device_info.release_number, - to find the native device model - - When a native device model is found and the device is not in it's native mode, - trigger mode switch, then return false, so the next probe cycle would take - the device in the new mode - */ + int i; if (vendor_id != USB_VENDOR_ID_LOGITECH) { return false; } - for (int i = 0;i < sizeof(supported_device_ids) / sizeof(Uint32);i++) { + for (i = 0;i < sizeof(supported_device_ids) / sizeof(Uint32);i++) { if (supported_device_ids[i] == product_id) { - return true; + break; } } - return false; + if (i == sizeof(supported_device_ids) / sizeof(Uint32)) { + return false; + } + Uint16 real_id = HIDAPI_DriverLg4ff_IdentifyWheel(product_id, version); + if (real_id == product_id || real_id == 0) { + // either it is already in native mode, or we don't know what the native mode is + return true; + } + // a supported native mode is found, send mode change command, then still state that we support the device + if (device != NULL && SDL_HIDAPI_DriverLg4ff_GetEnvInt("SDL_HIDAPI_LG4FF_NO_MODE_SWITCH", 0, 1, 0) == 0) { + HIDAPI_DriverLg4ff_SwitchMode(device, real_id); + } + return true; } /* @@ -595,23 +727,6 @@ static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, return state_changed; } -static int SDL_HIDAPI_DriverLg4ff_GetEnvInt(const char *env_name, int min, int max, int def) -{ - const char *env = SDL_getenv(env_name); - int value = 0; - if(env == NULL) { - return def; - } - value = SDL_atoi(env); - if (value < min) { - value = min; - } - if (value > max) { - value = max; - } - return value; -} - static bool HIDAPI_DriverLg4ff_UpdateDevice(SDL_HIDAPI_Device *device) { SDL_Joystick *joystick = NULL; From 0ee5cba3a61b2695f2a5fca9f8fdf01bb1467770 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Thu, 26 Dec 2024 11:50:23 +0100 Subject: [PATCH 15/26] do not touch SDL_Joystick ref_count from SDL_hidapihaptic.c --- src/haptic/hidapi/SDL_hidapihaptic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haptic/hidapi/SDL_hidapihaptic.c b/src/haptic/hidapi/SDL_hidapihaptic.c index 5b1ce6fb31f56..9340fc79ade17 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic.c +++ b/src/haptic/hidapi/SDL_hidapihaptic.c @@ -149,7 +149,7 @@ bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystic list_node->next = NULL; // grab a joystick ref so that it doesn't get fully destroyed before the haptic is closed - joystick->ref_count++; + SDL_OpenJoystick(SDL_GetJoystickID(joystick)); SDL_LockMutex(haptic_list_mutex); if (haptic_list_head == NULL) { From 87c25074358482efffdcdc88e9387a7df5f989c2 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Fri, 27 Dec 2024 11:42:04 +0100 Subject: [PATCH 16/26] send ffex autocenter command when identified --- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 48 +++++++++++++--------- src/joystick/hidapi/SDL_hidapi_lg4ff.c | 40 ++++++++++-------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index eed0a4f3d0057..ce8f6267bdb09 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -97,6 +97,7 @@ struct lg4ff_slot{ typedef struct lg4ff_device{ Uint16 product_id; + Uint16 release_number; struct lg4ff_effect_state states[LG4FF_MAX_EFFECTS]; struct lg4ff_slot slots[4]; Sint32 effects_used; @@ -117,7 +118,8 @@ typedef struct lg4ff_device{ char thread_name_buf[256]; SDL_Mutex *mutex; - int cmd_errors; + + bool is_ffex; } lg4ff_device; Uint64 get_time_ms(){ @@ -864,6 +866,13 @@ static int SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt(const char *env_name, int min, return value; } +/* + ffex identification method by: + Simon Wood + Michal Malý + lg4ff_init + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ static void *SDL_HIDAPI_HapticDriverLg4ff_Open(SDL_Joystick *joystick) { lg4ff_device *ctx; @@ -899,11 +908,20 @@ static void *SDL_HIDAPI_HapticDriverLg4ff_Open(SDL_Joystick *joystick) ctx->app_gain = 65535; ctx->product_id = SDL_GetJoystickProduct(joystick); + ctx->release_number = SDL_GetJoystickProductVersion(joystick); SDL_snprintf(ctx->thread_name_buf, sizeof(ctx->thread_name_buf), "SDL_hidapihaptic_lg4ff %d %04x:%04x", SDL_GetJoystickID(joystick), USB_VENDOR_ID_LOGITECH, ctx->product_id); ctx->stop_thread = false; ctx->thread = SDL_CreateThread(SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction, ctx->thread_name_buf, ctx); + if (ctx->product_id == USB_DEVICE_ID_LOGITECH_WHEEL && + (ctx->release_number >> 8) == 0x21 && + (ctx->release_number & 0xff) == 0x00) { + ctx->is_ffex = true; + } else { + ctx->is_ffex = false; + } + return ctx; } @@ -1118,11 +1136,6 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_SetGain(SDL_HIDAPI_HapticDevice *device static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice *device, int autocenter) { lg4ff_device *ctx = (lg4ff_device *)device->ctx; - /* - TODO - - Check joystick instance to see if device is ffex or not - */ Uint8 cmd[7] = {0}; bool ret; @@ -1134,27 +1147,22 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice * } SDL_LockMutex(ctx->mutex); - #if 0 - if (is_ffex) { + if (ctx->is_ffex) { int magnitude = (90 * autocenter) / 100; cmd[0] = 0xfe; cmd[1] = 0x03; - cmd[2] = (uint16_t)magnitude >> 14; - cmd[3] = (uint16_t)magnitude >> 14; - cmd[4] = (uint16_t)magnitude; - cmd[5] = 0x00; - cmd[6] = 0x00; - - ret = SDL_hid_write(ctx->dev, cmd, sizeof(cmd)); - if(ret == -1){ + cmd[2] = (Uint8)((Uint16)magnitude >> 14); + cmd[3] = (Uint8)((Uint16)magnitude >> 14); + cmd[4] = (Uint8)magnitude; + + ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); + if(!ret){ SDL_UnlockMutex(ctx->mutex); SDL_SetError("Failed sending autocenter command"); - return -1; + return false; } - }else - #endif - { + } else { Uint32 expand_a; Uint32 expand_b; int magnitude = (65535 * autocenter) / 100; diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index 6d3b77d576bbc..d393ef8800052 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -92,6 +92,7 @@ typedef struct { Uint8 last_report_buf[32]; bool initialized; + bool is_ffex; } SDL_DriverLg4ff_Context; static void HIDAPI_DriverLg4ff_RegisterHints(SDL_HintCallback callback, void *userdata) @@ -384,14 +385,7 @@ static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) */ static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int magnitude) { - /* - TODO - - Review hidapi code to see if SDL_hid_device_info.release_number is reliable - - Use SDL_hid_get_device_info(device) to grab SDL_hid_device_info.release_number, - to tell between ffex and dfex - */ + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; Uint8 cmd[7] = {0}; int ret; @@ -402,25 +396,20 @@ static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int magn magnitude = 65535; } -#if 0 - if (is_ffex) { + if (ctx->is_ffex) { magnitude = magnitude * 90 / 65535; cmd[0] = 0xfe; cmd[1] = 0x03; - cmd[2] = (uint16_t)magnitude >> 14; - cmd[3] = (uint16_t)magnitude >> 14; - cmd[4] = (uint16_t)magnitude; - cmd[5] = 0x00; - cmd[6] = 0x00; + cmd[2] = (Uint8)((Uint16)magnitude >> 14); + cmd[3] = (Uint8)((Uint16)magnitude >> 14); + cmd[4] = (Uint8)magnitude; ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); if(ret == -1){ return false; } - }else -#endif - { + } else { Uint32 expand_a; Uint32 expand_b; // first disable @@ -471,6 +460,13 @@ static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int magn return true; } +/* + ffex identification method by: + Simon Wood + Michal Malý + lg4ff_init + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ static bool HIDAPI_DriverLg4ff_InitDevice(SDL_HIDAPI_Device *device) { SDL_DriverLg4ff_Context *ctx; @@ -495,6 +491,14 @@ static bool HIDAPI_DriverLg4ff_InitDevice(SDL_HIDAPI_Device *device) return false; } + if (device->product_id == USB_DEVICE_ID_LOGITECH_WHEEL && + (device->version >> 8) == 0x21 && + (device->version & 0xff) == 0x00) { + ctx->is_ffex = true; + } else { + ctx->is_ffex = false; + } + return HIDAPI_JoystickConnected(device, NULL); } From ceab2c69f03e96695a8f36a0376303d6d142099a Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Fri, 27 Dec 2024 11:49:49 +0100 Subject: [PATCH 17/26] fix building on platforms without abs and llabs --- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index ce8f6267bdb09..87c2d6bc6b5a7 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -141,7 +141,14 @@ Uint64 get_time_ms(){ #define SCALE_VALUE_U16(x, bits) (CLAMP_VALUE_U16(x) >> (16 - bits)) #define CLAMP_VALUE_S16(x) ((Uint16)((x) <= -0x8000 ? -0x8000 : ((x) > 0x7fff ? 0x7fff : (x)))) #define TRANSLATE_FORCE(x) ((CLAMP_VALUE_S16(x) + 0x8000) >> 8) -#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(abs(x) * 2, bits) +#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(_abs(x) * 2, bits) + +static Sint32 _abs(Sint32 x) { + return x < 0 ? -x : x; +} +static Sint64 _llabs(Sint64 x) { + return x < 0 ? -x : x; +} bool effect_is_periodic(const SDL_HapticEffect *effect) { @@ -506,7 +513,7 @@ static Sint32 lg4ff_calculate_periodic(struct lg4ff_effect_state *state) level += (state->phase < 180 ? 1 : -1) * magnitude; break; case SDL_HAPTIC_TRIANGLE: - level += (Sint32) (llabs((Sint64)state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude); + level += (Sint32) (_llabs((Sint64)state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude); break; case SDL_HAPTIC_SAWTOOTHUP: level += state->phase * magnitude * 2 / 360 - magnitude; @@ -616,8 +623,8 @@ static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_param d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11); s1 = parameters->k1 < 0; s2 = parameters->k2 < 0; - k1 = abs(parameters->k1); - k2 = abs(parameters->k2); + k1 = _abs(parameters->k1); + k2 = _abs(parameters->k2); if (k1 < 2048) { d1 = 0; } else { @@ -794,7 +801,7 @@ static int lg4ff_timer(struct lg4ff_device *device) parameters[2].clip = parameters[2].clip * device->damper_level / 100; parameters[3].clip = parameters[3].clip * device->friction_level / 100; - ffb_level = abs(parameters[0].level); + ffb_level = _abs(parameters[0].level); for (i = 1; i < 4; i++) { parameters[i].k1 = (Sint64)parameters[i].k1 * gain / 0xffff; parameters[i].k2 = (Sint64)parameters[i].k2 * gain / 0xffff; From dfdbaddf4cfa5554777c402767a110f92a7c8784 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Sat, 28 Dec 2024 11:37:33 +0100 Subject: [PATCH 18/26] fix building on platforms without sin --- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index 87c2d6bc6b5a7..8de75cff0cf21 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -129,7 +129,7 @@ Uint64 get_time_ms(){ #define test_bit(bit, field) (*(field) & (1 << bit)) #define __set_bit(bit, field) {*(field) = *(field) | (1 << bit);} #define __clear_bit(bit, field) {*(field) = *(field) & ~(1 << bit);} -#define sin_deg(in) (double)(sin((double)(in) * SDL_PI_D / 180.0)) +#define sin_deg(in) (double)(SDL_sin((double)(in) * SDL_PI_D / 180.0)) #define time_after_eq(a, b) (a >= b) #define time_before(a, b) (a < b) From df68349a451d0985f24c410b63234e0b4d4f4d22 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Sat, 28 Dec 2024 14:11:06 +0100 Subject: [PATCH 19/26] adjust dfp input when custom range is applied --- src/joystick/hidapi/SDL_hidapi_lg4ff.c | 37 +++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index d393ef8800052..7859502ea6763 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -93,6 +93,7 @@ typedef struct Uint8 last_report_buf[32]; bool initialized; bool is_ffex; + Uint16 range; } SDL_DriverLg4ff_Context; static void HIDAPI_DriverLg4ff_RegisterHints(SDL_HintCallback callback, void *userdata) @@ -291,6 +292,7 @@ static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) { Uint8 cmd[7] = {0}; int ret = 0; + SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; if (range < 40) { range = 40; @@ -299,6 +301,7 @@ static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) range = 900; } + ctx->range = range; switch (device->product_id) { case USB_DEVICE_ID_LOGITECH_G29_WHEEL: case USB_DEVICE_ID_LOGITECH_G27_WHEEL: @@ -499,6 +502,8 @@ static bool HIDAPI_DriverLg4ff_InitDevice(SDL_HIDAPI_Device *device) ctx->is_ffex = false; } + ctx->range = 900; + return HIDAPI_JoystickConnected(device, NULL); } @@ -523,6 +528,36 @@ static bool HIDAPI_DriverLg4ff_GetBit(const Uint8 *buf, int bit_num, size_t buf_ return (buf[byte_offset] & mask) ? true : false; } +/* + *Ported* + Original functions by: + Michal Malý + lg4ff_adjust_dfp_x_axis + `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git +*/ +static Uint16 lg4ff_adjust_dfp_x_axis(Uint16 value, Uint16 range) +{ + Uint16 max_range; + Sint32 new_value; + + if (range == 900) + return value; + else if (range == 200) + return value; + else if (range < 200) + max_range = 200; + else + max_range = 900; + + new_value = 8192 + ((value - 8192) * max_range / range); + if (new_value < 0) + return 0; + else if (new_value > 16383) + return 16383; + else + return (Uint16)new_value; +} + static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 *report_buf, @@ -692,7 +727,7 @@ static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, last_x = last_x | (ctx->last_report_buf[1] & 0x3F) << 8; if (x != last_x) { state_changed = true; - SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, x * 4 - 32768); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, lg4ff_adjust_dfp_x_axis(x, ctx->range) * 4 - 32768); } if (report_buf[5] != ctx->last_report_buf[5]) { state_changed = true; From 6e6096073f3a1cd9ab65e1827ad56090fe53b78f Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Sun, 29 Dec 2024 01:19:57 +0100 Subject: [PATCH 20/26] destory mutex when closing lg4ff hidapihaptic --- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index 8de75cff0cf21..7f6826acf4018 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -958,6 +958,7 @@ static void SDL_HIDAPI_HapticDriverLg4ff_Close(SDL_HIDAPI_HapticDevice *device) ctx->stop_thread = true; SDL_WaitThread(ctx->thread, NULL); + SDL_DestroyMutex(ctx->mutex); } static int SDL_HIDAPI_HapticDriverLg4ff_NumEffects(SDL_HIDAPI_HapticDevice *device) From 9671105cc73c1f142879b4d93fff78a36d85b3f2 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Sun, 29 Dec 2024 13:27:36 +0100 Subject: [PATCH 21/26] fix SDL_hid_read return container --- src/joystick/hidapi/SDL_hidapi_lg4ff.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index 7859502ea6763..b891636a3cbf4 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -769,7 +769,7 @@ static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, static bool HIDAPI_DriverLg4ff_UpdateDevice(SDL_HIDAPI_Device *device) { SDL_Joystick *joystick = NULL; - size_t r; + int r; Uint8 report_buf[32] = {0}; size_t report_size = 0; SDL_DriverLg4ff_Context *ctx = (SDL_DriverLg4ff_Context *)device->context; @@ -804,7 +804,6 @@ static bool HIDAPI_DriverLg4ff_UpdateDevice(SDL_HIDAPI_Device *device) do { r = SDL_hid_read(device->dev, report_buf, report_size); - if (r < 0) { /* Failed to read from controller */ HIDAPI_JoystickDisconnected(device, device->joysticks[0]); From edccd2a8d0608ac6989654eef26113cffb38dee8 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Sun, 29 Dec 2024 17:10:21 +0100 Subject: [PATCH 22/26] cleanup patch written by Ozkan Sezer for SDL2 Original commit: Author: Ozkan Sezer Date: Sun Dec 29 17:55:04 2024 +0300 minor clean-ups: - make _abs,_llabs, get_time_ms, effect_is_periodic, effect_is_condition helpers static inline. - rename _abs and _llabs to abs32 and abs64 to avoid analyzers complain about reserved names. - make the drivers[] array static. - NULL terminate the drivers[] array to avoid an empty array in case if SDL_HAPTIC_HIDAPI_LG4FF isn't configured. - minor formatting clean-ups here and there. - remove #endif comments about SDL_JOYSTICK_HIDAPI for readability. those #if / #endif blocks are short enough already. --- src/haptic/SDL_haptic.c | 38 +++++++------- src/haptic/hidapi/SDL_hidapihaptic.c | 24 +++++---- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 58 +++++++++++----------- 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/src/haptic/SDL_haptic.c b/src/haptic/SDL_haptic.c index 6037c1e405415..8a771213ece0c 100644 --- a/src/haptic/SDL_haptic.c +++ b/src/haptic/SDL_haptic.c @@ -23,7 +23,7 @@ #include "SDL_syshaptic.h" #ifdef SDL_JOYSTICK_HIDAPI #include "SDL_hidapihaptic.h" -#endif //SDL_JOYSTICK_HIDAPI +#endif #include "SDL_haptic_c.h" #include "../joystick/SDL_joystick_c.h" // For SDL_IsJoystickValid #include "../SDL_hints_c.h" @@ -123,7 +123,7 @@ bool SDL_InitHaptics(void) SDL_SYS_HapticQuit(); return false; } - #endif //SDL_JOYSTICK_HIDAPI + #endif return true; } @@ -311,9 +311,9 @@ bool SDL_IsJoystickHaptic(SDL_Joystick *joystick) !SDL_IsGamepad(SDL_GetJoystickID(joystick))) { #ifdef SDL_JOYSTICK_HIDAPI result = SDL_SYS_JoystickIsHaptic(joystick) || SDL_HIDAPI_JoystickIsHaptic(joystick); - #else //SDL_JOYSTICK_HIDAPI + #else result = SDL_SYS_JoystickIsHaptic(joystick); - #endif //SDL_JOYSTICK_HIDAPI + #endif } } SDL_UnlockJoysticks(); @@ -342,7 +342,7 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick) || SDL_HIDAPI_JoystickSameHaptic(hapticlist, joystick)) { #else if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick)) { - #endif //SDL_JOYSTICK_HIDAPI + #endif haptic = hapticlist; ++haptic->ref_count; SDL_UnlockJoysticks(); @@ -372,7 +372,7 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick) return NULL; } } else - #endif //SDL_JOYSTICK_HIDAPI + #endif if (!SDL_SYS_HapticOpenFromJoystick(haptic, joystick)) { SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed."); SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false); @@ -433,7 +433,7 @@ void SDL_CloseHaptic(SDL_Haptic *haptic) if (SDL_HIDAPI_HapticIsHidapi(haptic)) { SDL_HIDAPI_HapticClose(haptic); } else - #endif //SDL_JOYSTICK_HIDAPI + #endif { // Close it, properly removing effects if needed for (i = 0; i < haptic->neffects; i++) { @@ -476,7 +476,7 @@ void SDL_QuitHaptics(void) #ifdef SDL_JOYSTICK_HIDAPI SDL_HIDAPI_HapticQuit(); - #endif //SDL_JOYSTICK_HIDAPI + #endif SDL_SYS_HapticQuit(); } @@ -543,7 +543,7 @@ int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect) if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticNewEffect(haptic, effect); } - #endif //SDL_JOYSTICK_HIDAPI + #endif // See if there's a free slot for (i = 0; i < haptic->neffects; i++) { @@ -581,7 +581,7 @@ bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffe if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticUpdateEffect(haptic, effect, data); } - #endif //SDL_JOYSTICK_HIDAPI + #endif if (!ValidEffect(haptic, effect)) { return false; @@ -614,7 +614,7 @@ bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations) if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticRunEffect(haptic, effect, iterations); } - #endif //SDL_JOYSTICK_HIDAPI + #endif if (!ValidEffect(haptic, effect)) { return false; @@ -636,7 +636,7 @@ bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect) if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticStopEffect(haptic, effect); } - #endif //SDL_JOYSTICK_HIDAPI + #endif if (!ValidEffect(haptic, effect)) { return false; @@ -659,7 +659,7 @@ void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect) SDL_HIDAPI_HapticDestroyEffect(haptic, effect); return; } - #endif //SDL_JOYSTICK_HIDAPI + #endif if (!ValidEffect(haptic, effect)) { return; @@ -681,7 +681,7 @@ bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect) if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticGetEffectStatus(haptic, effect); } - #endif //SDL_JOYSTICK_HIDAPI + #endif if (!ValidEffect(haptic, effect)) { return false; @@ -733,7 +733,7 @@ bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain) if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticSetGain(haptic, real_gain); } - #endif //SDL_JOYSTICK_HIDAPI + #endif return SDL_SYS_HapticSetGain(haptic, real_gain); } @@ -754,7 +754,7 @@ bool SDL_SetHapticAutocenter(SDL_Haptic *haptic, int autocenter) if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticSetAutocenter(haptic, autocenter); } - #endif //SDL_JOYSTICK_HIDAPI + #endif return SDL_SYS_HapticSetAutocenter(haptic, autocenter); } @@ -771,7 +771,7 @@ bool SDL_PauseHaptic(SDL_Haptic *haptic) if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticPause(haptic); } - #endif //SDL_JOYSTICK_HIDAPI + #endif return SDL_SYS_HapticPause(haptic); } @@ -788,7 +788,7 @@ bool SDL_ResumeHaptic(SDL_Haptic *haptic) if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticResume(haptic); } - #endif //SDL_JOYSTICK_HIDAPI + #endif return SDL_SYS_HapticResume(haptic); } @@ -801,7 +801,7 @@ bool SDL_StopHapticEffects(SDL_Haptic *haptic) if (SDL_HIDAPI_HapticIsHidapi(haptic)) { return SDL_HIDAPI_HapticStopAll(haptic); } - #endif //SDL_JOYSTICK_HIDAPI + #endif return SDL_SYS_HapticStopAll(haptic); } diff --git a/src/haptic/hidapi/SDL_hidapihaptic.c b/src/haptic/hidapi/SDL_hidapihaptic.c index 9340fc79ade17..7571d4f278990 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic.c +++ b/src/haptic/hidapi/SDL_hidapihaptic.c @@ -37,10 +37,11 @@ typedef struct haptic_list_node static haptic_list_node *haptic_list_head = NULL; static SDL_Mutex *haptic_list_mutex = NULL; -SDL_HIDAPI_HapticDriver *drivers[] = { +static SDL_HIDAPI_HapticDriver *drivers[] = { #ifdef SDL_HAPTIC_HIDAPI_LG4FF &SDL_HIDAPI_HapticDriverLg4ff, - #endif //SDL_HAPTIC_HIDAPI_LG4FF + #endif + NULL }; bool SDL_HIDAPI_HapticInit() @@ -76,16 +77,17 @@ bool SDL_HIDAPI_HapticIsHidapi(SDL_Haptic *haptic) bool SDL_HIDAPI_JoystickIsHaptic(SDL_Joystick *joystick) { + const int numdrivers = SDL_arraysize(drivers) - 1; int i; SDL_AssertJoysticksLocked(); - if (joystick->driver != &SDL_HIDAPI_JoystickDriver){ + if (joystick->driver != &SDL_HIDAPI_JoystickDriver) { return false; } - for (i = 0; i < SDL_arraysize(drivers); ++i) { - if(drivers[i]->JoystickSupported(joystick)) { + for (i = 0; i < numdrivers; ++i) { + if (drivers[i]->JoystickSupported(joystick)) { return true; } } @@ -94,15 +96,17 @@ bool SDL_HIDAPI_JoystickIsHaptic(SDL_Joystick *joystick) bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick) { + const int numdrivers = SDL_arraysize(drivers) - 1; int i; + SDL_AssertJoysticksLocked(); - if (joystick->driver != &SDL_HIDAPI_JoystickDriver){ + if (joystick->driver != &SDL_HIDAPI_JoystickDriver) { return SDL_SetError("Cannot open hidapi haptic from non hidapi joystick"); } - for (i = 0;i < SDL_arraysize(drivers);++i) { - if(drivers[i]->JoystickSupported(joystick)) { + for (i = 0; i < numdrivers; ++i) { + if (drivers[i]->JoystickSupported(joystick)) { SDL_HIDAPI_HapticDevice *device; haptic_list_node *list_node; // the driver is responsible for calling SDL_SetError @@ -127,7 +131,7 @@ bool SDL_HIDAPI_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystic device->ctx = ctx; list_node = SDL_malloc(sizeof(haptic_list_node)); - if(list_node == NULL) { + if (list_node == NULL) { device->driver->Close(device); SDL_free(device); return SDL_OutOfMemory(); @@ -175,7 +179,7 @@ bool SDL_HIDAPI_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick) SDL_HIDAPI_HapticDevice *device; SDL_AssertJoysticksLocked(); - if (joystick->driver != &SDL_HIDAPI_JoystickDriver){ + if (joystick->driver != &SDL_HIDAPI_JoystickDriver) { return false; } diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index 7f6826acf4018..d99d236a4259a 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -59,7 +59,7 @@ static Uint32 supported_device_ids[] = { #define FF_EFFECT_PLAYING 2 #define FF_EFFECT_UPDATING 3 -struct lg4ff_effect_state{ +struct lg4ff_effect_state { SDL_HapticEffect effect; Uint64 start_at; Uint64 play_at; @@ -74,10 +74,10 @@ struct lg4ff_effect_state{ double direction_gain; Sint32 slope; -bool allocated; + bool allocated; }; -struct lg4ff_effect_parameters{ +struct lg4ff_effect_parameters { Sint32 level; Sint32 d1; Sint32 d2; @@ -86,7 +86,7 @@ struct lg4ff_effect_parameters{ Uint32 clip; }; -struct lg4ff_slot{ +struct lg4ff_slot { Sint32 id; struct lg4ff_effect_parameters parameters; Uint8 current_cmd[7]; @@ -95,7 +95,7 @@ struct lg4ff_slot{ Uint32 effect_type; }; -typedef struct lg4ff_device{ +typedef struct lg4ff_device { Uint16 product_id; Uint16 release_number; struct lg4ff_effect_state states[LG4FF_MAX_EFFECTS]; @@ -122,7 +122,7 @@ typedef struct lg4ff_device{ bool is_ffex; } lg4ff_device; -Uint64 get_time_ms(){ +static SDL_INLINE Uint64 get_time_ms(void) { return SDL_GetTicks(); } @@ -141,30 +141,30 @@ Uint64 get_time_ms(){ #define SCALE_VALUE_U16(x, bits) (CLAMP_VALUE_U16(x) >> (16 - bits)) #define CLAMP_VALUE_S16(x) ((Uint16)((x) <= -0x8000 ? -0x8000 : ((x) > 0x7fff ? 0x7fff : (x)))) #define TRANSLATE_FORCE(x) ((CLAMP_VALUE_S16(x) + 0x8000) >> 8) -#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(_abs(x) * 2, bits) +#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(abs32(x) * 2, bits) -static Sint32 _abs(Sint32 x) { +static SDL_INLINE Sint32 abs32(Sint32 x) { return x < 0 ? -x : x; } -static Sint64 _llabs(Sint64 x) { +static SDL_INLINE Sint64 abs64(Sint64 x) { return x < 0 ? -x : x; } -bool effect_is_periodic(const SDL_HapticEffect *effect) +static SDL_INLINE bool effect_is_periodic(const SDL_HapticEffect *effect) { -return effect->type == SDL_HAPTIC_SINE || - effect->type == SDL_HAPTIC_TRIANGLE || - effect->type == SDL_HAPTIC_SAWTOOTHUP || - effect->type == SDL_HAPTIC_SAWTOOTHDOWN || - effect->type == SDL_HAPTIC_SQUARE; + return effect->type == SDL_HAPTIC_SINE || + effect->type == SDL_HAPTIC_TRIANGLE || + effect->type == SDL_HAPTIC_SAWTOOTHUP || + effect->type == SDL_HAPTIC_SAWTOOTHDOWN || + effect->type == SDL_HAPTIC_SQUARE; } -bool effect_is_condition(const SDL_HapticEffect *effect) +static SDL_INLINE bool effect_is_condition(const SDL_HapticEffect *effect) { -return effect->type == SDL_HAPTIC_SPRING || - effect->type == SDL_HAPTIC_DAMPER || - effect->type == SDL_HAPTIC_FRICTION; + return effect->type == SDL_HAPTIC_SPRING || + effect->type == SDL_HAPTIC_DAMPER || + effect->type == SDL_HAPTIC_FRICTION; } // linux SDL_syshaptic.c SDL_SYS_ToDirection @@ -513,7 +513,7 @@ static Sint32 lg4ff_calculate_periodic(struct lg4ff_effect_state *state) level += (state->phase < 180 ? 1 : -1) * magnitude; break; case SDL_HAPTIC_TRIANGLE: - level += (Sint32) (_llabs((Sint64)state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude); + level += (Sint32) (abs64((Sint64)state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude); break; case SDL_HAPTIC_SAWTOOTHUP: level += state->phase * magnitude * 2 / 360 - magnitude; @@ -623,8 +623,8 @@ static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_param d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11); s1 = parameters->k1 < 0; s2 = parameters->k2 < 0; - k1 = _abs(parameters->k1); - k2 = _abs(parameters->k2); + k1 = abs32(parameters->k1); + k2 = abs32(parameters->k2); if (k1 < 2048) { d1 = 0; } else { @@ -688,7 +688,7 @@ static int lg4ff_init_slots(struct lg4ff_device *device) //cmd[1] = fixed_loop ? 1 : 0; cmd[1] = 0; ret = SDL_SendJoystickEffect(device->hid_handle, cmd, 7); - if(!ret){ + if (!ret) { return -1; } @@ -705,7 +705,7 @@ static int lg4ff_init_slots(struct lg4ff_device *device) device->slots[i].id = i; lg4ff_update_slot(&device->slots[i], ¶meters); ret = SDL_SendJoystickEffect(device->hid_handle, cmd, 7); - if(!ret){ + if (!ret) { return -1; } device->slots[i].is_updated = 0; @@ -801,7 +801,7 @@ static int lg4ff_timer(struct lg4ff_device *device) parameters[2].clip = parameters[2].clip * device->damper_level / 100; parameters[3].clip = parameters[3].clip * device->friction_level / 100; - ffb_level = _abs(parameters[0].level); + ffb_level = abs32(parameters[0].level); for (i = 1; i < 4; i++) { parameters[i].k1 = (Sint64)parameters[i].k1 * gain / 0xffff; parameters[i].k2 = (Sint64)parameters[i].k2 * gain / 0xffff; @@ -817,7 +817,7 @@ static int lg4ff_timer(struct lg4ff_device *device) lg4ff_update_slot(slot, ¶meters[i]); if (slot->is_updated) { bool ret = SDL_SendJoystickEffect(device->hid_handle, slot->current_cmd, 7); - if(!ret){ + if (!ret) { status = -1; } slot->is_updated = 0; @@ -860,7 +860,7 @@ static int SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt(const char *env_name, int min, { const char *env = SDL_getenv(env_name); int value = 0; - if(env == NULL) { + if (env == NULL) { return def; } value = SDL_atoi(env); @@ -1112,7 +1112,7 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus(SDL_HIDAPI_HapticDevice return false; } - if(test_bit(FF_EFFECT_STARTED, &ctx->states[id].flags)){ + if (test_bit(FF_EFFECT_STARTED, &ctx->states[id].flags)) { ret = true; } SDL_UnlockMutex(ctx->mutex); @@ -1165,7 +1165,7 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice * cmd[4] = (Uint8)magnitude; ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); - if(!ret){ + if (!ret) { SDL_UnlockMutex(ctx->mutex); SDL_SetError("Failed sending autocenter command"); return false; From 66e8ad5c1c875fd8643d06667ab1cdf195e6dadc Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Fri, 10 Jan 2025 01:00:29 +0100 Subject: [PATCH 23/26] fix env name for adjusting lg4ff damper effect gain --- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index d99d236a4259a..723e315b27317 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -909,7 +909,7 @@ static void *SDL_HIDAPI_HapticDriverLg4ff_Open(SDL_Joystick *joystick) } ctx->spring_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_SPRING", 0, 100, 30); - ctx->damper_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_SPRING", 0, 100, 30); + ctx->damper_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_DAMPER", 0, 100, 30); ctx->friction_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_FRICTION", 0, 100, 30); ctx->gain = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_GAIN", 0, 65535, 65535); ctx->app_gain = 65535; From 3e14e48b3f7cbd962239060d5523e8cfacf6f343 Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Thu, 16 Jan 2025 19:10:41 +0100 Subject: [PATCH 24/26] add copyrights of authors of ported code to license header, update license year --- src/haptic/SDL_hidapihaptic.h | 2 +- src/haptic/hidapi/SDL_hidapihaptic.c | 2 +- src/haptic/hidapi/SDL_hidapihaptic_c.h | 2 +- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 5 ++++- src/joystick/hidapi/SDL_hidapi_lg4ff.c | 4 +++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/haptic/SDL_hidapihaptic.h b/src/haptic/SDL_hidapihaptic.h index 3d14617f44145..ad5679097de57 100644 --- a/src/haptic/SDL_hidapihaptic.h +++ b/src/haptic/SDL_hidapihaptic.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 2024 Katharine Chui + Copyright (C) 2025 Katharine Chui This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/src/haptic/hidapi/SDL_hidapihaptic.c b/src/haptic/hidapi/SDL_hidapihaptic.c index 7571d4f278990..eec71a34dd652 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic.c +++ b/src/haptic/hidapi/SDL_hidapihaptic.c @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 2024 Katharine Chui + Copyright (C) 2025 Katharine Chui This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/src/haptic/hidapi/SDL_hidapihaptic_c.h b/src/haptic/hidapi/SDL_hidapihaptic_c.h index 04270c4aa512e..83f1a89b1c197 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_c.h +++ b/src/haptic/hidapi/SDL_hidapihaptic_c.h @@ -1,6 +1,6 @@ /* Simple DirectMedia Layer - Copyright (C) 2024 Katharine Chui + Copyright (C) 2025 Katharine Chui This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index 723e315b27317..3bd3c6b2d6f8a 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -1,6 +1,9 @@ /* Simple DirectMedia Layer - Copyright (C) 2024 Katharine Chui + Copyright (C) 2025 Simon Wood + Copyright (C) 2025 Michal Malý + Copyright (C) 2025 Bernat Arlandis + Copyright (C) 2025 Katharine Chui This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index b891636a3cbf4..dc8a194ae3f41 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -1,6 +1,8 @@ /* Simple DirectMedia Layer - Copyright (C) 2024 Katharine Chui + Copyright (C) 2025 Simon Wood + Copyright (C) 2025 Michal Malý + Copyright (C) 2025 Katharine Chui This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages From 912781d5122ad022d84bd10ff42c11ac85b1dc1d Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Thu, 16 Jan 2025 21:00:14 +0100 Subject: [PATCH 25/26] fix windows ci builds --- src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 30 ++++++++++------------ src/joystick/hidapi/SDL_hidapi_lg4ff.c | 20 +++++++-------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c index 3bd3c6b2d6f8a..0ff1544e17d34 100644 --- a/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c +++ b/src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c @@ -249,7 +249,7 @@ static Uint16 get_effect_direction(SDL_HapticEffect *effect) static Uint32 get_effect_replay_length(SDL_HapticEffect *effect) { - int length = 0; + Uint32 length = 0; if (effect_is_periodic(effect)) { length = effect->periodic.length; } else if (effect_is_condition(effect)) { @@ -276,7 +276,7 @@ static Uint32 get_effect_replay_length(SDL_HapticEffect *effect) static Uint16 get_effect_replay_delay(SDL_HapticEffect *effect) { - int delay = 0; + Uint16 delay = 0; if (effect_is_periodic(effect)) { delay = effect->periodic.delay; } else if (effect_is_condition(effect)) { @@ -601,7 +601,7 @@ static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_param } } - slot->current_cmd[0] = (0x10 << slot->id) + slot->cmd_op; + slot->current_cmd[0] = (Uint8)((0x10 << slot->id) + slot->cmd_op); if (slot->cmd_op == 3) { slot->current_cmd[1] = 0; @@ -639,10 +639,10 @@ static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_param k2 -= 2048; } slot->current_cmd[1] = 0x0b; - slot->current_cmd[2] = d1 >> 3; - slot->current_cmd[3] = d2 >> 3; + slot->current_cmd[2] = (Uint8)(d1 >> 3); + slot->current_cmd[3] = (Uint8)(d2 >> 3); slot->current_cmd[4] = (SCALE_COEFF(k2, 4) << 4) + SCALE_COEFF(k1, 4); - slot->current_cmd[5] = ((d2 & 7) << 5) + ((d1 & 7) << 1) + (s2 << 4) + s1; + slot->current_cmd[5] = (Uint8)(((d2 & 7) << 5) + ((d1 & 7) << 1) + (s2 << 4) + s1); slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); break; case SDL_HAPTIC_DAMPER: @@ -650,9 +650,9 @@ static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_param s2 = parameters->k2 < 0; slot->current_cmd[1] = 0x0c; slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 4); - slot->current_cmd[3] = s1; + slot->current_cmd[3] = (Uint8)s1; slot->current_cmd[4] = SCALE_COEFF(parameters->k2, 4); - slot->current_cmd[5] = s2; + slot->current_cmd[5] = (Uint8)s2; slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8); break; case SDL_HAPTIC_FRICTION: @@ -662,7 +662,7 @@ static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_param slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 8); slot->current_cmd[3] = SCALE_COEFF(parameters->k2, 8); slot->current_cmd[4] = SCALE_VALUE_U16(parameters->clip, 8); - slot->current_cmd[5] = (s2 << 4) + s1; + slot->current_cmd[5] = (Uint8)((s2 << 4) + s1); slot->current_cmd[6] = 0; break; } @@ -740,7 +740,7 @@ static int lg4ff_timer(struct lg4ff_device *device) SDL_memset(parameters, 0, sizeof(parameters)); - gain = (Uint32)device->gain * device->app_gain / 0xffff; + gain = (Uint16)((Uint32)device->gain * device->app_gain / 0xffff); count = device->effects_used; @@ -809,7 +809,7 @@ static int lg4ff_timer(struct lg4ff_device *device) parameters[i].k1 = (Sint64)parameters[i].k1 * gain / 0xffff; parameters[i].k2 = (Sint64)parameters[i].k2 * gain / 0xffff; parameters[i].clip = parameters[i].clip * gain / 0xffff; - ffb_level += parameters[i].clip * 0x7fff / 0xffff; + ffb_level = (Sint32)(ffb_level + parameters[i].clip * 0x7fff / 0xffff); } if (ffb_level > device->peak_ffb_level) { device->peak_ffb_level = ffb_level; @@ -1029,8 +1029,6 @@ static int SDL_HIDAPI_HapticDriverLg4ff_CreateEffect(SDL_HIDAPI_HapticDevice *de SDL_SetError("Bad effect parameters"); return -1; } - - return 0; } // assumes ctx->mutex locked @@ -1206,9 +1204,9 @@ static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice * SDL_memset(cmd, 0x00, 7); cmd[0] = 0xfe; cmd[1] = 0x0d; - cmd[2] = expand_a / 0xaaaa; - cmd[3] = expand_a / 0xaaaa; - cmd[4] = expand_b / 0xaaaa; + cmd[2] = (Uint8)(expand_a / 0xaaaa); + cmd[3] = (Uint8)(expand_a / 0xaaaa); + cmd[4] = (Uint8)(expand_b / 0xaaaa); ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd)); if (!ret) { diff --git a/src/joystick/hidapi/SDL_hidapi_lg4ff.c b/src/joystick/hidapi/SDL_hidapi_lg4ff.c index dc8a194ae3f41..84378d795fb67 100644 --- a/src/joystick/hidapi/SDL_hidapi_lg4ff.c +++ b/src/joystick/hidapi/SDL_hidapi_lg4ff.c @@ -303,7 +303,7 @@ static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) range = 900; } - ctx->range = range; + ctx->range = (Uint16)range; switch (device->product_id) { case USB_DEVICE_ID_LOGITECH_G29_WHEEL: case USB_DEVICE_ID_LOGITECH_G27_WHEEL: @@ -357,8 +357,8 @@ static bool HIDAPI_DriverLg4ff_SetRange(SDL_HIDAPI_Device *device, int range) start_left = (((full_range - range + 1) * 2047) / full_range); start_right = 0xfff - start_left; - cmd[2] = start_left >> 4; - cmd[3] = start_right >> 4; + cmd[2] = (Uint8)(start_left >> 4); + cmd[3] = (Uint8)(start_right >> 4); cmd[4] = 0xff; cmd[5] = (start_right & 0xe) << 4 | (start_left & 0xe); cmd[6] = 0xff; @@ -444,9 +444,9 @@ static bool HIDAPI_DriverLg4ff_SetAutoCenter(SDL_HIDAPI_Device *device, int magn SDL_memset(cmd, 0x00, sizeof(cmd)); cmd[0] = 0xfe; cmd[1] = 0x0d; - cmd[2] = expand_a / 0xaaaa; - cmd[3] = expand_a / 0xaaaa; - cmd[4] = expand_b / 0xaaaa; + cmd[2] = (Uint8)(expand_a / 0xaaaa); + cmd[3] = (Uint8)(expand_a / 0xaaaa); + cmd[4] = (Uint8)(expand_b / 0xaaaa); ret = SDL_hid_write(device->dev, cmd, sizeof(cmd)); if (ret == -1) { @@ -572,7 +572,7 @@ static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, int bit_offset = 0; Uint64 timestamp = SDL_GetTicksNS(); - bool state_changed; + bool state_changed = false; switch (device->product_id) { case USB_DEVICE_ID_LOGITECH_G29_WHEEL: @@ -653,7 +653,7 @@ static bool HIDAPI_DriverLg4ff_HandleState(SDL_HIDAPI_Device *device, bool button_was_on = HIDAPI_DriverLg4ff_GetBit(ctx->last_report_buf, bit_num, report_size); if(button_on != button_was_on){ state_changed = true; - SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH + i, button_on); + SDL_SendJoystickButton(timestamp, joystick, (Uint8)(SDL_GAMEPAD_BUTTON_SOUTH + i), button_on); } } @@ -810,7 +810,7 @@ static bool HIDAPI_DriverLg4ff_UpdateDevice(SDL_HIDAPI_Device *device) /* Failed to read from controller */ HIDAPI_JoystickDisconnected(device, device->joysticks[0]); return false; - } else if (r == report_size) { + } else if ((size_t)r == report_size) { bool state_changed = HIDAPI_DriverLg4ff_HandleState(device, joystick, report_buf, report_size); if(state_changed && !ctx->initialized) { ctx->initialized = true; @@ -934,7 +934,7 @@ static bool HIDAPI_DriverLg4ff_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joy max_led = blue; } - return HIDAPI_DriverLg4ff_SendLedCommand(device, (5 * max_led) / 255); + return HIDAPI_DriverLg4ff_SendLedCommand(device, (Uint8)((5 * max_led) / 255)); } static bool HIDAPI_DriverLg4ff_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) From a0314224ee0257f07fec893481295956e029f1bc Mon Sep 17 00:00:00 2001 From: Katharine Chui Date: Wed, 12 Mar 2025 11:00:56 +0100 Subject: [PATCH 26/26] SDL_IsGamepad cache verdict obtained from joystick type check --- src/joystick/SDL_gamepad.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index 690028cb8fa50..59bd217f5b920 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -2630,15 +2630,14 @@ bool SDL_IsGamepad(SDL_JoystickID instance_id) SDL_LockJoysticks(); { const void *value; - SDL_JoystickType js_type = SDL_GetJoystickTypeForID(instance_id); - if (js_type != SDL_JOYSTICK_TYPE_GAMEPAD && js_type != SDL_JOYSTICK_TYPE_UNKNOWN) - { - // avoid creating HIDAPI mapping if type is SDL_Joystick knows it is not a game pad - result = false; - } else if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) { + if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) { result = (bool)(uintptr_t)value; } else { - if (SDL_PrivateGetGamepadMapping(instance_id, true) != NULL) { + SDL_JoystickType js_type = SDL_GetJoystickTypeForID(instance_id); + if (js_type != SDL_JOYSTICK_TYPE_GAMEPAD && js_type != SDL_JOYSTICK_TYPE_UNKNOWN) { + // avoid creating HIDAPI mapping if SDL_Joystick knows it is not a game pad + result = false; + } else if (SDL_PrivateGetGamepadMapping(instance_id, true) != NULL) { result = true; } else { result = false;