sdl2-compat: Updated for SDL3 changes to gamepad face buttons

From 72204b949ef23a3ce14886fd0d2164e9cba9f5cc Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 10 Nov 2023 18:39:50 -0800
Subject: [PATCH] Updated for SDL3 changes to gamepad face buttons

---
 src/sdl2_compat.c          | 97 +++++++++++++++++++++++++++++++++++++-
 src/sdl3_include_wrapper.h | 10 ++++
 src/sdl3_syms.h            |  4 +-
 3 files changed, 109 insertions(+), 2 deletions(-)

diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index 18c619c..5e3c4a1 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -1162,6 +1162,8 @@ static SDL_mutex *EventWatchListMutex = NULL;
 static EventFilterWrapperData *EventWatchers2 = NULL;
 static SDL_JoystickID *joystick_list = NULL;
 static int num_joysticks = 0;
+static SDL_JoystickID *gamepad_button_swap_list = NULL;
+static int num_gamepad_button_swap_list = 0;
 static SDL_SensorID *sensor_list = NULL;
 static int num_sensors = 0;
 
@@ -1350,6 +1352,79 @@ SDL_LOG_IMPL(Error, ERROR)
 SDL_LOG_IMPL(Critical, CRITICAL)
 #undef SDL_LOG_IMPL
 
+/* !!! FIXME: when we override SDL_Quit(), we need to free/reset gamepad_button_swap_list */
+
+static void UpdateGamepadButtonSwap(SDL_Gamepad *gamepad)
+{
+    int i;
+    SDL_JoystickID instance_id = SDL3_GetGamepadInstanceID(gamepad);
+    SDL_bool swap_buttons = SDL_FALSE;
+
+    if (SDL3_GetHintBoolean("SDL_GAMECONTROLLER_USE_BUTTON_LABELS", SDL_TRUE)) {
+        if (SDL3_GetGamepadButtonLabel(gamepad, SDL_GAMEPAD_BUTTON_SOUTH) == SDL_GAMEPAD_BUTTON_LABEL_B) {
+            swap_buttons = SDL_TRUE;
+        }
+    }
+
+    if (swap_buttons) {
+        SDL_bool has_gamepad = SDL_FALSE;
+
+        for (i = 0; i < num_gamepad_button_swap_list; ++i) {
+            if (gamepad_button_swap_list[i] == instance_id) {
+                has_gamepad = SDL_TRUE;
+                break;
+            }
+        }
+
+        if (!has_gamepad) {
+            int new_count = num_gamepad_button_swap_list + 1;
+            SDL_JoystickID *new_list = (SDL_JoystickID *)SDL3_realloc(gamepad_button_swap_list, new_count * sizeof(*new_list));
+            if (new_list) {
+                new_list[num_gamepad_button_swap_list] = instance_id;
+                gamepad_button_swap_list = new_list;
+                ++num_gamepad_button_swap_list;
+            }
+        }
+    } else {
+        for (i = 0; i < num_gamepad_button_swap_list; ++i) {
+            if (gamepad_button_swap_list[i] == instance_id) {
+                if (i < (num_gamepad_button_swap_list - 1)) {
+                    SDL3_memmove(&gamepad_button_swap_list[i], &gamepad_button_swap_list[i+1], (num_gamepad_button_swap_list - i - 1) * sizeof(gamepad_button_swap_list[i]));
+                }
+                --num_gamepad_button_swap_list;
+                break;
+            }
+        }
+    }
+}
+
+static SDL_bool ShouldSwapGamepadButtons(SDL_JoystickID instance_id)
+{
+    int i;
+
+    for (i = 0; i < num_gamepad_button_swap_list; ++i) {
+        if (gamepad_button_swap_list[i] == instance_id) {
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE;
+}
+
+static Uint8 SwapGamepadButton(Uint8 button)
+{
+    switch (button) {
+    case SDL_GAMEPAD_BUTTON_SOUTH:
+        return SDL_GAMEPAD_BUTTON_EAST;
+    case SDL_GAMEPAD_BUTTON_EAST:
+        return SDL_GAMEPAD_BUTTON_SOUTH;
+    case SDL_GAMEPAD_BUTTON_WEST:
+        return SDL_GAMEPAD_BUTTON_NORTH;
+    case SDL_GAMEPAD_BUTTON_NORTH:
+        return SDL_GAMEPAD_BUTTON_WEST;
+    default:
+        return button;
+    }
+}
 
 /* (current) strategy for SDL_Events:
    in sdl12-compat, we built our own event queue, so when the SDL2 queue is pumped, we
@@ -1452,6 +1527,12 @@ Event3to2(const SDL_Event *event3, SDL2_Event *event2)
         event2->wheel.mouseX = (Sint32)event3->wheel.mouseX;
         event2->wheel.mouseY = (Sint32)event3->wheel.mouseY;
         break;
+    case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
+    case SDL_EVENT_GAMEPAD_BUTTON_UP:
+        if (ShouldSwapGamepadButtons(event2->cbutton.which)) {
+            event2->cbutton.button = SwapGamepadButton(event2->cbutton.button);
+        }
+        break;
     /* sensor timestamps are in nanosecond in SDL3 */
     case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
         event2->csensor.timestamp_us = SDL_NS_TO_US(event3->gsensor.sensor_timestamp);
@@ -6018,7 +6099,11 @@ DECLSPEC SDL_GameController* SDLCALL
 SDL_GameControllerOpen(int idx)
 {
     const SDL_JoystickID jid = GetJoystickInstanceFromIndex(idx);
-    return jid ? SDL3_OpenGamepad(jid) : NULL;
+    SDL_GameController *gamepad = jid ? SDL3_OpenGamepad(jid) : NULL;
+    if (gamepad) {
+        UpdateGamepadButtonSwap(gamepad);
+    }
+    return gamepad;
 }
 
 static SDL_GameControllerType SDLCALL
@@ -6083,6 +6168,16 @@ SDL_GameControllerPathForIndex(int idx)
     return jid ? SDL3_GetGamepadInstancePath(jid) : NULL;
 }
 
+DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *controller, SDL_GameControllerButton button)
+{
+    SDL_JoystickID instance_id = SDL3_GetGamepadInstanceID(controller);
+
+    if (ShouldSwapGamepadButtons(instance_id)) {
+        button = (SDL_GameControllerButton)SwapGamepadButton(button);
+    }
+    return SDL3_GetGamepadButton(controller, button);
+}
+
 DECLSPEC SDL_GameControllerButtonBind SDLCALL
 SDL_GameControllerGetBindForAxis(SDL_GameController *controller,
                                  SDL_GameControllerAxis axis)
diff --git a/src/sdl3_include_wrapper.h b/src/sdl3_include_wrapper.h
index f80ff39..bb7a8fc 100644
--- a/src/sdl3_include_wrapper.h
+++ b/src/sdl3_include_wrapper.h
@@ -964,6 +964,8 @@
 #define SDL_StopVideoCapture IGNORE_THIS_VERSION_OF_SDL_StopVideoCapture
 #define SDL_CloseVideoCapture IGNORE_THIS_VERSION_OF_SDL_CloseVideoCapture
 #define SDL_GetVideoCaptureDevices IGNORE_THIS_VERSION_OF_SDL_GetVideoCaptureDevices
+#define SDL_GetGamepadButtonLabelForType IGNORE_THIS_VERSION_OF_SDL_GetGamepadButtonLabelForType
+#define SDL_GetGamepadButtonLabel IGNORE_THIS_VERSION_OF_SDL_GetGamepadButtonLabel
 
 
 #define SDL_FUNCTION_POINTER_IS_VOID_POINTER 1
@@ -4736,6 +4738,14 @@
 #undef SDL_GetVideoCaptureDevices
 #endif
 
+#ifdef SDL_GetGamepadButtonLabelForType
+#undef SDL_GetGamepadButtonLabelForType
+#endif
+
+#ifdef SDL_GetGamepadButtonLabel
+#undef SDL_GetGamepadButtonLabel
+#endif
+
 /* undefine these macros too: */
 /* redefine as SDL3_xxx, if needed. */
 
diff --git a/src/sdl3_syms.h b/src/sdl3_syms.h
index 1314b0d..588393b 100644
--- a/src/sdl3_syms.h
+++ b/src/sdl3_syms.h
@@ -138,7 +138,7 @@ SDL3_SYM_RENAMED(const char*,GameControllerGetStringForAxis,GetGamepadStringForA
 SDL3_SYM_RENAMED(Sint16,GameControllerGetAxis,GetGamepadAxis,(SDL_GameController *a, SDL_GameControllerAxis b),(a,b),return)
 SDL3_SYM_RENAMED(SDL_GameControllerButton,GameControllerGetButtonFromString,GetGamepadButtonFromString,(const char *a),(a),return)
 SDL3_SYM_RENAMED(const char*,GameControllerGetStringForButton,GetGamepadStringForButton,(SDL_GameControllerButton a),(a),return)
-SDL3_SYM_RENAMED(Uint8,GameControllerGetButton,GetGamepadButton,(SDL_GameController *a, SDL_GameControllerButton b),(a,b),return)
+SDL3_SYM(Uint8,GetGamepadButton,(SDL_Gamepad *a, SDL_GamepadButton b),(a,b),return)
 SDL3_SYM_RENAMED(void,GameControllerClose,CloseGamepad,(SDL_GameController *a),(a),)
 SDL3_SYM_PASSTHROUGH(int,NumHaptics,(void),(),return)
 SDL3_SYM_PASSTHROUGH(const char*,HapticName,(int a),(a),return)
@@ -894,6 +894,8 @@ SDL3_SYM(void*,GetProperty,(SDL_PropertiesID a, const char *b),(a,b),return)
 SDL3_SYM(SDL_PropertiesID,GetWindowProperties,(SDL_Window *a),(a),return)
 SDL3_SYM(SDL_PropertiesID,GetTextureProperties,(SDL_Texture *a),(a),return)
 SDL3_SYM(SDL_PropertiesID,GetRendererProperties,(SDL_Renderer *a),(a),return)
+SDL3_SYM(SDL_GamepadButtonLabel,GetGamepadButtonLabelForType,(SDL_GamepadType a, SDL_GamepadButton b),(a,b),return)
+SDL3_SYM(SDL_GamepadButtonLabel,GetGamepadButtonLabel,(SDL_Gamepad *a, SDL_GamepadButton b),(a,b),return)
 
 #undef SDL3_SYM
 #undef SDL3_SYM_PASSTHROUGH