Browse Source

Added the ability to specify a gamepad type in the mapping

Also renamed most cases of SDL_GAMEPAD_TYPE_UNKNOWN to SDL_GAMEPAD_TYPE_STANDARD, and SDL_GetGamepadType() will return SDL_GAMEPAD_TYPE_UNKNOWN only if the gamepad is invalid.
Sam Lantinga 1 year ago
parent
commit
b271e92c6e

+ 1 - 4
docs/README-migration.md

@@ -390,17 +390,14 @@ The following symbols have been renamed:
 * 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_TYPE_AMAZON_LUNA => SDL_GAMEPAD_TYPE_AMAZON_LUNA
-* SDL_CONTROLLER_TYPE_GOOGLE_STADIA => SDL_GAMEPAD_TYPE_GOOGLE_STADIA
 * 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
 * SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO => SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO
-* SDL_CONTROLLER_TYPE_NVIDIA_SHIELD => SDL_GAMEPAD_TYPE_NVIDIA_SHIELD
 * SDL_CONTROLLER_TYPE_PS3 => SDL_GAMEPAD_TYPE_PS3
 * SDL_CONTROLLER_TYPE_PS4 => SDL_GAMEPAD_TYPE_PS4
 * SDL_CONTROLLER_TYPE_PS5 => SDL_GAMEPAD_TYPE_PS5
-* SDL_CONTROLLER_TYPE_UNKNOWN => SDL_GAMEPAD_TYPE_UNKNOWN
+* SDL_CONTROLLER_TYPE_UNKNOWN => SDL_GAMEPAD_TYPE_STANDARD
 * SDL_CONTROLLER_TYPE_VIRTUAL => SDL_GAMEPAD_TYPE_VIRTUAL
 * SDL_CONTROLLER_TYPE_XBOX360 => SDL_GAMEPAD_TYPE_XBOX360
 * SDL_CONTROLLER_TYPE_XBOXONE => SDL_GAMEPAD_TYPE_XBOXONE

+ 63 - 11
include/SDL3/SDL_gamepad.h

@@ -61,6 +61,7 @@ typedef struct SDL_Gamepad SDL_Gamepad;
 typedef enum
 {
     SDL_GAMEPAD_TYPE_UNKNOWN = 0,
+    SDL_GAMEPAD_TYPE_STANDARD,
     SDL_GAMEPAD_TYPE_XBOX360,
     SDL_GAMEPAD_TYPE_XBOXONE,
     SDL_GAMEPAD_TYPE_PS3,
@@ -70,6 +71,7 @@ typedef enum
     SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT,
     SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT,
     SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR,
+    SDL_GAMEPAD_TYPE_MAX
 } SDL_GamepadType;
 
 /**
@@ -411,6 +413,18 @@ extern DECLSPEC Uint16 SDLCALL SDL_GetGamepadInstanceProductVersion(SDL_Joystick
  */
 extern DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadInstanceType(SDL_JoystickID instance_id);
 
+/**
+ * Get the type of a gamepad, ignoring any mapping override.
+ *
+ * This can be called before any gamepads are opened.
+ *
+ * \param instance_id the joystick instance ID
+ * \returns the gamepad type.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC SDL_GamepadType SDLCALL SDL_GetRealGamepadInstanceType(SDL_JoystickID instance_id);
+
 /**
  * Get the mapping of a gamepad.
  *
@@ -481,9 +495,6 @@ extern DECLSPEC SDL_JoystickID SDLCALL SDL_GetGamepadInstanceID(SDL_Gamepad *gam
 /**
  * Get the implementation-dependent name for an opened gamepad.
  *
- * This is the same name as returned by SDL_GetGamepadNameForIndex(), but it
- * takes a gamepad identifier instead of the (unstable) device index.
- *
  * \param gamepad a gamepad identifier previously returned by
  *                SDL_OpenGamepad()
  * \returns the implementation dependent name for the gamepad, or NULL if
@@ -499,9 +510,6 @@ extern DECLSPEC const char *SDLCALL SDL_GetGamepadName(SDL_Gamepad *gamepad);
 /**
  * Get the implementation-dependent path for an opened gamepad.
  *
- * This is the same path as returned by SDL_GetGamepadNameForIndex(), but it
- * takes a gamepad identifier instead of the (unstable) device index.
- *
  * \param gamepad a gamepad identifier previously returned by
  *                SDL_OpenGamepad()
  * \returns the implementation dependent path for the gamepad, or NULL if
@@ -514,18 +522,29 @@ extern DECLSPEC const char *SDLCALL SDL_GetGamepadName(SDL_Gamepad *gamepad);
 extern DECLSPEC const char *SDLCALL SDL_GetGamepadPath(SDL_Gamepad *gamepad);
 
 /**
- * Get the type of this currently opened gamepad
- *
- * This is the same name as returned by SDL_GetGamepadInstanceType(), but it
- * takes a gamepad identifier instead of the (unstable) device index.
+ * Get the type of an opened gamepad.
  *
  * \param gamepad the gamepad object to query.
- * \returns the gamepad type.
+ * \returns the gamepad type, or SDL_GAMEPAD_TYPE_INVALID if it's not available.
  *
  * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetGamepadInstanceType
  */
 extern DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadType(SDL_Gamepad *gamepad);
 
+/**
+ * Get the type of an opened gamepad, ignoring any mapping override.
+ *
+ * \param gamepad the gamepad object to query.
+ * \returns the gamepad type, or SDL_GAMEPAD_TYPE_INVALID if it's not available.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetRealGamepadInstanceType
+ */
+extern DECLSPEC SDL_GamepadType SDLCALL SDL_GetRealGamepadType(SDL_Gamepad *gamepad);
+
 /**
  * Get the player index of an opened gamepad.
  *
@@ -697,6 +716,39 @@ extern DECLSPEC SDL_bool SDLCALL SDL_GamepadEventsEnabled(void);
  */
 extern DECLSPEC void SDLCALL SDL_UpdateGamepads(void);
 
+/**
+ * Convert a string into SDL_GamepadType enum.
+ *
+ * This function is called internally to translate SDL_Gamepad mapping strings
+ * for the underlying joystick device into the consistent SDL_Gamepad mapping.
+ * You do not normally need to call this function unless you are parsing
+ * SDL_Gamepad mappings in your own code.
+ *
+ * \param str string representing a SDL_GamepadType type
+ * \returns the SDL_GamepadType enum corresponding to the input string, or
+ *          `SDL_GAMEPAD_TYPE_INVALID` if no match was found.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetGamepadStringForType
+ */
+extern DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadTypeFromString(const char *str);
+
+/**
+ * Convert from an SDL_GamepadType enum to a string.
+ *
+ * The caller should not SDL_free() the returned string.
+ *
+ * \param type an enum value for a given SDL_GamepadType
+ * \returns a string for the given type, or NULL if an invalid type is
+ *          specified. The string returned is of the format used by
+ *          SDL_Gamepad mapping strings.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetGamepadTypeFromString
+ */
+extern DECLSPEC const char *SDLCALL SDL_GetGamepadStringForType(SDL_GamepadType type);
 
 /**
  * Convert a string into SDL_GamepadAxis enum.

+ 2 - 8
include/SDL3/SDL_oldnames.h

@@ -184,17 +184,14 @@
 #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_TYPE_AMAZON_LUNA SDL_GAMEPAD_TYPE_AMAZON_LUNA
-#define SDL_CONTROLLER_TYPE_GOOGLE_STADIA SDL_GAMEPAD_TYPE_GOOGLE_STADIA
 #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
 #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO
-#define SDL_CONTROLLER_TYPE_NVIDIA_SHIELD SDL_GAMEPAD_TYPE_NVIDIA_SHIELD
 #define SDL_CONTROLLER_TYPE_PS3 SDL_GAMEPAD_TYPE_PS3
 #define SDL_CONTROLLER_TYPE_PS4 SDL_GAMEPAD_TYPE_PS4
 #define SDL_CONTROLLER_TYPE_PS5 SDL_GAMEPAD_TYPE_PS5
-#define SDL_CONTROLLER_TYPE_UNKNOWN SDL_GAMEPAD_TYPE_UNKNOWN
+#define SDL_CONTROLLER_TYPE_UNKNOWN SDL_GAMEPAD_TYPE_STANDARD
 #define SDL_CONTROLLER_TYPE_VIRTUAL SDL_GAMEPAD_TYPE_VIRTUAL
 #define SDL_CONTROLLER_TYPE_XBOX360 SDL_GAMEPAD_TYPE_XBOX360
 #define SDL_CONTROLLER_TYPE_XBOXONE SDL_GAMEPAD_TYPE_XBOXONE
@@ -626,17 +623,14 @@
 #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_TYPE_AMAZON_LUNA SDL_CONTROLLER_TYPE_AMAZON_LUNA_renamed_SDL_GAMEPAD_TYPE_AMAZON_LUNA
-#define SDL_CONTROLLER_TYPE_GOOGLE_STADIA SDL_CONTROLLER_TYPE_GOOGLE_STADIA_renamed_SDL_GAMEPAD_TYPE_GOOGLE_STADIA
 #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
 #define SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO_renamed_SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO
-#define SDL_CONTROLLER_TYPE_NVIDIA_SHIELD SDL_CONTROLLER_TYPE_NVIDIA_SHIELD_renamed_SDL_GAMEPAD_TYPE_NVIDIA_SHIELD
 #define SDL_CONTROLLER_TYPE_PS3 SDL_CONTROLLER_TYPE_PS3_renamed_SDL_GAMEPAD_TYPE_PS3
 #define SDL_CONTROLLER_TYPE_PS4 SDL_CONTROLLER_TYPE_PS4_renamed_SDL_GAMEPAD_TYPE_PS4
 #define SDL_CONTROLLER_TYPE_PS5 SDL_CONTROLLER_TYPE_PS5_renamed_SDL_GAMEPAD_TYPE_PS5
-#define SDL_CONTROLLER_TYPE_UNKNOWN SDL_CONTROLLER_TYPE_UNKNOWN_renamed_SDL_GAMEPAD_TYPE_UNKNOWN
+#define SDL_CONTROLLER_TYPE_UNKNOWN SDL_CONTROLLER_TYPE_UNKNOWN_renamed_SDL_GAMEPAD_TYPE_STANDARD
 #define SDL_CONTROLLER_TYPE_VIRTUAL SDL_CONTROLLER_TYPE_VIRTUAL_renamed_SDL_GAMEPAD_TYPE_VIRTUAL
 #define SDL_CONTROLLER_TYPE_XBOX360 SDL_CONTROLLER_TYPE_XBOX360_renamed_SDL_GAMEPAD_TYPE_XBOX360
 #define SDL_CONTROLLER_TYPE_XBOXONE SDL_CONTROLLER_TYPE_XBOXONE_renamed_SDL_GAMEPAD_TYPE_XBOXONE

+ 4 - 0
src/dynapi/SDL_dynapi.sym

@@ -871,6 +871,10 @@ SDL3_0.0.0 {
     SDL_GetGamepadPowerLevel;
     SDL_SetGamepadMapping;
     SDL_strndup;
+    SDL_GetGamepadTypeFromString;
+    SDL_GetGamepadStringForType;
+    SDL_GetRealGamepadInstanceType;
+    SDL_GetRealGamepadType;
     # extra symbols go here (don't modify this line)
   local: *;
 };

+ 4 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -897,3 +897,7 @@
 #define SDL_GetGamepadPowerLevel SDL_GetGamepadPowerLevel_REAL
 #define SDL_SetGamepadMapping SDL_SetGamepadMapping_REAL
 #define SDL_strndup SDL_strndup_REAL
+#define SDL_GetGamepadTypeFromString SDL_GetGamepadTypeFromString_REAL
+#define SDL_GetGamepadStringForType SDL_GetGamepadStringForType_REAL
+#define SDL_GetRealGamepadInstanceType SDL_GetRealGamepadInstanceType_REAL
+#define SDL_GetRealGamepadType SDL_GetRealGamepadType_REAL

+ 4 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -942,3 +942,7 @@ SDL_DYNAPI_PROC(SDL_JoystickID,SDL_GetGamepadInstanceID,(SDL_Gamepad *a),(a),ret
 SDL_DYNAPI_PROC(SDL_JoystickPowerLevel,SDL_GetGamepadPowerLevel,(SDL_Gamepad *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_SetGamepadMapping,(SDL_JoystickID a, const char *b),(a,b),return)
 SDL_DYNAPI_PROC(char*,SDL_strndup,(const char *a, size_t b),(a,b),return)
+SDL_DYNAPI_PROC(SDL_GamepadType,SDL_GetGamepadTypeFromString,(const char *a),(a),return)
+SDL_DYNAPI_PROC(const char*,SDL_GetGamepadStringForType,(SDL_GamepadType a),(a),return)
+SDL_DYNAPI_PROC(SDL_GamepadType,SDL_GetRealGamepadInstanceType,(SDL_JoystickID a),(a),return)
+SDL_DYNAPI_PROC(SDL_GamepadType,SDL_GetRealGamepadType,(SDL_Gamepad *a),(a),return)

+ 110 - 12
src/joystick/SDL_gamepad.c

@@ -43,6 +43,8 @@
 
 #define SDL_GAMEPAD_CRC_FIELD           "crc:"
 #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_PLATFORM_FIELD      "platform:"
 #define SDL_GAMEPAD_PLATFORM_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_PLATFORM_FIELD)
 #define SDL_GAMEPAD_HINT_FIELD          "hint:"
@@ -864,22 +866,71 @@ static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_JoystickGUID gu
     return mapping;
 }
 
+static const char *map_StringForGamepadType[] = {
+    "unknown",
+    "standard",
+    "xbox360",
+    "xboxone",
+    "ps3",
+    "ps4",
+    "ps5",
+    "switchpro",
+    "joyconleft",
+    "joyconright",
+    "joyconpair"
+};
+SDL_COMPILE_TIME_ASSERT(map_StringForGamepadType, SDL_arraysize(map_StringForGamepadType) == SDL_GAMEPAD_TYPE_MAX);
+
+/*
+ * convert a string to its enum equivalent
+ */
+SDL_GamepadType SDL_GetGamepadTypeFromString(const char *str)
+{
+    int i;
+
+    if (str == NULL || str[0] == '\0') {
+        return SDL_GAMEPAD_TYPE_UNKNOWN;
+    }
+
+    if (*str == '+' || *str == '-') {
+        ++str;
+    }
+
+    for (i = 0; i < SDL_arraysize(map_StringForGamepadType); ++i) {
+        if (SDL_strcasecmp(str, map_StringForGamepadType[i]) == 0) {
+            return (SDL_GamepadType)i;
+        }
+    }
+    return SDL_GAMEPAD_TYPE_UNKNOWN;
+}
+
+/*
+ * convert an enum to its string equivalent
+ */
+const char *SDL_GetGamepadStringForType(SDL_GamepadType type)
+{
+    if (type >= SDL_GAMEPAD_TYPE_STANDARD && type < SDL_GAMEPAD_TYPE_MAX) {
+        return map_StringForGamepadType[type];
+    }
+    return NULL;
+}
+
 static const char *map_StringForGamepadAxis[] = {
     "leftx",
     "lefty",
     "rightx",
     "righty",
     "lefttrigger",
-    "righttrigger",
-    NULL
+    "righttrigger"
 };
+SDL_COMPILE_TIME_ASSERT(map_StringForGamepadAxis, SDL_arraysize(map_StringForGamepadAxis) == SDL_GAMEPAD_AXIS_MAX);
 
 /*
  * convert a string to its enum equivalent
  */
 SDL_GamepadAxis SDL_GetGamepadAxisFromString(const char *str)
 {
-    int entry;
+    int i;
 
     if (str == NULL || str[0] == '\0') {
         return SDL_GAMEPAD_AXIS_INVALID;
@@ -889,9 +940,9 @@ SDL_GamepadAxis SDL_GetGamepadAxisFromString(const char *str)
         ++str;
     }
 
-    for (entry = 0; map_StringForGamepadAxis[entry]; ++entry) {
-        if (SDL_strcasecmp(str, map_StringForGamepadAxis[entry]) == 0) {
-            return (SDL_GamepadAxis)entry;
+    for (i = 0; i < SDL_arraysize(map_StringForGamepadAxis); ++i) {
+        if (SDL_strcasecmp(str, map_StringForGamepadAxis[i]) == 0) {
+            return (SDL_GamepadAxis)i;
         }
     }
     return SDL_GAMEPAD_AXIS_INVALID;
@@ -929,23 +980,24 @@ static const char *map_StringForGamepadButton[] = {
     "paddle2",
     "paddle3",
     "paddle4",
-    "touchpad",
-    NULL
+    "touchpad"
 };
+SDL_COMPILE_TIME_ASSERT(map_StringForGamepadButton, SDL_arraysize(map_StringForGamepadButton) == SDL_GAMEPAD_BUTTON_MAX);
 
 /*
  * convert a string to its enum equivalent
  */
 SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str)
 {
-    int entry;
+    int i;
+
     if (str == NULL || str[0] == '\0') {
         return SDL_GAMEPAD_BUTTON_INVALID;
     }
 
-    for (entry = 0; map_StringForGamepadButton[entry]; ++entry) {
-        if (SDL_strcasecmp(str, map_StringForGamepadButton[entry]) == 0) {
-            return (SDL_GamepadButton)entry;
+    for (i = 0; i < SDL_arraysize(map_StringForGamepadButton); ++i) {
+        if (SDL_strcasecmp(str, map_StringForGamepadButton[i]) == 0) {
+            return (SDL_GamepadButton)i;
         }
     }
     return SDL_GAMEPAD_BUTTON_INVALID;
@@ -2048,6 +2100,37 @@ Uint16 SDL_GetGamepadInstanceProductVersion(SDL_JoystickID instance_id)
 }
 
 SDL_GamepadType SDL_GetGamepadInstanceType(SDL_JoystickID instance_id)
+{
+    SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
+
+    SDL_LockJoysticks();
+    {
+        GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id);
+        if (mapping != NULL) {
+            char *type_string, *comma;
+
+            type_string = SDL_strstr(mapping->mapping, SDL_GAMEPAD_TYPE_FIELD);
+            if (type_string != NULL) {
+                type_string += SDL_GAMEPAD_TYPE_FIELD_SIZE;
+                comma = SDL_strchr(type_string, ',');
+                if (comma != NULL) {
+                    *comma = '\0';
+                    type = SDL_GetGamepadTypeFromString(type_string);
+                    *comma = ',';
+                }
+            }
+
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    if (type != SDL_GAMEPAD_TYPE_UNKNOWN) {
+        return type;
+    }
+    return SDL_GetRealGamepadInstanceType(instance_id);
+}
+
+SDL_GamepadType SDL_GetRealGamepadInstanceType(SDL_JoystickID instance_id)
 {
     return SDL_GetGamepadTypeFromGUID(SDL_GetJoystickInstanceGUID(instance_id), SDL_GetJoystickInstanceName(instance_id));
 }
@@ -2753,6 +2836,21 @@ const char *SDL_GetGamepadPath(SDL_Gamepad *gamepad)
 }
 
 SDL_GamepadType SDL_GetGamepadType(SDL_Gamepad *gamepad)
+{
+    SDL_JoystickID instance_id = 0;
+
+    SDL_LockJoysticks();
+    {
+        CHECK_GAMEPAD_MAGIC(gamepad, SDL_GAMEPAD_TYPE_UNKNOWN);
+
+        instance_id = gamepad->joystick->instance_id;
+    }
+    SDL_UnlockJoysticks();
+
+    return SDL_GetGamepadInstanceType(instance_id);
+}
+
+SDL_GamepadType SDL_GetRealGamepadType(SDL_Gamepad *gamepad)
 {
     SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
 

+ 6 - 6
src/joystick/SDL_joystick.c

@@ -2271,7 +2271,7 @@ void SDL_SetJoystickGUIDCRC(SDL_JoystickGUID *guid, Uint16 crc)
 
 SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, const char *name, SDL_bool forUI)
 {
-    SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
+    SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD;
 
     if (vendor == 0x0000 && product == 0x0000) {
         /* Some devices are only identifiable by their name */
@@ -2284,7 +2284,7 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons
         }
 
     } else if (vendor == 0x0001 && product == 0x0001) {
-        type = SDL_GAMEPAD_TYPE_UNKNOWN;
+        type = SDL_GAMEPAD_TYPE_STANDARD;
 
     } else if (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX_ONE_XINPUT_CONTROLLER) {
         type = SDL_GAMEPAD_TYPE_XBOXONE;
@@ -2295,7 +2295,7 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons
     } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) {
         if (name && SDL_strstr(name, "NES Controller") != NULL) {
             /* We don't have a type for the Nintendo Online NES Controller */
-            type = SDL_GAMEPAD_TYPE_UNKNOWN;
+            type = SDL_GAMEPAD_TYPE_STANDARD;
         } else {
             type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;
         }
@@ -2331,7 +2331,7 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons
             if (forUI) {
                 type = SDL_GAMEPAD_TYPE_PS4;
             } else {
-                type = SDL_GAMEPAD_TYPE_UNKNOWN;
+                type = SDL_GAMEPAD_TYPE_STANDARD;
             }
             break;
         case k_eControllerType_SwitchProController:
@@ -2342,7 +2342,7 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons
             if (forUI) {
                 type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
             } else {
-                type = SDL_GAMEPAD_TYPE_UNKNOWN;
+                type = SDL_GAMEPAD_TYPE_STANDARD;
             }
             break;
         default:
@@ -2359,7 +2359,7 @@ SDL_GamepadType SDL_GetGamepadTypeFromGUID(SDL_JoystickGUID guid, const char *na
 
     SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
     type = SDL_GetGamepadTypeFromVIDPID(vendor, product, name, SDL_TRUE);
-    if (type == SDL_GAMEPAD_TYPE_UNKNOWN) {
+    if (type == SDL_GAMEPAD_TYPE_STANDARD) {
         if (SDL_IsJoystickXInput(guid)) {
             /* This is probably an Xbox One controller */
             return SDL_GAMEPAD_TYPE_XBOXONE;

+ 8 - 8
src/joystick/hidapi/SDL_hidapi_switch.c

@@ -1219,40 +1219,40 @@ static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
         break;
     case k_eSwitchDeviceInfoControllerType_HVCLeft:
         HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (1)");
-        device->type = SDL_GAMEPAD_TYPE_UNKNOWN;
+        device->type = SDL_GAMEPAD_TYPE_STANDARD;
         break;
     case k_eSwitchDeviceInfoControllerType_HVCRight:
         HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (2)");
-        device->type = SDL_GAMEPAD_TYPE_UNKNOWN;
+        device->type = SDL_GAMEPAD_TYPE_STANDARD;
         break;
     case k_eSwitchDeviceInfoControllerType_NESLeft:
         HIDAPI_SetDeviceName(device, "Nintendo NES Controller (L)");
-        device->type = SDL_GAMEPAD_TYPE_UNKNOWN;
+        device->type = SDL_GAMEPAD_TYPE_STANDARD;
         break;
     case k_eSwitchDeviceInfoControllerType_NESRight:
         HIDAPI_SetDeviceName(device, "Nintendo NES Controller (R)");
-        device->type = SDL_GAMEPAD_TYPE_UNKNOWN;
+        device->type = SDL_GAMEPAD_TYPE_STANDARD;
         break;
     case k_eSwitchDeviceInfoControllerType_SNES:
         HIDAPI_SetDeviceName(device, "Nintendo SNES Controller");
         HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SNES_CONTROLLER);
-        device->type = SDL_GAMEPAD_TYPE_UNKNOWN;
+        device->type = SDL_GAMEPAD_TYPE_STANDARD;
         break;
     case k_eSwitchDeviceInfoControllerType_N64:
         HIDAPI_SetDeviceName(device, "Nintendo N64 Controller");
         HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_N64_CONTROLLER);
-        device->type = SDL_GAMEPAD_TYPE_UNKNOWN;
+        device->type = SDL_GAMEPAD_TYPE_STANDARD;
         break;
     case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:
         HIDAPI_SetDeviceName(device, "Nintendo SEGA Genesis Controller");
         HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER);
-        device->type = SDL_GAMEPAD_TYPE_UNKNOWN;
+        device->type = SDL_GAMEPAD_TYPE_STANDARD;
         break;
     case k_eSwitchDeviceInfoControllerType_Unknown:
         /* We couldn't read the device info for this controller, might not be fully compliant */
         return;
     default:
-        device->type = SDL_GAMEPAD_TYPE_UNKNOWN;
+        device->type = SDL_GAMEPAD_TYPE_STANDARD;
         break;
     }
     device->guid.data[15] = ctx->m_eControllerType;

+ 4 - 4
src/joystick/hidapi/SDL_hidapijoystick.c

@@ -139,7 +139,7 @@ SDL_bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product)
     /* If we already know the controller is a different type, don't try to detect it.
      * This fixes a hang with the HORIPAD for Nintendo Switch (0x0f0d/0x00c1)
      */
-    if (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, SDL_FALSE) != SDL_GAMEPAD_TYPE_UNKNOWN) {
+    if (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, SDL_FALSE) != SDL_GAMEPAD_TYPE_STANDARD) {
         return SDL_FALSE;
     }
 
@@ -208,7 +208,7 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U
     static const int XBONE_IFACE_SUBCLASS = 71;
     static const int XBONE_IFACE_PROTOCOL = 208;
 
-    SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
+    SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD;
 
     /* This code should match the checks in libusb/hid.c and HIDDeviceManager.java */
     if (interface_class == LIBUSB_CLASS_VENDOR_SPEC &&
@@ -283,7 +283,7 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U
         }
     }
 
-    if (type == SDL_GAMEPAD_TYPE_UNKNOWN) {
+    if (type == SDL_GAMEPAD_TYPE_STANDARD) {
         type = SDL_GetGamepadTypeFromVIDPID(vendor, product, name, SDL_FALSE);
     }
     return type;
@@ -1284,7 +1284,7 @@ SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_JoystickGUID guid)
 SDL_GamepadType HIDAPI_GetGamepadTypeFromGUID(SDL_JoystickGUID guid)
 {
     SDL_HIDAPI_Device *device;
-    SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
+    SDL_GamepadType type = SDL_GAMEPAD_TYPE_STANDARD;
 
     SDL_LockJoysticks();
     for (device = SDL_HIDAPI_devices; device; device = device->next) {

+ 4 - 0
src/joystick/sort_controllers.py

@@ -71,6 +71,10 @@ def save_controller(line):
             print("Controller '%s' not unique, skipping" % name)
             return
 
+    pos = find_element("type", bindings)
+    if pos >= 0:
+        bindings.insert(0, bindings.pop(pos))
+
     pos = find_element("platform", bindings)
     if pos >= 0:
         bindings.insert(0, bindings.pop(pos))

+ 211 - 7
test/gamepadutils.c

@@ -1268,6 +1268,177 @@ void DestroyGamepadDisplay(GamepadDisplay *ctx)
     SDL_free(ctx);
 }
 
+struct GamepadTypeDisplay
+{
+    SDL_Renderer *renderer;
+
+    int type_highlighted;
+    SDL_bool type_pressed;
+    int type_selected;
+    SDL_GamepadType real_type;
+
+    SDL_Rect area;
+};
+
+GamepadTypeDisplay *CreateGamepadTypeDisplay(SDL_Renderer *renderer)
+{
+    GamepadTypeDisplay *ctx = SDL_calloc(1, sizeof(*ctx));
+    if (ctx) {
+        ctx->renderer = renderer;
+
+        ctx->type_highlighted = SDL_GAMEPAD_TYPE_UNSELECTED;
+        ctx->type_selected = SDL_GAMEPAD_TYPE_UNSELECTED;
+        ctx->real_type = SDL_GAMEPAD_TYPE_UNKNOWN;
+    }
+    return ctx;
+}
+
+void SetGamepadTypeDisplayArea(GamepadTypeDisplay *ctx, const SDL_Rect *area)
+{
+    if (!ctx) {
+        return;
+    }
+
+    SDL_copyp(&ctx->area, area);
+}
+
+void SetGamepadTypeDisplayHighlight(GamepadTypeDisplay *ctx, int type, SDL_bool pressed)
+{
+    if (!ctx) {
+        return;
+    }
+
+    ctx->type_highlighted = type;
+    ctx->type_pressed = pressed;
+}
+
+void SetGamepadTypeDisplaySelected(GamepadTypeDisplay *ctx, int type)
+{
+    if (!ctx) {
+        return;
+    }
+
+    ctx->type_selected = type;
+}
+
+void SetGamepadTypeDisplayRealType(GamepadTypeDisplay *ctx, SDL_GamepadType type)
+{
+    if (!ctx) {
+        return;
+    }
+
+    ctx->real_type = type;
+}
+
+int GetGamepadTypeDisplayAt(GamepadTypeDisplay *ctx, float x, float y)
+{
+    int i;
+    const float margin = 8.0f;
+    const float line_height = 16.0f;
+    SDL_FRect highlight;
+    SDL_FPoint point;
+
+    if (!ctx) {
+        return SDL_GAMEPAD_TYPE_UNSELECTED;
+    }
+
+    point.x = x;
+    point.y = y;
+
+    x = ctx->area.x + margin;
+    y = ctx->area.y + margin;
+
+    for (i = SDL_GAMEPAD_TYPE_UNKNOWN; i < SDL_GAMEPAD_TYPE_MAX; ++i) {
+        highlight.x = x;
+        highlight.y = y;
+        highlight.w = (float)ctx->area.w - (margin * 2);
+        highlight.h = (float)line_height;
+
+        if (SDL_PointInRectFloat(&point, &highlight)) {
+            return i;
+        }
+
+        y += line_height;
+    }
+    return SDL_GAMEPAD_TYPE_UNSELECTED;
+}
+
+static void RenderGamepadTypeHighlight(GamepadTypeDisplay *ctx, int type, const SDL_FRect *area)
+{
+    if (type == ctx->type_highlighted || type == ctx->type_selected) {
+        Uint8 r, g, b, a;
+
+        SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
+
+        if (type == ctx->type_highlighted) {
+            if (ctx->type_pressed) {
+                SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR);
+            } else {
+                SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR);
+            }
+        } else {
+            SDL_SetRenderDrawColor(ctx->renderer, SELECTED_COLOR);
+        }
+        SDL_RenderFillRect(ctx->renderer, area);
+
+        SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
+    }
+}
+
+void RenderGamepadTypeDisplay(GamepadTypeDisplay *ctx)
+{
+    float x, y;
+    int i;
+    char text[128];
+    const float margin = 8.0f;
+    const float line_height = 16.0f;
+    SDL_FPoint dst;
+    SDL_FRect highlight;
+
+    if (!ctx) {
+        return;
+    }
+
+    x = ctx->area.x + margin;
+    y = ctx->area.y + margin;
+
+    for (i = SDL_GAMEPAD_TYPE_UNKNOWN; i < SDL_GAMEPAD_TYPE_MAX; ++i) {
+        highlight.x = x;
+        highlight.y = y;
+        highlight.w = (float)ctx->area.w - (margin * 2);
+        highlight.h = (float)line_height;
+        RenderGamepadTypeHighlight(ctx, i, &highlight);
+
+        if (i == SDL_GAMEPAD_TYPE_UNKNOWN) {
+            if (ctx->real_type == SDL_GAMEPAD_TYPE_UNKNOWN ||
+                ctx->real_type == SDL_GAMEPAD_TYPE_STANDARD) {
+                SDL_strlcpy(text, "Auto (Standard)", sizeof(text));
+            } else {
+                SDL_snprintf(text, sizeof(text), "Auto (%s)", GetGamepadTypeString(ctx->real_type));
+            }
+        } else if (i == SDL_GAMEPAD_TYPE_STANDARD) {
+            SDL_strlcpy(text, "Standard", sizeof(text));
+        } else {
+            SDL_strlcpy(text, GetGamepadTypeString((SDL_GamepadType)i), sizeof(text));
+        }
+
+        dst.x = x + margin;
+        dst.y = y + line_height / 2 - FONT_CHARACTER_SIZE / 2;
+        SDLTest_DrawString(ctx->renderer, dst.x, dst.y, text);
+
+        y += line_height;
+    }
+}
+
+void DestroyGamepadTypeDisplay(GamepadTypeDisplay *ctx)
+{
+    if (!ctx) {
+        return;
+    }
+
+    SDL_free(ctx);
+}
+
 
 struct JoystickDisplay
 {
@@ -2185,13 +2356,14 @@ static char *JoinMapping(MappingParts *parts)
     }
     length += 1;
 
-    /* The sort order is: crc, platform, *, sdk, hint */
+    /* The sort order is: crc, platform, type, *, sdk, hint */
     sort_order = SDL_stack_alloc(MappingSortEntry, parts->num_elements);
     for (i = 0; i < parts->num_elements; ++i) {
         sort_order[i].parts = parts;
         sort_order[i].index = i;
     }
     SDL_qsort(sort_order, parts->num_elements, sizeof(*sort_order), SortMapping);
+    MoveSortedEntry("type", sort_order, parts->num_elements, SDL_TRUE);
     MoveSortedEntry("platform", sort_order, parts->num_elements, SDL_TRUE);
     MoveSortedEntry("crc", sort_order, parts->num_elements, SDL_TRUE);
     MoveSortedEntry("sdk>=", sort_order, parts->num_elements, SDL_FALSE);
@@ -2334,7 +2506,7 @@ static char *SetMappingValue(char *mapping, const char *key, const char *value)
     return mapping;
 }
 
-static char *RemoveMappingKey(char *mapping, const char *key)
+static char *RemoveMappingValue(char *mapping, const char *key)
 {
     MappingParts parts;
     int i;
@@ -2447,14 +2619,46 @@ char *SetMappingName(char *mapping, const char *name)
     return RecreateMapping(&parts, mapping);
 }
 
-char *GetMappingType(const char *mapping)
+
+const char *GetGamepadTypeString(SDL_GamepadType type)
+{
+    switch (type) {
+    case SDL_GAMEPAD_TYPE_XBOX360:
+        return "Xbox 360";
+    case SDL_GAMEPAD_TYPE_XBOXONE:
+        return "Xbox One";
+    case SDL_GAMEPAD_TYPE_PS3:
+        return "PS3";
+    case SDL_GAMEPAD_TYPE_PS4:
+        return "PS4";
+    case SDL_GAMEPAD_TYPE_PS5:
+        return "PS5";
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
+        return "Nintendo Switch";
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
+        return "Joy-Con (L)";
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
+        return "Joy-Con (R)";
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
+        return "Joy-Con Pair";
+    default:
+        return "";
+    }
+}
+
+SDL_GamepadType GetMappingType(const char *mapping)
 {
-    return GetMappingValue(mapping, "type");
+    return SDL_GetGamepadTypeFromString(GetMappingValue(mapping, "type"));
 }
 
-char *SetMappingType(char *mapping, const char *type)
+char *SetMappingType(char *mapping, SDL_GamepadType type)
 {
-    return SetMappingValue(mapping, "type", type);
+    const char *type_string = SDL_GetGamepadStringForType(type);
+    if (type_string == NULL || type == SDL_GAMEPAD_TYPE_UNKNOWN) {
+        return RemoveMappingValue(mapping, "type");
+    } else {
+        return SetMappingValue(mapping, "type", type_string);
+    }
 }
 
 static const char *GetElementKey(int element)
@@ -2527,7 +2731,7 @@ char *SetElementBinding(char *mapping, int element, const char *binding)
     if (binding) {
         return SetMappingValue(mapping, GetElementKey(element), binding);
     } else {
-        return RemoveMappingKey(mapping, GetElementKey(element));
+        return RemoveMappingValue(mapping, GetElementKey(element));
     }
 }
 

+ 23 - 2
test/gamepadutils.h

@@ -92,6 +92,24 @@ extern void SetGamepadDisplaySelected(GamepadDisplay *ctx, int element);
 extern void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad);
 extern void DestroyGamepadDisplay(GamepadDisplay *ctx);
 
+/* Gamepad type display */
+
+enum
+{
+    SDL_GAMEPAD_TYPE_UNSELECTED = -1
+};
+
+typedef struct GamepadTypeDisplay GamepadTypeDisplay;
+
+extern GamepadTypeDisplay *CreateGamepadTypeDisplay(SDL_Renderer *renderer);
+extern void SetGamepadTypeDisplayArea(GamepadTypeDisplay *ctx, const SDL_Rect *area);
+extern int GetGamepadTypeDisplayAt(GamepadTypeDisplay *ctx, float x, float y);
+extern void SetGamepadTypeDisplayHighlight(GamepadTypeDisplay *ctx, int type, SDL_bool pressed);
+extern void SetGamepadTypeDisplaySelected(GamepadTypeDisplay *ctx, int type);
+extern void SetGamepadTypeDisplayRealType(GamepadTypeDisplay *ctx, SDL_GamepadType type);
+extern void RenderGamepadTypeDisplay(GamepadTypeDisplay *ctx);
+extern void DestroyGamepadTypeDisplay(GamepadTypeDisplay *ctx);
+
 /* Joystick element display */
 
 typedef struct JoystickDisplay JoystickDisplay;
@@ -131,11 +149,14 @@ extern char *GetMappingName(const char *mapping);
 /* Set the name in a mapping, freeing the mapping passed in and returning a new mapping */
 extern char *SetMappingName(char *mapping, const char *name);
 
+/* Get the friendly string for an SDL_GamepadType */
+extern const char *GetGamepadTypeString(SDL_GamepadType type);
+
 /* Return the type from a mapping, which should be freed using SDL_free(), or NULL if there is no type specified */
-extern char *GetMappingType(const char *mapping);
+extern SDL_GamepadType GetMappingType(const char *mapping);
 
 /* Set the type in a mapping, freeing the mapping passed in and returning a new mapping */
-extern char *SetMappingType(char *mapping, const char *type);
+extern char *SetMappingType(char *mapping, SDL_GamepadType type);
 
 /* Return true if a mapping has this element bound */
 extern SDL_bool MappingHasElement(const char *mapping, int element);

+ 86 - 3
test/testcontroller.c

@@ -68,6 +68,7 @@ static SDL_Renderer *screen = NULL;
 static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING;
 static GamepadImage *image = NULL;
 static GamepadDisplay *gamepad_elements = NULL;
+static GamepadTypeDisplay *gamepad_type = NULL;
 static JoystickDisplay *joystick_elements = NULL;
 static GamepadButton *setup_mapping_button = NULL;
 static GamepadButton *done_mapping_button = NULL;
@@ -89,6 +90,9 @@ static Uint64 binding_advance_time = 0;
 static SDL_FRect title_area;
 static SDL_bool title_highlighted;
 static SDL_bool title_pressed;
+static SDL_FRect type_area;
+static SDL_bool type_highlighted;
+static SDL_bool type_pressed;
 static char *controller_name;
 static SDL_Joystick *virtual_joystick = NULL;
 static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
@@ -210,8 +214,12 @@ static void ClearButtonHighlights(void)
     title_highlighted = SDL_FALSE;
     title_pressed = SDL_FALSE;
 
+    type_highlighted = SDL_FALSE;
+    type_pressed = SDL_FALSE;
+
     ClearGamepadImage(image);
     SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
+    SetGamepadTypeDisplayHighlight(gamepad_type, SDL_GAMEPAD_TYPE_UNSELECTED, SDL_FALSE);
     SetGamepadButtonHighlight(setup_mapping_button, SDL_FALSE, SDL_FALSE);
     SetGamepadButtonHighlight(done_mapping_button, SDL_FALSE, SDL_FALSE);
     SetGamepadButtonHighlight(cancel_button, SDL_FALSE, SDL_FALSE);
@@ -241,6 +249,14 @@ static void UpdateButtonHighlights(float x, float y, SDL_bool button_down)
             title_pressed = SDL_FALSE;
         }
 
+        if (SDL_PointInRectFloat(&point, &type_area)) {
+            type_highlighted = SDL_TRUE;
+            type_pressed = button_down;
+        } else {
+            type_highlighted = SDL_FALSE;
+            type_pressed = SDL_FALSE;
+        }
+
         if (controller->joystick != virtual_joystick) {
             gamepad_highlight_element = GetGamepadImageElementAt(image, x, y);
         }
@@ -249,6 +265,11 @@ static void UpdateButtonHighlights(float x, float y, SDL_bool button_down)
         }
         SetGamepadDisplayHighlight(gamepad_elements, gamepad_highlight_element, button_down);
 
+        if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
+            int gamepad_highlight_type = GetGamepadTypeDisplayAt(gamepad_type, x, y);
+            SetGamepadTypeDisplayHighlight(gamepad_type, gamepad_highlight_type, button_down);
+        }
+
         joystick_highlight_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, x, y);
         SetJoystickDisplayHighlight(joystick_elements, joystick_highlight_element, button_down);
         SDL_free(joystick_highlight_element);
@@ -606,6 +627,19 @@ static void PasteControllerName(void)
     CommitControllerName();
 }
 
+static void CommitGamepadType(SDL_GamepadType type)
+{
+    char *mapping = NULL;
+
+    if (controller->mapping) {
+        mapping = SDL_strdup(controller->mapping);
+    } else {
+        mapping = NULL;
+    }
+    mapping = SetMappingType(mapping, type);
+    SetAndFreeGamepadMapping(mapping);
+}
+
 static const char *GetBindingInstruction(void)
 {
     switch (binding_element) {
@@ -1097,6 +1131,7 @@ static void DrawGamepadWaiting(SDL_Renderer *renderer)
 
 static void DrawGamepadInfo(SDL_Renderer *renderer)
 {
+    const char *type;
     const char *serial;
     char text[128];
     float x, y;
@@ -1116,9 +1151,24 @@ static void DrawGamepadInfo(SDL_Renderer *renderer)
         SDL_SetRenderDrawColor(renderer, r, g, b, a);
     }
 
+    if (type_highlighted) {
+        Uint8 r, g, b, a;
+
+        SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
+
+        if (type_pressed) {
+            SDL_SetRenderDrawColor(renderer, PRESSED_COLOR);
+        } else {
+            SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR);
+        }
+        SDL_RenderFillRect(renderer, &type_area);
+
+        SDL_SetRenderDrawColor(renderer, r, g, b, a);
+    }
+
     if (controller_name && *controller_name) {
-        x = (float)SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2;
-        y = (float)TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2;
+        x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2;
+        y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2;
         SDLTest_DrawString(renderer, x, y, controller_name);
     }
 
@@ -1129,6 +1179,11 @@ static void DrawGamepadInfo(SDL_Renderer *renderer)
         SDLTest_DrawString(renderer, x, y, text);
     }
 
+    type = GetGamepadTypeString(SDL_GetGamepadType(controller->gamepad));
+    x = type_area.x + type_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(type)) / 2;
+    y = type_area.y + type_area.h / 2 - FONT_CHARACTER_SIZE / 2;
+    SDLTest_DrawString(renderer, x, y, type);
+
     if (display_mode == CONTROLLER_MODE_TESTING) {
         SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x",
                      SDL_GetJoystickVendor(controller->joystick),
@@ -1185,6 +1240,8 @@ static void DrawBindingTips(SDL_Renderer *renderer)
 
         if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
             text = "(press RETURN to complete)";
+        } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
+            text = "(press ESC to cancel)";
         } else {
             bound_A = MappingHasElement(controller->mapping, SDL_GAMEPAD_BUTTON_A);
             bound_B = MappingHasElement(controller->mapping, SDL_GAMEPAD_BUTTON_B);
@@ -1467,6 +1524,14 @@ static void loop(void *arg)
                     PasteMapping();
                 } else if (title_pressed) {
                     SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, SDL_FALSE);
+                } else if (type_pressed) {
+                    SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_TYPE, SDL_FALSE);
+                } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
+                    int type = GetGamepadTypeDisplayAt(gamepad_type, event.button.x, event.button.y);
+                    if (type != SDL_GAMEPAD_TYPE_UNSELECTED) {
+                        CommitGamepadType((SDL_GamepadType)type);
+                        StopBinding();
+                    }
                 } else {
                     int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID;
                     char *joystick_element;
@@ -1599,7 +1664,12 @@ static void loop(void *arg)
         }
         RenderGamepadImage(image);
 
-        RenderGamepadDisplay(gamepad_elements, controller->gamepad);
+        if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
+            SetGamepadTypeDisplayRealType(gamepad_type, SDL_GetRealGamepadType(controller->gamepad));
+            RenderGamepadTypeDisplay(gamepad_type);
+        } else {
+            RenderGamepadDisplay(gamepad_elements, controller->gamepad);
+        }
         RenderJoystickDisplay(joystick_elements, controller->joystick);
 
         if (display_mode == CONTROLLER_MODE_TESTING) {
@@ -1741,6 +1811,11 @@ int main(int argc, char *argv[])
     title_area.x = (float)PANEL_WIDTH + PANEL_SPACING;
     title_area.y = (float)TITLE_HEIGHT / 2 - title_area.h / 2;
 
+    type_area.w = (float)PANEL_WIDTH - 2 * BUTTON_MARGIN;
+    type_area.h = (float)FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN;
+    type_area.x = (float)BUTTON_MARGIN;
+    type_area.y = (float)TITLE_HEIGHT / 2 - type_area.h / 2;
+
     image = CreateGamepadImage(screen);
     if (image == NULL) {
         SDL_DestroyRenderer(screen);
@@ -1756,6 +1831,13 @@ int main(int argc, char *argv[])
     area.h = GAMEPAD_HEIGHT;
     SetGamepadDisplayArea(gamepad_elements, &area);
 
+    gamepad_type = CreateGamepadTypeDisplay(screen);
+    area.x = 0;
+    area.y = TITLE_HEIGHT;
+    area.w = PANEL_WIDTH;
+    area.h = GAMEPAD_HEIGHT;
+    SetGamepadTypeDisplayArea(gamepad_type, &area);
+
     joystick_elements = CreateJoystickDisplay(screen);
     area.x = PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING;
     area.y = TITLE_HEIGHT;
@@ -1829,6 +1911,7 @@ int main(int argc, char *argv[])
     }
     DestroyGamepadImage(image);
     DestroyGamepadDisplay(gamepad_elements);
+    DestroyGamepadTypeDisplay(gamepad_type);
     DestroyJoystickDisplay(joystick_elements);
     DestroyGamepadButton(copy_button);
     SDL_DestroyRenderer(screen);