SDL: Added miscellaneous gamepad buttons for additional macro buttons on some controllers

From cb70e972e3e7db7b5a543e69e5d6db60ab193733 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 1 Feb 2024 20:15:01 -0800
Subject: [PATCH] Added miscellaneous gamepad buttons for additional macro
 buttons on some controllers

---
 include/SDL3/SDL_gamepad.h                 |  5 ++++-
 src/joystick/SDL_gamepad.c                 | 18 ++++++++++++------
 src/joystick/SDL_joystick_c.h              |  3 +++
 src/joystick/hidapi/SDL_hidapi_stadia.c    |  4 ++--
 src/joystick/virtual/SDL_virtualjoystick.c | 20 ++++++++++++++++++++
 test/gamepadutils.c                        |  3 +++
 test/testcontroller.c                      |  6 +++++-
 7 files changed, 49 insertions(+), 10 deletions(-)

diff --git a/include/SDL3/SDL_gamepad.h b/include/SDL3/SDL_gamepad.h
index 035ac9d4754e..b2eb44589c33 100644
--- a/include/SDL3/SDL_gamepad.h
+++ b/include/SDL3/SDL_gamepad.h
@@ -113,12 +113,15 @@ typedef enum
     SDL_GAMEPAD_BUTTON_DPAD_DOWN,
     SDL_GAMEPAD_BUTTON_DPAD_LEFT,
     SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
-    SDL_GAMEPAD_BUTTON_MISC1,           /* Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button) */
+    SDL_GAMEPAD_BUTTON_MISC1,           /* Additional button (e.g. Xbox Series X share button, PS5 microphone button, Nintendo Switch Pro capture button, Amazon Luna microphone button, Google Stadia capture button) */
     SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1,   /* Upper or primary paddle, under your right hand (e.g. Xbox Elite paddle P1) */
     SDL_GAMEPAD_BUTTON_LEFT_PADDLE1,    /* Upper or primary paddle, under your left hand (e.g. Xbox Elite paddle P3) */
     SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2,   /* Lower or secondary paddle, under your right hand (e.g. Xbox Elite paddle P2) */
     SDL_GAMEPAD_BUTTON_LEFT_PADDLE2,    /* Lower or secondary paddle, under your left hand (e.g. Xbox Elite paddle P4) */
     SDL_GAMEPAD_BUTTON_TOUCHPAD,        /* PS4/PS5 touchpad button */
+    SDL_GAMEPAD_BUTTON_MISC2,           /* Additional button */
+    SDL_GAMEPAD_BUTTON_MISC3,           /* Additional button */
+    SDL_GAMEPAD_BUTTON_MISC4,           /* Additional button */
     SDL_GAMEPAD_BUTTON_MAX
 } SDL_GamepadButton;
 
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index c699dbc9c243..02c4221510b4 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -790,14 +790,14 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_JoystickGUID guid
             SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
         } else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) {
             /* The Google Stadia controller has a share button and a Google Assistant button */
-            SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
+            SDL_strlcat(mapping_string, "misc1:b11,misc2:b12", sizeof(mapping_string));
         } else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) {
             /* The NVIDIA SHIELD controller has a share button between back and start buttons */
             SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
 
             if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) {
-                /* The original SHIELD controller has a touchpad as well */
-                SDL_strlcat(mapping_string, "touchpad:b12,", sizeof(mapping_string));
+                /* The original SHIELD controller has a touchpad and plus/minus buttons as well */
+                SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14", sizeof(mapping_string));
             }
         } else if (SDL_IsJoystickPS4(vendor, product)) {
             /* PS4 controllers have an additional touchpad button */
@@ -805,7 +805,7 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_JoystickGUID guid
         } else if (SDL_IsJoystickPS5(vendor, product)) {
             /* PS5 controllers have a microphone button and an additional touchpad button */
             SDL_strlcat(mapping_string, "touchpad:b11,misc1:b12,", sizeof(mapping_string));
-            /* DualSense Edge controllers have paddles */
+            /* DualSense Edge controllers have paddles and a microphone button */
             if (SDL_IsJoystickDualSenseEdge(vendor, product)) {
                 SDL_strlcat(mapping_string, "paddle1:b16,paddle2:b15,paddle3:b14,paddle4:b13,", sizeof(mapping_string));
             }
@@ -1085,7 +1085,10 @@ static const char *map_StringForGamepadButton[] = {
     "paddle2",
     "paddle3",
     "paddle4",
-    "touchpad"
+    "touchpad",
+    "misc2",
+    "misc3",
+    "misc4"
 };
 SDL_COMPILE_TIME_ASSERT(map_StringForGamepadButton, SDL_arraysize(map_StringForGamepadButton) == SDL_GAMEPAD_BUTTON_MAX);
 
@@ -1518,7 +1521,7 @@ static char *SDL_PrivateGetGamepadMappingFromMappingString(const char *pMapping)
 
     /* Trim whitespace */
     length = SDL_strlen(result);
-    while (SDL_isspace(result[length - 1])) {
+    while (length > 0 && SDL_isspace(result[length - 1])) {
         --length;
     }
     result[length] = '\0';
@@ -1751,6 +1754,9 @@ static GamepadMapping_t *SDL_PrivateGenerateAutomaticGamepadMapping(const char *
     SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpleft", &raw_map->dpleft);
     SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpright", &raw_map->dpright);
     SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc1", &raw_map->misc1);
+    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc2", &raw_map->misc2);
+    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc3", &raw_map->misc3);
+    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc4", &raw_map->misc4);
     /* Keep using paddle1-4 in the generated mapping so that it can be
      * reused with SDL2 */
     SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle1", &raw_map->right_paddle1);
diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h
index 95ab4f71e179..14fe0ce72dd5 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/src/joystick/SDL_joystick_c.h
@@ -212,6 +212,9 @@ typedef struct SDL_GamepadMapping
     SDL_InputMapping dpleft;
     SDL_InputMapping dpright;
     SDL_InputMapping misc1;
+    SDL_InputMapping misc2;
+    SDL_InputMapping misc3;
+    SDL_InputMapping misc4;
     SDL_InputMapping right_paddle1;
     SDL_InputMapping left_paddle1;
     SDL_InputMapping right_paddle2;
diff --git a/src/joystick/hidapi/SDL_hidapi_stadia.c b/src/joystick/hidapi/SDL_hidapi_stadia.c
index 0d7dd83f06af..5f284b209b2b 100644
--- a/src/joystick/hidapi/SDL_hidapi_stadia.c
+++ b/src/joystick/hidapi/SDL_hidapi_stadia.c
@@ -33,7 +33,7 @@
 
 enum
 {
-    SDL_GAMEPAD_BUTTON_STADIA_SHARE = 11,
+    SDL_GAMEPAD_BUTTON_STADIA_CAPTURE = 11,
     SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT,
     SDL_GAMEPAD_NUM_STADIA_BUTTONS,
 };
@@ -218,7 +218,7 @@ static void HIDAPI_DriverStadia_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
-        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_SHARE, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_CAPTURE, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
     }
 
diff --git a/src/joystick/virtual/SDL_virtualjoystick.c b/src/joystick/virtual/SDL_virtualjoystick.c
index d756f368dc7f..80177fbdb094 100644
--- a/src/joystick/virtual/SDL_virtualjoystick.c
+++ b/src/joystick/virtual/SDL_virtualjoystick.c
@@ -682,6 +682,26 @@ static SDL_bool VIRTUAL_JoystickGetGamepadMapping(int device_index, SDL_GamepadM
         out->left_paddle2.target = current_button++;
     }
 
+    if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_TOUCHPAD))) {
+        out->touchpad.kind = EMappingKind_Button;
+        out->touchpad.target = current_button++;
+    }
+
+    if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC2))) {
+        out->misc2.kind = EMappingKind_Button;
+        out->misc2.target = current_button++;
+    }
+
+    if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC3))) {
+        out->misc3.kind = EMappingKind_Button;
+        out->misc3.target = current_button++;
+    }
+
+    if (current_button < hwdata->desc.nbuttons && (hwdata->desc.button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC4))) {
+        out->misc4.kind = EMappingKind_Button;
+        out->misc4.target = current_button++;
+    }
+
     if (current_axis < hwdata->desc.naxes && (hwdata->desc.axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTX))) {
         out->leftx.kind = EMappingKind_Axis;
         out->leftx.target = current_axis++;
diff --git a/test/gamepadutils.c b/test/gamepadutils.c
index 656c9970a143..43b98aeb20f6 100644
--- a/test/gamepadutils.c
+++ b/test/gamepadutils.c
@@ -649,6 +649,9 @@ static const char *gamepad_button_names[] = {
     "Right Paddle 2",
     "Left Paddle 2",
     "Touchpad",
+    "Misc2",
+    "Misc3",
+    "Misc4",
 };
 SDL_COMPILE_TIME_ASSERT(gamepad_button_names, SDL_arraysize(gamepad_button_names) == SDL_GAMEPAD_BUTTON_MAX);
 
diff --git a/test/testcontroller.c b/test/testcontroller.c
index 16908afbb7c9..ab695b0ff804 100644
--- a/test/testcontroller.c
+++ b/test/testcontroller.c
@@ -35,7 +35,7 @@
 #define BUTTON_MARGIN 16
 #define BUTTON_PADDING 12
 #define GAMEPAD_WIDTH 512
-#define GAMEPAD_HEIGHT 480
+#define GAMEPAD_HEIGHT 560
 
 #define SCREEN_WIDTH  (PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING + PANEL_WIDTH)
 #define SCREEN_HEIGHT (TITLE_HEIGHT + GAMEPAD_HEIGHT)
@@ -782,6 +782,10 @@ static const char *GetBindingInstruction(void)
         return "Press the lower paddle under your left hand";
     case SDL_GAMEPAD_BUTTON_TOUCHPAD:
         return "Press down on the touchpad";
+    case SDL_GAMEPAD_BUTTON_MISC2:
+    case SDL_GAMEPAD_BUTTON_MISC3:
+    case SDL_GAMEPAD_BUTTON_MISC4:
+        return "Press any additional button not already bound";
     case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE:
         return "Move the left thumbstick to the left";
     case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: