diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index 123b534b2ab32..ff85632b31238 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -30,6 +30,7 @@ #include "controller_type.h" #include "usb_ids.h" #include "hidapi/SDL_hidapi_nintendo.h" +#include "hidapi/SDL_hidapi_sinput.h" #include "../events/SDL_events_c.h" @@ -57,16 +58,6 @@ static bool SDL_gamepads_initialized; static SDL_Gamepad *SDL_gamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL; -// The face button style of a gamepad -typedef enum -{ - SDL_GAMEPAD_FACE_STYLE_UNKNOWN, - SDL_GAMEPAD_FACE_STYLE_ABXY, - SDL_GAMEPAD_FACE_STYLE_AXBY, - SDL_GAMEPAD_FACE_STYLE_BAYX, - SDL_GAMEPAD_FACE_STYLE_SONY, -} SDL_GamepadFaceStyle; - // our hard coded list of mapping support typedef enum { @@ -697,10 +688,11 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) char mapping_string[1024]; Uint16 vendor; Uint16 product; + Uint16 version; SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string)); - SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, &version, NULL); if (SDL_IsJoystickWheel(vendor, product)) { // We don't want to pick up Logitech FFB wheels here @@ -799,55 +791,42 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) // 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] & 0xE0) >> 5; - Uint8 sub_type = guid.data[15] & 0x1F; + struct SDL_SInputFeatures features; + HIDAPI_DriverSInput_GetControllerType(vendor, product, version, guid.data[15], &features); - // Apply face style according to gamepad response - switch (face_style) { + // Apply mapping profile for type + switch (features.controller_type) { + case k_eSInputControllerType_HHL_PROGCC: + SDL_strlcat(mapping_string, "a:b1,b:b0,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b4,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b7,rightstick:b5,righttrigger:b9,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + break; + case k_eSInputControllerType_HHL_GCCULT: + SDL_strlcat(mapping_string, "a:b0,b:b2,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b4,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b13,misc2:b14,rightshoulder:b7,rightstick:b5,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b1,y:b3,misc3:b8,misc4:b9,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); + break; default: + case k_eSInputControllerType_Dynamic: + // Default Fully Exposed Mapping + // TODO... + SDL_strlcat(mapping_string, "leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,b:b0,a:b1,y:b2,x:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b4,rightstick:b5,leftshoulder:b6,rightshoulder:b7,paddle1:b10,paddle2:b11,start:b12,back:b13,guide:b14,misc1:b15,paddle3:b16,paddle4:b17,touchpad:b18,misc2:b19,misc3:b20,misc4:b21,misc5:b22,misc6:b23", sizeof(mapping_string)); + break; + } + + // Apply face style + switch (features.face_style) { + case SDL_GAMEPAD_FACE_STYLE_ABXY: SDL_strlcat(mapping_string, "face:abxy,", sizeof(mapping_string)); break; - case 2: + case SDL_GAMEPAD_FACE_STYLE_AXBY: SDL_strlcat(mapping_string, "face:axby,", sizeof(mapping_string)); break; - case 3: + case SDL_GAMEPAD_FACE_STYLE_BAYX: SDL_strlcat(mapping_string, "face:bayx,", sizeof(mapping_string)); break; - case 4: + case SDL_GAMEPAD_FACE_STYLE_SONY: SDL_strlcat(mapping_string, "face:sony,", sizeof(mapping_string)); break; - } - - switch (product) { - case USB_PRODUCT_HANDHELDLEGEND_PROGCC: - switch (sub_type) { - default: - // ProGCC Primary Mapping - SDL_strlcat(mapping_string, "a:b1,b:b0,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b4,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b7,rightstick:b5,righttrigger:b9,rightx:a2,righty:a3,start:b10,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); - break; - } - break; - case USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE: - switch (sub_type) { - default: - // GC Ultimate Primary Map - SDL_strlcat(mapping_string, "a:b0,b:b2,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b4,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b13,misc2:b14,rightshoulder:b7,rightstick:b5,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b1,y:b3,misc3:b8,misc4:b9,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); - break; - } - break; - case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC: - switch (sub_type) { - default: - // Default Fully Exposed Mapping (Development Purposes) - SDL_strlcat(mapping_string, "leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,b:b0,a:b1,y:b2,x:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b4,rightstick:b5,leftshoulder:b6,rightshoulder:b7,paddle1:b10,paddle2:b11,start:b12,back:b13,guide:b14,misc1:b15,paddle3:b16,paddle4:b17,touchpad:b18,misc2:b19,misc3:b20,misc4:b21,misc5:b22,misc6:b23", sizeof(mapping_string)); - break; - } - break; - - case USB_PRODUCT_BONZIRICHANNEL_FIREBIRD: + case SDL_GAMEPAD_FACE_STYLE_UNKNOWN: default: - // Unmapped device - return NULL; + break; } } else { // All other gamepads have the standard set of 19 buttons and 6 axes diff --git a/src/joystick/SDL_gamepad_c.h b/src/joystick/SDL_gamepad_c.h index f1b1d10e158f2..055766a892259 100644 --- a/src/joystick/SDL_gamepad_c.h +++ b/src/joystick/SDL_gamepad_c.h @@ -24,6 +24,16 @@ #include "SDL_internal.h" +// The face button style of a gamepad +typedef enum +{ + SDL_GAMEPAD_FACE_STYLE_UNKNOWN, + SDL_GAMEPAD_FACE_STYLE_ABXY, + SDL_GAMEPAD_FACE_STYLE_AXBY, + SDL_GAMEPAD_FACE_STYLE_BAYX, + SDL_GAMEPAD_FACE_STYLE_SONY, +} SDL_GamepadFaceStyle; + // Useful functions and variables from SDL_gamepad.c // Initialization and shutdown functions diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index a8bc76f1bf263..f7425e30b425f 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -3216,7 +3216,9 @@ bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id) return true; } } - return false; + + EControllerType eType = GuessControllerType(vendor_id, product_id); + return eType == k_eControllerType_Sinput; } bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id) diff --git a/src/joystick/controller_list.h b/src/joystick/controller_list.h index cd7c4871e50dd..cb61ee1e174ea 100644 --- a/src/joystick/controller_list.h +++ b/src/joystick/controller_list.h @@ -598,4 +598,7 @@ static const ControllerDescription_t arrControllers[] = { { MAKE_CONTROLLER_ID( 0x28de, 0x1201 ), k_eControllerType_SteamControllerV2, NULL }, // Valve wired Steam Controller (HEADCRAB) { MAKE_CONTROLLER_ID( 0x28de, 0x1202 ), k_eControllerType_SteamControllerV2, NULL }, // Valve Bluetooth Steam Controller (HEADCRAB) { MAKE_CONTROLLER_ID( 0x28de, 0x1205 ), k_eControllerType_SteamControllerNeptune, NULL }, // Valve Steam Deck Builtin Controller + + // Sinput controllers + { MAKE_CONTROLLER_ID( 0x16d0, 0x145b ), k_eControllerType_Sinput, NULL }, }; diff --git a/src/joystick/controller_type.c b/src/joystick/controller_type.c index f179d2731bc4f..19627a937727c 100644 --- a/src/joystick/controller_type.c +++ b/src/joystick/controller_type.c @@ -106,6 +106,10 @@ EControllerType GuessControllerType( int nVID, int nPID ) { return k_eControllerType_SteamController; } + if ( SDL_strncasecmp( pszOverride, "sinput", 5 ) == 0 ) + { + return k_eControllerType_Sinput; + } return k_eControllerType_UnknownNonSteamController; } diff --git a/src/joystick/controller_type.h b/src/joystick/controller_type.h index 155c8ad11e159..a3ab2235bc71f 100644 --- a/src/joystick/controller_type.h +++ b/src/joystick/controller_type.h @@ -57,6 +57,7 @@ typedef enum k_eControllerType_XInputSwitchController = 44, // Client-side only, used to mark Nintendo Switch style controllers as using XInput instead of the Nintendo Switch protocol k_eControllerType_PS5Controller = 45, k_eControllerType_XInputPS4Controller = 46, // Client-side only, used to mark DualShock 4 style controllers using XInput instead of the DualShock 4 controller protocol + k_eControllerType_Sinput = 47, k_eControllerType_LastController, // Don't add game controllers below this enumeration - this enumeration can change value // Keyboards and Mice diff --git a/src/joystick/hidapi/SDL_hidapi_sinput.c b/src/joystick/hidapi/SDL_hidapi_sinput.c index 5bb708d1e973d..d85f562e2179b 100644 --- a/src/joystick/hidapi/SDL_hidapi_sinput.c +++ b/src/joystick/hidapi/SDL_hidapi_sinput.c @@ -27,6 +27,7 @@ #include "SDL_hidapijoystick_c.h" #include "SDL_hidapi_rumble.h" +#include "SDL_hidapi_sinput.h" #ifdef SDL_JOYSTICK_HIDAPI_SINPUT @@ -62,63 +63,6 @@ #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 23 -#define SINPUT_REPORT_IDX_IMU_ACCEL_Y 25 -#define SINPUT_REPORT_IDX_IMU_ACCEL_Z 27 -#define SINPUT_REPORT_IDX_IMU_GYRO_X 29 -#define SINPUT_REPORT_IDX_IMU_GYRO_Y 31 -#define SINPUT_REPORT_IDX_IMU_GYRO_Z 33 -#define SINPUT_REPORT_IDX_TOUCH1_X 35 -#define SINPUT_REPORT_IDX_TOUCH1_Y 37 -#define SINPUT_REPORT_IDX_TOUCH1_P 39 -#define SINPUT_REPORT_IDX_TOUCH2_X 41 -#define SINPUT_REPORT_IDX_TOUCH2_Y 43 -#define SINPUT_REPORT_IDX_TOUCH2_P 45 - -#define SINPUT_BUTTON_IDX_EAST 0 -#define SINPUT_BUTTON_IDX_SOUTH 1 -#define SINPUT_BUTTON_IDX_NORTH 2 -#define SINPUT_BUTTON_IDX_WEST 3 -#define SINPUT_BUTTON_IDX_DPAD_UP 4 -#define SINPUT_BUTTON_IDX_DPAD_DOWN 5 -#define SINPUT_BUTTON_IDX_DPAD_LEFT 6 -#define SINPUT_BUTTON_IDX_DPAD_RIGHT 7 -#define SINPUT_BUTTON_IDX_LEFT_STICK 8 -#define SINPUT_BUTTON_IDX_RIGHT_STICK 9 -#define SINPUT_BUTTON_IDX_LEFT_BUMPER 10 -#define SINPUT_BUTTON_IDX_RIGHT_BUMPER 11 -#define SINPUT_BUTTON_IDX_LEFT_TRIGGER 12 -#define SINPUT_BUTTON_IDX_RIGHT_TRIGGER 13 -#define SINPUT_BUTTON_IDX_LEFT_PADDLE1 14 -#define SINPUT_BUTTON_IDX_RIGHT_PADDLE1 15 -#define SINPUT_BUTTON_IDX_START 16 -#define SINPUT_BUTTON_IDX_BACK 17 -#define SINPUT_BUTTON_IDX_GUIDE 18 -#define SINPUT_BUTTON_IDX_CAPTURE 19 -#define SINPUT_BUTTON_IDX_LEFT_PADDLE2 20 -#define SINPUT_BUTTON_IDX_RIGHT_PADDLE2 21 -#define SINPUT_BUTTON_IDX_TOUCHPAD1 22 -#define SINPUT_BUTTON_IDX_TOUCHPAD2 23 -#define SINPUT_BUTTON_IDX_POWER 24 -#define SINPUT_BUTTON_IDX_MISC4 25 -#define SINPUT_BUTTON_IDX_MISC5 26 -#define SINPUT_BUTTON_IDX_MISC6 27 -#define SINPUT_BUTTON_IDX_MISC7 28 -#define SINPUT_BUTTON_IDX_MISC8 29 -#define SINPUT_BUTTON_IDX_MISC9 30 -#define SINPUT_BUTTON_IDX_MISC10 31 - #define SINPUT_REPORT_IDX_COMMAND_RESPONSE_ID 1 #define SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK 2 @@ -204,7 +148,8 @@ typedef struct 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 + Uint8 subtype; + struct SDL_SInputFeatures features; Uint16 accelRange; // Example would be 2,4,8,16 +/- (g-force) Uint16 gyroRange; // Example would be 1000,2000,4000 +/- (degrees per second) @@ -239,71 +184,153 @@ static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data) // Obtain protocol version ctx->protocol_version = EXTRACTUINT16(data, 0); - - // Bitfields are not portable, so we unpack them into a struct value - ctx->rumble_supported = (data[2] & 0x01) != 0; - ctx->player_leds_supported = (data[2] & 0x02) != 0; - ctx->accelerometer_supported = (data[2] & 0x04) != 0; - ctx->gyroscope_supported = (data[2] & 0x08) != 0; - - ctx->left_analog_stick_supported = (data[2] & 0x10) != 0; - ctx->right_analog_stick_supported = (data[2] & 0x20) != 0; - ctx->left_analog_trigger_supported = (data[2] & 0x40) != 0; - ctx->right_analog_trigger_supported = (data[2] & 0x80) != 0; - - ctx->touchpad_supported = (data[3] & 0x01) != 0; - ctx->joystick_rgb_supported = (data[3] & 0x02) != 0; - - ctx->is_handheld = (data[3] & 0x04) != 0; - - // The gamepad type represents a style of gamepad that most closely - // resembles the gamepad in question (Button style, button layout) + Uint8 *fflags = data + 2; + Uint8 *buttons = data + 12; + + // + // Unpack feature flags into context + // + ctx->rumble_supported = (fflags[0] & 0x01) != 0; + ctx->player_leds_supported = (fflags[0] & 0x02) != 0; + ctx->accelerometer_supported = (fflags[0] & 0x04) != 0; + ctx->gyroscope_supported = (fflags[0] & 0x08) != 0; + + // Axes cannot be dynamic, so we only sanity check them + bool left_analog_stick_supported = (fflags[0] & 0x10) != 0; + bool right_analog_stick_supported = (fflags[0] & 0x20) != 0; + bool left_analog_trigger_supported = (fflags[0] & 0x40) != 0; + bool right_analog_trigger_supported = (fflags[0] & 0x80) != 0; + + ctx->touchpad_supported = (fflags[1] & 0x01) != 0; + ctx->joystick_rgb_supported = (fflags[1] & 0x02) != 0; + ctx->is_handheld = (fflags[1] & 0x04) != 0; + + // + // Gamepad Info + // SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN; type = (SDL_GamepadType)SDL_clamp(data[4], SDL_GAMEPAD_TYPE_UNKNOWN, SDL_GAMEPAD_TYPE_COUNT); device->type = type; + + // Update version to the custom one + Uint16 version = HIDAPI_DriverSInput_DeriveVersion(buttons, left_analog_stick_supported, right_analog_stick_supported, + left_analog_trigger_supported, right_analog_trigger_supported); + device->guid.data[6] = version & 0xFF; + device->guid.data[7] = (version >> 8) & 0xFF; // The 3 MSB represent a button layout style SDL_GamepadFaceStyle // The 5 LSB represent a device sub-type device->guid.data[15] = data[5]; - - ctx->sub_type = (data[5] & 0x1F); + ctx->subtype = data[5]; #if defined(DEBUG_SINPUT_INIT) SDL_Log("SInput Face Style: %d", (data[5] & 0xE0) >> 5); SDL_Log("SInput Sub-type: %d", (data[5] & 0x1F)); #endif + // + // IMU Info + // ctx->polling_rate_ms = data[6]; - ctx->accelRange = EXTRACTUINT16(data, 8); ctx->gyroRange = EXTRACTUINT16(data, 10); + // + // Get mappings based on SDL subtype and assert that they match. + // + HIDAPI_DriverSInput_GetControllerType(device->vendor_id, device->product_id, version, ctx->subtype, &ctx->features); + struct SDL_SInputFeatures *f = &ctx->features; + + switch (f->controller_type) { + case k_eSInputControllerType_HHL_PROGCC: + case k_eSInputControllerType_HHL_GCCULT: + case k_eSInputControllerType_LoadFirmware: + ctx->usage_masks[0] = buttons[0]; + ctx->usage_masks[1] = buttons[1]; + ctx->usage_masks[2] = buttons[2]; + ctx->usage_masks[3] = buttons[3]; + ctx->left_analog_stick_supported = left_analog_stick_supported; + ctx->right_analog_stick_supported = right_analog_stick_supported; + ctx->left_analog_trigger_supported = left_analog_trigger_supported; + ctx->right_analog_trigger_supported = right_analog_trigger_supported; + break; + case k_eSInputControllerType_Dynamic: + default: + ctx->usage_masks[0] = 0xFF; + ctx->usage_masks[1] = 0x00; + if (ctx->left_analog_stick_supported) + ctx->usage_masks[1] |= 0x01 << (SINPUT_BTN_IDX_LEFT_STICK % 8); + if (ctx->right_analog_stick_supported) + ctx->usage_masks[1] |= 0x01 << (SINPUT_BTN_IDX_RIGHT_STICK % 8); + if (f->trigger_style == k_eSInputMiscTriggerStyle_DigitalBumpers || + f->trigger_style == k_eSInputMiscTriggerStyle_AnalogBumpers) + ctx->usage_masks[1] |= (0x01 << (SINPUT_BTN_IDX_LEFT_BUMPER % 8)) | + (0x01 << (SINPUT_BTN_IDX_RIGHT_BUMPER % 8)); + if (f->trigger_style == k_eSInputMiscTriggerStyle_Digital || + f->trigger_style == k_eSInputMiscTriggerStyle_DigitalBumpers) + ctx->usage_masks[1] |= (0x01 << (SINPUT_BTN_IDX_LEFT_TRIGGER % 8)) | + (0x01 << (SINPUT_BTN_IDX_RIGHT_TRIGGER % 8)); + if (f->paddle_style != k_eSInputPaddleStyle_None) + ctx->usage_masks[1] |= (0x01 << (SINPUT_BTN_IDX_LEFT_PADDLE1 % 8)) | + (0x01 << (SINPUT_BTN_IDX_RIGHT_PADDLE1 % 8)); + + ctx->usage_masks[2] = 0x03; + if (f->guide_style == k_eSInputHasButtonStyle_Yes) + ctx->usage_masks[2] |= (0x01 << (SINPUT_BTN_IDX_GUIDE % 8)); + if (f->guide_style == k_eSInputHasButtonStyle_Yes) + ctx->usage_masks[2] |= (0x01 << (SINPUT_BTN_IDX_GUIDE % 8)); + if (f->misc_button_style != k_eSInputMiscButtonStyle_None) + ctx->usage_masks[2] |= (0x01 << (SINPUT_BTN_IDX_CAPTURE % 8)); + if (f->paddle_style == k_eSInputPaddleStyle_Four) + ctx->usage_masks[2] |= (0x01 << (SINPUT_BTN_IDX_LEFT_PADDLE2 % 8)) | + (0x01 << (SINPUT_BTN_IDX_RIGHT_PADDLE2 % 8)); + if (f->misc_button_style != k_eSInputMiscButtonStyle_Two && f->misc_button_style != k_eSInputMiscButtonStyle_None) + ctx->usage_masks[2] |= 0x01 << (SINPUT_BTN_IDX_TOUCHPAD2 % 8); + + ctx->usage_masks[3] = 0x00; + for (int i = k_eSInputMiscButtonStyle_Two; i < k_eSInputMiscButtonStyle_Max; ++i) + ctx->usage_masks[3] |= 0x01 << (i - 2); + + ctx->left_analog_stick_supported = f->analog_style != k_eSInputanalog_None; + ctx->right_analog_stick_supported = f->analog_style == k_eSInputanalog_Two; + + ctx->left_analog_trigger_supported = (f->trigger_style == k_eSInputMiscTriggerStyle_Analog && + f->trigger_style == k_eSInputMiscTriggerStyle_DigitalBumpers); + ctx->right_analog_trigger_supported = ctx->left_analog_trigger_supported; + break; + } - if ((device->product_id == USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC) && (device->vendor_id == USB_VENDOR_RASPBERRYPI)) { - switch (ctx->sub_type) { - // SInput generic device, exposes all buttons - default: - case 0: - ctx->usage_masks[0] = 0xFF; - ctx->usage_masks[1] = 0xFF; - ctx->usage_masks[2] = 0xFF; - ctx->usage_masks[3] = 0xFF; - break; - } - } else { + // Since SDL uses fixed mappings, unfortunately we cannot use the + // button mask from the protocol. SInput defines a set of predefined + // sub-types for this use. However, we can check it matches our expectations. + if ( // Masks in LSB to MSB // South, East, West, North, DUp, DDown, DLeft, DRight - ctx->usage_masks[0] = data[12]; - - // Stick Left, Stick Right, L Shoulder, R Shoulder, - // L Digital Trigger, R Digital Trigger, L Paddle 1, R Paddle 1 - ctx->usage_masks[1] = data[13]; - + ctx->usage_masks[0] != buttons[0] || + // Left Stick, Right Stick, L Shoulder, R Shoulder, + // L Trigger, R Trigger, L Paddle 1, R Paddle 1 + ctx->usage_masks[1] != buttons[1] || // Start, Back, Guide, Capture, L Paddle 2, R Paddle 2, Touchpad L, Touchpad R - ctx->usage_masks[2] = data[14]; - + ctx->usage_masks[2] != buttons[2] || // Power, Misc 4 to 10 - ctx->usage_masks[3] = data[15]; + ctx->usage_masks[3] != buttons[3] || + // Check Axes + ctx->left_analog_stick_supported != left_analog_stick_supported || + ctx->right_analog_stick_supported != right_analog_stick_supported || + ctx->left_analog_trigger_supported != left_analog_trigger_supported || + ctx->right_analog_trigger_supported != right_analog_trigger_supported + ) { + SDL_LogWarn( + SDL_LOG_CATEGORY_INPUT, + "SInput device %s has different button mask than controller type 0x%.8x or that type is uknown. Found: %.2x%.2x%.2x%.2x-%d%d%d%d and will use: %.2x%.2x%.2x%.2x-%d%d%d%d", + device->name, f->controller_type, + buttons[0], buttons[1], buttons[2], buttons[3], + left_analog_stick_supported, right_analog_stick_supported, + left_analog_trigger_supported, right_analog_trigger_supported, + ctx->usage_masks[0], ctx->usage_masks[1], ctx->usage_masks[2], ctx->usage_masks[3], + ctx->left_analog_stick_supported, ctx->right_analog_stick_supported, + ctx->left_analog_trigger_supported, ctx->right_analog_trigger_supported + ); } // Derive button count from mask @@ -316,10 +343,10 @@ static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data) } // Convert DPAD to hat - const int DPAD_MASK = (1 << SINPUT_BUTTON_IDX_DPAD_UP) | - (1 << SINPUT_BUTTON_IDX_DPAD_DOWN) | - (1 << SINPUT_BUTTON_IDX_DPAD_LEFT) | - (1 << SINPUT_BUTTON_IDX_DPAD_RIGHT); + const int DPAD_MASK = (1 << SINPUT_BTN_IDX_DPAD_UP) | + (1 << SINPUT_BTN_IDX_DPAD_DOWN) | + (1 << SINPUT_BTN_IDX_DPAD_LEFT) | + (1 << SINPUT_BTN_IDX_DPAD_RIGHT); if ((ctx->usage_masks[0] & DPAD_MASK) == DPAD_MASK) { ctx->dpad_supported = true; ctx->usage_masks[0] &= ~DPAD_MASK; @@ -540,16 +567,6 @@ static bool HIDAPI_DriverSInput_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys ++axes; } - if ((device->product_id == USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC) && (device->vendor_id == USB_VENDOR_RASPBERRYPI)) { - switch (ctx->sub_type) { - // Default generic device, exposes all axes - default: - case 0: - axes = 6; - break; - } - } - joystick->naxes = axes; if (ctx->dpad_supported) { @@ -704,16 +721,16 @@ static void HIDAPI_DriverSInput_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr if (ctx->dpad_supported) { Uint8 hat = SDL_HAT_CENTERED; - if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_UP)) { + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BTN_IDX_DPAD_UP)) { hat |= SDL_HAT_UP; } - if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_DOWN)) { + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BTN_IDX_DPAD_DOWN)) { hat |= SDL_HAT_DOWN; } - if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_LEFT)) { + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BTN_IDX_DPAD_LEFT)) { hat |= SDL_HAT_LEFT; } - if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BUTTON_IDX_DPAD_RIGHT)) { + if (data[SINPUT_REPORT_IDX_BUTTONS_0] & (1 << SINPUT_BTN_IDX_DPAD_RIGHT)) { hat |= SDL_HAT_RIGHT; } SDL_SendJoystickHat(timestamp, joystick, 0, hat); diff --git a/src/joystick/hidapi/SDL_hidapi_sinput.h b/src/joystick/hidapi/SDL_hidapi_sinput.h new file mode 100644 index 0000000000000..70507f1f2a50c --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_sinput.h @@ -0,0 +1,244 @@ +/* + Simple DirectMedia Layer + Copyright (C) 2025 Antheas Kapenekakis + + 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_gamepad_c.h" + +#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 23 +#define SINPUT_REPORT_IDX_IMU_ACCEL_Y 25 +#define SINPUT_REPORT_IDX_IMU_ACCEL_Z 27 +#define SINPUT_REPORT_IDX_IMU_GYRO_X 29 +#define SINPUT_REPORT_IDX_IMU_GYRO_Y 31 +#define SINPUT_REPORT_IDX_IMU_GYRO_Z 33 +#define SINPUT_REPORT_IDX_TOUCH1_X 35 +#define SINPUT_REPORT_IDX_TOUCH1_Y 37 +#define SINPUT_REPORT_IDX_TOUCH1_P 39 +#define SINPUT_REPORT_IDX_TOUCH2_X 41 +#define SINPUT_REPORT_IDX_TOUCH2_Y 43 +#define SINPUT_REPORT_IDX_TOUCH2_P 45 + +#define SINPUT_BTN_IDX_EAST 0 +#define SINPUT_BTN_IDX_SOUTH 1 +#define SINPUT_BTN_IDX_NORTH 2 +#define SINPUT_BTN_IDX_WEST 3 +#define SINPUT_BTN_IDX_DPAD_UP 4 +#define SINPUT_BTN_IDX_DPAD_DOWN 5 +#define SINPUT_BTN_IDX_DPAD_LEFT 6 +#define SINPUT_BTN_IDX_DPAD_RIGHT 7 + +#define SINPUT_BTN_IDX_LEFT_STICK 8 +#define SINPUT_BTN_IDX_RIGHT_STICK 9 +#define SINPUT_BTN_IDX_LEFT_BUMPER 10 +#define SINPUT_BTN_IDX_RIGHT_BUMPER 11 +#define SINPUT_BTN_IDX_LEFT_TRIGGER 12 +#define SINPUT_BTN_IDX_RIGHT_TRIGGER 13 +#define SINPUT_BTN_IDX_LEFT_PADDLE1 14 +#define SINPUT_BTN_IDX_RIGHT_PADDLE1 15 + +#define SINPUT_BTN_IDX_START 16 +#define SINPUT_BTN_IDX_BACK 17 +#define SINPUT_BTN_IDX_GUIDE 18 +#define SINPUT_BTN_IDX_CAPTURE 19 +#define SINPUT_BTN_IDX_LEFT_PADDLE2 20 +#define SINPUT_BTN_IDX_RIGHT_PADDLE2 21 +#define SINPUT_BTN_IDX_TOUCHPAD1 22 +#define SINPUT_BTN_IDX_TOUCHPAD2 23 + +#define SINPUT_BTN_IDX_POWER 24 +#define SINPUT_BTN_IDX_MISC4 25 +#define SINPUT_BTN_IDX_MISC5 26 +#define SINPUT_BTN_IDX_MISC6 27 +#define SINPUT_BTN_IDX_MISC7 28 +#define SINPUT_BTN_IDX_MISC8 29 +#define SINPUT_BTN_IDX_MISC9 30 +#define SINPUT_BTN_IDX_MISC10 31 + +typedef enum +{ + k_eSInputanalog_None, + k_eSInputanalog_One, + k_eSInputanalog_Two, + k_eSInputanalog_Max, +} ESInputanalogStyle; + +typedef enum +{ + k_eSInputMiscTriggerStyle_None, + k_eSInputMiscTriggerStyle_Analog, + k_eSInputMiscTriggerStyle_Digital, + k_eSInputMiscTriggerStyle_AnalogBumpers, + k_eSInputMiscTriggerStyle_DigitalBumpers, + k_eSInputMiscTriggerStyle_Max, +} ESInputTriggerStyle; + +typedef enum +{ + k_eSInputPaddleStyle_None, + k_eSInputPaddleStyle_Two, + k_eSInputPaddleStyle_Four, + k_eSInputPaddleStyle_Max, +} ESInputPaddleStyle; + +typedef enum +{ + k_eSInputHasButtonStyle_No, + k_eSInputHasButtonStyle_Yes, + k_eSInputHasButtonStyle_Max, +} ESInputHasButtonStyle; + +typedef enum +{ + k_eSInputMiscButtonStyle_None = 0, + k_eSInputMiscButtonStyle_One = 1, + k_eSInputMiscButtonStyle_Two, + k_eSInputMiscButtonStyle_Three, + k_eSInputMiscButtonStyle_Four, + k_eSInputMiscButtonStyle_Five, + k_eSInputMiscButtonStyle_Six, + k_eSInputMiscButtonStyle_Seven, + k_eSInputMiscButtonStyle_Max, +} ESInputMiscButtonStyle; + +typedef enum +{ + k_eSInputControllerType_Dynamic = 0x00, + k_eSInputControllerType_HHL_PROGCC = 0xffff0100, + k_eSInputControllerType_HHL_GCCULT = 0xffff0101, + k_eSInputControllerType_LoadFirmware = 0xffffffff, +} ESinputControllerType; + +struct SDL_SInputFeatures +{ + ESinputControllerType controller_type; + SDL_GamepadFaceStyle face_style; + + ESInputanalogStyle analog_style; + ESInputTriggerStyle trigger_style; + ESInputHasButtonStyle guide_style; + ESInputPaddleStyle paddle_style; + ESInputHasButtonStyle click_style; + ESInputMiscButtonStyle misc_button_style; +}; + +static inline Uint16 HIDAPI_DriverSInput_DeriveVersion(Uint8 *buttons, bool left_analog, bool right_analog, bool left_trigger, bool right_trigger) +{ + Uint16 version = 0; + + // Analog sticks + if (right_analog) { + version += k_eSInputanalog_Two; + } else if (left_analog) { + version += k_eSInputanalog_One; + } + version *= k_eSInputanalog_Max; + + // Trigger style + bool analogTriggers = left_trigger || right_trigger; + bool digitalTriggers = (buttons[SINPUT_BTN_IDX_LEFT_TRIGGER >> 3] & (1 << (SINPUT_BTN_IDX_LEFT_TRIGGER & 0x07))) || + (buttons[SINPUT_BTN_IDX_RIGHT_TRIGGER >> 3] & (1 << (SINPUT_BTN_IDX_RIGHT_TRIGGER & 0x07))); + bool bumpers = (buttons[SINPUT_BTN_IDX_LEFT_BUMPER >> 3] & (1 << (SINPUT_BTN_IDX_LEFT_BUMPER & 0x07))) || + (buttons[SINPUT_BTN_IDX_RIGHT_BUMPER >> 3] & (1 << (SINPUT_BTN_IDX_RIGHT_BUMPER & 0x07))); + if (analogTriggers && bumpers) { + version += k_eSInputMiscTriggerStyle_AnalogBumpers; + } else if (digitalTriggers && bumpers) { + version += k_eSInputMiscTriggerStyle_DigitalBumpers; + } else if (analogTriggers) { + version += k_eSInputMiscTriggerStyle_Analog; + } else if (digitalTriggers) { + version += k_eSInputMiscTriggerStyle_Digital; + } else { + version += k_eSInputMiscTriggerStyle_None; + } + version *= k_eSInputMiscTriggerStyle_Max; + + // Guide button style + if ((buttons[SINPUT_BTN_IDX_GUIDE >> 3] & (1 << (SINPUT_BTN_IDX_GUIDE & 0x07))) != 0) + version += k_eSInputHasButtonStyle_Yes; + version *= k_eSInputHasButtonStyle_Max; + + // Paddles + bool hasTopPaddles = (buttons[SINPUT_BTN_IDX_LEFT_PADDLE1 >> 3] & (1 << (SINPUT_BTN_IDX_LEFT_PADDLE1 & 0x07))) != 0 || + (buttons[SINPUT_BTN_IDX_RIGHT_PADDLE1 >> 3] & (1 << (SINPUT_BTN_IDX_RIGHT_PADDLE1 & 0x07))) != 0; + bool hasBottomPaddles = (buttons[SINPUT_BTN_IDX_LEFT_PADDLE2 >> 3] & (1 << (SINPUT_BTN_IDX_LEFT_PADDLE2 & 0x07))) != 0 || + (buttons[SINPUT_BTN_IDX_RIGHT_PADDLE2 >> 3] & (1 << (SINPUT_BTN_IDX_RIGHT_PADDLE2 & 0x07))) != 0; + if (hasTopPaddles && hasBottomPaddles) { + version += k_eSInputPaddleStyle_Four; + } else if (hasTopPaddles) { + version += k_eSInputPaddleStyle_Two; + } else { + version += k_eSInputPaddleStyle_None; + } + version *= k_eSInputPaddleStyle_Max; + + // Touchpad click style + if ((buttons[SINPUT_BTN_IDX_TOUCHPAD1 >> 3] & (1 << (SINPUT_BTN_IDX_TOUCHPAD1 & 0x07))) != 0) + version += k_eSInputHasButtonStyle_Yes; + version *= k_eSInputHasButtonStyle_Max; + + // Misc button style + Uint8 miscButtonCount = 0; + if ((buttons[SINPUT_BTN_IDX_CAPTURE >> 3] & (1 << (SINPUT_BTN_IDX_CAPTURE & 0x07))) != 0) + miscButtonCount++; + for (int i = SINPUT_BTN_IDX_TOUCHPAD2; i <= SINPUT_BTN_IDX_MISC10; ++i) { + if (buttons[i >> 3] & (1 << (i & 0x07))) { + ++miscButtonCount; + } + } + + return version; +} + +static inline void HIDAPI_DriverSInput_GetControllerType( + Uint16 vendor, Uint16 product, Uint16 version, Uint8 subtype, struct SDL_SInputFeatures *features) +{ + features->face_style = (subtype & 0xE0) >> 5; + features->controller_type = k_eSInputControllerType_Dynamic; + + if (vendor == USB_VENDOR_RASPBERRYPI && product == USB_PRODUCT_HANDHELDLEGEND_PROGCC) { + features->controller_type = k_eSInputControllerType_HHL_PROGCC; + } else if (vendor == USB_VENDOR_RASPBERRYPI && product == USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE) { + features->controller_type = k_eSInputControllerType_HHL_GCCULT; + } + + // Decode dynamic features + features->analog_style = version % k_eSInputanalog_Max; + version /= k_eSInputanalog_Max; + features->trigger_style = version % k_eSInputMiscTriggerStyle_Max; + version /= k_eSInputMiscTriggerStyle_Max; + features->guide_style = version % k_eSInputHasButtonStyle_Max; + version /= k_eSInputHasButtonStyle_Max; + features->paddle_style = version % k_eSInputPaddleStyle_Max; + version /= k_eSInputPaddleStyle_Max; + features->click_style = version % k_eSInputHasButtonStyle_Max; + // version /= k_eSInputHasButtonStyle_Max; + features->misc_button_style = version % k_eSInputMiscButtonStyle_Max; + version /= k_eSInputMiscButtonStyle_Max; +} \ No newline at end of file