SDL: Keep track of whether a controller was a gamepad

From 4fc68a48f20574326eb18022eed0b7c0fa52a10e Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 9 Jun 2024 17:42:27 -0700
Subject: [PATCH] Keep track of whether a controller was a gamepad

Fixes https://github.com/libsdl-org/SDL/issues/9996
---
 include/SDL3/SDL_events.h      |  6 +++---
 src/joystick/SDL_gamepad.c     | 26 +++++++++++++++++++++++---
 src/joystick/SDL_joystick.c    | 13 ++++---------
 src/joystick/SDL_sysjoystick.h |  1 -
 4 files changed, 30 insertions(+), 16 deletions(-)

diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index 5abcc9f503419..3488cd05ac0c8 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -170,9 +170,9 @@ typedef enum SDL_EventType
     SDL_EVENT_GAMEPAD_AXIS_MOTION  = 0x650, /**< Gamepad axis motion */
     SDL_EVENT_GAMEPAD_BUTTON_DOWN,          /**< Gamepad button pressed */
     SDL_EVENT_GAMEPAD_BUTTON_UP,            /**< Gamepad button released */
-    SDL_EVENT_GAMEPAD_ADDED,               /**< A new gamepad has been inserted into the system */
-    SDL_EVENT_GAMEPAD_REMOVED,             /**< An opened gamepad has been removed */
-    SDL_EVENT_GAMEPAD_REMAPPED,            /**< The gamepad mapping was updated */
+    SDL_EVENT_GAMEPAD_ADDED,                /**< A new gamepad has been inserted into the system */
+    SDL_EVENT_GAMEPAD_REMOVED,              /**< A gamepad has been removed */
+    SDL_EVENT_GAMEPAD_REMAPPED,             /**< The gamepad mapping was updated */
     SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN,        /**< Gamepad touchpad was touched */
     SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION,      /**< Gamepad touchpad finger was moved */
     SDL_EVENT_GAMEPAD_TOUCHPAD_UP,          /**< Gamepad touchpad finger was lifted */
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index 854afa3ac9f69..146fe61876477 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -31,6 +31,7 @@
 #include "usb_ids.h"
 #include "hidapi/SDL_hidapi_nintendo.h"
 #include "../events/SDL_events_c.h"
+#include "../SDL_hashtable.h"
 
 
 #ifdef SDL_PLATFORM_ANDROID
@@ -103,6 +104,7 @@ static GamepadMapping_t *s_pSupportedGamepads SDL_GUARDED_BY(SDL_joystick_lock)
 static GamepadMapping_t *s_pDefaultMapping SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
 static GamepadMapping_t *s_pXInputMapping SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
 static MappingChangeTracker *s_mappingChangeTracker SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
+static SDL_HashTable *s_gamepadInstanceIDs SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
 
 #define _guarded SDL_GUARDED_BY(SDL_joystick_lock)
 
@@ -561,8 +563,11 @@ static void PopMappingChangeTracking(void)
             GamepadMapping_t *old_mapping = gamepad ? gamepad->mapping : tracker->joystick_mappings[i];
 
             if (new_mapping && !old_mapping) {
+                SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)SDL_TRUE);
                 SDL_PrivateGamepadAdded(joystick);
             } else if (old_mapping && !new_mapping) {
+                SDL_RemoveFromHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick);
+                SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)SDL_FALSE);
                 SDL_PrivateGamepadRemoved(joystick);
             } else if (old_mapping != new_mapping || HasMappingChangeTracking(tracker, new_mapping)) {
                 if (gamepad) {
@@ -2560,10 +2565,20 @@ SDL_bool SDL_IsGamepad(SDL_JoystickID instance_id)
 
     SDL_LockJoysticks();
     {
-        if (SDL_PrivateGetGamepadMapping(instance_id, SDL_TRUE) != NULL) {
-            retval = SDL_TRUE;
+        const void *value;
+        if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) {
+            retval = (SDL_bool)(uintptr_t)value;
         } else {
-            retval = SDL_FALSE;
+            if (SDL_PrivateGetGamepadMapping(instance_id, SDL_TRUE) != NULL) {
+                retval = SDL_TRUE;
+            } else {
+                retval = SDL_FALSE;
+            }
+
+            if (!s_gamepadInstanceIDs) {
+                s_gamepadInstanceIDs = SDL_CreateHashTable(NULL, 4, SDL_HashID, SDL_KeyMatchID, NULL, SDL_FALSE);
+            }
+            SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, (void *)(uintptr_t)retval);
         }
     }
     SDL_UnlockJoysticks();
@@ -3669,6 +3684,11 @@ void SDL_QuitGamepads(void)
         SDL_CloseGamepad(SDL_gamepads);
     }
 
+    if (s_gamepadInstanceIDs) {
+        SDL_DestroyHashTable(s_gamepadInstanceIDs);
+        s_gamepadInstanceIDs = NULL;
+    }
+
     SDL_UnlockJoysticks();
 }
 
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index c24b3e1bd6a9f..028ff1a4f07bd 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -898,7 +898,7 @@ static SDL_bool ShouldAttemptSensorFusion(SDL_Joystick *joystick, SDL_bool *inve
     *invert_sensors = SDL_FALSE;
 
     /* The SDL controller sensor API is only available for gamepads (at the moment) */
-    if (!joystick->is_gamepad) {
+    if (!SDL_IsGamepad(joystick->instance_id)) {
         return SDL_FALSE;
     }
 
@@ -1150,8 +1150,6 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
         }
     }
 
-    joystick->is_gamepad = SDL_IsGamepad(instance_id);
-
     /* Get the Steam Input API handle */
     info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id);
     if (info) {
@@ -2135,10 +2133,7 @@ void SDL_PrivateJoystickRemoved(SDL_JoystickID instance_id)
         }
     }
 
-    /* FIXME: The driver no longer provides the name and GUID at this point, so we
-     *        don't know whether this was a gamepad. For now always send the event.
-     */
-    if (SDL_TRUE /*SDL_IsGamepad(instance_id)*/) {
+    if (SDL_IsGamepad(instance_id)) {
         SDL_PrivateGamepadRemoved(instance_id);
     }
 
@@ -2363,7 +2358,7 @@ static void SendSteamHandleUpdateEvents(void)
     for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
         SDL_bool changed = SDL_FALSE;
 
-        if (!joystick->is_gamepad) {
+        if (!SDL_IsGamepad(joystick->instance_id)) {
             continue;
         }
 
@@ -3449,7 +3444,7 @@ SDL_JoystickType SDL_GetJoystickType(SDL_Joystick *joystick)
         {
             CHECK_JOYSTICK_MAGIC(joystick, SDL_JOYSTICK_TYPE_UNKNOWN);
 
-            if (joystick->is_gamepad) {
+            if (SDL_IsGamepad(joystick->instance_id)) {
                 type = SDL_JOYSTICK_TYPE_GAMEPAD;
             }
         }
diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h
index 52bcd17d36598..e006681f8c6a0 100644
--- a/src/joystick/SDL_sysjoystick.h
+++ b/src/joystick/SDL_sysjoystick.h
@@ -118,7 +118,6 @@ struct SDL_Joystick
     Uint64 led_expiration _guarded;
 
     SDL_bool attached _guarded;
-    SDL_bool is_gamepad _guarded;
     SDL_JoystickConnectionState connection_state _guarded;
     SDL_PowerState battery_state _guarded;
     int battery_percent _guarded;