فهرست منبع

Implement SInput Device Support (#13343)

This implements a new SDL HID driver for a format developed by Hand Held Legend for their gamepad devices called SInput

Devices that are supported by this change with well-defined mappings

GC Ultimate ( https://gcultimate.com )
ProGCC ( https://handheldlegend.com/products/progcc-kit-wireless-wired-bundle )

The SInput format is documented here: https://github.com/HandHeldLegend/SInput-HID
mitchellcairns 1 هفته پیش
والد
کامیت
18eeaea054

+ 1 - 0
VisualC-GDK/SDL/SDL.vcxproj

@@ -723,6 +723,7 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps5.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_rumble.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_shield.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_sinput.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_stadia.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam_hori.c" />

+ 1 - 0
VisualC-GDK/SDL/SDL.vcxproj.filters

@@ -74,6 +74,7 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps5.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_rumble.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_shield.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_sinput.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_stadia.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam_hori.c" />

+ 2 - 1
VisualC/SDL/SDL.vcxproj

@@ -613,6 +613,7 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps5.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_rumble.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_shield.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_sinput.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_stadia.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam.c" />
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam_hori.c" />
@@ -774,4 +775,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 4 - 1
VisualC/SDL/SDL.vcxproj.filters

@@ -1215,6 +1215,9 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_shield.c">
       <Filter>joystick\hidapi</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_sinput.c">
+      <Filter>joystick\hidapi</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_stadia.c">
       <Filter>joystick\hidapi</Filter>
     </ClCompile>
@@ -1615,4 +1618,4 @@
   <ItemGroup>
     <ResourceCompile Include="..\..\src\core\windows\version.rc" />
   </ItemGroup>
-</Project>
+</Project>

+ 4 - 0
Xcode/SDL/SDL.xcodeproj/project.pbxproj

@@ -76,6 +76,7 @@
 		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 */; };
+		02D6A1C228A84B8F00A7F002 /* SDL_hidapi_sinput.c in Sources */ = {isa = PBXBuildFile; fileRef = 02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.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 */; };
 		A1BB8B6327F6CF330057CFA8 /* SDL_list.c in Sources */ = {isa = PBXBuildFile; fileRef = A1BB8B6127F6CF320057CFA8 /* SDL_list.c */; };
@@ -620,6 +621,7 @@
 		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>"; };
+		02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_sinput.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>"; };
 		A1BB8B6127F6CF320057CFA8 /* SDL_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_list.c; sourceTree = "<group>"; };
@@ -1943,6 +1945,7 @@
 				A75FDBC323EA380300529352 /* SDL_hidapi_rumble.h */,
 				A75FDBC423EA380300529352 /* SDL_hidapi_rumble.c */,
 				9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */,
+				02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */,
 				F3984CCF25BCC92800374F43 /* SDL_hidapi_stadia.c */,
 				A75FDAAC23E2795C00529352 /* SDL_hidapi_steam.c */,
 				F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */,
@@ -2877,6 +2880,7 @@
 				A7D8B62F23E2514300DCD162 /* SDL_sysfilesystem.m in Sources */,
 				A7D8B41C23E2514300DCD162 /* SDL_systls.c in Sources */,
 				9846B07C287A9020000C35C8 /* SDL_hidapi_shield.c in Sources */,
+				02D6A1C228A84B8F00A7F002 /* SDL_hidapi_sinput.c in Sources */,
 				F31013C72C24E98200FBE946 /* SDL_keymap.c in Sources */,
 				F3A9AE992C8A13C100AAC390 /* SDL_render_gpu.c in Sources */,
 				A7D8BBD923E2574800DCD162 /* SDL_uikitmessagebox.m in Sources */,

+ 12 - 0
include/SDL3/SDL_hints.h

@@ -1746,6 +1746,18 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_HIDAPI_8BITDO "SDL_JOYSTICK_HIDAPI_8BITDO"
 
+/**
+ * A variable controlling whether the HIDAPI driver for SInput controllers
+ * should be used. More info - https://github.com/HandHeldLegend/SInput-HID
+ *
+ * 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_SINPUT "SDL_JOYSTICK_HIDAPI_SINPUT"
+
 /**
  * A variable controlling whether the HIDAPI driver for Flydigi controllers
  * should be used.

+ 49 - 0
src/joystick/SDL_gamepad.c

@@ -798,6 +798,54 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
                 product == USB_PRODUCT_8BITDO_SF30_PRO_BT)) {
             // This controller has no guide button
             SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string));
+    } else if (SDL_IsJoystickSInputController(vendor, product)) {
+        Uint8 face_style = (guid.data[15] & 0xF0) >> 4;
+        Uint8 u_id = guid.data[15] & 0x0F;
+
+        switch (product) {
+        case USB_PRODUCT_HANDHELDLEGEND_PROGCC:
+            // ProGCC Mapping
+            SDL_strlcat(mapping_string, "a:b1,b:b0,back:b15,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b8,lefttrigger:b12,leftx:a0,lefty:a1,misc1:b17,rightshoulder:b11,rightstick:b9,righttrigger:b13,rightx:a2,righty:a3,start:b14,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string));
+            break;
+
+        case USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE:
+            // GC Ultimate Map
+            SDL_strlcat(mapping_string, "a:b0,b:b2,back:b15,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b8,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b17,misc3:b18,paddle1:b13,paddle2:b12,rightshoulder:b11,rightstick:b9,righttrigger:a5,rightx:a2,righty:a3,start:b14,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string));
+            break;
+
+        case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC:
+            if (u_id != 1) {
+                return NULL;
+            }
+
+            // SuperGamepad+ Map
+            if (u_id == 1) {
+                SDL_strlcat(mapping_string, "a:b1,b:b0,back:b11,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,leftshoulder:b8,rightshoulder:b9,start:b10,x:b3,y:b2,", sizeof(mapping_string));
+            }
+
+            // Apply face style
+            switch (face_style) {
+            default:
+            case 1:
+                SDL_strlcat(mapping_string, "face:abxy,", sizeof(mapping_string));
+                break;
+            case 2:
+                SDL_strlcat(mapping_string, "face:axby,", sizeof(mapping_string));
+                break;
+            case 3:
+                SDL_strlcat(mapping_string, "face:bayx,", sizeof(mapping_string));
+                break;
+            case 4:
+                SDL_strlcat(mapping_string, "face:sony,", sizeof(mapping_string));
+                break;
+            }
+            break;
+
+        default:
+        case USB_PRODUCT_BONJIRICHANNEL_FIREBIRD:
+            // Unmapped devices
+            return NULL;
+        }
     } else {
         // All other gamepads have the standard set of 19 buttons and 6 axes
         if (SDL_IsJoystickGameCube(vendor, product)) {
@@ -1235,6 +1283,7 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG
     if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) {
         baxy_mapping = true;
     }
+
     // FIXME: We fix these up when loading the mapping, does this ever get hit?
     //SDL_assert(!axby_mapping && !baxy_mapping);
 

+ 11 - 0
src/joystick/SDL_joystick.c

@@ -3202,6 +3202,17 @@ bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id)
     return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT);
 }
 
+bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id)
+{
+    bool vendor_match = (vendor_id == USB_VENDOR_RASPBERRYPI);
+    bool product_match =
+        (product_id == USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC) |
+        (product_id == USB_PRODUCT_HANDHELDLEGEND_PROGCC) |
+        (product_id == USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE) |
+        (product_id == USB_PRODUCT_BONJIRICHANNEL_FIREBIRD);
+    return (vendor_match && product_match);
+}
+
 bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id)
 {
     return vendor_id == USB_VENDOR_FLYDIGI && product_id == USB_PRODUCT_FLYDIGI_GAMEPAD;

+ 3 - 0
src/joystick/SDL_joystick_c.h

@@ -135,6 +135,9 @@ extern bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id);
 // Function to return whether a joystick is a HORI Steam controller
 extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id);
 
+// Function to return whether a joystick is an SInput (Open Format) controller
+extern bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id);
+
 // Function to return whether a joystick is a Flydigi controller
 extern bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id);
 

+ 809 - 0
src/joystick/hidapi/SDL_hidapi_sinput.c

@@ -0,0 +1,809 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 2025 Mitchell Cairns <[email protected]>
+
+  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_hints_c.h"
+#include "../SDL_sysjoystick.h"
+
+#include "SDL_hidapijoystick_c.h"
+#include "SDL_hidapi_rumble.h"
+
+#ifdef SDL_JOYSTICK_HIDAPI_SINPUT
+
+/*****************************************************************************************************/
+
+// Define this if you want to log all packets from the controller
+#if 0
+#define DEBUG_SINPUT_PROTOCOL
+#endif
+
+#if 0
+#define DEBUG_SINPUT_INIT
+#endif
+
+#define SINPUT_DEVICE_REPORT_SIZE           64 // Size of input reports (And CMD Input reports)
+#define SINPUT_DEVICE_REPORT_COMMAND_SIZE   48 // Size of command OUTPUT reports
+
+#define SINPUT_DEVICE_REPORT_ID_JOYSTICK_INPUT  0x01
+#define SINPUT_DEVICE_REPORT_ID_INPUT_CMDDAT    0x02
+#define SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT   0x03
+
+#define SINPUT_DEVICE_COMMAND_HAPTIC        0x01
+#define SINPUT_DEVICE_COMMAND_FEATURES      0x02
+#define SINPUT_DEVICE_COMMAND_PLAYERLED     0x03
+#define SINPUT_DEVICE_COMMAND_JOYSTICKRGB   0x04
+
+#define SINPUT_HAPTIC_TYPE_PRECISE          0x01
+#define SINPUT_HAPTIC_TYPE_ERMSIMULATION    0x02
+
+#define SINPUT_DEFAULT_GYRO_SENS  2000
+#define SINPUT_DEFAULT_ACCEL_SENS 8
+
+#define SINPUT_REPORT_IDX_BUTTONS_0         3
+#define SINPUT_REPORT_IDX_BUTTONS_1         4
+#define SINPUT_REPORT_IDX_BUTTONS_2         5
+#define SINPUT_REPORT_IDX_BUTTONS_3         6
+#define SINPUT_REPORT_IDX_LEFT_X            7
+#define SINPUT_REPORT_IDX_LEFT_Y            9
+#define SINPUT_REPORT_IDX_RIGHT_X           11
+#define SINPUT_REPORT_IDX_RIGHT_Y           13
+#define SINPUT_REPORT_IDX_LEFT_TRIGGER      15
+#define SINPUT_REPORT_IDX_RIGHT_TRIGGER     17
+#define SINPUT_REPORT_IDX_IMU_TIMESTAMP     19
+#define SINPUT_REPORT_IDX_IMU_ACCEL_X       21
+#define SINPUT_REPORT_IDX_IMU_ACCEL_Y       23
+#define SINPUT_REPORT_IDX_IMU_ACCEL_Z       25
+#define SINPUT_REPORT_IDX_IMU_GYRO_X        27
+#define SINPUT_REPORT_IDX_IMU_GYRO_Y        29
+#define SINPUT_REPORT_IDX_IMU_GYRO_Z        31
+#define SINPUT_REPORT_IDX_TOUCH1_X          33
+#define SINPUT_REPORT_IDX_TOUCH1_Y          35
+#define SINPUT_REPORT_IDX_TOUCH1_P          37
+#define SINPUT_REPORT_IDX_TOUCH2_X          39
+#define SINPUT_REPORT_IDX_TOUCH2_Y          41
+#define SINPUT_REPORT_IDX_TOUCH2_P          43
+
+#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_ID   1
+#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK 2
+
+#define SINPUT_REPORT_IDX_PLUG_STATUS     1
+#define SINPUT_REPORT_IDX_CHARGE_LEVEL    2
+
+#define SINPUT_MAX_ALLOWED_TOUCHPADS 2
+
+#ifndef EXTRACTSINT16
+#define EXTRACTSINT16(data, idx) ((Sint16)((data)[(idx)] | ((data)[(idx) + 1] << 8)))
+#endif
+
+#ifndef EXTRACTUINT16
+#define EXTRACTUINT16(data, idx) ((Uint16)((data)[(idx)] | ((data)[(idx) + 1] << 8)))
+#endif
+
+
+typedef struct
+{
+    uint8_t type;
+
+    union {
+        // Frequency Amplitude pairs
+        struct {
+            struct {
+                uint16_t frequency_1;
+                uint16_t amplitude_1;
+                uint16_t frequency_2;
+                uint16_t amplitude_2;
+            } left;
+
+            struct {
+                uint16_t frequency_1;
+                uint16_t amplitude_1;
+                uint16_t frequency_2;
+                uint16_t amplitude_2;
+            } right;
+
+        } type_1;
+
+        // Basic ERM simulation model
+        struct {
+            struct {
+                uint8_t amplitude;
+                bool brake;
+            } left;
+
+            struct {
+                uint8_t amplitude;
+                bool brake;
+            } right;
+
+        } type_2;
+    };
+} SINPUT_HAPTIC_S;
+
+typedef struct
+{
+    SDL_HIDAPI_Device *device;
+    bool sensors_enabled;
+
+    Uint8 player_idx;
+
+    bool player_leds_supported;
+    bool joystick_rgb_supported;
+    bool rumble_supported;
+    bool accelerometer_supported;
+    bool gyroscope_supported;
+    bool left_analog_stick_supported;
+    bool right_analog_stick_supported;
+    bool left_analog_trigger_supported;
+    bool right_analog_trigger_supported;
+    bool touchpad_supported;
+
+    Uint8 touchpad_count;        // 2 touchpads maximum
+    Uint8 touchpad_finger_count; // 2 fingers for one touchpad, or 1 per touchpad (2 max)
+
+    Uint8  polling_rate_ms;
+    Uint8  sub_type;    // Subtype of the device, 0 in most cases
+
+    Uint16 accelRange; // Example would be 2,4,8,16 +/- (g-force)
+    Uint16 gyroRange;  // Example would be 1000,2000,4000 +/- (degrees per second)
+
+    float accelScale; // Scale factor for accelerometer values
+    float gyroScale;  // Scale factor for gyroscope values
+    Uint8 last_state[USB_PACKET_LENGTH];
+
+    Uint8 buttons_count;
+    Uint8 usage_masks[4];
+
+    Uint64 imu_timestamp; // Nanoseconds. We accumulate with received deltas
+} SDL_DriverSInput_Context;
+
+// Converts raw int16_t gyro scale setting
+static inline float CalculateGyroScale(uint16_t dps_range)
+{
+    return SDL_PI_F / 180.0f / (32768.0f / (float)dps_range);
+}
+
+// Converts raw int16_t accel scale setting
+static inline float CalculateAccelScale(uint16_t g_range)
+{
+    return SDL_STANDARD_GRAVITY / (32768.0f / (float)g_range);
+}
+
+static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data)
+{
+    SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
+
+    // Bitfields are not portable, so we unpack them into a struct value
+    ctx->rumble_supported = (data[0] & 0x01) != 0;
+    ctx->player_leds_supported = (data[0] & 0x02) != 0;
+    ctx->accelerometer_supported = (data[0] & 0x04) != 0;
+    ctx->gyroscope_supported = (data[0] & 0x08) != 0;
+
+    ctx->left_analog_stick_supported = (data[0] & 0x10) != 0;
+    ctx->right_analog_stick_supported = (data[0] & 0x20) != 0;
+    ctx->left_analog_trigger_supported = (data[0] & 0x40) != 0;
+    ctx->right_analog_trigger_supported = (data[0] & 0x80) != 0;
+
+    ctx->touchpad_supported = (data[1] & 0x01) != 0;
+    ctx->joystick_rgb_supported = (data[1] & 0x02) != 0;
+
+    SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
+    type = (SDL_GamepadType)SDL_clamp(data[2], SDL_GAMEPAD_TYPE_UNKNOWN, SDL_GAMEPAD_TYPE_COUNT);
+    device->type = type;
+
+    // The 4 MSB represent a button layout style SDL_GamepadFaceStyle
+    // The 4 LSB represent a device sub-type
+    device->guid.data[15] = data[3];
+
+#if defined(DEBUG_SINPUT_INIT)
+        SDL_Log("SInput Face Style: %d", (data[3] & 0xF0) >> 4);
+        SDL_Log("SInput Sub-type: %d", (data[3] & 0xF));
+#endif
+
+    ctx->polling_rate_ms = data[4];
+
+    ctx->accelRange = EXTRACTUINT16(data, 6);
+    ctx->gyroRange = EXTRACTUINT16(data, 8);
+
+    // Masks in LSB to MSB
+    // South, East, West, North, DUp, DDown, DLeft, DRight
+    ctx->usage_masks[0] = data[10];
+
+    // Stick Left, Stick Right, L Shoulder, R Shoulder,
+    // L Trigger, R Trigger, L Paddle 1, R Paddle 1
+    ctx->usage_masks[1] = data[11];
+
+    // Start, Back, Guide, Capture, L Paddle 2, R Paddle 2, Touchpad L, Touchpad R
+    ctx->usage_masks[2] = data[12];
+
+    // Power, Misc 4 to 10
+    ctx->usage_masks[3] = data[13];
+
+    // Derive button count from mask
+    for (Uint8 byte = 0; byte < 4; ++byte) {
+        for (Uint8 bit = 0; bit < 8; ++bit) {
+            if ((ctx->usage_masks[byte] & (1 << bit)) != 0) {
+                ++ctx->buttons_count;
+            }
+        }
+    }
+
+#if defined(DEBUG_SINPUT_INIT)
+    SDL_Log("Buttons count: %d", ctx->buttons_count);
+#endif
+
+    // Get and validate touchpad parameters
+    ctx->touchpad_count = data[14];
+    ctx->touchpad_finger_count = data[15];
+
+#if defined(DEBUG_SINPUT_INIT)
+    SDL_Log("Accelerometer Range: %d", ctx->accelRange);
+#endif
+
+#if defined(DEBUG_SINPUT_INIT)
+    SDL_Log("Gyro Range: %d", ctx->gyroRange);
+#endif
+
+    ctx->accelScale = CalculateAccelScale(ctx->accelRange);
+    ctx->gyroScale = CalculateGyroScale(ctx->gyroRange);
+}
+
+static bool RetrieveSDLFeatures(SDL_HIDAPI_Device *device)
+{
+    int written = 0;
+
+    // Attempt to send the SDL features get command. 
+    for (int attempt = 0; attempt < 8; ++attempt) {
+        const Uint8 featuresGetCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_FEATURES };
+        // This write will occasionally return -1, so ignore failure here and try again
+        written = SDL_hid_write(device->dev, featuresGetCommand, sizeof(featuresGetCommand));
+
+        if (written == SINPUT_DEVICE_REPORT_COMMAND_SIZE) {
+            break;
+        }
+    }
+
+    if (written < SINPUT_DEVICE_REPORT_COMMAND_SIZE) {
+        SDL_SetError("SInput device SDL Features GET command could not write");
+        return false;
+    } 
+
+    int read = 0;
+
+    // Read the reply
+    for (int i = 0; i < 100; ++i) {
+        SDL_Delay(1);
+
+        Uint8 data[USB_PACKET_LENGTH];
+        read = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0);
+        if (read < 0) {
+            SDL_SetError("SInput device SDL Features GET command could not read");
+            return false;
+        }
+        if (read == 0) {
+            continue;
+        }
+
+#ifdef DEBUG_SINPUT_PROTOCOL
+        HIDAPI_DumpPacket("SInput packet: size = %d", data, size);
+#endif
+
+        if ((read == USB_PACKET_LENGTH) && (data[0] == SINPUT_DEVICE_REPORT_ID_INPUT_CMDDAT) && (data[1] == SINPUT_DEVICE_COMMAND_FEATURES)) {
+            ProcessSDLFeaturesResponse(device, &(data[SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK]));
+#if defined(DEBUG_SINPUT_INIT)
+            SDL_Log("Received SInput SDL Features command response");
+#endif
+            return true;
+        }
+    }
+
+    return false;
+}
+
+// Type 2 haptics are for more traditional rumble such as
+// ERM motors or simulated ERM motors
+static inline void HapticsType2Pack(SINPUT_HAPTIC_S *in, Uint8 *out)
+{
+    // Type of haptics
+    out[0] = 2;
+
+    out[1] = in->type_2.left.amplitude;
+    out[2] = in->type_2.left.brake;
+
+    out[3] = in->type_2.right.amplitude;
+    out[4] = in->type_2.right.brake;
+}
+
+static void HIDAPI_DriverSInput_RegisterHints(SDL_HintCallback callback, void *userdata)
+{
+    SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, callback, userdata);
+}
+
+static void HIDAPI_DriverSInput_UnregisterHints(SDL_HintCallback callback, void *userdata)
+{
+    SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, callback, userdata);
+}
+
+static bool HIDAPI_DriverSInput_IsEnabled(void)
+{
+    return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SINPUT, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
+}
+
+static bool HIDAPI_DriverSInput_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)
+{
+    return SDL_IsJoystickSInputController(vendor_id, product_id);
+}
+
+static bool HIDAPI_DriverSInput_InitDevice(SDL_HIDAPI_Device *device)
+{
+#if defined(DEBUG_SINPUT_INIT)
+    SDL_Log("SInput device Init");
+#endif
+
+    SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)SDL_calloc(1, sizeof(*ctx));
+    if (!ctx) {
+        return false;
+    }
+
+    ctx->device = device;
+    device->context = ctx;
+
+    if (!RetrieveSDLFeatures(device)) {
+        return false;
+    }
+    
+    return HIDAPI_JoystickConnected(device, NULL);
+}
+
+static int HIDAPI_DriverSInput_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
+{
+    return -1;
+}
+
+static void HIDAPI_DriverSInput_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
+{
+    SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
+
+    if (ctx->player_leds_supported) {
+        player_index = SDL_clamp(player_index + 1, 0, 255);
+        Uint8 player_num = (Uint8)player_index;
+
+        ctx->player_idx = player_num;
+
+        // Set player number, finalizing the setup
+        Uint8 playerLedCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_PLAYERLED, ctx->player_idx };
+        int playerNumBytesWritten = SDL_hid_write(device->dev, playerLedCommand, SINPUT_DEVICE_REPORT_COMMAND_SIZE);
+
+        if (playerNumBytesWritten < 0) {
+            SDL_SetError("SInput device player led command could not write");
+        }
+    }
+}
+
+#ifndef DEG2RAD
+#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))
+#endif
+
+
+static bool HIDAPI_DriverSInput_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+#if defined(DEBUG_SINPUT_INIT)
+    SDL_Log("SInput device Open");
+#endif
+
+    SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
+
+    SDL_AssertJoysticksLocked();
+
+    joystick->nbuttons = ctx->buttons_count;
+
+    SDL_zeroa(ctx->last_state);
+
+    int axes = 0;
+    if (ctx->left_analog_stick_supported) {
+        axes += 2;
+    }
+
+    if (ctx->right_analog_stick_supported) {
+        axes += 2;
+    }
+
+    if (ctx->left_analog_trigger_supported) {
+        ++axes;
+    }
+
+    if (ctx->right_analog_trigger_supported) {
+        ++axes;
+    }
+
+    joystick->naxes = axes;
+
+    if (ctx->accelerometer_supported) {
+        SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, (float)1000.0f/ctx->polling_rate_ms);
+    }
+
+    if (ctx->gyroscope_supported) {
+        SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, (float)1000.0f / ctx->polling_rate_ms);
+    }
+
+    if (ctx->touchpad_supported) {
+        // If touchpad is supported, minimum 1, max is capped
+        ctx->touchpad_count = SDL_clamp(ctx->touchpad_count, 1, SINPUT_MAX_ALLOWED_TOUCHPADS);
+
+        if (ctx->touchpad_count > 1) {
+            // Support two separate touchpads with 1 finger each
+            // or support one touchpad with 2 fingers max
+            ctx->touchpad_finger_count = 1;
+        }
+
+        if (ctx->touchpad_count > 0) {
+            SDL_PrivateJoystickAddTouchpad(joystick, ctx->touchpad_finger_count);
+        }
+
+        if (ctx->touchpad_count > 1) {
+            SDL_PrivateJoystickAddTouchpad(joystick, ctx->touchpad_finger_count);
+        }
+    }
+
+    return true;
+}
+
+static bool HIDAPI_DriverSInput_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+{
+
+    SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
+
+    if (ctx->rumble_supported) {
+        SINPUT_HAPTIC_S hapticData = { 0 };
+        Uint8 hapticReport[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_HAPTIC };
+
+        // Low Frequency  = Left
+        // High Frequency = Right
+        hapticData.type_2.left.amplitude = (Uint8) (low_frequency_rumble >> 8);
+        hapticData.type_2.right.amplitude = (Uint8)(high_frequency_rumble >> 8);
+
+        HapticsType2Pack(&hapticData, &(hapticReport[2]));
+
+        SDL_HIDAPI_SendRumble(device, hapticReport, SINPUT_DEVICE_REPORT_COMMAND_SIZE);
+
+        return true;
+    }
+
+    return SDL_Unsupported();
+}
+
+static bool HIDAPI_DriverSInput_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
+{
+    return SDL_Unsupported();
+}
+
+static Uint32 HIDAPI_DriverSInput_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
+
+    Uint32 caps = 0;
+    if (ctx->rumble_supported) {
+        caps |= SDL_JOYSTICK_CAP_RUMBLE;
+    }
+
+    if (ctx->player_leds_supported) {
+        caps |= SDL_JOYSTICK_CAP_PLAYER_LED;
+    }
+
+    if (ctx->joystick_rgb_supported) {
+        caps |= SDL_JOYSTICK_CAP_RGB_LED;
+    }
+    
+    return caps;
+}
+
+static bool HIDAPI_DriverSInput_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+    SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
+
+    if (ctx->player_leds_supported) {
+
+        // Set player number, finalizing the setup
+        Uint8 joystickRGBCommand[SINPUT_DEVICE_REPORT_COMMAND_SIZE] = { SINPUT_DEVICE_REPORT_ID_OUTPUT_CMDDAT, SINPUT_DEVICE_COMMAND_JOYSTICKRGB, red, green, blue };
+        int joystickRGBBytesWritten = SDL_hid_write(device->dev, joystickRGBCommand, SINPUT_DEVICE_REPORT_COMMAND_SIZE);
+
+        if (joystickRGBBytesWritten < 0) {
+            SDL_SetError("SInput device joystick rgb command could not write");
+            return false;
+        }
+
+        return true;
+    }
+    return SDL_Unsupported();
+}
+
+static bool HIDAPI_DriverSInput_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
+static bool HIDAPI_DriverSInput_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
+{
+    SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
+
+    if (ctx->accelerometer_supported || ctx->gyroscope_supported) {
+        ctx->sensors_enabled = enabled;
+        return true;
+    }
+    return SDL_Unsupported();
+}
+
+static void HIDAPI_DriverSInput_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSInput_Context *ctx, Uint8 *data, int size)
+{
+    Sint16 axis = 0;
+    Sint16 accel = 0;
+    Sint16 gyro = 0;
+    Uint64 timestamp = SDL_GetTicksNS();
+    float imu_values[3] = { 0 };
+    Uint8 output_idx = 0;
+
+    // Process digital buttons according to the supplied
+    // button mask to create a contiguous button input set
+    for (Uint8 processes = 0; processes < 4; ++processes) {
+
+        Uint8 button_idx = SINPUT_REPORT_IDX_BUTTONS_0 + processes;
+
+        for (Uint8 buttons = 0; buttons < 8; ++buttons) {
+
+            // If a button is enabled by our usage mask
+            const Uint8 mask = (0x01 << buttons);
+            if ((ctx->usage_masks[processes] & mask) != 0) {
+
+                bool down = (data[button_idx] & mask) != 0;
+
+                if ( (output_idx < SDL_GAMEPAD_BUTTON_COUNT) && (ctx->last_state[button_idx] != data[button_idx]) ) {
+                    SDL_SendJoystickButton(timestamp, joystick, output_idx, down);
+                }
+
+                ++output_idx;
+            }
+        }
+    }
+
+    // Analog inputs map to a signed Sint16 range of -32768 to 32767 from the device.
+    // Use an axis index because not all gamepads will have the same axis inputs.
+    Uint8 axis_idx = 0;
+
+    // Left Analog Stick
+    axis = 0; // Reset axis value for joystick
+    if (ctx->left_analog_stick_supported) {
+        axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_X);
+        SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
+        ++axis_idx;
+
+        axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_Y);
+        SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
+        ++axis_idx;
+    }
+    
+    // Right Analog Stick
+    axis = 0; // Reset axis value for joystick
+    if (ctx->right_analog_stick_supported) {
+        axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_X);
+        SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
+        ++axis_idx;
+
+        axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_Y);
+        SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
+        ++axis_idx;
+    }
+
+    // Left Analog Trigger
+    axis = SDL_MIN_SINT16; // Reset axis value for trigger
+    if (ctx->left_analog_trigger_supported) {
+        axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_LEFT_TRIGGER);
+        SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
+        ++axis_idx;
+    }
+    
+    // Right Analog Trigger
+    axis = SDL_MIN_SINT16; // Reset axis value for trigger
+    if (ctx->right_analog_trigger_supported) {
+        axis = EXTRACTSINT16(data, SINPUT_REPORT_IDX_RIGHT_TRIGGER);
+        SDL_SendJoystickAxis(timestamp, joystick, axis_idx, axis);
+    }
+
+    // Battery/Power state handling
+    if (ctx->last_state[SINPUT_REPORT_IDX_PLUG_STATUS]  != data[SINPUT_REPORT_IDX_PLUG_STATUS] ||
+        ctx->last_state[SINPUT_REPORT_IDX_CHARGE_LEVEL] != data[SINPUT_REPORT_IDX_CHARGE_LEVEL]) {
+
+        SDL_PowerState state = SDL_POWERSTATE_NO_BATTERY;
+        Uint8 status = data[SINPUT_REPORT_IDX_PLUG_STATUS];
+        int percent = data[SINPUT_REPORT_IDX_CHARGE_LEVEL];
+
+        percent = SDL_clamp(percent, 0, 100); // Ensure percent is within valid range
+
+        switch (status) {
+        case 1:
+            state = SDL_POWERSTATE_NO_BATTERY;
+            percent = 0;
+            break;
+        case 2:
+            state = SDL_POWERSTATE_CHARGING;
+            break;
+        case 3:
+            state = SDL_POWERSTATE_CHARGED;
+            percent = 100;
+            break;
+        case 4:
+            state = SDL_POWERSTATE_ON_BATTERY;
+            break;
+        default: // Wired/No Battery Supported
+            state = SDL_POWERSTATE_UNKNOWN;
+            percent = 0;
+            break;
+        }
+
+        if (state > 0) {
+            SDL_SendJoystickPowerInfo(joystick, state, percent);
+        } 
+    }
+
+    // Extract the IMU timestamp delta (in microseconds)
+    Uint16 imu_timestamp_delta = EXTRACTUINT16(data, SINPUT_REPORT_IDX_IMU_TIMESTAMP);
+
+    // Check if we should process IMU data and if sensors are enabled
+    if ((imu_timestamp_delta > 0) && (ctx->sensors_enabled)) {
+
+        // Process IMU timestamp by adding the delta to the accumulated timestamp and converting to nanoseconds
+        ctx->imu_timestamp += ((Uint64) imu_timestamp_delta * 1000);
+
+        // Process Accelerometer
+        if (ctx->accelerometer_supported) {
+
+            accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_Y);
+            imu_values[2] = -(float)accel * ctx->accelScale; // Y-axis acceleration
+
+            accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_Z);
+            imu_values[1] = (float)accel * ctx->accelScale; // Z-axis acceleration
+
+            accel = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_ACCEL_X);
+            imu_values[0] = -(float)accel * ctx->accelScale; // X-axis acceleration
+
+            SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->imu_timestamp, imu_values, 3);
+        }
+
+        // Process Gyroscope
+        if (ctx->gyroscope_supported) {
+            
+            gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_Y);
+            imu_values[2] = -(float)gyro * ctx->gyroScale; // Y-axis rotation
+
+            gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_Z);
+            imu_values[1] = (float)gyro * ctx->gyroScale; // Z-axis rotation
+
+            gyro = EXTRACTSINT16(data, SINPUT_REPORT_IDX_IMU_GYRO_X);
+            imu_values[0] = -(float)gyro * ctx->gyroScale; // X-axis rotation
+
+            SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->imu_timestamp, imu_values, 3);
+        }
+    }
+
+    // Check if we should process touchpad
+    if (ctx->touchpad_supported && ctx->touchpad_count > 0) {
+        Uint8 touchpad = 0;
+        Uint8 finger = 0;
+
+        Sint16 touch1X = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH1_X);
+        Sint16 touch1Y = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH1_Y);
+        Uint16 touch1P = EXTRACTUINT16(data, SINPUT_REPORT_IDX_TOUCH1_P);
+
+        Sint16 touch2X = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH2_X);
+        Sint16 touch2Y = EXTRACTSINT16(data, SINPUT_REPORT_IDX_TOUCH2_Y);
+        Uint16 touch2P = EXTRACTUINT16(data, SINPUT_REPORT_IDX_TOUCH2_P);
+
+        SDL_SendJoystickTouchpad(timestamp, joystick, touchpad, finger,
+            touch1P > 0,
+            touch1X / 65536.0f + 0.5f,
+            touch1Y / 65536.0f + 0.5f,
+            touch1P / 32768.0f);
+
+        if (ctx->touchpad_count > 1) {
+            ++touchpad;
+        } else if (ctx->touchpad_finger_count > 1) {
+            ++finger;
+        }
+
+        if ((touchpad > 0) || (finger > 0)) {
+            SDL_SendJoystickTouchpad(timestamp, joystick, touchpad, finger,
+                                     touch2P > 0,
+                                     touch2X / 65536.0f + 0.5f,
+                                     touch2Y / 65536.0f + 0.5f,
+                                     touch2P / 32768.0f);
+        }
+    }
+
+    SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
+}
+
+static bool HIDAPI_DriverSInput_UpdateDevice(SDL_HIDAPI_Device *device)
+{
+    SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
+    SDL_Joystick *joystick = NULL;
+    Uint8 data[USB_PACKET_LENGTH];
+    int size = 0;
+
+    if (device->num_joysticks > 0) {
+        joystick = SDL_GetJoystickFromID(device->joysticks[0]);
+    } else {
+        return false;
+    }
+
+    while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
+#ifdef DEBUG_SINPUT_PROTOCOL
+        HIDAPI_DumpPacket("SInput packet: size = %d", data, size);
+#endif
+        if (!joystick) {
+            continue;
+        }
+
+        // Handle command response information
+        if (data[0] == SINPUT_DEVICE_REPORT_ID_JOYSTICK_INPUT) {
+            HIDAPI_DriverSInput_HandleStatePacket(joystick, ctx, data, size);
+        }
+    }
+
+    if (size < 0) {
+        // Read error, device is disconnected
+        HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
+    }
+    return (size >= 0);
+}
+
+static void HIDAPI_DriverSInput_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+}
+
+static void HIDAPI_DriverSInput_FreeDevice(SDL_HIDAPI_Device *device)
+{
+}
+
+SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSInput = {
+    SDL_HINT_JOYSTICK_HIDAPI_SINPUT,
+    true,
+    HIDAPI_DriverSInput_RegisterHints,
+    HIDAPI_DriverSInput_UnregisterHints,
+    HIDAPI_DriverSInput_IsEnabled,
+    HIDAPI_DriverSInput_IsSupportedDevice,
+    HIDAPI_DriverSInput_InitDevice,
+    HIDAPI_DriverSInput_GetDevicePlayerIndex,
+    HIDAPI_DriverSInput_SetDevicePlayerIndex,
+    HIDAPI_DriverSInput_UpdateDevice,
+    HIDAPI_DriverSInput_OpenJoystick,
+    HIDAPI_DriverSInput_RumbleJoystick,
+    HIDAPI_DriverSInput_RumbleJoystickTriggers,
+    HIDAPI_DriverSInput_GetJoystickCapabilities,
+    HIDAPI_DriverSInput_SetJoystickLED,
+    HIDAPI_DriverSInput_SendJoystickEffect,
+    HIDAPI_DriverSInput_SetJoystickSensorsEnabled,
+    HIDAPI_DriverSInput_CloseJoystick,
+    HIDAPI_DriverSInput_FreeDevice,
+};
+
+#endif // SDL_JOYSTICK_HIDAPI_SINPUT
+
+#endif // SDL_JOYSTICK_HIDAPI

+ 3 - 0
src/joystick/hidapi/SDL_hidapijoystick.c

@@ -97,6 +97,9 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = {
 #ifdef SDL_JOYSTICK_HIDAPI_FLYDIGI
     &SDL_HIDAPI_DriverFlydigi,
 #endif
+#ifdef SDL_JOYSTICK_HIDAPI_SINPUT
+    &SDL_HIDAPI_DriverSInput,
+#endif
 };
 static int SDL_HIDAPI_numdrivers = 0;
 static SDL_AtomicInt SDL_HIDAPI_updating_devices;

+ 2 - 0
src/joystick/hidapi/SDL_hidapijoystick_c.h

@@ -44,6 +44,7 @@
 #define SDL_JOYSTICK_HIDAPI_8BITDO
 #define SDL_JOYSTICK_HIDAPI_FLYDIGI
 #define SDL_JOYSTICK_HIDAPI_GIP
+#define SDL_JOYSTICK_HIDAPI_SINPUT
 
 // Joystick capability definitions
 #define SDL_JOYSTICK_CAP_MONO_LED       0x00000001
@@ -165,6 +166,7 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi;
+extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSInput;
 
 // Return true if a HID device is present and supported as a joystick of the given type
 extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type);

+ 5 - 0
src/joystick/usb_ids.h

@@ -59,6 +59,7 @@
 #define USB_VENDOR_SWITCH       0x2563
 #define USB_VENDOR_VALVE        0x28de
 #define USB_VENDOR_ZEROPLUS     0x0c12
+#define USB_VENDOR_RASPBERRYPI  0x2e8a // Commercial hardware from various companies are registered under this VID
 
 #define USB_PRODUCT_8BITDO_SF30_PRO                       0x6000    // B + START
 #define USB_PRODUCT_8BITDO_SF30_PRO_BT                    0x6100    // B + START
@@ -161,6 +162,10 @@
 #define USB_PRODUCT_XBOX_SERIES_X_BLE                     0x0b13
 #define USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER           0x02ff // XBOXGIP driver software PID
 #define USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD                 0x11ff
+#define USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC         0x10c6
+#define USB_PRODUCT_HANDHELDLEGEND_PROGCC                 0x10df
+#define USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE             0x10dd
+#define USB_PRODUCT_BONJIRICHANNEL_FIREBIRD               0x10e0 
 
 // USB usage pages
 #define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001