SDL: SDL now represents gamepad buttons as positional elements with a separate label

From 2991b9f6ac9b48cdb079ce36e62d1ccac935090f Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 6 Nov 2023 13:07:12 -0800
Subject: [PATCH] SDL now represents gamepad buttons as positional elements
 with a separate label

This gives applications and binding systems a clearer view of what the hardware is so they can make intelligent decisions about how to present things to the user.

Gamepad mappings continue to use abxy for the face buttons for simplicity and compatibility with earlier versions of SDL, however the "SDL_GAMECONTROLLER_USE_BUTTON_LABELS" hint no longer has any effect.

Fixes https://github.com/libsdl-org/SDL/issues/6117
---
 docs/README-migration.md                    |  15 +-
 include/SDL3/SDL_gamepad.h                  |  54 +++-
 include/SDL3/SDL_hints.h                    |  23 --
 include/SDL3/SDL_joystick.h                 |   2 +-
 include/SDL3/SDL_oldnames.h                 |  16 +-
 src/dynapi/SDL_dynapi.sym                   |   2 +
 src/dynapi/SDL_dynapi_overrides.h           |   2 +
 src/dynapi/SDL_dynapi_procs.h               |   2 +
 src/joystick/SDL_gamepad.c                  | 295 ++++++++++++++++++--
 src/joystick/SDL_gamepad_db.h               | 117 --------
 src/joystick/android/SDL_sysjoystick.c      |  14 +-
 src/joystick/apple/SDL_mfijoystick.m        |  28 +-
 src/joystick/hidapi/SDL_hidapi_gamecube.c   |  39 +--
 src/joystick/hidapi/SDL_hidapi_luna.c       |  16 +-
 src/joystick/hidapi/SDL_hidapi_ps3.c        |  56 ++--
 src/joystick/hidapi/SDL_hidapi_ps4.c        |   8 +-
 src/joystick/hidapi/SDL_hidapi_ps5.c        |  16 +-
 src/joystick/hidapi/SDL_hidapi_shield.c     |  16 +-
 src/joystick/hidapi/SDL_hidapi_stadia.c     |   8 +-
 src/joystick/hidapi/SDL_hidapi_steam.c      |   8 +-
 src/joystick/hidapi/SDL_hidapi_switch.c     |  99 +++----
 src/joystick/hidapi/SDL_hidapi_wii.c        | 107 +------
 src/joystick/hidapi/SDL_hidapi_xbox360.c    |   8 +-
 src/joystick/hidapi/SDL_hidapi_xbox360w.c   |   8 +-
 src/joystick/hidapi/SDL_hidapi_xboxone.c    |  24 +-
 src/joystick/virtual/SDL_virtualjoystick.c  |   8 +-
 src/joystick/windows/SDL_rawinputjoystick.c |  24 +-
 src/test/SDL_test_common.c                  |   8 +-
 test/gamepadutils.c                         | 262 ++++++++++++-----
 test/gamepadutils.h                         |  12 +-
 test/testautomation_joystick.c              |  61 +++-
 test/testcontroller.c                       | 105 ++++---
 32 files changed, 852 insertions(+), 611 deletions(-)

diff --git a/docs/README-migration.md b/docs/README-migration.md
index 3bd20f135b1f..6e64bf126099 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -390,6 +390,8 @@ The SDL_EVENT_GAMEPAD_ADDED event now provides the joystick instance ID in the w
 
 The functions SDL_GetGamepads(), SDL_GetGamepadInstanceName(), SDL_GetGamepadInstancePath(), SDL_GetGamepadInstancePlayerIndex(), SDL_GetGamepadInstanceGUID(), SDL_GetGamepadInstanceVendor(), SDL_GetGamepadInstanceProduct(), SDL_GetGamepadInstanceProductVersion(), and SDL_GetGamepadInstanceType() have been added to directly query the list of available gamepads.
 
+The gamepad face buttons have been renamed from A/B/X/Y to North/South/East/West to indicate that they are positional rather than hardware-specific. You can use SDL_GetGamepadButtonLabel() to get the labels for the face buttons, e.g. A/B/X/Y or Cross/Circle/Square/Triangle. The hint SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS is ignored, and mappings that use this hint are translated correctly into positional buttons. Applications will now need to provide a way for users to swap between South/East as their accept/cancel buttons, as this varies based on region and muscle memory. Using South as the accept button and East as the cancel button is a good default.
+
 SDL_GameControllerGetSensorDataWithTimestamp() has been removed. If you want timestamps for the sensor data, you should use the sensor_timestamp member of SDL_EVENT_GAMEPAD_SENSOR_UPDATE events.
 
 SDL_CONTROLLER_TYPE_VIRTUAL has been removed, so virtual controllers can emulate other gamepad types. If you need to know whether a controller is virtual, you can use SDL_IsJoystickVirtual().
@@ -502,8 +504,8 @@ The following symbols have been renamed:
 * SDL_CONTROLLER_BINDTYPE_BUTTON => SDL_GAMEPAD_BINDTYPE_BUTTON
 * SDL_CONTROLLER_BINDTYPE_HAT => SDL_GAMEPAD_BINDTYPE_HAT
 * SDL_CONTROLLER_BINDTYPE_NONE => SDL_GAMEPAD_BINDTYPE_NONE
-* SDL_CONTROLLER_BUTTON_A => SDL_GAMEPAD_BUTTON_A
-* SDL_CONTROLLER_BUTTON_B => SDL_GAMEPAD_BUTTON_B
+* SDL_CONTROLLER_BUTTON_A => SDL_GAMEPAD_BUTTON_SOUTH
+* SDL_CONTROLLER_BUTTON_B => SDL_GAMEPAD_BUTTON_EAST
 * SDL_CONTROLLER_BUTTON_BACK => SDL_GAMEPAD_BUTTON_BACK
 * SDL_CONTROLLER_BUTTON_DPAD_DOWN => SDL_GAMEPAD_BUTTON_DPAD_DOWN
 * SDL_CONTROLLER_BUTTON_DPAD_LEFT => SDL_GAMEPAD_BUTTON_DPAD_LEFT
@@ -523,8 +525,8 @@ The following symbols have been renamed:
 * SDL_CONTROLLER_BUTTON_RIGHTSTICK => SDL_GAMEPAD_BUTTON_RIGHT_STICK
 * SDL_CONTROLLER_BUTTON_START => SDL_GAMEPAD_BUTTON_START
 * SDL_CONTROLLER_BUTTON_TOUCHPAD => SDL_GAMEPAD_BUTTON_TOUCHPAD
-* SDL_CONTROLLER_BUTTON_X => SDL_GAMEPAD_BUTTON_X
-* SDL_CONTROLLER_BUTTON_Y => SDL_GAMEPAD_BUTTON_Y
+* SDL_CONTROLLER_BUTTON_X => SDL_GAMEPAD_BUTTON_WEST
+* SDL_CONTROLLER_BUTTON_Y => SDL_GAMEPAD_BUTTON_NORTH
 * SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT
 * SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR
 * SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT
@@ -552,14 +554,15 @@ SDL_AddHintCallback() now returns a standard int result instead of void, returni
 Calling SDL_GetHint() with the name of the hint being changed from within a hint callback will now return the new value rather than the old value. The old value is still passed as a parameter to the hint callback.
 
 The following hints have been removed:
-* SDL_HINT_VIDEO_HIGHDPI_DISABLED - high DPI support is always enabled
+* SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS - gamepad buttons are always positional
 * SDL_HINT_IDLE_TIMER_DISABLED - use SDL_DisableScreenSaver instead
+* SDL_HINT_IME_SUPPORT_EXTENDED_TEXT - the normal text editing event has extended text
 * SDL_HINT_MOUSE_RELATIVE_SCALING - mouse coordinates are no longer automatically scaled by the SDL renderer
 * SDL_HINT_RENDER_LOGICAL_SIZE_MODE - the logical size mode is explicitly set with SDL_SetRenderLogicalPresentation()
+* SDL_HINT_VIDEO_HIGHDPI_DISABLED - high DPI support is always enabled
 * SDL_HINT_VIDEO_X11_FORCE_EGL - use SDL_HINT_VIDEO_FORCE_EGL instead
 * SDL_HINT_VIDEO_X11_XINERAMA - Xinerama no longer supported by the X11 backend
 * SDL_HINT_VIDEO_X11_XVIDMODE - Xvidmode no longer supported by the X11 backend
-* SDL_HINT_IME_SUPPORT_EXTENDED_TEXT - the normal text editing event has extended text
 
 * Renamed hints SDL_HINT_VIDEODRIVER and SDL_HINT_AUDIODRIVER to SDL_HINT_VIDEO_DRIVER and SDL_HINT_AUDIO_DRIVER
 * Renamed environment variables SDL_VIDEODRIVER and SDL_AUDIODRIVER to SDL_VIDEO_DRIVER and SDL_AUDIO_DRIVER
diff --git a/include/SDL3/SDL_gamepad.h b/include/SDL3/SDL_gamepad.h
index 04b27671805d..ccbd5d8baaf1 100644
--- a/include/SDL3/SDL_gamepad.h
+++ b/include/SDL3/SDL_gamepad.h
@@ -81,10 +81,10 @@ typedef enum
 typedef enum
 {
     SDL_GAMEPAD_BUTTON_INVALID = -1,
-    SDL_GAMEPAD_BUTTON_A,
-    SDL_GAMEPAD_BUTTON_B,
-    SDL_GAMEPAD_BUTTON_X,
-    SDL_GAMEPAD_BUTTON_Y,
+    SDL_GAMEPAD_BUTTON_SOUTH,
+    SDL_GAMEPAD_BUTTON_EAST,
+    SDL_GAMEPAD_BUTTON_WEST,
+    SDL_GAMEPAD_BUTTON_NORTH,
     SDL_GAMEPAD_BUTTON_BACK,
     SDL_GAMEPAD_BUTTON_GUIDE,
     SDL_GAMEPAD_BUTTON_START,
@@ -105,6 +105,26 @@ typedef enum
     SDL_GAMEPAD_BUTTON_MAX
 } SDL_GamepadButton;
 
+/**
+ *  The set of gamepad button labels
+ *
+ *  This isn't a complete set, just the face buttons to make it easy to show button prompts.
+ *
+ *  For a complete set, you should look at the button and gamepad type and have a set of symbols that work well with your art style.
+ */
+typedef enum
+{
+    SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN,
+    SDL_GAMEPAD_BUTTON_LABEL_A,         /**< The south button for Xbox controllers, the east button for Nintendo controllers */
+    SDL_GAMEPAD_BUTTON_LABEL_B,         /**< The east button for Xbox controllers, the south button for Nintendo controllers */
+    SDL_GAMEPAD_BUTTON_LABEL_X,         /**< The west button for Xbox controllers, the north button for Nintendo controllers */
+    SDL_GAMEPAD_BUTTON_LABEL_Y,         /**< The north button for Xbox controllers, the west button for Nintendo controllers */
+    SDL_GAMEPAD_BUTTON_LABEL_CROSS,     /**< The south button for Playstation controllers */
+    SDL_GAMEPAD_BUTTON_LABEL_CIRCLE,    /**< The east button for Playstation controllers */
+    SDL_GAMEPAD_BUTTON_LABEL_SQUARE,    /**< The west button for Playstation controllers */
+    SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE   /**< The north button for Playstation controllers */
+} SDL_GamepadButtonLabel;
+
 /**
  *  The list of axes available on a gamepad
  *
@@ -990,6 +1010,32 @@ extern DECLSPEC SDL_bool SDLCALL SDL_GamepadHasButton(SDL_Gamepad *gamepad, SDL_
  */
 extern DECLSPEC Uint8 SDLCALL SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_GamepadButton button);
 
+/**
+ * Get the label of a button on a gamepad.
+ *
+ * \param type the type of gamepad to check
+ * \param button a button index (one of the SDL_GamepadButton values)
+ * \returns the SDL_GamepadButtonLabel enum corresponding to the button label
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetGamepadButtonLabel
+ */
+extern DECLSPEC SDL_GamepadButtonLabel SDLCALL SDL_GetGamepadButtonLabelForType(SDL_GamepadType type, SDL_GamepadButton button);
+
+/**
+ * Get the label of a button on a gamepad.
+ *
+ * \param gamepad a gamepad
+ * \param button a button index (one of the SDL_GamepadButton values)
+ * \returns the SDL_GamepadButtonLabel enum corresponding to the button label
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetGamepadButtonLabelForType
+ */
+extern DECLSPEC SDL_GamepadButtonLabel SDLCALL SDL_GetGamepadButtonLabel(SDL_Gamepad *gamepad, SDL_GamepadButton button);
+
 /**
  * Get the number of touchpads on a gamepad.
  *
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index a2d06b2b18fb..0e4bf7565231 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -537,29 +537,6 @@ extern "C" {
  */
 #define SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT"
 
-/**
- *  If set, game controller face buttons report their values according to their labels instead of their positional layout.
- *
- *  For example, on Nintendo Switch controllers, normally you'd get:
- *
- *      (Y)
- *  (X)     (B)
- *      (A)
- *
- *  but if this hint is set, you'll get:
- *
- *      (X)
- *  (Y)     (A)
- *      (B)
- *
- *  The variable can be set to the following values:
- *    "0"       - Report the face buttons by position, as though they were on an Xbox controller.
- *    "1"       - Report the face buttons by label instead of position
- *
- *  The default value is "1".  This hint may be set at any time.
- */
-#define SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS "SDL_GAMECONTROLLER_USE_BUTTON_LABELS"
-
 /**
  *  Controls whether the device's built-in accelerometer and gyro should be used as sensors for gamepads.
  *
diff --git a/include/SDL3/SDL_joystick.h b/include/SDL3/SDL_joystick.h
index efb7d629ed52..6fa5d171deae 100644
--- a/include/SDL3/SDL_joystick.h
+++ b/include/SDL3/SDL_joystick.h
@@ -343,7 +343,7 @@ typedef struct SDL_VirtualJoystickDesc
     Uint16 product_id;  /**< the USB product ID of this joystick */
     Uint16 padding;     /**< unused */
     Uint32 button_mask; /**< A mask of which buttons are valid for this controller
-                             e.g. (1 << SDL_GAMEPAD_BUTTON_A) */
+                             e.g. (1 << SDL_GAMEPAD_BUTTON_SOUTH) */
     Uint32 axis_mask;   /**< A mask of which axes are valid for this controller
                              e.g. (1 << SDL_GAMEPAD_AXIS_LEFTX) */
     const char *name;   /**< the name of the joystick */
diff --git a/include/SDL3/SDL_oldnames.h b/include/SDL3/SDL_oldnames.h
index 805921292bbb..39b1a61598cd 100644
--- a/include/SDL3/SDL_oldnames.h
+++ b/include/SDL3/SDL_oldnames.h
@@ -160,8 +160,8 @@
 #define SDL_CONTROLLER_BINDTYPE_BUTTON SDL_GAMEPAD_BINDTYPE_BUTTON
 #define SDL_CONTROLLER_BINDTYPE_HAT SDL_GAMEPAD_BINDTYPE_HAT
 #define SDL_CONTROLLER_BINDTYPE_NONE SDL_GAMEPAD_BINDTYPE_NONE
-#define SDL_CONTROLLER_BUTTON_A SDL_GAMEPAD_BUTTON_A
-#define SDL_CONTROLLER_BUTTON_B SDL_GAMEPAD_BUTTON_B
+#define SDL_CONTROLLER_BUTTON_A SDL_GAMEPAD_BUTTON_SOUTH
+#define SDL_CONTROLLER_BUTTON_B SDL_GAMEPAD_BUTTON_EAST
 #define SDL_CONTROLLER_BUTTON_BACK SDL_GAMEPAD_BUTTON_BACK
 #define SDL_CONTROLLER_BUTTON_DPAD_DOWN SDL_GAMEPAD_BUTTON_DPAD_DOWN
 #define SDL_CONTROLLER_BUTTON_DPAD_LEFT SDL_GAMEPAD_BUTTON_DPAD_LEFT
@@ -181,8 +181,8 @@
 #define SDL_CONTROLLER_BUTTON_RIGHTSTICK SDL_GAMEPAD_BUTTON_RIGHT_STICK
 #define SDL_CONTROLLER_BUTTON_START SDL_GAMEPAD_BUTTON_START
 #define SDL_CONTROLLER_BUTTON_TOUCHPAD SDL_GAMEPAD_BUTTON_TOUCHPAD
-#define SDL_CONTROLLER_BUTTON_X SDL_GAMEPAD_BUTTON_X
-#define SDL_CONTROLLER_BUTTON_Y SDL_GAMEPAD_BUTTON_Y
+#define SDL_CONTROLLER_BUTTON_X SDL_GAMEPAD_BUTTON_WEST
+#define SDL_CONTROLLER_BUTTON_Y SDL_GAMEPAD_BUTTON_NORTH
 #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT
 #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR
 #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT
@@ -603,8 +603,8 @@
 #define SDL_CONTROLLER_BINDTYPE_BUTTON SDL_CONTROLLER_BINDTYPE_BUTTON_renamed_SDL_GAMEPAD_BINDTYPE_BUTTON
 #define SDL_CONTROLLER_BINDTYPE_HAT SDL_CONTROLLER_BINDTYPE_HAT_renamed_SDL_GAMEPAD_BINDTYPE_HAT
 #define SDL_CONTROLLER_BINDTYPE_NONE SDL_CONTROLLER_BINDTYPE_NONE_renamed_SDL_GAMEPAD_BINDTYPE_NONE
-#define SDL_CONTROLLER_BUTTON_A SDL_CONTROLLER_BUTTON_A_renamed_SDL_GAMEPAD_BUTTON_A
-#define SDL_CONTROLLER_BUTTON_B SDL_CONTROLLER_BUTTON_B_renamed_SDL_GAMEPAD_BUTTON_B
+#define SDL_CONTROLLER_BUTTON_A SDL_CONTROLLER_BUTTON_A_renamed_SDL_GAMEPAD_BUTTON_SOUTH
+#define SDL_CONTROLLER_BUTTON_B SDL_CONTROLLER_BUTTON_B_renamed_SDL_GAMEPAD_BUTTON_EAST
 #define SDL_CONTROLLER_BUTTON_BACK SDL_CONTROLLER_BUTTON_BACK_renamed_SDL_GAMEPAD_BUTTON_BACK
 #define SDL_CONTROLLER_BUTTON_DPAD_DOWN SDL_CONTROLLER_BUTTON_DPAD_DOWN_renamed_SDL_GAMEPAD_BUTTON_DPAD_DOWN
 #define SDL_CONTROLLER_BUTTON_DPAD_LEFT SDL_CONTROLLER_BUTTON_DPAD_LEFT_renamed_SDL_GAMEPAD_BUTTON_DPAD_LEFT
@@ -624,8 +624,8 @@
 #define SDL_CONTROLLER_BUTTON_RIGHTSTICK SDL_CONTROLLER_BUTTON_RIGHTSTICK_renamed_SDL_GAMEPAD_BUTTON_RIGHT_STICK
 #define SDL_CONTROLLER_BUTTON_START SDL_CONTROLLER_BUTTON_START_renamed_SDL_GAMEPAD_BUTTON_START
 #define SDL_CONTROLLER_BUTTON_TOUCHPAD SDL_CONTROLLER_BUTTON_TOUCHPAD_renamed_SDL_GAMEPAD_BUTTON_TOUCHPAD
-#define SDL_CONTROLLER_BUTTON_X SDL_CONTROLLER_BUTTON_X_renamed_SDL_GAMEPAD_BUTTON_X
-#define SDL_CONTROLLER_BUTTON_Y SDL_CONTROLLER_BUTTON_Y_renamed_SDL_GAMEPAD_BUTTON_Y
+#define SDL_CONTROLLER_BUTTON_X SDL_CONTROLLER_BUTTON_X_renamed_SDL_GAMEPAD_BUTTON_WEST
+#define SDL_CONTROLLER_BUTTON_Y SDL_CONTROLLER_BUTTON_Y_renamed_SDL_GAMEPAD_BUTTON_NORTH
 #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT_renamed_SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT
 #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR_renamed_SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR
 #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT_renamed_SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 96e662f2c51e..51e67d6e54ad 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -939,6 +939,8 @@ SDL3_0.0.0 {
     SDL_StopVideoCapture;
     SDL_CloseVideoCapture;
     SDL_GetVideoCaptureDevices;
+    SDL_GetGamepadButtonLabelForType;
+    SDL_GetGamepadButtonLabel;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index adb202523cbc..ac3e1623326f 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -964,3 +964,5 @@
 #define SDL_StopVideoCapture SDL_StopVideoCapture_REAL
 #define SDL_CloseVideoCapture SDL_CloseVideoCapture_REAL
 #define SDL_GetVideoCaptureDevices SDL_GetVideoCaptureDevices_REAL
+#define SDL_GetGamepadButtonLabelForType SDL_GetGamepadButtonLabelForType_REAL
+#define SDL_GetGamepadButtonLabel SDL_GetGamepadButtonLabel_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 7f3b0e521125..dd9d7802b663 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -997,3 +997,5 @@ SDL_DYNAPI_PROC(int,SDL_ReleaseVideoCaptureFrame,(SDL_VideoCaptureDevice *a, SDL
 SDL_DYNAPI_PROC(int,SDL_StopVideoCapture,(SDL_VideoCaptureDevice *a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_CloseVideoCapture,(SDL_VideoCaptureDevice *a),(a),)
 SDL_DYNAPI_PROC(SDL_VideoCaptureDeviceID*,SDL_GetVideoCaptureDevices,(int *a),(a),return)
+SDL_DYNAPI_PROC(SDL_GamepadButtonLabel,SDL_GetGamepadButtonLabelForType,(SDL_GamepadType a, SDL_GamepadButton b),(a,b),return)
+SDL_DYNAPI_PROC(SDL_GamepadButtonLabel,SDL_GetGamepadButtonLabel,(SDL_Gamepad *a, SDL_GamepadButton b),(a,b),return)
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index bb7e55c84b97..9db1c31c2db2 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -45,6 +45,8 @@
 #define SDL_GAMEPAD_CRC_FIELD_SIZE      4 /* hard-coded for speed */
 #define SDL_GAMEPAD_TYPE_FIELD          "type:"
 #define SDL_GAMEPAD_TYPE_FIELD_SIZE     SDL_strlen(SDL_GAMEPAD_TYPE_FIELD)
+#define SDL_GAMEPAD_FACE_FIELD          "face:"
+#define SDL_GAMEPAD_FACE_FIELD_SIZE     5 /* hard-coded for speed */
 #define SDL_GAMEPAD_PLATFORM_FIELD      "platform:"
 #define SDL_GAMEPAD_PLATFORM_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_PLATFORM_FIELD)
 #define SDL_GAMEPAD_HINT_FIELD          "hint:"
@@ -57,6 +59,15 @@
 static SDL_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_BAYX,
+    SDL_GAMEPAD_FACE_STYLE_SONY,
+} SDL_GamepadFaceStyle;
+
 /* our hard coded list of mapping support */
 typedef enum
 {
@@ -107,6 +118,8 @@ struct SDL_Gamepad
     int ref_count _guarded;
 
     const char *name _guarded;
+    SDL_GamepadType type _guarded;
+    SDL_GamepadFaceStyle face_style _guarded;
     GamepadMapping_t *mapping _guarded;
     int num_bindings _guarded;
     SDL_GamepadBinding *bindings _guarded;
@@ -576,10 +589,10 @@ static void PopMappingChangeTracking(void)
  */
 static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_JoystickGUID guid)
 {
-    const int face_button_mask = ((1 << SDL_GAMEPAD_BUTTON_A) |
-                                  (1 << SDL_GAMEPAD_BUTTON_B) |
-                                  (1 << SDL_GAMEPAD_BUTTON_X) |
-                                  (1 << SDL_GAMEPAD_BUTTON_Y));
+    const int face_button_mask = ((1 << SDL_GAMEPAD_BUTTON_SOUTH) |
+                                  (1 << SDL_GAMEPAD_BUTTON_EAST) |
+                                  (1 << SDL_GAMEPAD_BUTTON_WEST) |
+                                  (1 << SDL_GAMEPAD_BUTTON_NORTH));
     SDL_bool existing;
     char mapping_string[1024];
     int button_mask;
@@ -598,20 +611,20 @@ static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_JoystickGUID gui
 
     SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));
 
-    if (button_mask & (1 << SDL_GAMEPAD_BUTTON_A)) {
+    if (button_mask & (1 << SDL_GAMEPAD_BUTTON_SOUTH)) {
         SDL_strlcat(mapping_string, "a:b0,", sizeof(mapping_string));
     }
-    if (button_mask & (1 << SDL_GAMEPAD_BUTTON_B)) {
+    if (button_mask & (1 << SDL_GAMEPAD_BUTTON_EAST)) {
         SDL_strlcat(mapping_string, "b:b1,", sizeof(mapping_string));
     } else if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {
         /* Use the back button as "B" for easy UI navigation with TV remotes */
         SDL_strlcat(mapping_string, "b:b4,", sizeof(mapping_string));
         button_mask &= ~(1 << SDL_GAMEPAD_BUTTON_BACK);
     }
-    if (button_mask & (1 << SDL_GAMEPAD_BUTTON_X)) {
+    if (button_mask & (1 << SDL_GAMEPAD_BUTTON_WEST)) {
         SDL_strlcat(mapping_string, "x:b2,", sizeof(mapping_string));
     }
-    if (button_mask & (1 << SDL_GAMEPAD_BUTTON_Y)) {
+    if (button_mask & (1 << SDL_GAMEPAD_BUTTON_NORTH)) {
         SDL_strlcat(mapping_string, "y:b3,", sizeof(mapping_string));
     }
     if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {
@@ -1059,10 +1072,10 @@ const char *SDL_GetGamepadStringForAxis(SDL_GamepadAxis axis)
 }
 
 static const char *map_StringForGamepadButton[] = {
-    "a",
-    "b",
-    "x",
-    "y",
+    "s",
+    "e",
+    "w",
+    "n",
     "back",
     "guide",
     "start",
@@ -1086,7 +1099,7 @@ SDL_COMPILE_TIME_ASSERT(map_StringForGamepadButton, SDL_arraysize(map_StringForG
 /*
  * convert a string to its enum equivalent
  */
-SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str)
+SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, SDL_bool baxy)
 {
     int i;
 
@@ -1099,8 +1112,46 @@ SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str)
             return (SDL_GamepadButton)i;
         }
     }
+
+    if (SDL_strcasecmp(str, "a") == 0) {
+        if (baxy) {
+            return SDL_GAMEPAD_BUTTON_EAST;
+        } else {
+            return SDL_GAMEPAD_BUTTON_SOUTH;
+        }
+    } else if (SDL_strcasecmp(str, "b") == 0) {
+        if (baxy) {
+            return SDL_GAMEPAD_BUTTON_SOUTH;
+        } else {
+            return SDL_GAMEPAD_BUTTON_EAST;
+        }
+    } else if (SDL_strcasecmp(str, "x") == 0) {
+        if (baxy) {
+            return SDL_GAMEPAD_BUTTON_NORTH;
+        } else {
+            return SDL_GAMEPAD_BUTTON_WEST;
+        }
+    } else if (SDL_strcasecmp(str, "y") == 0) {
+        if (baxy) {
+            return SDL_GAMEPAD_BUTTON_WEST;
+        } else {
+            return SDL_GAMEPAD_BUTTON_NORTH;
+        }
+    } else if (SDL_strcasecmp(str, "cross") == 0) {
+        return SDL_GAMEPAD_BUTTON_SOUTH;
+    } else if (SDL_strcasecmp(str, "circle") == 0) {
+        return SDL_GAMEPAD_BUTTON_EAST;
+    } else if (SDL_strcasecmp(str, "square") == 0) {
+        return SDL_GAMEPAD_BUTTON_WEST;
+    } else if (SDL_strcasecmp(str, "triangle") == 0) {
+        return SDL_GAMEPAD_BUTTON_NORTH;
+    }
     return SDL_GAMEPAD_BUTTON_INVALID;
 }
+SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str)
+{
+    return SDL_PrivateGetGamepadButtonFromString(str, SDL_FALSE);
+}
 
 /*
  * convert an enum to its string equivalent
@@ -1116,7 +1167,7 @@ const char *SDL_GetGamepadStringForButton(SDL_GamepadButton button)
 /*
  * given a gamepad button name and a joystick name update our mapping structure with it
  */
-static int SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGameButton, const char *szJoystickButton)
+static SDL_bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGameButton, const char *szJoystickButton)
 {
     SDL_GamepadBinding bind;
     SDL_GamepadButton button;
@@ -1124,16 +1175,24 @@ static int SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGa
     SDL_bool invert_input = SDL_FALSE;
     char half_axis_input = 0;
     char half_axis_output = 0;
+    int i;
     SDL_GamepadBinding *new_bindings;
+    SDL_bool baxy_mapping = SDL_FALSE;
 
     SDL_AssertJoysticksLocked();
 
+    SDL_zero(bind);
+
     if (*szGameButton == '+' || *szGameButton == '-') {
         half_axis_output = *szGameButton++;
     }
 
+    if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) {
+        baxy_mapping = SDL_TRUE;
+    }
+
     axis = SDL_GetGamepadAxisFromString(szGameButton);
-    button = SDL_GetGamepadButtonFromString(szGameButton);
+    button = SDL_PrivateGetGamepadButtonFromString(szGameButton, baxy_mapping);
     if (axis != SDL_GAMEPAD_AXIS_INVALID) {
         bind.outputType = SDL_GAMEPAD_BINDTYPE_AXIS;
         bind.output.axis.axis = axis;
@@ -1156,7 +1215,7 @@ static int SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGa
         bind.outputType = SDL_GAMEPAD_BINDTYPE_BUTTON;
         bind.output.button = button;
     } else {
-        return SDL_SetError("Unexpected gamepad element %s", szGameButton);
+        return SDL_FALSE;
     }
 
     if (*szJoystickButton == '+' || *szJoystickButton == '-') {
@@ -1195,7 +1254,14 @@ static int SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGa
         bind.input.hat.hat = hat;
         bind.input.hat.hat_mask = mask;
     } else {
-        return SDL_SetError("Unexpected joystick element: %s", szJoystickButton);
+        return SDL_FALSE;
+    }
+
+    for (i = 0; i < gamepad->num_bindings; ++i) {
+        if (SDL_memcmp(&gamepad->bindings[i], &bind, sizeof(bind)) == 0) {
+            /* We already have this binding, could be different face button names? */
+            return SDL_TRUE;
+        }
     }
 
     ++gamepad->num_bindings;
@@ -1204,11 +1270,11 @@ static int SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGa
         SDL_free(gamepad->bindings);
         gamepad->num_bindings = 0;
         gamepad->bindings = NULL;
-        return SDL_OutOfMemory();
+        return SDL_FALSE;
     }
     gamepad->bindings = new_bindings;
     gamepad->bindings[gamepad->num_bindings - 1] = bind;
-    return 0;
+    return SDL_TRUE;
 }
 
 /*
@@ -1261,6 +1327,93 @@ static int SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char
     return 0;
 }
 
+static void SDL_UpdateGamepadType(SDL_Gamepad *gamepad)
+{
+    char *type_string, *comma;
+
+    SDL_AssertJoysticksLocked();
+
+    gamepad->type = SDL_GAMEPAD_TYPE_UNKNOWN;
+
+    type_string = SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_TYPE_FIELD);
+    if (type_string) {
+        type_string += SDL_GAMEPAD_TYPE_FIELD_SIZE;
+        comma = SDL_strchr(type_string, ',');
+        if (comma) {
+            *comma = '\0';
+            gamepad->type = SDL_GetGamepadTypeFromString(type_string);
+            *comma = ',';
+        } else {
+            gamepad->type = SDL_GetGamepadTypeFromString(type_string);
+        }
+    }
+    if (gamepad->type == SDL_GAMEPAD_TYPE_UNKNOWN) {
+        gamepad->type = SDL_GetRealGamepadInstanceType(gamepad->joystick->instance_id);
+    }
+}
+
+static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleFromString(const char *string)
+{
+    if (SDL_strcmp(string, "abxy") == 0) {
+        return SDL_GAMEPAD_FACE_STYLE_ABXY;
+    } else if (SDL_strcmp(string, "bayx") == 0) {
+        return SDL_GAMEPAD_FACE_STYLE_BAYX;
+    } else if (SDL_strcmp(string, "sony") == 0) {
+        return SDL_GAMEPAD_FACE_STYLE_SONY;
+    } else {
+        return SDL_GAMEPAD_FACE_STYLE_UNKNOWN;
+    }
+}
+
+static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleForGamepadType(SDL_GamepadType type)
+{
+    switch (type) {
+    case SDL_GAMEPAD_TYPE_PS3:
+    case SDL_GAMEPAD_TYPE_PS4:
+    case SDL_GAMEPAD_TYPE_PS5:
+        return SDL_GAMEPAD_FACE_STYLE_SONY;
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
+        return SDL_GAMEPAD_FACE_STYLE_BAYX;
+    default:
+        return SDL_GAMEPAD_FACE_STYLE_ABXY;
+    }
+}
+
+static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad)
+{
+    char *face_string, *comma;
+
+    SDL_AssertJoysticksLocked();
+
+    gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_UNKNOWN;
+
+    face_string = SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_FACE_FIELD);
+    if (face_string) {
+        face_string += SDL_GAMEPAD_TYPE_FIELD_SIZE;
+        comma = SDL_strchr(face_string, ',');
+        if (comma) {
+            *comma = '\0';
+            gamepad->face_style = SDL_GetGamepadFaceStyleFromString(face_string);
+            *comma = ',';
+        } else {
+            gamepad->face_style = SDL_GetGamepadFaceStyleFromString(face_string);
+        }
+    }
+
+    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 */
+        gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_BAYX;
+    }
+    if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN) {
+        gamepad->face_style = SDL_GetGamepadFaceStyleForGamepadType(gamepad->type);
+    }
+}
+
+
 /*
  * Make a new button mapping struct
  */
@@ -1277,6 +1430,9 @@ static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t
         SDL_memset(gamepad->last_match_axis, 0, gamepad->joystick->naxes * sizeof(*gamepad->last_match_axis));
     }
 
+    SDL_UpdateGamepadType(gamepad);
+    SDL_UpdateGamepadFaceStyle(gamepad);
+
     SDL_PrivateParseGamepadConfigString(gamepad, pGamepadMapping->mapping);
 
     /* Set the zero point for triggers */
@@ -2246,7 +2402,6 @@ SDL_GamepadType SDL_GetGamepadInstanceType(SDL_JoystickID instance_id)
                     *comma = ',';
                 }
             }
-
         }
     }
     SDL_UnlockJoysticks();
@@ -2658,6 +2813,100 @@ Uint8 SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)
     return retval;
 }
 
+/**
+ * Get the label of a button on a gamepad.
+ */
+SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForFaceStyle(SDL_GamepadFaceStyle face_style, SDL_GamepadButton button)
+{
+    SDL_GamepadButtonLabel label = SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN;
+
+    switch (face_style) {
+    case SDL_GAMEPAD_FACE_STYLE_ABXY:
+        switch (button) {
+        case SDL_GAMEPAD_BUTTON_SOUTH:
+            label = SDL_GAMEPAD_BUTTON_LABEL_A;
+            break;
+        case SDL_GAMEPAD_BUTTON_EAST:
+            label = SDL_GAMEPAD_BUTTON_LABEL_B;
+            break;
+        case SDL_GAMEPAD_BUTTON_WEST:
+            label = SDL_GAMEPAD_BUTTON_LABEL_X;
+            break;
+        case SDL_GAMEPAD_BUTTON_NORTH:
+            label = SDL_GAMEPAD_BUTTON_LABEL_Y;
+            break;
+        default:
+            break;
+        }
+        break;
+    case SDL_GAMEPAD_FACE_STYLE_BAYX:
+        switch (button) {
+        case SDL_GAMEPAD_BUTTON_SOUTH:
+            label = SDL_GAMEPAD_BUTTON_LABEL_B;
+            break;
+        case SDL_GAMEPAD_BUTTON_EAST:
+            label = SDL_GAMEPAD_BUTTON_LABEL_A;
+            break;
+        case SDL_GAMEPAD_BUTTON_WEST:
+            label = SDL_GAMEPAD_BUTTON_LABEL_Y;
+            break;
+        case SDL_GAMEPAD_BUTTON_NORTH:
+            label = SDL_GAMEPAD_BUTTON_LABEL_X;
+            break;
+        default:
+            break;
+        }
+        break;
+    case SDL_GAMEPAD_FACE_STYLE_SONY:
+        switch (button) {
+        case SDL_GAMEPAD_BUTTON_SOUTH:
+            label = SDL_GAMEPAD_BUTTON_LABEL_CROSS;
+            b

(Patch may be truncated, please check the link at the top of this post.)