From 48dfc03a8727a7a27f6b3ee5aeb925b3e1a59caa Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 1 May 2025 10:35:49 -0700 Subject: [PATCH] Added the gamepad hint SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS This is for internal use to signal that a mapping uses positional GameCube buttons. It's set so we know whether a mapping uses the older style labeled buttons or the newer style positional buttons. If a positional mapping is used with SDL2, then it will be ignored, since the hint is marked as defaulting true and the mapping conditional is that the hint is false. --- src/joystick/SDL_gamepad.c | 170 ++++++++++++++++++++++----------- src/joystick/SDL_joystick.c | 4 +- test/testautomation_joystick.c | 17 ++++ test/testcontroller.c | 11 --- 4 files changed, 132 insertions(+), 70 deletions(-) diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index a54b79996d..5f08f94752 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -714,7 +714,7 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 || product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER3))) { // GameCube driver has 12 buttons and 6 axes - SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b2,y:b3,", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); } else if (vendor == USB_VENDOR_NINTENDO && (guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft || guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight || @@ -781,7 +781,11 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) } } else { // All other gamepads have the standard set of 19 buttons and 6 axes - SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + if (SDL_IsJoystickGameCube(vendor, product)) { + SDL_strlcat(mapping_string, "a:b0,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b1,y:b3,hint:!SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", sizeof(mapping_string)); + } else { + SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", sizeof(mapping_string)); + } if (SDL_IsJoystickSteamController(vendor, product)) { // Steam controllers have 2 back paddle buttons @@ -1110,7 +1114,7 @@ SDL_COMPILE_TIME_ASSERT(map_StringForGamepadButton, SDL_arraysize(map_StringForG /* * convert a string to its enum equivalent */ -static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, bool baxy) +static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, bool axby, bool baxy) { int i; @@ -1120,7 +1124,17 @@ static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, for (i = 0; i < SDL_arraysize(map_StringForGamepadButton); ++i) { if (SDL_strcasecmp(str, map_StringForGamepadButton[i]) == 0) { - if (baxy) { + if (axby) { + // Need to swap face buttons + switch (i) { + case SDL_GAMEPAD_BUTTON_EAST: + return SDL_GAMEPAD_BUTTON_WEST; + case SDL_GAMEPAD_BUTTON_WEST: + return SDL_GAMEPAD_BUTTON_EAST; + default: + break; + } + } else if (baxy) { // Need to swap face buttons switch (i) { case SDL_GAMEPAD_BUTTON_SOUTH: @@ -1142,7 +1156,7 @@ static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, } SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str) { - return SDL_PrivateGetGamepadButtonFromString(str, false); + return SDL_PrivateGetGamepadButtonFromString(str, false, false); } /* @@ -1169,6 +1183,7 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG char half_axis_output = 0; int i; SDL_GamepadBinding *new_bindings; + bool axby_mapping = false; bool baxy_mapping = false; SDL_AssertJoysticksLocked(); @@ -1179,12 +1194,17 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG half_axis_output = *szGameButton++; } + if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1") != NULL) { + axby_mapping = true; + } if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { baxy_mapping = true; } + // FIXME: We fix these up when loading the mapping, does this ever get hit? + //SDL_assert(!axby_mapping && !baxy_mapping); axis = SDL_GetGamepadAxisFromString(szGameButton); - button = SDL_PrivateGetGamepadButtonFromString(szGameButton, baxy_mapping); + button = SDL_PrivateGetGamepadButtonFromString(szGameButton, axby_mapping, baxy_mapping); if (axis != SDL_GAMEPAD_AXIS_INVALID) { bind.output_type = SDL_GAMEPAD_BINDTYPE_AXIS; bind.output.axis.axis = axis; @@ -1401,6 +1421,11 @@ static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad) } } + if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN && + SDL_strstr(gamepad->mapping->mapping, "SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS") != NULL) { + // This controller uses GameCube button style + gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_AXBY; + } if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN && SDL_strstr(gamepad->mapping->mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") != NULL) { // This controller uses Nintendo button style @@ -1466,24 +1491,6 @@ static void SDL_FixupHIDAPIMapping(SDL_Gamepad *gamepad) } } -static void SDL_FixupGameCubeMapping(SDL_Gamepad *gamepad, const char *pchString) -{ - if (SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_FACE_FIELD) != NULL) { - return; - } - - for (int i = 0; i < gamepad->num_bindings; ++i) { - SDL_GamepadBinding *binding = &gamepad->bindings[i]; - if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON) { - if (binding->output.button == SDL_GAMEPAD_BUTTON_EAST) { - binding->output.button = SDL_GAMEPAD_BUTTON_WEST; - } else if (binding->output.button == SDL_GAMEPAD_BUTTON_WEST) { - binding->output.button = SDL_GAMEPAD_BUTTON_EAST; - } - } - } -} - /* * Make a new button mapping struct */ @@ -1509,10 +1516,6 @@ static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t SDL_FixupHIDAPIMapping(gamepad); } - if (SDL_IsJoystickGameCube(SDL_GetGamepadVendor(gamepad), SDL_GetGamepadProduct(gamepad))) { - SDL_FixupGameCubeMapping(gamepad, pGamepadMapping->mapping); - } - // Set the zero point for triggers for (i = 0; i < gamepad->num_bindings; ++i) { SDL_GamepadBinding *binding = &gamepad->bindings[i]; @@ -1983,7 +1986,37 @@ bool SDL_ReloadGamepadMappings(void) return true; } -static char *SDL_ConvertMappingToPositional(const char *mapping) +static char *SDL_ConvertMappingToPositionalAXBY(const char *mapping) +{ + // Add space for '!' and null terminator + size_t length = SDL_strlen(mapping) + 1 + 1; + char *remapped = (char *)SDL_malloc(length); + if (remapped) { + char *button_B; + char *button_X; + char *hint; + + SDL_strlcpy(remapped, mapping, length); + button_B = SDL_strstr(remapped, ",b:"); + button_X = SDL_strstr(remapped, ",x:"); + hint = SDL_strstr(remapped, "hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS"); + + if (button_B) { + button_B[1] = 'x'; + } + if (button_X) { + button_X[1] = 'b'; + } + if (hint) { + hint += 5; + SDL_memmove(hint + 1, hint, SDL_strlen(hint) + 1); + *hint = '!'; + } + } + return remapped; +} + +static char *SDL_ConvertMappingToPositionalBAXY(const char *mapping) { // Add space for '!' and null terminator size_t length = SDL_strlen(mapping) + 1 + 1; @@ -1996,23 +2029,23 @@ static char *SDL_ConvertMappingToPositional(const char *mapping) char *hint; SDL_strlcpy(remapped, mapping, length); - button_A = SDL_strstr(remapped, "a:"); - button_B = SDL_strstr(remapped, "b:"); - button_X = SDL_strstr(remapped, "x:"); - button_Y = SDL_strstr(remapped, "y:"); + button_A = SDL_strstr(remapped, ",a:"); + button_B = SDL_strstr(remapped, ",b:"); + button_X = SDL_strstr(remapped, ",x:"); + button_Y = SDL_strstr(remapped, ",y:"); hint = SDL_strstr(remapped, "hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS"); if (button_A) { - *button_A = 'b'; + button_A[1] = 'b'; } if (button_B) { - *button_B = 'a'; + button_B[1] = 'a'; } if (button_X) { - *button_X = 'y'; + button_X[1] = 'y'; } if (button_Y) { - *button_Y = 'x'; + button_Y[1] = 'x'; } if (hint) { hint += 5; @@ -2028,9 +2061,11 @@ static char *SDL_ConvertMappingToPositional(const char *mapping) */ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMappingPriority priority) { + char *appended = NULL; char *remapped = NULL; char *pchGUID; - SDL_GUID jGUID; + SDL_GUID guid; + Uint16 vendor, product; bool is_default_mapping = false; bool is_xinput_mapping = false; bool existing = false; @@ -2044,6 +2079,28 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa return -1; } + pchGUID = SDL_PrivateGetGamepadGUIDFromMappingString(mappingString); + if (!pchGUID) { + SDL_SetError("Couldn't parse GUID from %s", mappingString); + return -1; + } + if (!SDL_strcasecmp(pchGUID, "default")) { + is_default_mapping = true; + } else if (!SDL_strcasecmp(pchGUID, "xinput")) { + is_xinput_mapping = true; + } + guid = SDL_StringToGUID(pchGUID); + SDL_free(pchGUID); + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + if (SDL_IsJoystickGameCube(vendor, product) && + SDL_strstr(mappingString, "SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS") == NULL) { + SDL_asprintf(&appended, "%shint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,", mappingString); + if (appended) { + mappingString = appended; + } + } + { // Extract and verify the hint field const char *tmp; @@ -2075,18 +2132,31 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa default_value = false; } - if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") == 0) { + if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS") == 0) { // This hint is used to signal whether the mapping uses positional buttons or not if (negate) { // This mapping uses positional buttons, we can use it as-is } else { // This mapping uses labeled buttons, we need to swap them to positional - remapped = SDL_ConvertMappingToPositional(mappingString); + remapped = SDL_ConvertMappingToPositionalAXBY(mappingString); if (!remapped) { goto done; } mappingString = remapped; } + } else if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") == 0) { + // This hint is used to signal whether the mapping uses positional buttons or not + if (negate) { + // This mapping uses positional buttons, we can use it as-is + } else { + // This mapping uses labeled buttons, we need to swap them to positional + remapped = SDL_ConvertMappingToPositionalBAXY(mappingString); + if (!remapped) { + goto done; + } + mappingString = remapped; + } + } else { value = SDL_GetHintBoolean(hint, default_value); if (negate) { @@ -2123,20 +2193,7 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa } #endif - pchGUID = SDL_PrivateGetGamepadGUIDFromMappingString(mappingString); - if (!pchGUID) { - SDL_SetError("Couldn't parse GUID from %s", mappingString); - goto done; - } - if (!SDL_strcasecmp(pchGUID, "default")) { - is_default_mapping = true; - } else if (!SDL_strcasecmp(pchGUID, "xinput")) { - is_xinput_mapping = true; - } - jGUID = SDL_StringToGUID(pchGUID); - SDL_free(pchGUID); - - pGamepadMapping = SDL_PrivateAddMappingForGUID(jGUID, mappingString, &existing, priority); + pGamepadMapping = SDL_PrivateAddMappingForGUID(guid, mappingString, &existing, priority); if (!pGamepadMapping) { goto done; } @@ -2152,9 +2209,8 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa result = 1; } done: - if (remapped) { - SDL_free(remapped); - } + SDL_free(appended); + SDL_free(remapped); return result; } diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index 298b4aa65b..df6988909a 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -828,8 +828,6 @@ bool SDL_InitJoysticks(void) SDL_joysticks_initialized = true; - SDL_InitGamepadMappings(); - SDL_LoadVIDPIDList(&old_xboxone_controllers); SDL_LoadVIDPIDList(&arcadestick_devices); SDL_LoadVIDPIDList(&blacklist_devices); @@ -840,6 +838,8 @@ bool SDL_InitJoysticks(void) SDL_LoadVIDPIDList(&wheel_devices); SDL_LoadVIDPIDList(&zero_centered_devices); + SDL_InitGamepadMappings(); + // See if we should allow joystick events while in the background SDL_AddHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, SDL_JoystickAllowBackgroundEventsChanged, NULL); diff --git a/test/testautomation_joystick.c b/test/testautomation_joystick.c index 274ba79f6b..bce6c7f2d1 100644 --- a/test/testautomation_joystick.c +++ b/test/testautomation_joystick.c @@ -132,6 +132,23 @@ static int SDLCALL TestVirtualJoystick(void *arg) SDL_UpdateJoysticks(); SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == false, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_SOUTH) == false"); + /* Set an explicit mapping with legacy GameCube style buttons */ + SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff0013db5669727475616c2043007601,Virtual Nintendo GameCube,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_GAMECUBE_LABELS:=1,"); + { + const char *name = SDL_GetGamepadName(gamepad); + SDLTest_AssertCheck(name && SDL_strcmp(name, "Virtual Nintendo GameCube") == 0, "SDL_GetGamepadName() -> \"%s\" (expected \"%s\")", name, "Virtual Nintendo GameCube"); + } + SDLTest_AssertCheck(SDL_GetGamepadButtonLabel(gamepad, SDL_GAMEPAD_BUTTON_EAST) == SDL_GAMEPAD_BUTTON_LABEL_X, "SDL_GetGamepadButtonLabel(SDL_GAMEPAD_BUTTON_EAST) == SDL_GAMEPAD_BUTTON_LABEL_X"); + + /* Set the east button and verify that the gamepad responds, mapping "B" to the west button */ + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_EAST, true), "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_EAST, true)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST) == true, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_WEST) == true"); + + SDLTest_AssertCheck(SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_EAST, false), "SDL_SetJoystickVirtualButton(SDL_GAMEPAD_BUTTON_EAST, false)"); + SDL_UpdateJoysticks(); + SDLTest_AssertCheck(SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST) == false, "SDL_GetGamepadButton(SDL_GAMEPAD_BUTTON_WEST) == false"); + /* Set an explicit mapping with legacy Nintendo style buttons */ SDL_SetGamepadMapping(SDL_GetJoystickID(joystick), "ff0013db5669727475616c2043007601,Virtual Nintendo Gamepad,a:b1,b:b0,x:b3,y:b2,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,misc1:b15,paddle1:b16,paddle2:b17,paddle3:b18,paddle4:b19,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,"); { diff --git a/test/testcontroller.c b/test/testcontroller.c index c15aa65ab7..b717b30eb9 100644 --- a/test/testcontroller.c +++ b/test/testcontroller.c @@ -478,17 +478,6 @@ static void CommitBindingElement(const char *binding, bool force) mapping = NULL; } - if (mapping && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_GAMECUBE) { - if (SDL_strstr(mapping, "face:") == NULL) { - char *new_mapping = NULL; - SDL_asprintf(&new_mapping, "%sface:axby,", mapping); - if (new_mapping) { - SDL_free(mapping); - mapping = new_mapping; - } - } - } - /* If the controller generates multiple events for a single element, pick the best one */ if (!force && binding_advance_time) { char *current = GetElementBinding(mapping, binding_element);