mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-09-29 14:38:29 +00:00
SInput: Version as a capabilities vehicle (#13667)
* SInput: version capabilities compression This commit includes additions relating to SInput generic device reporting capabilities in a bit more detail, to automatically choose the best input map possible for the given device. Thanks to Antheas Kapenekakis (git@antheas.dev) for contributing the neat compression algorithm, this is pulled from the PR Draft here: https://github.com/libsdl-org/SDL/pull/13565 Co-authored-by: Antheas Kapenekakis <git@antheas.dev>
This commit is contained in:
@@ -31,6 +31,7 @@
|
|||||||
#include "usb_ids.h"
|
#include "usb_ids.h"
|
||||||
#include "hidapi/SDL_hidapi_flydigi.h"
|
#include "hidapi/SDL_hidapi_flydigi.h"
|
||||||
#include "hidapi/SDL_hidapi_nintendo.h"
|
#include "hidapi/SDL_hidapi_nintendo.h"
|
||||||
|
#include "hidapi/SDL_hidapi_sinput.h"
|
||||||
#include "../events/SDL_events_c.h"
|
#include "../events/SDL_events_c.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -55,6 +56,26 @@
|
|||||||
#define SDL_GAMEPAD_SDKLE_FIELD "sdk<=:"
|
#define SDL_GAMEPAD_SDKLE_FIELD "sdk<=:"
|
||||||
#define SDL_GAMEPAD_SDKLE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKLE_FIELD)
|
#define SDL_GAMEPAD_SDKLE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKLE_FIELD)
|
||||||
|
|
||||||
|
// Helper function to add button mapping
|
||||||
|
#ifndef ADD_BUTTON_MAPPING
|
||||||
|
#define SDL_ADD_BUTTON_MAPPING(sdl_name, button_id, maxlen) \
|
||||||
|
do { \
|
||||||
|
char temp[32]; \
|
||||||
|
(void)SDL_snprintf(temp, sizeof(temp), "%s:b%d,", sdl_name, button_id); \
|
||||||
|
SDL_strlcat(mapping_string, temp, maxlen); \
|
||||||
|
} while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Helper function to add axis mapping
|
||||||
|
#ifndef ADD_AXIS_MAPPING
|
||||||
|
#define SDL_ADD_AXIS_MAPPING(sdl_name, axis_id, maxlen) \
|
||||||
|
do { \
|
||||||
|
char temp[32]; \
|
||||||
|
(void)SDL_snprintf(temp, sizeof(temp), "%s:a%d,", sdl_name, axis_id); \
|
||||||
|
SDL_strlcat(mapping_string, temp, maxlen); \
|
||||||
|
} while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
static bool SDL_gamepads_initialized;
|
static bool SDL_gamepads_initialized;
|
||||||
static SDL_Gamepad *SDL_gamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
|
static SDL_Gamepad *SDL_gamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
|
||||||
|
|
||||||
@@ -689,6 +710,304 @@ static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_GUID guid)
|
|||||||
}
|
}
|
||||||
#endif // SDL_PLATFORM_ANDROID
|
#endif // SDL_PLATFORM_ANDROID
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper function to apply SInput decoded styles to the mapping string
|
||||||
|
*/
|
||||||
|
static inline void SDL_SInputStylesMapExtraction(SDL_SInputStyles_t* styles, char* mapping_string, size_t mapping_string_len)
|
||||||
|
{
|
||||||
|
int current_button = 0;
|
||||||
|
int current_axis = 0;
|
||||||
|
int misc_buttons = 0;
|
||||||
|
bool digital_triggers = false;
|
||||||
|
bool dualstage_triggers = false;
|
||||||
|
int bumpers = 0;
|
||||||
|
bool left_stick = false;
|
||||||
|
bool right_stick = false;
|
||||||
|
int paddle_pairs = 0;
|
||||||
|
|
||||||
|
// Determine how many misc buttons are used
|
||||||
|
switch (styles->misc_style) {
|
||||||
|
case SINPUT_MISCSTYLE_1:
|
||||||
|
misc_buttons = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SINPUT_MISCSTYLE_2:
|
||||||
|
misc_buttons = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SINPUT_MISCSTYLE_3:
|
||||||
|
misc_buttons = 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SINPUT_MISCSTYLE_4:
|
||||||
|
misc_buttons = 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analog joysticks (always come first in axis mapping)
|
||||||
|
switch (styles->analog_style) {
|
||||||
|
case SINPUT_ANALOGSTYLE_LEFTONLY:
|
||||||
|
SDL_ADD_AXIS_MAPPING("leftx", current_axis++, mapping_string_len);
|
||||||
|
SDL_ADD_AXIS_MAPPING("lefty", current_axis++, mapping_string_len);
|
||||||
|
left_stick = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SINPUT_ANALOGSTYLE_LEFTRIGHT:
|
||||||
|
SDL_ADD_AXIS_MAPPING("leftx", current_axis++, mapping_string_len);
|
||||||
|
SDL_ADD_AXIS_MAPPING("lefty", current_axis++, mapping_string_len);
|
||||||
|
SDL_ADD_AXIS_MAPPING("rightx", current_axis++, mapping_string_len);
|
||||||
|
SDL_ADD_AXIS_MAPPING("righty", current_axis++, mapping_string_len);
|
||||||
|
left_stick = true;
|
||||||
|
right_stick = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SINPUT_ANALOGSTYLE_RIGHTONLY:
|
||||||
|
SDL_ADD_AXIS_MAPPING("rightx", current_axis++, mapping_string_len);
|
||||||
|
SDL_ADD_AXIS_MAPPING("righty", current_axis++, mapping_string_len);
|
||||||
|
right_stick = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bumpers
|
||||||
|
switch (styles->bumper_style) {
|
||||||
|
case SINPUT_BUMPERSTYLE_ONE:
|
||||||
|
bumpers = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SINPUT_BUMPERSTYLE_TWO:
|
||||||
|
bumpers = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analog triggers
|
||||||
|
switch (styles->trigger_style) {
|
||||||
|
// Analog triggers
|
||||||
|
case SINPUT_TRIGGERSTYLE_ANALOG:
|
||||||
|
SDL_ADD_AXIS_MAPPING("lefttrigger", current_axis++, mapping_string_len);
|
||||||
|
SDL_ADD_AXIS_MAPPING("righttrigger", current_axis++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Digital triggers
|
||||||
|
case SINPUT_TRIGGERSTYLE_DIGITAL:
|
||||||
|
digital_triggers = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Analog triggers with digital press
|
||||||
|
case SINPUT_TRIGGERSTYLE_DUALSTAGE:
|
||||||
|
SDL_ADD_AXIS_MAPPING("lefttrigger", current_axis++, mapping_string_len);
|
||||||
|
SDL_ADD_AXIS_MAPPING("righttrigger", current_axis++, mapping_string_len);
|
||||||
|
dualstage_triggers = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (styles->paddle_style) {
|
||||||
|
case SINPUT_PADDLESTYLE_TWO:
|
||||||
|
paddle_pairs = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SINPUT_PADDLESTYLE_FOUR:
|
||||||
|
paddle_pairs = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Digital button mappings
|
||||||
|
// ABXY buttons (always applied as South, East, West, North)
|
||||||
|
SDL_ADD_BUTTON_MAPPING("a", current_button++, mapping_string_len); // South (typically A on Xbox, X on PlayStation)
|
||||||
|
SDL_ADD_BUTTON_MAPPING("b", current_button++, mapping_string_len); // East (typically B on Xbox, Circle on PlayStation)
|
||||||
|
SDL_ADD_BUTTON_MAPPING("x", current_button++, mapping_string_len); // West (typically X on Xbox, Square on PlayStation)
|
||||||
|
SDL_ADD_BUTTON_MAPPING("y", current_button++, mapping_string_len); // North (typically Y on Xbox, Triangle on PlayStation)
|
||||||
|
|
||||||
|
// D-Pad (always applied)
|
||||||
|
SDL_strlcat(mapping_string, "dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,", mapping_string_len);
|
||||||
|
|
||||||
|
// Left and Right stick buttons
|
||||||
|
if (left_stick) {
|
||||||
|
SDL_ADD_BUTTON_MAPPING("leftstick", current_button++, mapping_string_len);
|
||||||
|
}
|
||||||
|
if (right_stick) {
|
||||||
|
SDL_ADD_BUTTON_MAPPING("rightstick", current_button++, mapping_string_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Digital shoulder buttons (L/R Shoulder)
|
||||||
|
if (bumpers > 0) {
|
||||||
|
SDL_ADD_BUTTON_MAPPING("leftshoulder", current_button++, mapping_string_len);
|
||||||
|
}
|
||||||
|
if (bumpers > 1) {
|
||||||
|
SDL_ADD_BUTTON_MAPPING("rightshoulder", current_button++, mapping_string_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Digital trigger buttons (capability overrides analog)
|
||||||
|
if (digital_triggers) {
|
||||||
|
SDL_ADD_BUTTON_MAPPING("lefttrigger", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("righttrigger", current_button++, mapping_string_len);
|
||||||
|
} else if (dualstage_triggers) {
|
||||||
|
// Dual-stage trigger buttons are appended as MISC buttons
|
||||||
|
// but only if we have the space to use them.
|
||||||
|
if (misc_buttons <= 2) {
|
||||||
|
switch (misc_buttons) {
|
||||||
|
case 0:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc3", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc4", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc4", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc5", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc5", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc6", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// We do not overwrite other misc buttons if they are used.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paddle 1/2
|
||||||
|
if (paddle_pairs > 0) {
|
||||||
|
SDL_ADD_BUTTON_MAPPING("paddle1", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("paddle2", current_button++, mapping_string_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start/Plus
|
||||||
|
SDL_ADD_BUTTON_MAPPING("start", current_button++, mapping_string_len);
|
||||||
|
|
||||||
|
// Back/Minus, Guide/Home, Share/Capture
|
||||||
|
switch (styles->meta_style) {
|
||||||
|
case SINPUT_METASTYLE_BACK:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("back", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SINPUT_METASTYLE_BACKGUIDE:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("back", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("guide", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SINPUT_METASTYLE_BACKGUIDESHARE:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("back", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("guide", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc1", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paddle 3/4
|
||||||
|
if (paddle_pairs > 1) {
|
||||||
|
SDL_ADD_BUTTON_MAPPING("paddle3", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("paddle4", current_button++, mapping_string_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touchpad buttons
|
||||||
|
switch (styles->touch_style) {
|
||||||
|
case SINPUT_TOUCHSTYLE_SINGLE:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("touchpad", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SINPUT_TOUCHSTYLE_DOUBLE:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("touchpad", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc2", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (misc_buttons) {
|
||||||
|
case 1:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc3", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc3", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc4", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc3", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc4", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc5", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc3", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc4", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc5", current_button++, mapping_string_len);
|
||||||
|
SDL_ADD_BUTTON_MAPPING("misc6", current_button++, mapping_string_len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper function to decode SInput features information packed into version
|
||||||
|
*/
|
||||||
|
static void SDL_CreateMappingStringForSInputGamepad(Uint16 vendor, Uint16 product, Uint8 sub_product, Uint16 version, Uint8 face_style, char* mapping_string, size_t mapping_string_len)
|
||||||
|
{
|
||||||
|
SDL_SInputStyles_t decoded = { 0 };
|
||||||
|
|
||||||
|
switch (face_style) {
|
||||||
|
default:
|
||||||
|
SDL_strlcat(mapping_string, "face:abxy,", mapping_string_len);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
SDL_strlcat(mapping_string, "face:axby,", mapping_string_len);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
SDL_strlcat(mapping_string, "face:bayx,", mapping_string_len);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
SDL_strlcat(mapping_string, "face:sony,", mapping_string_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpret the mapping string
|
||||||
|
// dynamically based on the feature responses
|
||||||
|
decoded.misc_style = (SInput_MiscStyleType)(version % SINPUT_MISCSTYLE_MAX);
|
||||||
|
version /= SINPUT_MISCSTYLE_MAX;
|
||||||
|
|
||||||
|
decoded.touch_style = (SInput_TouchStyleType)(version % SINPUT_TOUCHSTYLE_MAX);
|
||||||
|
version /= SINPUT_TOUCHSTYLE_MAX;
|
||||||
|
|
||||||
|
decoded.meta_style = (SInput_MetaStyleType)(version % SINPUT_METASTYLE_MAX);
|
||||||
|
version /= SINPUT_METASTYLE_MAX;
|
||||||
|
|
||||||
|
decoded.paddle_style = (SInput_PaddleStyleType)(version % SINPUT_PADDLESTYLE_MAX);
|
||||||
|
version /= SINPUT_PADDLESTYLE_MAX;
|
||||||
|
|
||||||
|
decoded.trigger_style = (SInput_TriggerStyleType)(version % SINPUT_TRIGGERSTYLE_MAX);
|
||||||
|
version /= SINPUT_TRIGGERSTYLE_MAX;
|
||||||
|
|
||||||
|
decoded.bumper_style = (SInput_BumperStyleType)(version % SINPUT_BUMPERSTYLE_MAX);
|
||||||
|
version /= SINPUT_BUMPERSTYLE_MAX;
|
||||||
|
|
||||||
|
decoded.analog_style = (SInput_AnalogStyleType)(version % SINPUT_ANALOGSTYLE_MAX);
|
||||||
|
|
||||||
|
SDL_SInputStylesMapExtraction(&decoded, mapping_string, mapping_string_len);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Helper function to guess at a mapping for HIDAPI gamepads
|
* Helper function to guess at a mapping for HIDAPI gamepads
|
||||||
*/
|
*/
|
||||||
@@ -698,10 +1017,11 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
|
|||||||
char mapping_string[1024];
|
char mapping_string[1024];
|
||||||
Uint16 vendor;
|
Uint16 vendor;
|
||||||
Uint16 product;
|
Uint16 product;
|
||||||
|
Uint16 version;
|
||||||
|
|
||||||
SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));
|
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)) {
|
if (SDL_IsJoystickWheel(vendor, product)) {
|
||||||
// We don't want to pick up Logitech FFB wheels here
|
// We don't want to pick up Logitech FFB wheels here
|
||||||
@@ -827,56 +1147,11 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
|
|||||||
// This controller has no guide button
|
// 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));
|
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)) {
|
} else if (SDL_IsJoystickSInputController(vendor, product)) {
|
||||||
|
|
||||||
Uint8 face_style = (guid.data[15] & 0xE0) >> 5;
|
Uint8 face_style = (guid.data[15] & 0xE0) >> 5;
|
||||||
Uint8 sub_type = guid.data[15] & 0x1F;
|
Uint8 sub_product = guid.data[15] & 0x1F;
|
||||||
|
|
||||||
// Apply face style according to gamepad response
|
SDL_CreateMappingStringForSInputGamepad(vendor, product, sub_product, version, face_style, mapping_string, sizeof(mapping_string));
|
||||||
switch (face_style) {
|
|
||||||
default:
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (product) {
|
|
||||||
case USB_PRODUCT_HANDHELDLEGEND_PROGCC:
|
|
||||||
switch (sub_type) {
|
|
||||||
default:
|
|
||||||
// ProGCC Primary Mapping
|
|
||||||
SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,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,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:b1,x:b2,y:b3,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,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,a:b0,b:b1,x:b2,y: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:
|
|
||||||
default:
|
|
||||||
// Unmapped device
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// All other gamepads have the standard set of 19 buttons and 6 axes
|
// All other gamepads have the standard set of 19 buttons and 6 axes
|
||||||
if (SDL_IsJoystickGameCube(vendor, product)) {
|
if (SDL_IsJoystickGameCube(vendor, product)) {
|
||||||
|
@@ -3221,7 +3221,8 @@ bool SDL_IsJoystickSInputController(Uint16 vendor_id, Uint16 product_id)
|
|||||||
if (product_id == USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC ||
|
if (product_id == USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC ||
|
||||||
product_id == USB_PRODUCT_HANDHELDLEGEND_PROGCC ||
|
product_id == USB_PRODUCT_HANDHELDLEGEND_PROGCC ||
|
||||||
product_id == USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE ||
|
product_id == USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE ||
|
||||||
product_id == USB_PRODUCT_BONZIRICHANNEL_FIREBIRD) {
|
product_id == USB_PRODUCT_BONZIRICHANNEL_FIREBIRD ||
|
||||||
|
product_id == USB_PRODUCT_VOIDGAMING_PS4FIREBIRD) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,12 +27,13 @@
|
|||||||
|
|
||||||
#include "SDL_hidapijoystick_c.h"
|
#include "SDL_hidapijoystick_c.h"
|
||||||
#include "SDL_hidapi_rumble.h"
|
#include "SDL_hidapi_rumble.h"
|
||||||
|
#include "SDL_hidapi_sinput.h"
|
||||||
|
|
||||||
#ifdef SDL_JOYSTICK_HIDAPI_SINPUT
|
#ifdef SDL_JOYSTICK_HIDAPI_SINPUT
|
||||||
|
|
||||||
/*****************************************************************************************************/
|
/*****************************************************************************************************/
|
||||||
// This protocol is documented at:
|
// This protocol is documented at:
|
||||||
// https://docs.handheldlegend.com/s/sinput/doc/sinput-hid-protocol-TkPYWlDMAg
|
// https://docs.handheldlegend.com/s/sinput
|
||||||
/*****************************************************************************************************/
|
/*****************************************************************************************************/
|
||||||
|
|
||||||
// Define this if you want to log all packets from the controller
|
// Define this if you want to log all packets from the controller
|
||||||
@@ -119,6 +120,39 @@
|
|||||||
#define SINPUT_BUTTON_IDX_MISC9 30
|
#define SINPUT_BUTTON_IDX_MISC9 30
|
||||||
#define SINPUT_BUTTON_IDX_MISC10 31
|
#define SINPUT_BUTTON_IDX_MISC10 31
|
||||||
|
|
||||||
|
#define SINPUT_BUTTONMASK_EAST 0x01
|
||||||
|
#define SINPUT_BUTTONMASK_SOUTH 0x02
|
||||||
|
#define SINPUT_BUTTONMASK_NORTH 0x04
|
||||||
|
#define SINPUT_BUTTONMASK_WEST 0x08
|
||||||
|
#define SINPUT_BUTTONMASK_DPAD_UP 0x10
|
||||||
|
#define SINPUT_BUTTONMASK_DPAD_DOWN 0x20
|
||||||
|
#define SINPUT_BUTTONMASK_DPAD_LEFT 0x40
|
||||||
|
#define SINPUT_BUTTONMASK_DPAD_RIGHT 0x80
|
||||||
|
#define SINPUT_BUTTONMASK_LEFT_STICK 0x01
|
||||||
|
#define SINPUT_BUTTONMASK_RIGHT_STICK 0x02
|
||||||
|
#define SINPUT_BUTTONMASK_LEFT_BUMPER 0x04
|
||||||
|
#define SINPUT_BUTTONMASK_RIGHT_BUMPER 0x08
|
||||||
|
#define SINPUT_BUTTONMASK_LEFT_TRIGGER 0x10
|
||||||
|
#define SINPUT_BUTTONMASK_RIGHT_TRIGGER 0x20
|
||||||
|
#define SINPUT_BUTTONMASK_LEFT_PADDLE1 0x40
|
||||||
|
#define SINPUT_BUTTONMASK_RIGHT_PADDLE1 0x80
|
||||||
|
#define SINPUT_BUTTONMASK_START 0x01
|
||||||
|
#define SINPUT_BUTTONMASK_BACK 0x02
|
||||||
|
#define SINPUT_BUTTONMASK_GUIDE 0x04
|
||||||
|
#define SINPUT_BUTTONMASK_CAPTURE 0x08
|
||||||
|
#define SINPUT_BUTTONMASK_LEFT_PADDLE2 0x10
|
||||||
|
#define SINPUT_BUTTONMASK_RIGHT_PADDLE2 0x20
|
||||||
|
#define SINPUT_BUTTONMASK_TOUCHPAD1 0x40
|
||||||
|
#define SINPUT_BUTTONMASK_TOUCHPAD2 0x80
|
||||||
|
#define SINPUT_BUTTONMASK_POWER 0x01
|
||||||
|
#define SINPUT_BUTTONMASK_MISC4 0x02
|
||||||
|
#define SINPUT_BUTTONMASK_MISC5 0x04
|
||||||
|
#define SINPUT_BUTTONMASK_MISC6 0x08
|
||||||
|
#define SINPUT_BUTTONMASK_MISC7 0x10
|
||||||
|
#define SINPUT_BUTTONMASK_MISC8 0x20
|
||||||
|
#define SINPUT_BUTTONMASK_MISC9 0x40
|
||||||
|
#define SINPUT_BUTTONMASK_MISC10 0x80
|
||||||
|
|
||||||
#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_ID 1
|
#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_ID 1
|
||||||
#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK 2
|
#define SINPUT_REPORT_IDX_COMMAND_RESPONSE_BULK 2
|
||||||
|
|
||||||
@@ -139,7 +173,6 @@
|
|||||||
#define EXTRACTUINT32(data, idx) ((Uint32)((data)[(idx)] | ((data)[(idx) + 1] << 8) | ((data)[(idx) + 2] << 16) | ((data)[(idx) + 3] << 24)))
|
#define EXTRACTUINT32(data, idx) ((Uint32)((data)[(idx)] | ((data)[(idx) + 1] << 8) | ((data)[(idx) + 2] << 16) | ((data)[(idx) + 3] << 24)))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
@@ -183,6 +216,7 @@ typedef struct
|
|||||||
{
|
{
|
||||||
SDL_HIDAPI_Device *device;
|
SDL_HIDAPI_Device *device;
|
||||||
Uint16 protocol_version;
|
Uint16 protocol_version;
|
||||||
|
Uint16 usb_device_version;
|
||||||
bool sensors_enabled;
|
bool sensors_enabled;
|
||||||
|
|
||||||
Uint8 player_idx;
|
Uint8 player_idx;
|
||||||
@@ -203,8 +237,8 @@ typedef struct
|
|||||||
Uint8 touchpad_count; // 2 touchpads maximum
|
Uint8 touchpad_count; // 2 touchpads maximum
|
||||||
Uint8 touchpad_finger_count; // 2 fingers for one touchpad, or 1 per touchpad (2 max)
|
Uint8 touchpad_finger_count; // 2 fingers for one touchpad, or 1 per touchpad (2 max)
|
||||||
|
|
||||||
Uint8 polling_rate_ms;
|
Uint16 polling_rate_us;
|
||||||
Uint8 sub_type; // Subtype of the device, 0 in most cases
|
Uint8 sub_product; // Subtype of the device, 0 in most cases
|
||||||
|
|
||||||
Uint16 accelRange; // Example would be 2,4,8,16 +/- (g-force)
|
Uint16 accelRange; // Example would be 2,4,8,16 +/- (g-force)
|
||||||
Uint16 gyroRange; // Example would be 1000,2000,4000 +/- (degrees per second)
|
Uint16 gyroRange; // Example would be 1000,2000,4000 +/- (degrees per second)
|
||||||
@@ -213,6 +247,7 @@ typedef struct
|
|||||||
float gyroScale; // Scale factor for gyroscope values
|
float gyroScale; // Scale factor for gyroscope values
|
||||||
Uint8 last_state[USB_PACKET_LENGTH];
|
Uint8 last_state[USB_PACKET_LENGTH];
|
||||||
|
|
||||||
|
Uint8 axes_count;
|
||||||
Uint8 buttons_count;
|
Uint8 buttons_count;
|
||||||
Uint8 usage_masks[4];
|
Uint8 usage_masks[4];
|
||||||
|
|
||||||
@@ -233,6 +268,172 @@ static inline float CalculateAccelScale(uint16_t g_range)
|
|||||||
return SDL_STANDARD_GRAVITY / (32768.0f / (float)g_range);
|
return SDL_STANDARD_GRAVITY / (32768.0f / (float)g_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function uses base-n encoding to encode features into the version GUID bytes
|
||||||
|
// that properly represents the supported device features
|
||||||
|
// This also sets the driver context button mask correctly based on the features
|
||||||
|
static void DeviceDynamicEncodingSetup(SDL_HIDAPI_Device *device)
|
||||||
|
{
|
||||||
|
SDL_DriverSInput_Context *ctx = device->context;
|
||||||
|
|
||||||
|
// A new button mask is generated to provide
|
||||||
|
// SDL with a mapping string that is sane. In case of
|
||||||
|
// an unconventional gamepad setup, the closest sane
|
||||||
|
// mapping is provided to the driver.
|
||||||
|
Uint8 mask[4] = { 0 };
|
||||||
|
|
||||||
|
// For all gamepads, there is a minimum SInput expectation
|
||||||
|
// to have dpad, abxy, and start buttons
|
||||||
|
|
||||||
|
// ABXY + D-Pad
|
||||||
|
mask[0] = 0xFF;
|
||||||
|
ctx->dpad_supported = true;
|
||||||
|
|
||||||
|
// Start button
|
||||||
|
mask[2] |= SINPUT_BUTTONMASK_START;
|
||||||
|
|
||||||
|
// Bumpers
|
||||||
|
bool left_bumper = (ctx->usage_masks[1] & SINPUT_BUTTONMASK_LEFT_BUMPER) != 0;
|
||||||
|
bool right_bumper = (ctx->usage_masks[1] & SINPUT_BUTTONMASK_RIGHT_BUMPER) != 0;
|
||||||
|
|
||||||
|
int bumperStyle = SINPUT_BUMPERSTYLE_NONE;
|
||||||
|
if (left_bumper && right_bumper) {
|
||||||
|
bumperStyle = SINPUT_BUMPERSTYLE_TWO;
|
||||||
|
mask[1] |= (SINPUT_BUTTONMASK_LEFT_BUMPER | SINPUT_BUTTONMASK_RIGHT_BUMPER);
|
||||||
|
} else if (left_bumper || right_bumper) {
|
||||||
|
bumperStyle = SINPUT_BUMPERSTYLE_ONE;
|
||||||
|
|
||||||
|
if (left_bumper) {
|
||||||
|
mask[1] |= SINPUT_BUTTONMASK_LEFT_BUMPER;
|
||||||
|
} else if (right_bumper) {
|
||||||
|
mask[1] |= SINPUT_BUTTONMASK_RIGHT_BUMPER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger bits live in mask[1]
|
||||||
|
bool digital_triggers = (ctx->usage_masks[1] & (SINPUT_BUTTONMASK_LEFT_TRIGGER | SINPUT_BUTTONMASK_RIGHT_TRIGGER)) != 0;
|
||||||
|
|
||||||
|
bool analog_triggers = ctx->left_analog_trigger_supported || ctx->right_analog_trigger_supported;
|
||||||
|
|
||||||
|
// Touchpads
|
||||||
|
bool t1 = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_TOUCHPAD1) != 0;
|
||||||
|
bool t2 = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_TOUCHPAD2) != 0;
|
||||||
|
|
||||||
|
int analogStyle = SINPUT_ANALOGSTYLE_NONE;
|
||||||
|
if (ctx->left_analog_stick_supported && ctx->right_analog_stick_supported) {
|
||||||
|
analogStyle = SINPUT_ANALOGSTYLE_LEFTRIGHT;
|
||||||
|
mask[1] |= (SINPUT_BUTTONMASK_LEFT_STICK | SINPUT_BUTTONMASK_RIGHT_STICK);
|
||||||
|
} else if (ctx->left_analog_stick_supported) {
|
||||||
|
analogStyle = SINPUT_ANALOGSTYLE_LEFTONLY;
|
||||||
|
mask[1] |= SINPUT_BUTTONMASK_LEFT_STICK;
|
||||||
|
} else if (ctx->right_analog_stick_supported) {
|
||||||
|
analogStyle = SINPUT_ANALOGSTYLE_RIGHTONLY;
|
||||||
|
mask[1] |= SINPUT_BUTTONMASK_RIGHT_STICK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int triggerStyle = SINPUT_TRIGGERSTYLE_NONE;
|
||||||
|
|
||||||
|
if (analog_triggers && digital_triggers) {
|
||||||
|
// When we have both analog triggers and digital triggers
|
||||||
|
// this is interpreted as having dual-stage triggers
|
||||||
|
triggerStyle = SINPUT_TRIGGERSTYLE_DUALSTAGE;
|
||||||
|
mask[1] |= (SINPUT_BUTTONMASK_LEFT_TRIGGER | SINPUT_BUTTONMASK_RIGHT_TRIGGER);
|
||||||
|
} else if (analog_triggers) {
|
||||||
|
triggerStyle = SINPUT_TRIGGERSTYLE_ANALOG;
|
||||||
|
} else if (digital_triggers) {
|
||||||
|
triggerStyle = SINPUT_TRIGGERSTYLE_DIGITAL;
|
||||||
|
mask[1] |= (SINPUT_BUTTONMASK_LEFT_TRIGGER | SINPUT_BUTTONMASK_RIGHT_TRIGGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paddle bits may touch mask[1] and mask[2]
|
||||||
|
bool pg1 = (ctx->usage_masks[1] & (SINPUT_BUTTONMASK_LEFT_PADDLE1 | SINPUT_BUTTONMASK_RIGHT_PADDLE1)) != 0;
|
||||||
|
bool pg2 = (ctx->usage_masks[2] & (SINPUT_BUTTONMASK_LEFT_PADDLE2 | SINPUT_BUTTONMASK_RIGHT_PADDLE2)) != 0;
|
||||||
|
|
||||||
|
int paddleStyle = SINPUT_PADDLESTYLE_NONE;
|
||||||
|
if (pg1 && pg2) {
|
||||||
|
paddleStyle = SINPUT_PADDLESTYLE_FOUR;
|
||||||
|
mask[1] |= (SINPUT_BUTTONMASK_LEFT_PADDLE1 | SINPUT_BUTTONMASK_RIGHT_PADDLE1);
|
||||||
|
mask[2] |= (SINPUT_BUTTONMASK_LEFT_PADDLE2 | SINPUT_BUTTONMASK_RIGHT_PADDLE2);
|
||||||
|
} else if (pg1) {
|
||||||
|
paddleStyle = SINPUT_PADDLESTYLE_TWO;
|
||||||
|
mask[1] |= (SINPUT_BUTTONMASK_LEFT_PADDLE1 | SINPUT_BUTTONMASK_RIGHT_PADDLE1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Meta Buttons (Back, Guide, Share)
|
||||||
|
bool back = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_BACK) != 0;
|
||||||
|
bool guide = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_GUIDE) != 0;
|
||||||
|
bool share = (ctx->usage_masks[2] & SINPUT_BUTTONMASK_CAPTURE) != 0;
|
||||||
|
|
||||||
|
int metaStyle = SINPUT_METASTYLE_NONE;
|
||||||
|
if (share) {
|
||||||
|
metaStyle = SINPUT_METASTYLE_BACKGUIDESHARE;
|
||||||
|
mask[2] |= (SINPUT_BUTTONMASK_BACK | SINPUT_BUTTONMASK_GUIDE | SINPUT_BUTTONMASK_CAPTURE);
|
||||||
|
} else if (guide) {
|
||||||
|
metaStyle = SINPUT_METASTYLE_BACKGUIDE;
|
||||||
|
mask[2] |= (SINPUT_BUTTONMASK_BACK | SINPUT_BUTTONMASK_GUIDE);
|
||||||
|
} else if (back) {
|
||||||
|
metaStyle = SINPUT_METASTYLE_BACK;
|
||||||
|
mask[2] |= (SINPUT_BUTTONMASK_BACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
int touchStyle = SINPUT_TOUCHSTYLE_NONE;
|
||||||
|
if (t1 && t2) {
|
||||||
|
touchStyle = SINPUT_TOUCHSTYLE_DOUBLE;
|
||||||
|
mask[2] |= (SINPUT_BUTTONMASK_TOUCHPAD1 | SINPUT_BUTTONMASK_TOUCHPAD2);
|
||||||
|
} else if (t1) {
|
||||||
|
touchStyle = SINPUT_TOUCHSTYLE_SINGLE;
|
||||||
|
mask[2] |= SINPUT_BUTTONMASK_TOUCHPAD1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Misc Buttons
|
||||||
|
int miscStyle = SINPUT_MISCSTYLE_NONE;
|
||||||
|
Uint8 extra_misc = ctx->usage_masks[3] & 0x0F;
|
||||||
|
switch (extra_misc) {
|
||||||
|
case 0x0F:
|
||||||
|
miscStyle = SINPUT_MISCSTYLE_4;
|
||||||
|
mask[3] = 0x0F;
|
||||||
|
break;
|
||||||
|
case 0x07:
|
||||||
|
miscStyle = SINPUT_MISCSTYLE_3;
|
||||||
|
mask[3] = 0x07;
|
||||||
|
break;
|
||||||
|
case 0x03:
|
||||||
|
miscStyle = SINPUT_MISCSTYLE_2;
|
||||||
|
mask[3] = 0x03;
|
||||||
|
break;
|
||||||
|
case 0x01:
|
||||||
|
miscStyle = SINPUT_MISCSTYLE_1;
|
||||||
|
mask[3] = 0x01;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
miscStyle = SINPUT_MISCSTYLE_NONE;
|
||||||
|
mask[3] = 0x00;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int version = analogStyle;
|
||||||
|
version = (version * (int)SINPUT_BUMPERSTYLE_MAX) + bumperStyle;
|
||||||
|
version = (version * (int)SINPUT_TRIGGERSTYLE_MAX) + triggerStyle;
|
||||||
|
version = (version * (int)SINPUT_PADDLESTYLE_MAX) + paddleStyle;
|
||||||
|
version = (version * (int)SINPUT_METASTYLE_MAX) + metaStyle;
|
||||||
|
version = (version * (int)SINPUT_TOUCHSTYLE_MAX) + touchStyle;
|
||||||
|
version = (version * (int)SINPUT_MISCSTYLE_MAX) + miscStyle;
|
||||||
|
|
||||||
|
// Overwrite our button usage masks
|
||||||
|
// with our sanitized masks
|
||||||
|
ctx->usage_masks[0] = mask[0];
|
||||||
|
ctx->usage_masks[1] = mask[1];
|
||||||
|
ctx->usage_masks[2] = mask[2];
|
||||||
|
ctx->usage_masks[3] = mask[3];
|
||||||
|
|
||||||
|
version = SDL_clamp(version, 0, UINT16_MAX);
|
||||||
|
|
||||||
|
// Overwrite 'Version' field of the GUID data
|
||||||
|
device->guid.data[12] = (Uint8)(version & 0xFF);
|
||||||
|
device->guid.data[13] = (Uint8)(version >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data)
|
static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data)
|
||||||
{
|
{
|
||||||
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
|
SDL_DriverSInput_Context *ctx = (SDL_DriverSInput_Context *)device->context;
|
||||||
@@ -266,69 +467,26 @@ static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data)
|
|||||||
// The 5 LSB represent a device sub-type
|
// The 5 LSB represent a device sub-type
|
||||||
device->guid.data[15] = data[5];
|
device->guid.data[15] = data[5];
|
||||||
|
|
||||||
ctx->sub_type = (data[5] & 0x1F);
|
ctx->sub_product = (data[5] & 0x1F);
|
||||||
|
|
||||||
#if defined(DEBUG_SINPUT_INIT)
|
#if defined(DEBUG_SINPUT_INIT)
|
||||||
SDL_Log("SInput Face Style: %d", (data[5] & 0xE0) >> 5);
|
SDL_Log("SInput Face Style: %d", (data[5] & 0xE0) >> 5);
|
||||||
SDL_Log("SInput Sub-type: %d", (data[5] & 0x1F));
|
SDL_Log("SInput Sub-product: %d", (data[5] & 0x1F));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ctx->polling_rate_ms = data[6];
|
ctx->polling_rate_us = EXTRACTUINT16(data, 6);
|
||||||
|
|
||||||
|
#if defined(DEBUG_SINPUT_INIT)
|
||||||
|
SDL_Log("SInput polling interval (microseconds): %d", ctx->polling_rate_us);
|
||||||
|
#endif
|
||||||
|
|
||||||
ctx->accelRange = EXTRACTUINT16(data, 8);
|
ctx->accelRange = EXTRACTUINT16(data, 8);
|
||||||
ctx->gyroRange = EXTRACTUINT16(data, 10);
|
ctx->gyroRange = EXTRACTUINT16(data, 10);
|
||||||
|
|
||||||
|
|
||||||
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 {
|
|
||||||
// Masks in LSB to MSB
|
|
||||||
// South, East, West, North, DUp, DDown, DLeft, DRight
|
|
||||||
ctx->usage_masks[0] = data[12];
|
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[1] = data[13];
|
||||||
|
|
||||||
// Start, Back, Guide, Capture, L Paddle 2, R Paddle 2, Touchpad L, Touchpad R
|
|
||||||
ctx->usage_masks[2] = data[14];
|
ctx->usage_masks[2] = data[14];
|
||||||
|
|
||||||
// Power, Misc 4 to 10
|
|
||||||
ctx->usage_masks[3] = data[15];
|
ctx->usage_masks[3] = data[15];
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
if ((ctx->usage_masks[0] & DPAD_MASK) == DPAD_MASK) {
|
|
||||||
ctx->dpad_supported = true;
|
|
||||||
ctx->usage_masks[0] &= ~DPAD_MASK;
|
|
||||||
ctx->buttons_count -= 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(DEBUG_SINPUT_INIT)
|
|
||||||
SDL_Log("Buttons count: %d", ctx->buttons_count);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Get and validate touchpad parameters
|
// Get and validate touchpad parameters
|
||||||
ctx->touchpad_count = data[16];
|
ctx->touchpad_count = data[16];
|
||||||
@@ -354,6 +512,48 @@ static void ProcessSDLFeaturesResponse(SDL_HIDAPI_Device *device, Uint8 *data)
|
|||||||
|
|
||||||
ctx->accelScale = CalculateAccelScale(ctx->accelRange);
|
ctx->accelScale = CalculateAccelScale(ctx->accelRange);
|
||||||
ctx->gyroScale = CalculateGyroScale(ctx->gyroRange);
|
ctx->gyroScale = CalculateGyroScale(ctx->gyroRange);
|
||||||
|
|
||||||
|
Uint8 axes = 0;
|
||||||
|
if (ctx->left_analog_stick_supported) {
|
||||||
|
axes += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->right_analog_stick_supported) {
|
||||||
|
axes += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->left_analog_trigger_supported || ctx->right_analog_trigger_supported) {
|
||||||
|
// Always add both analog trigger axes if one is present
|
||||||
|
axes += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->axes_count = axes;
|
||||||
|
|
||||||
|
DeviceDynamicEncodingSetup(device);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
if ((ctx->usage_masks[0] & DPAD_MASK) == DPAD_MASK) {
|
||||||
|
ctx->dpad_supported = true;
|
||||||
|
ctx->usage_masks[0] &= ~DPAD_MASK;
|
||||||
|
ctx->buttons_count -= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(DEBUG_SINPUT_INIT)
|
||||||
|
SDL_Log("Buttons count: %d", ctx->buttons_count);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool RetrieveSDLFeatures(SDL_HIDAPI_Device *device)
|
static bool RetrieveSDLFeatures(SDL_HIDAPI_Device *device)
|
||||||
@@ -460,6 +660,9 @@ static bool HIDAPI_DriverSInput_InitDevice(SDL_HIDAPI_Device *device)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the USB Device Version because we will overwrite this data
|
||||||
|
ctx->usb_device_version = device->version;
|
||||||
|
|
||||||
switch (device->product_id) {
|
switch (device->product_id) {
|
||||||
case USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE:
|
case USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE:
|
||||||
HIDAPI_SetDeviceName(device, "HHL GC Ultimate");
|
HIDAPI_SetDeviceName(device, "HHL GC Ultimate");
|
||||||
@@ -467,8 +670,11 @@ static bool HIDAPI_DriverSInput_InitDevice(SDL_HIDAPI_Device *device)
|
|||||||
case USB_PRODUCT_HANDHELDLEGEND_PROGCC:
|
case USB_PRODUCT_HANDHELDLEGEND_PROGCC:
|
||||||
HIDAPI_SetDeviceName(device, "HHL ProGCC");
|
HIDAPI_SetDeviceName(device, "HHL ProGCC");
|
||||||
break;
|
break;
|
||||||
|
case USB_PRODUCT_VOIDGAMING_PS4FIREBIRD:
|
||||||
|
HIDAPI_SetDeviceName(device, "Void Gaming PS4 FireBird");
|
||||||
|
break;
|
||||||
case USB_PRODUCT_BONZIRICHANNEL_FIREBIRD:
|
case USB_PRODUCT_BONZIRICHANNEL_FIREBIRD:
|
||||||
HIDAPI_SetDeviceName(device, "Bonziri Firebird");
|
HIDAPI_SetDeviceName(device, "Bonziri FireBird");
|
||||||
break;
|
break;
|
||||||
case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC:
|
case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC:
|
||||||
default:
|
default:
|
||||||
@@ -523,45 +729,18 @@ static bool HIDAPI_DriverSInput_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys
|
|||||||
|
|
||||||
SDL_zeroa(ctx->last_state);
|
SDL_zeroa(ctx->last_state);
|
||||||
|
|
||||||
int axes = 0;
|
joystick->naxes = ctx->axes_count;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if (ctx->dpad_supported) {
|
||||||
joystick->nhats = 1;
|
joystick->nhats = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx->accelerometer_supported) {
|
if (ctx->accelerometer_supported) {
|
||||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 1000.0f / ctx->polling_rate_ms);
|
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 1000000.0f / ctx->polling_rate_us);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx->gyroscope_supported) {
|
if (ctx->gyroscope_supported) {
|
||||||
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 1000.0f / ctx->polling_rate_ms);
|
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 1000000.0f / ctx->polling_rate_us);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx->touchpad_supported) {
|
if (ctx->touchpad_supported) {
|
||||||
|
92
src/joystick/hidapi/SDL_hidapi_sinput.h
Normal file
92
src/joystick/hidapi/SDL_hidapi_sinput.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
Simple DirectMedia Layer
|
||||||
|
Copyright (C) 2025 Mitchell Cairns <mitch.cairns@handheldlegend.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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SINPUT_ANALOGSTYLE_NONE,
|
||||||
|
SINPUT_ANALOGSTYLE_LEFTONLY,
|
||||||
|
SINPUT_ANALOGSTYLE_RIGHTONLY,
|
||||||
|
SINPUT_ANALOGSTYLE_LEFTRIGHT,
|
||||||
|
SINPUT_ANALOGSTYLE_MAX,
|
||||||
|
} SInput_AnalogStyleType;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SINPUT_BUMPERSTYLE_NONE,
|
||||||
|
SINPUT_BUMPERSTYLE_ONE,
|
||||||
|
SINPUT_BUMPERSTYLE_TWO,
|
||||||
|
SINPUT_BUMPERSTYLE_MAX,
|
||||||
|
} SInput_BumperStyleType;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SINPUT_TRIGGERSTYLE_NONE,
|
||||||
|
SINPUT_TRIGGERSTYLE_ANALOG,
|
||||||
|
SINPUT_TRIGGERSTYLE_DIGITAL,
|
||||||
|
SINPUT_TRIGGERSTYLE_DUALSTAGE,
|
||||||
|
SINPUT_TRIGGERSTYLE_MAX,
|
||||||
|
} SInput_TriggerStyleType;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SINPUT_PADDLESTYLE_NONE,
|
||||||
|
SINPUT_PADDLESTYLE_TWO,
|
||||||
|
SINPUT_PADDLESTYLE_FOUR,
|
||||||
|
SINPUT_PADDLESTYLE_MAX,
|
||||||
|
} SInput_PaddleStyleType;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SINPUT_METASTYLE_NONE,
|
||||||
|
SINPUT_METASTYLE_BACK,
|
||||||
|
SINPUT_METASTYLE_BACKGUIDE,
|
||||||
|
SINPUT_METASTYLE_BACKGUIDESHARE,
|
||||||
|
SINPUT_METASTYLE_MAX,
|
||||||
|
} SInput_MetaStyleType;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SINPUT_TOUCHSTYLE_NONE,
|
||||||
|
SINPUT_TOUCHSTYLE_SINGLE,
|
||||||
|
SINPUT_TOUCHSTYLE_DOUBLE,
|
||||||
|
SINPUT_TOUCHSTYLE_MAX,
|
||||||
|
} SInput_TouchStyleType;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
SINPUT_MISCSTYLE_NONE,
|
||||||
|
SINPUT_MISCSTYLE_1,
|
||||||
|
SINPUT_MISCSTYLE_2,
|
||||||
|
SINPUT_MISCSTYLE_3,
|
||||||
|
SINPUT_MISCSTYLE_4,
|
||||||
|
SINPUT_MISCSTYLE_MAX,
|
||||||
|
} SInput_MiscStyleType;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
Uint16 analog_style;
|
||||||
|
Uint16 bumper_style;
|
||||||
|
Uint16 trigger_style;
|
||||||
|
Uint16 paddle_style;
|
||||||
|
Uint16 meta_style;
|
||||||
|
Uint16 touch_style;
|
||||||
|
Uint16 misc_style;
|
||||||
|
} SDL_SInputStyles_t;
|
@@ -171,6 +171,7 @@
|
|||||||
#define USB_PRODUCT_HANDHELDLEGEND_PROGCC 0x10df
|
#define USB_PRODUCT_HANDHELDLEGEND_PROGCC 0x10df
|
||||||
#define USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE 0x10dd
|
#define USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE 0x10dd
|
||||||
#define USB_PRODUCT_BONZIRICHANNEL_FIREBIRD 0x10e0
|
#define USB_PRODUCT_BONZIRICHANNEL_FIREBIRD 0x10e0
|
||||||
|
#define USB_PRODUCT_VOIDGAMING_PS4FIREBIRD 0x10e5
|
||||||
|
|
||||||
// USB usage pages
|
// USB usage pages
|
||||||
#define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001
|
#define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001
|
||||||
|
Reference in New Issue
Block a user