SDL: Fixed Nintendo controller face button layout under Steam (21e6c)

From 21e6c5814c1e6c30157e015a3db8131c66fe005f Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 10 Mar 2025 17:25:01 -0700
Subject: [PATCH] Fixed Nintendo controller face button layout under Steam

(cherry picked from commit 2ce3dfdf1df248dbd3a694bfe3a4d4a869db3476)
---
 src/joystick/SDL_joystick.c    | 43 ++++++++++++++++++++++++++++++++++
 src/joystick/SDL_sysjoystick.h |  1 +
 2 files changed, 44 insertions(+)

diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index 7574adc6c0771..a4d86688c7302 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -1043,6 +1043,27 @@ static void CleanupSensorFusion(SDL_Joystick *joystick)
     }
 }
 
+static bool ShouldSwapFaceButtons(const SDL_SteamVirtualGamepadInfo *info)
+{
+    // When "Use Nintendo Button Layout" is enabled under Steam (the default)
+    // it will send button 0 for the A (east) button and button 1 for the
+    // B (south) button. This is done so that games that interpret the
+    // buttons as Xbox input will get button 0 for "A" as they expect.
+    //
+    // However, SDL reports positional buttons, so we need to swap
+    // the buttons so they show up in the correct position. This provides
+    // consistent behavior regardless of whether we're running under Steam,
+    // under the default settings.
+    if (info &&
+        (info->type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO ||
+         info->type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT ||
+         info->type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT ||
+         info->type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR)) {
+        return true;
+    }
+    return false;
+}
+
 /*
  * Open a joystick for use - the index passed as an argument refers to
  * the N'th joystick on the system.  This index is the value which will
@@ -1148,6 +1169,7 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
     info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id);
     if (info) {
         joystick->steam_handle = info->handle;
+        joystick->swap_face_buttons = ShouldSwapFaceButtons(info);
     }
 
     // Use system gyro and accelerometer if the gamepad doesn't have built-in sensors
@@ -2305,6 +2327,25 @@ void SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 butt
         event.type = SDL_EVENT_JOYSTICK_BUTTON_UP;
     }
 
+    if (joystick->swap_face_buttons) {
+        switch (button) {
+        case 0:
+            button = 1;
+            break;
+        case 1:
+            button = 0;
+            break;
+        case 2:
+            button = 3;
+            break;
+        case 3:
+            button = 2;
+            break;
+        default:
+            break;
+        }
+    }
+
     // Make sure we're not getting garbage or duplicate events
     if (button >= joystick->nbuttons) {
         return;
@@ -2353,11 +2394,13 @@ static void SendSteamHandleUpdateEvents(void)
         if (info) {
             if (joystick->steam_handle != info->handle) {
                 joystick->steam_handle = info->handle;
+                joystick->swap_face_buttons = ShouldSwapFaceButtons(info);
                 changed = true;
             }
         } else {
             if (joystick->steam_handle != 0) {
                 joystick->steam_handle = 0;
+                joystick->swap_face_buttons = false;
                 changed = true;
             }
         }
diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h
index 343d2061e7178..97f145de9a28e 100644
--- a/src/joystick/SDL_sysjoystick.h
+++ b/src/joystick/SDL_sysjoystick.h
@@ -83,6 +83,7 @@ struct SDL_Joystick
     SDL_GUID guid _guarded;      // Joystick guid
     Uint16 firmware_version _guarded;    // Firmware version, if available
     Uint64 steam_handle _guarded;        // Steam controller API handle
+    bool swap_face_buttons _guarded;     // Whether we should swap face buttons
 
     int naxes _guarded; // Number of axis controls on the joystick
     SDL_JoystickAxisInfo *axes _guarded;