SDL: gamecontroller: Backport 3.0 'type:' field to 2.0

From 757c984ddb8e95098b227f85d2687fb5f9d82880 Mon Sep 17 00:00:00 2001
From: Ethan Lee <[EMAIL REDACTED]>
Date: Mon, 27 Nov 2023 17:12:01 -0500
Subject: [PATCH] gamecontroller: Backport 3.0 'type:' field to 2.0

---
 include/SDL_gamecontroller.h      |  3 +-
 src/joystick/SDL_gamecontroller.c | 95 ++++++++++++++++++++++++++++++-
 2 files changed, 96 insertions(+), 2 deletions(-)

diff --git a/include/SDL_gamecontroller.h b/include/SDL_gamecontroller.h
index 140054d36ee6..bee07c4df0ae 100644
--- a/include/SDL_gamecontroller.h
+++ b/include/SDL_gamecontroller.h
@@ -73,7 +73,8 @@ typedef enum
     SDL_CONTROLLER_TYPE_NVIDIA_SHIELD,
     SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT,
     SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT,
-    SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR
+    SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR,
+    SDL_CONTROLLER_TYPE_MAX
 } SDL_GameControllerType;
 
 typedef enum
diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c
index b4c08a793b13..c4f8d654220f 100644
--- a/src/joystick/SDL_gamecontroller.c
+++ b/src/joystick/SDL_gamecontroller.c
@@ -45,6 +45,8 @@
 
 #define SDL_CONTROLLER_CRC_FIELD           "crc:"
 #define SDL_CONTROLLER_CRC_FIELD_SIZE      4 /* hard-coded for speed */
+#define SDL_CONTROLLER_TYPE_FIELD          "type:"
+#define SDL_CONTROLLER_TYPE_FIELD_SIZE     SDL_strlen(SDL_CONTROLLER_TYPE_FIELD)
 #define SDL_CONTROLLER_PLATFORM_FIELD      "platform:"
 #define SDL_CONTROLLER_PLATFORM_FIELD_SIZE SDL_strlen(SDL_CONTROLLER_PLATFORM_FIELD)
 #define SDL_CONTROLLER_HINT_FIELD          "hint:"
@@ -133,6 +135,7 @@ struct _SDL_GameController
     int ref_count _guarded;
 
     const char *name _guarded;
+    SDL_GameControllerType type _guarded;
     ControllerMapping_t *mapping _guarded;
     int num_bindings _guarded;
     SDL_ExtendedGameControllerBind *bindings _guarded;
@@ -850,6 +853,47 @@ static ControllerMapping_t *SDL_PrivateGetControllerMappingForGUID(SDL_JoystickG
     return mapping;
 }
 
+static const char *map_StringForGameControllerType[] = {
+    "unknown",
+    "xbox360",
+    "xboxone",
+    "ps3",
+    "ps4",
+    "switchpro",
+    "virtual",
+    "ps5",
+    "amazonluna",
+    "googlestadia",
+    "nvidiashield",
+    "joyconleft",
+    "joyconright",
+    "joyconpair"
+};
+SDL_COMPILE_TIME_ASSERT(map_StringForGameControllerType, SDL_arraysize(map_StringForGameControllerType) == SDL_CONTROLLER_TYPE_MAX);
+
+/*
+ * convert a string to its enum equivalent
+ */
+static SDL_GameControllerType SDL_GetGameControllerTypeFromString(const char *str)
+{
+    int i;
+
+    if (!str || str[0] == '\0') {
+        return SDL_CONTROLLER_TYPE_UNKNOWN;
+    }
+
+    if (*str == '+' || *str == '-') {
+        ++str;
+    }
+
+    for (i = 0; i < SDL_arraysize(map_StringForGameControllerType); ++i) {
+        if (SDL_strcasecmp(str, map_StringForGameControllerType[i]) == 0) {
+            return (SDL_GameControllerType)i;
+        }
+    }
+    return SDL_CONTROLLER_TYPE_UNKNOWN;
+}
+
 static const char *map_StringForControllerAxis[] = {
     "leftx",
     "lefty",
@@ -1095,6 +1139,31 @@ static void SDL_PrivateGameControllerParseControllerConfigString(SDL_GameControl
     }
 }
 
+static void SDL_UpdateGameControllerType(SDL_GameController *gamecontroller)
+{
+    char *type_string, *comma;
+
+    SDL_AssertJoysticksLocked();
+
+    gamecontroller->type = SDL_CONTROLLER_TYPE_UNKNOWN;
+
+    type_string = SDL_strstr(gamecontroller->mapping->mapping, SDL_CONTROLLER_TYPE_FIELD);
+    if (type_string) {
+        type_string += SDL_CONTROLLER_TYPE_FIELD_SIZE;
+        comma = SDL_strchr(type_string, ',');
+        if (comma) {
+            *comma = '\0';
+            gamecontroller->type = SDL_GetGameControllerTypeFromString(type_string);
+            *comma = ',';
+        } else {
+            gamecontroller->type = SDL_GetGameControllerTypeFromString(type_string);
+        }
+    }
+    if (gamecontroller->type == SDL_CONTROLLER_TYPE_UNKNOWN) {
+        gamecontroller->type = SDL_GetJoystickGameControllerTypeFromGUID(SDL_JoystickGetGUID(gamecontroller->joystick), SDL_JoystickName(gamecontroller->joystick));
+    }
+}
+
 /*
  * Make a new button mapping struct
  */
@@ -1113,6 +1182,8 @@ static void SDL_PrivateLoadButtonMapping(SDL_GameController *gamecontroller, Con
 
     SDL_PrivateGameControllerParseControllerConfigString(gamecontroller, pControllerMapping->mapping);
 
+    SDL_UpdateGameControllerType(gamecontroller);
+
     /* Set the zero point for triggers */
     for (i = 0; i < gamecontroller->num_bindings; ++i) {
         SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i];
@@ -1976,7 +2047,26 @@ const char *SDL_GameControllerPathForIndex(int joystick_index)
  */
 SDL_GameControllerType SDL_GameControllerTypeForIndex(int joystick_index)
 {
-    return SDL_GetJoystickGameControllerTypeFromGUID(SDL_JoystickGetDeviceGUID(joystick_index), SDL_JoystickNameForIndex(joystick_index));
+    SDL_JoystickGUID joystick_guid = SDL_JoystickGetDeviceGUID(joystick_index);
+    const char *mapping = SDL_GameControllerMappingForGUID(joystick_guid);
+    char *type_string, *comma;
+    SDL_GameControllerType type;
+    if (mapping) {
+        type_string = SDL_strstr(mapping, SDL_CONTROLLER_TYPE_FIELD);
+        if (type_string) {
+            type_string += SDL_CONTROLLER_TYPE_FIELD_SIZE;
+            comma = SDL_strchr(type_string, ',');
+            if (comma) {
+                *comma = '\0';
+                type = SDL_GetGameControllerTypeFromString(type_string);
+                *comma = ',';
+            } else {
+                type = SDL_GetGameControllerTypeFromString(type_string);
+            }
+            return type;
+        }
+    }
+    return SDL_GetJoystickGameControllerTypeFromGUID(joystick_guid, SDL_JoystickNameForIndex(joystick_index));
 }
 
 /**
@@ -2675,6 +2765,9 @@ SDL_GameControllerType SDL_GameControllerGetType(SDL_GameController *gamecontrol
     if (!joystick) {
         return SDL_CONTROLLER_TYPE_UNKNOWN;
     }
+    if (gamecontroller->type != SDL_CONTROLLER_TYPE_UNKNOWN) {
+        return gamecontroller->type;
+    }
     return SDL_GetJoystickGameControllerTypeFromGUID(SDL_JoystickGetGUID(joystick), SDL_JoystickName(joystick));
 }