Skip to content

Commit 0f50b57

Browse files
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 ([email protected]) for contributing the neat compression algorithm, this is pulled from the PR Draft here: libsdl-org#13565 Co-authored-by: Antheas Kapenekakis <[email protected]>
1 parent 7bb045c commit 0f50b57

File tree

3 files changed

+593
-80
lines changed

3 files changed

+593
-80
lines changed

src/joystick/SDL_gamepad.c

Lines changed: 288 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "controller_type.h"
3131
#include "usb_ids.h"
3232
#include "hidapi/SDL_hidapi_nintendo.h"
33+
#include "hidapi/SDL_hidapi_sinput.h"
3334
#include "../events/SDL_events_c.h"
3435

3536

@@ -54,6 +55,26 @@
5455
#define SDL_GAMEPAD_SDKLE_FIELD "sdk<=:"
5556
#define SDL_GAMEPAD_SDKLE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKLE_FIELD)
5657

58+
// Helper function to add button mapping
59+
#ifndef ADD_BUTTON_MAPPING
60+
#define SDL_ADD_BUTTON_MAPPING(sdl_name, button_id, maxlen) \
61+
do { \
62+
char temp[32]; \
63+
(void)SDL_snprintf(temp, sizeof(temp), "%s:b%d,", sdl_name, button_id); \
64+
SDL_strlcat(mapping_string, temp, maxlen); \
65+
} while (0)
66+
#endif
67+
68+
// Helper function to add axis mapping
69+
#ifndef ADD_AXIS_MAPPING
70+
#define SDL_ADD_AXIS_MAPPING(sdl_name, axis_id, maxlen) \
71+
do { \
72+
char temp[32]; \
73+
(void)SDL_snprintf(temp, sizeof(temp), "%s:a%d,", sdl_name, axis_id); \
74+
SDL_strlcat(mapping_string, temp, maxlen); \
75+
} while (0)
76+
#endif
77+
5778
static bool SDL_gamepads_initialized;
5879
static SDL_Gamepad *SDL_gamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
5980

@@ -688,6 +709,268 @@ static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_GUID guid)
688709
}
689710
#endif // SDL_PLATFORM_ANDROID
690711

712+
/*
713+
* Helper function to apply SInput decoded styles to the mapping string
714+
*/
715+
static inline void SDL_SInputStylesMapExtraction(SDL_SInputStyles_t* styles, char* mapping_string, size_t mapping_string_len)
716+
{
717+
int current_button = 0;
718+
int current_axis = 0;
719+
bool digital_triggers = false;
720+
bool bumpers = false;
721+
bool left_stick = false;
722+
bool right_stick = false;
723+
bool paddle_second_pair = false;
724+
725+
// Analog joysticks (always come first in axis mapping)
726+
switch (styles->analog_style) {
727+
case SINPUT_ANALOGSTYLE_LEFTONLY:
728+
SDL_ADD_AXIS_MAPPING("leftx", current_axis++, mapping_string_len);
729+
SDL_ADD_AXIS_MAPPING("lefty", current_axis++, mapping_string_len);
730+
left_stick = true;
731+
break;
732+
733+
case SINPUT_ANALOGSTYLE_LEFTRIGHT:
734+
SDL_ADD_AXIS_MAPPING("leftx", current_axis++, mapping_string_len);
735+
SDL_ADD_AXIS_MAPPING("lefty", current_axis++, mapping_string_len);
736+
SDL_ADD_AXIS_MAPPING("rightx", current_axis++, mapping_string_len);
737+
SDL_ADD_AXIS_MAPPING("righty", current_axis++, mapping_string_len);
738+
left_stick = true;
739+
right_stick = true;
740+
break;
741+
742+
case SINPUT_ANALOGSTYLE_RIGHTONLY:
743+
SDL_ADD_AXIS_MAPPING("rightx", current_axis++, mapping_string_len);
744+
SDL_ADD_AXIS_MAPPING("righty", current_axis++, mapping_string_len);
745+
right_stick = true;
746+
break;
747+
748+
default:
749+
break;
750+
}
751+
752+
// Analog triggers
753+
switch (styles->trigger_style) {
754+
// Analog triggers + bumpers
755+
case SINPUT_TRIGGERSTYLE_ANALOG:
756+
SDL_ADD_AXIS_MAPPING("lefttrigger", current_axis++, mapping_string_len);
757+
SDL_ADD_AXIS_MAPPING("righttrigger", current_axis++, mapping_string_len);
758+
break;
759+
760+
// Digital triggers + bumpers
761+
case SINPUT_TRIGGERSTYLE_DIGITAL:
762+
digital_triggers = true;
763+
bumpers = true;
764+
break;
765+
766+
// Only bumpers
767+
case SINPUT_TRIGGERSTYLE_BUMPERS:
768+
bumpers = true;
769+
break;
770+
771+
default:
772+
break;
773+
}
774+
775+
// BAYX buttons (East, South, North, West)
776+
SDL_ADD_BUTTON_MAPPING("b", current_button++, mapping_string_len); // East (typically B on Xbox, Circle on PlayStation)
777+
SDL_ADD_BUTTON_MAPPING("a", current_button++, mapping_string_len); // South (typically A on Xbox, X on PlayStation)
778+
SDL_ADD_BUTTON_MAPPING("y", current_button++, mapping_string_len); // North (typically Y on Xbox, Triangle on PlayStation)
779+
SDL_ADD_BUTTON_MAPPING("x", current_button++, mapping_string_len); // West (typically X on Xbox, Square on PlayStation)
780+
781+
// DPad
782+
SDL_strlcat(mapping_string, "dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,", mapping_string_len);
783+
784+
// Left and Right stick buttons
785+
if (left_stick) {
786+
SDL_ADD_BUTTON_MAPPING("leftstick", current_button++, mapping_string_len);
787+
}
788+
if (right_stick) {
789+
SDL_ADD_BUTTON_MAPPING("rightstick", current_button++, mapping_string_len);
790+
}
791+
792+
// Digital shoulder buttons (L/R Shoulder)
793+
if (bumpers) {
794+
SDL_ADD_BUTTON_MAPPING("leftshoulder", current_button++, mapping_string_len);
795+
SDL_ADD_BUTTON_MAPPING("rightshoulder", current_button++, mapping_string_len);
796+
}
797+
798+
// Digital trigger buttons (capability overrides analog)
799+
if (digital_triggers) {
800+
SDL_ADD_BUTTON_MAPPING("lefttrigger", current_button++, mapping_string_len);
801+
SDL_ADD_BUTTON_MAPPING("righttrigger", current_button++, mapping_string_len);
802+
}
803+
804+
// Paddle 1/2
805+
switch (styles->paddle_style) {
806+
case SINPUT_PADDLESTYLE_TWO:
807+
SDL_ADD_BUTTON_MAPPING("paddle1", current_button++, mapping_string_len);
808+
SDL_ADD_BUTTON_MAPPING("paddle2", current_button++, mapping_string_len);
809+
break;
810+
811+
case SINPUT_PADDLESTYLE_FOUR:
812+
SDL_ADD_BUTTON_MAPPING("paddle1", current_button++, mapping_string_len);
813+
SDL_ADD_BUTTON_MAPPING("paddle2", current_button++, mapping_string_len);
814+
paddle_second_pair = true;
815+
break;
816+
817+
default:
818+
break;
819+
}
820+
821+
// Start/Plus & Select/Back
822+
SDL_ADD_BUTTON_MAPPING("start", current_button++, mapping_string_len);
823+
SDL_ADD_BUTTON_MAPPING("back", current_button++, mapping_string_len);
824+
825+
switch (styles->meta_style) {
826+
case SINPUT_METASTYLE_GUIDE:
827+
SDL_ADD_BUTTON_MAPPING("guide", current_button++, mapping_string_len);
828+
break;
829+
830+
case SINPUT_METASTYLE_GUIDESHARE:
831+
SDL_ADD_BUTTON_MAPPING("guide", current_button++, mapping_string_len);
832+
SDL_ADD_BUTTON_MAPPING("misc1", current_button++, mapping_string_len);
833+
break;
834+
835+
default:
836+
break;
837+
}
838+
839+
// Paddle 3/4
840+
if (paddle_second_pair) {
841+
SDL_ADD_BUTTON_MAPPING("paddle3", current_button++, mapping_string_len);
842+
SDL_ADD_BUTTON_MAPPING("paddle4", current_button++, mapping_string_len);
843+
}
844+
845+
// Touchpad buttons
846+
switch (styles->touch_style) {
847+
case SINPUT_TOUCHSTYLE_SINGLE:
848+
SDL_ADD_BUTTON_MAPPING("touchpad", current_button++, mapping_string_len);
849+
break;
850+
851+
case SINPUT_TOUCHSTYLE_DOUBLE:
852+
SDL_ADD_BUTTON_MAPPING("touchpad", current_button++, mapping_string_len);
853+
SDL_ADD_BUTTON_MAPPING("misc2", current_button++, mapping_string_len);
854+
break;
855+
856+
default:
857+
break;
858+
}
859+
860+
switch (styles->misc_style) {
861+
case SINPUT_MISCSTYLE_1:
862+
SDL_ADD_BUTTON_MAPPING("misc3", current_button++, mapping_string_len);
863+
break;
864+
865+
case SINPUT_MISCSTYLE_2:
866+
SDL_ADD_BUTTON_MAPPING("misc3", current_button++, mapping_string_len);
867+
SDL_ADD_BUTTON_MAPPING("misc4", current_button++, mapping_string_len);
868+
break;
869+
870+
case SINPUT_MISCSTYLE_3:
871+
SDL_ADD_BUTTON_MAPPING("misc3", current_button++, mapping_string_len);
872+
SDL_ADD_BUTTON_MAPPING("misc4", current_button++, mapping_string_len);
873+
SDL_ADD_BUTTON_MAPPING("misc5", current_button++, mapping_string_len);
874+
break;
875+
876+
case SINPUT_MISCSTYLE_4:
877+
SDL_ADD_BUTTON_MAPPING("misc3", current_button++, mapping_string_len);
878+
SDL_ADD_BUTTON_MAPPING("misc4", current_button++, mapping_string_len);
879+
SDL_ADD_BUTTON_MAPPING("misc5", current_button++, mapping_string_len);
880+
SDL_ADD_BUTTON_MAPPING("misc6", current_button++, mapping_string_len);
881+
break;
882+
883+
default:
884+
break;
885+
}
886+
887+
// Remove trailing comma
888+
size_t len = SDL_strlen(mapping_string);
889+
if (len > 0 && mapping_string[len - 1] == ',') {
890+
mapping_string[len - 1] = '\0';
891+
}
892+
}
893+
894+
/*
895+
* Helper function to decode SInput features information packed into version
896+
*/
897+
static bool SDL_CreateMappingStringForSInputGamepad(Uint16 vendor, Uint16 product, Uint8 sub_product, Uint16 version, Uint8 face_style, char* mapping_string, size_t mapping_string_len)
898+
{
899+
SDL_SInputStyles_t decoded = { 0 };
900+
901+
switch (face_style) {
902+
default:
903+
SDL_strlcat(mapping_string, "face:abxy,", mapping_string_len);
904+
break;
905+
case 2:
906+
SDL_strlcat(mapping_string, "face:axby,", mapping_string_len);
907+
break;
908+
case 3:
909+
SDL_strlcat(mapping_string, "face:bayx,", mapping_string_len);
910+
break;
911+
case 4:
912+
SDL_strlcat(mapping_string, "face:sony,", mapping_string_len);
913+
break;
914+
}
915+
916+
switch (product) {
917+
case USB_PRODUCT_HANDHELDLEGEND_PROGCC:
918+
switch (sub_product) {
919+
default:
920+
// ProGCC Primary Mapping
921+
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,", mapping_string_len);
922+
break;
923+
}
924+
return true;
925+
926+
case USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE:
927+
switch (sub_product) {
928+
default:
929+
// GC Ultimate Primary Map
930+
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,", mapping_string_len);
931+
break;
932+
}
933+
return true;
934+
935+
case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC:
936+
switch (sub_product) {
937+
default:
938+
case SINPUT_GENERIC_DEVMAP:
939+
// Default Fully Exposed Mapping (Development Purposes)
940+
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", mapping_string_len);
941+
break;
942+
943+
case SINPUT_GENERIC_DYNMAP:
944+
// Decode styles for correct dynamic features
945+
decoded.misc_style = (SINPUT_MISC_STYLE_E)(version % SINPUT_MISCSTYLE_MAX);
946+
version /= SINPUT_MISCSTYLE_MAX;
947+
948+
decoded.touch_style = (SINPUT_TOUCH_STYLE_E)(version % SINPUT_TOUCHSTYLE_MAX);
949+
version /= SINPUT_TOUCHSTYLE_MAX;
950+
951+
decoded.meta_style = (SINPUT_META_STYLE_E)(version % SINPUT_METASTYLE_MAX);
952+
version /= SINPUT_METASTYLE_MAX;
953+
954+
decoded.paddle_style = (SINPUT_PADDLE_STYLE_E)(version % SINPUT_PADDLESTYLE_MAX);
955+
version /= SINPUT_PADDLESTYLE_MAX;
956+
957+
decoded.trigger_style = (SINPUT_TRIGGER_STYLE_E)(version % SINPUT_TRIGGERSTYLE_MAX);
958+
version /= SINPUT_TRIGGERSTYLE_MAX;
959+
960+
decoded.analog_style = (SINPUT_ANALOG_STYLE_E)(version % SINPUT_ANALOGSTYLE_MAX);
961+
962+
SDL_SInputStylesMapExtraction(&decoded, mapping_string, mapping_string_len);
963+
break;
964+
}
965+
return true;
966+
967+
case USB_PRODUCT_BONZIRICHANNEL_FIREBIRD:
968+
default:
969+
// Unmapped device
970+
return false;
971+
}
972+
}
973+
691974
/*
692975
* Helper function to guess at a mapping for HIDAPI gamepads
693976
*/
@@ -697,10 +980,11 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
697980
char mapping_string[1024];
698981
Uint16 vendor;
699982
Uint16 product;
983+
Uint16 version;
700984

701985
SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));
702986

703-
SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
987+
SDL_GetJoystickGUIDInfo(guid, &vendor, &product, &version, NULL);
704988

705989
if (SDL_IsJoystickWheel(vendor, product)) {
706990
// We don't want to pick up Logitech FFB wheels here
@@ -799,54 +1083,11 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
7991083
// This controller has no guide button
8001084
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));
8011085
} else if (SDL_IsJoystickSInputController(vendor, product)) {
802-
Uint8 face_style = (guid.data[15] & 0xE0) >> 5;
803-
Uint8 sub_type = guid.data[15] & 0x1F;
8041086

805-
// Apply face style according to gamepad response
806-
switch (face_style) {
807-
default:
808-
SDL_strlcat(mapping_string, "face:abxy,", sizeof(mapping_string));
809-
break;
810-
case 2:
811-
SDL_strlcat(mapping_string, "face:axby,", sizeof(mapping_string));
812-
break;
813-
case 3:
814-
SDL_strlcat(mapping_string, "face:bayx,", sizeof(mapping_string));
815-
break;
816-
case 4:
817-
SDL_strlcat(mapping_string, "face:sony,", sizeof(mapping_string));
818-
break;
819-
}
1087+
Uint8 face_style = (guid.data[15] & 0xE0) >> 5;
1088+
Uint8 sub_product = guid.data[15] & 0x1F;
8201089

821-
switch (product) {
822-
case USB_PRODUCT_HANDHELDLEGEND_PROGCC:
823-
switch (sub_type) {
824-
default:
825-
// ProGCC Primary Mapping
826-
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));
827-
break;
828-
}
829-
break;
830-
case USB_PRODUCT_HANDHELDLEGEND_GCULTIMATE:
831-
switch (sub_type) {
832-
default:
833-
// GC Ultimate Primary Map
834-
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));
835-
break;
836-
}
837-
break;
838-
case USB_PRODUCT_HANDHELDLEGEND_SINPUT_GENERIC:
839-
switch (sub_type) {
840-
default:
841-
// Default Fully Exposed Mapping (Development Purposes)
842-
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));
843-
break;
844-
}
845-
break;
846-
847-
case USB_PRODUCT_BONZIRICHANNEL_FIREBIRD:
848-
default:
849-
// Unmapped device
1090+
if (!SDL_CreateMappingStringForSInputGamepad(vendor, product, sub_product, version, face_style, mapping_string, sizeof(mapping_string))) {
8501091
return NULL;
8511092
}
8521093
} else {

0 commit comments

Comments
 (0)