SDL: [SDL3] Adding input and FFB support for Logitech G29(PS3) on hidapi (#11598)

From 35c03774f379174f47d169a2dad5380471daeaaf Mon Sep 17 00:00:00 2001
From: Katharine Chui <[EMAIL REDACTED]>
Date: Mon, 17 Mar 2025 22:24:39 +0800
Subject: [PATCH] [SDL3] Adding input and FFB support for Logitech G29(PS3) on
 hidapi (#11598)

These changes enable the Logitech G29 wheel to run on hidapi with both SDL_Joystick and SDL_Haptic interfaces.

While it is already possible to use the wheel on Linux in WINE + SDL2 thanks to the in-tree evdev driver as well as new-lg4ff, these set of changes allow the G29 to be used with WINE under MacOS and FreeBSD

These wheels should also be supported, but I can only test them from G29's compat modes: G27, G25, DFGT, DFP, DFEX

Haptic and led support are ported from https://github.com/berarma/new-lg4ff
---
 Android.mk                                 |    1 +
 VisualC-GDK/SDL/SDL.vcxproj                |    5 +
 VisualC-GDK/SDL/SDL.vcxproj.filters        |    5 +
 VisualC/SDL/SDL.vcxproj                    |    5 +
 VisualC/SDL/SDL.vcxproj.filters            |   18 +
 Xcode/SDL/SDL.xcodeproj/project.pbxproj    |   24 +
 cmake/sdlchecks.cmake                      |    1 +
 include/SDL3/SDL_hints.h                   |   13 +
 src/haptic/SDL_haptic.c                    |  145 ++-
 src/haptic/SDL_hidapihaptic.h              |   48 +
 src/haptic/hidapi/SDL_hidapihaptic.c       |  305 +++++
 src/haptic/hidapi/SDL_hidapihaptic_c.h     |   70 ++
 src/haptic/hidapi/SDL_hidapihaptic_lg4ff.c | 1265 ++++++++++++++++++++
 src/joystick/SDL_gamepad.c                 |    6 +-
 src/joystick/hidapi/SDL_hidapi_lg4ff.c     |  989 +++++++++++++++
 src/joystick/hidapi/SDL_hidapijoystick.c   |    3 +
 src/joystick/hidapi/SDL_hidapijoystick_c.h |    2 +
 17 files changed, 2887 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
 create mode 100644 src/joystick/hidapi/SDL_hidapi_lg4ff.c

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) \
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 @@
     <ClInclude Include="..\..\src\io\SDL_sysasyncio.h" />
     <ClInclude Include="..\..\src\haptic\SDL_haptic_c.h" />
     <ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
+    <ClInclude Include="..\..\src\haptic\SDL_hidapihaptic.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_windowshaptic_c.h" />
+    <ClInclude Include="..\..\src\haptic\hidapi\SDL_hidapihaptic_c.h" />
     <ClInclude Include="..\..\src\hidapi\hidapi\hidapi.h" />
     <ClInclude Include="..\..\src\hidapi\SDL_hidapi_c.h" />
     <ClInclude Include="..\..\src\joystick\controller_type.h" />
@@ -703,6 +705,8 @@
       <LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Xbox.Scarlett.x64'">stdcpp17</LanguageStandard>
       <LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Xbox.XboxOne.x64'">stdcpp17</LanguageStandard>
     </ClCompile>
+    <ClCompile Include="..\..\src\haptic\hidapi\SDL_hidapihaptic.c" />
+    <ClCompile Include="..\..\src\haptic\hidapi\SDL_hidapihaptic_lg4ff.c" />
     <ClCompile Include="..\..\src\hidapi\SDL_hidapi.c" />
     <ClCompile Include="..\..\src\joystick\controller_type.c" />
     <ClCompile Include="..\..\src\joystick\dummy\SDL_sysjoystick.c" />
@@ -725,6 +729,7 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360w.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_lg4ff.c" />
     <ClCompile Include="..\..\src\joystick\SDL_gamepad.c" />
     <ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
     <ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c" />
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 @@
     <ClCompile Include="..\..\src\haptic\SDL_haptic.c" />
     <ClCompile Include="..\..\src\haptic\windows\SDL_dinputhaptic.c" />
     <ClCompile Include="..\..\src\haptic\windows\SDL_windowshaptic.c" />
+    <ClCompile Include="..\..\src\haptic\hidapi\SDL_hidapihaptic.c" />
+    <ClCompile Include="..\..\src\haptic\hidapi\SDL_hidapihaptic_lg4ff.c" />
     <ClCompile Include="..\..\src\hidapi\SDL_hidapi.c" />
     <ClCompile Include="..\..\src\joystick\controller_type.c" />
     <ClCompile Include="..\..\src\joystick\dummy\SDL_sysjoystick.c" />
@@ -78,6 +80,7 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360w.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_lg4ff.c" />
     <ClCompile Include="..\..\src\joystick\SDL_gamepad.c" />
     <ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
     <ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c" />
@@ -343,8 +346,10 @@
     <ClInclude Include="..\..\src\gpu\SDL_sysgpu.h" />
     <ClInclude Include="..\..\src\haptic\SDL_haptic_c.h" />
     <ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
+    <ClInclude Include="..\..\src\haptic\SDL_hidapihaptic.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_windowshaptic_c.h" />
+    <ClInclude Include="..\..\src\haptic\hidapi\SDL_hidapihaptic_c.h" />
     <ClInclude Include="..\..\src\hidapi\hidapi\hidapi.h" />
     <ClInclude Include="..\..\src\hidapi\SDL_hidapi_c.h" />
     <ClInclude Include="..\..\src\joystick\controller_type.h" />
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 @@
     <ClInclude Include="..\..\src\io\SDL_sysasyncio.h" />
     <ClInclude Include="..\..\src\haptic\SDL_haptic_c.h" />
     <ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
+    <ClInclude Include="..\..\src\haptic\SDL_hidapihaptic.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_windowshaptic_c.h" />
+    <ClInclude Include="..\..\src\haptic\hidapi\SDL_hidapihaptic_c.h" />
     <ClInclude Include="..\..\src\hidapi\hidapi\hidapi.h" />
     <ClInclude Include="..\..\src\hidapi\SDL_hidapi_c.h" />
     <ClInclude Include="..\..\src\joystick\controller_type.h" />
@@ -573,6 +575,8 @@
     <ClCompile Include="..\..\src\haptic\SDL_haptic.c" />
     <ClCompile Include="..\..\src\haptic\windows\SDL_dinputhaptic.c" />
     <ClCompile Include="..\..\src\haptic\windows\SDL_windowshaptic.c" />
+    <ClCompile Include="..\..\src\haptic\hidapi\SDL_hidapihaptic.c" />
+    <ClCompile Include="..\..\src\haptic\hidapi\SDL_hidapihaptic_lg4ff.c" />
     <ClCompile Include="..\..\src\hidapi\SDL_hidapi.c" />
     <ClCompile Include="..\..\src\joystick\controller_type.c" />
     <ClCompile Include="..\..\src\joystick\dummy\SDL_sysjoystick.c" />
@@ -595,6 +599,7 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360w.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_lg4ff.c" />
     <ClCompile Include="..\..\src\joystick\SDL_gamepad.c" />
     <ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
     <ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c" />
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 @@
     <Filter Include="haptic\windows">
       <UniqueIdentifier>{ebc2fca3-3c26-45e3-815e-3e0581d5e226}</UniqueIdentifier>
     </Filter>
+    <Filter Include="haptic\hidapi">
+      <UniqueIdentifier>{06DB01C0-65B5-4DE7-8ADC-C0B0CA3A1E69}</UniqueIdentifier>
+    </Filter>
     <Filter Include="haptic\dummy">
       <UniqueIdentifier>{47c445a2-7014-4e15-9660-7c89a27dddcf}</UniqueIdentifier>
     </Filter>
@@ -564,6 +567,9 @@
     <ClInclude Include="..\..\src\haptic\SDL_syshaptic.h">
       <Filter>haptic</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\haptic\SDL_hidapihaptic.h">
+      <Filter>haptic</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\haptic\SDL_haptic_c.h">
       <Filter>haptic</Filter>
     </ClInclude>
@@ -621,6 +627,9 @@
     <ClInclude Include="..\..\src\haptic\windows\SDL_windowshaptic_c.h">
       <Filter>haptic\windows</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\haptic\hidapi\SDL_hidapihaptic_c.h">
+      <Filter>haptic\hidapi</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapijoystick_c.h">
       <Filter>joystick\hidapi</Filter>
     </ClInclude>
@@ -1163,6 +1172,12 @@
     <ClCompile Include="..\..\src\haptic\windows\SDL_windowshaptic.c">
       <Filter>haptic\windows</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\haptic\hidapi\SDL_hidapihaptic.c">
+      <Filter>haptic\hidapi</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\src\haptic\hidapi\SDL_hidapihaptic_lg4ff.c">
+      <Filter>haptic\hidapi</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\haptic\dummy\SDL_syshaptic.c">
       <Filter>haptic\dummy</Filter>
     </ClCompile>
@@ -1223,6 +1238,9 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c">
       <Filter>joystick\hidapi</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_lg4ff.c">
+      <Filter>joystick\hidapi</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapijoystick.c">
       <Filter>joystick\hidapi</Filter>
     </ClCompile>
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index d9339b38eb3c2..426e80d45357d 100644
--- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj
+++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
@@ -71,6 +71,10 @@
 		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 */; };
+		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 */; };
@@ -608,6 +612,10 @@
 		63134A242A7902FD0021E9A6 /* SDL_pen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_pen.c; sourceTree = "<group>"; };
 		75E09158241EA924004729E1 /* SDL_virtualjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_virtualjoystick.c; sourceTree = "<group>"; };
 		75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_virtualjoystick_c.h; sourceTree = "<group>"; };
+		89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_lg4ff.c; sourceTree = "<group>"; };
+		89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapihaptic.c; sourceTree = "<group>"; };
+		89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapihaptic_c.h; sourceTree = "<group>"; };
+		89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapihaptic_lg4ff.c; sourceTree = "<group>"; };
 		9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = "<group>"; };
 		A1626A3D2617006A003F1973 /* SDL_triangle.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_triangle.c; sourceTree = "<group>"; };
 		A1626A512617008C003F1973 /* SDL_triangle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_triangle.h; sourceTree = "<group>"; };
@@ -1477,6 +1485,16 @@
 			path = virtual;
 			sourceTree = "<group>";
 		};
+		89E580222D03606400DAF6D3 /* hidapi */ = {
+			isa = PBXGroup;
+			children = (
+				89E5801F2D03606400DAF6D3 /* SDL_hidapihaptic.c */,
+				89E580202D03606400DAF6D3 /* SDL_hidapihaptic_c.h */,
+				89E580212D03606400DAF6D3 /* SDL_hidapihaptic_lg4ff.c */,
+			);
+			path = hidapi;
+			sourceTree = "<group>";
+		};
 		A75FDAA423E2790500529352 /* ios */ = {
 			isa = PBXGroup;
 			children = (
@@ -1535,6 +1553,7 @@
 		A7D8A5C223E2513D00DCD162 /* haptic */ = {
 			isa = PBXGroup;
 			children = (
+				89E580222D03606400DAF6D3 /* hidapi */,
 				A7D8A5CD23E2513D00DCD162 /* darwin */,
 				A7D8A5C323E2513D00DCD162 /* dummy */,
 				A7D8A5C623E2513D00DCD162 /* SDL_haptic_c.h */,
@@ -1904,6 +1923,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 */,
@@ -2617,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 */,
@@ -2905,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 */,
@@ -2966,6 +2989,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/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/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 1fe5b40c0b2d3..97e1eaabfdd56 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -1721,6 +1721,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/haptic/SDL_haptic.c b/src/haptic/SDL_haptic.c
index 1c11db686431f..8a771213ece0c 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
 #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
+
+    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)) {
@@ -227,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);
@@ -295,7 +309,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
             result = SDL_SYS_JoystickIsHaptic(joystick);
+            #endif
         }
     }
     SDL_UnlockJoysticks();
@@ -310,16 +328,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 +338,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
                 haptic = hapticlist;
                 ++haptic->ref_count;
                 SDL_UnlockJoysticks();
@@ -349,6 +363,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
         if (!SDL_SYS_HapticOpenFromJoystick(haptic, joystick)) {
             SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed.");
             SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);
@@ -379,6 +403,16 @@ SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick)
     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);
+    }
+    if (haptic->supported & SDL_HAPTIC_AUTOCENTER) {
+        SDL_SetHapticAutocenter(haptic, 0);
+    }
+
     return haptic;
 }
 
@@ -395,13 +429,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
+    {
+        // 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 +474,9 @@ void SDL_QuitHaptics(void)
         SDL_CloseHaptic(SDL_haptics);
     }
 
+    #ifdef SDL_JOYSTICK_HIDAPI
+    SDL_HIDAPI_HapticQuit();
+    #endif
     SDL_SYS_HapticQuit();
 }
 
@@ -495,6 +539,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
+
     // See if there's a free slot
     for (i = 0; i < haptic->neffects; i++) {
         if (haptic->effects[i].hweffect == NULL) {
@@ -527,6 +577,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
+
     if (!ValidEffect(haptic, effect)) {
         return false;
     }
@@ -554,6 +610,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
+
     if (!ValidEffect(haptic, effect)) {
         return false;
     }
@@ -570,6 +632,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
+
     if (!ValidEffect(haptic, effect)) {
         return false;
     }
@@ -586,6 +654,13 @@ void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect)
 {
     CHECK_HAPTIC_MAGIC(haptic,);
 
+    #ifdef SDL_JOYSTICK_HIDAPI
+    if (SDL_HIDAPI_HapticIsHidapi(haptic)) {
+        SDL_HIDAPI_HapticDestroyEffect(haptic, effect);
+        return;
+    }
+    #endif
+
     if (!ValidEffect(haptic, effect)) {
         return;
     }
@@ -602,6 +677,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
+
     if (!ValidEffect(haptic, effect)) {
         return false;
     }
@@ -648,6 +729,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
+
     return SDL_SYS_HapticSetGain(haptic, real_gain);
 }
 
@@ -663,6 +750,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
+
     return SDL_SYS_HapticSetAutocenter(haptic, autocenter);
 }
 
@@ -674,6 +767,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
+
     return SDL_SYS_HapticPause(haptic);
 }
 
@@ -685,6 +784,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
+
     return SDL_SYS_HapticResume(haptic);
 }
 
@@ -692,6 +797,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
+
     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..ad5679097de57
--- /dev/null
+++ b/src/haptic/SDL_hidapihaptic.h
@@ -0,0 +1,48 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 2025 Katharine Chui <katharine.chui@gmail.com>
+
+  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..eec71a34dd652
--- /dev/null
+++ b/src/haptic/hidapi/SDL_hidapihaptic.c
@@ -0,0 +1,305 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 2025 Katharine Chui <katharine.chui@gmail.com>
+
+  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"
+#inclu

(Patch may be truncated, please check the link at the top of this post.)