SDL: Updated gamepad, joystick, sensor APIs, removing device indices

From 16092f58bbc1ac99121b23a8d4a03a36c3e12f25 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 27 Dec 2022 18:10:06 -0800
Subject: [PATCH] Updated gamepad, joystick, sensor APIs, removing device
 indices

Instead of indexing into an internal list of devices which requires locking, we return a list of device IDs which can then be queried individually.

Reference: https://github.com/libsdl-org/SDL/issues/6889
---
 WhatsNew.txt                                 |   3 +
 docs/README-migration.md                     | 105 +++++--
 include/SDL3/SDL_events.h                    |   4 +-
 include/SDL3/SDL_gamepad.h                   | 187 ++++++++----
 include/SDL3/SDL_joystick.h                  | 163 +++++------
 include/SDL3/SDL_oldnames.h                  |  19 --
 include/SDL3/SDL_sensor.h                    |  67 ++---
 src/dynapi/SDL_dynapi.sym                    |  47 +--
 src/dynapi/SDL_dynapi_overrides.h            |  47 +--
 src/dynapi/SDL_dynapi_procs.h                |  63 ++--
 src/events/SDL_events.c                      |  17 +-
 src/joystick/SDL_gamepad.c                   | 157 ++++++----
 src/joystick/SDL_joystick.c                  | 289 +++++++------------
 src/joystick/SDL_joystick_c.h                |   9 +-
 src/joystick/SDL_sysjoystick.h               |   2 +-
 src/joystick/hidapi/SDL_hidapi_gamecube.c    |  20 +-
 src/joystick/virtual/SDL_virtualjoystick.c   |  36 ++-
 src/joystick/virtual/SDL_virtualjoystick_c.h |   4 +-
 src/sensor/SDL_sensor.c                      | 121 +++++---
 src/sensor/SDL_sensor_c.h                    |   3 +
 src/test/SDL_test_common.c                   |  30 +-
 test/gamepadmap.c                            |  55 ++--
 test/testautomation_joystick.c               |  16 +-
 test/testgamepad.c                           | 172 +++++------
 test/testhotplug.c                           |  12 +-
 test/testjoystick.c                          |  31 +-
 test/testsensor.c                            |  35 ++-
 27 files changed, 917 insertions(+), 797 deletions(-)

diff --git a/WhatsNew.txt b/WhatsNew.txt
index 21f82536fbf3..10625d97affb 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -12,6 +12,9 @@ General:
 * The preprocessor symbol __IPHONEOS__ has been renamed __IOS__
 * SDL_stdinc.h no longer includes stdio.h, stdlib.h, etc., it only provides the SDL C runtime functionality
 * Added SDL_CreateSurface() and SDL_CreateSurfaceFrom() which replace SDL_CreateRGBSurface*(), and can also be used to create YUV surfaces
+* Added SDL_HasJoysticks(), SDL_GetJoysticks(), SDL_GetJoystickInstanceName(), SDL_GetJoystickInstancePath(), SDL_GetJoystickInstancePlayerIndex(), SDL_GetJoystickInstanceGUID(), SDL_GetJoystickInstanceVendor(), SDL_GetJoystickInstanceProduct(), SDL_GetJoystickInstanceProductVersion(), and SDL_GetJoystickInstanceType() to directly query the list of available joysticks
+* Added SDL_HasGamepads(), SDL_GetGamepads(), SDL_GetGamepadInstanceName(), SDL_GetGamepadInstancePath(), SDL_GetGamepadInstancePlayerIndex(), SDL_GetGamepadInstanceGUID(), SDL_GetGamepadInstanceVendor(), SDL_GetGamepadInstanceProduct(), SDL_GetGamepadInstanceProductVersion(), and SDL_GetGamepadInstanceType() to directly query the list of available gamepads
+* Added SDL_HasSensors(), SDL_GetSensors(), SDL_GetSensorInstanceName(), SDL_GetSensorInstanceType(), and SDL_GetSensorInstanceNonPortableType() to directly query the list of available sensors
 * SDL_GetTicks() now returns a 64-bit value and the tick values should be directly compared instead of using the SDL_TICKS_PASSED macro
 * Added SDL_GetTicksNS() to return the number of nanoseconds since the SDL library initialized
 * Added SDL_DelayNS() to specify a delay in nanoseconds, to the highest precision the system will support
diff --git a/docs/README-migration.md b/docs/README-migration.md
index 82a2d26c8dcd..8d3e77c740c8 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -112,7 +112,11 @@ The following structures have been renamed:
 
 SDL_gamecontroller.h has been renamed SDL_gamepad.h, and all APIs have been renamed to match.
 
-Removed SDL_GameControllerGetSensorDataWithTimestamp(), if you want timestamps for the sensor data, you should use the sensor_timestamp member of SDL_GAMEPADSENSORUPDATE events.
+The SDL_GAMEPADADDED event now provides the joystick instance ID in the `which` member of the cdevice event structure.
+
+The functions SDL_HasGamepads(), SDL_GetGamepads(), SDL_GetGamepadInstanceName(), SDL_GetGamepadInstancePath(), SDL_GetGamepadInstancePlayerIndex(), SDL_GetGamepadInstanceGUID(), SDL_GetGamepadInstanceVendor(), SDL_GetGamepadInstanceProduct(), SDL_GetGamepadInstanceProductVersion(), and SDL_GetGamepadInstanceType() have been added to directly query the list of available gamepads.
+
+SDL_GameControllerGetSensorDataWithTimestamp() has been removed. If you want timestamps for the sensor data, you should use the sensor_timestamp member of SDL_GAMEPADSENSORUPDATE events.
 
 The following enums have been renamed:
 * SDL_GameControllerAxis => SDL_GamepadAxis
@@ -164,25 +168,27 @@ The following functions have been renamed:
 * SDL_GameControllerHasSensor => SDL_GamepadHasSensor
 * SDL_GameControllerIsSensorEnabled => SDL_IsGamepadSensorEnabled
 * SDL_GameControllerMapping => SDL_GetGamepadMapping
-* SDL_GameControllerMappingForDeviceIndex => SDL_GetGamepadMappingForDeviceIndex
 * SDL_GameControllerMappingForGUID => SDL_GetGamepadMappingForGUID
 * SDL_GameControllerMappingForIndex => SDL_GetGamepadMappingForIndex
 * SDL_GameControllerName => SDL_GetGamepadName
-* SDL_GameControllerNameForIndex => SDL_GetGamepadNameForIndex
 * SDL_GameControllerNumMappings => SDL_GetNumGamepadMappings
 * SDL_GameControllerOpen => SDL_OpenGamepad
 * SDL_GameControllerPath => SDL_GetGamepadPath
-* SDL_GameControllerPathForIndex => SDL_GetGamepadPathForIndex
 * SDL_GameControllerRumble => SDL_RumbleGamepad
 * SDL_GameControllerRumbleTriggers => SDL_RumbleGamepadTriggers
 * SDL_GameControllerSendEffect => SDL_SendGamepadEffect
 * SDL_GameControllerSetLED => SDL_SetGamepadLED
 * SDL_GameControllerSetPlayerIndex => SDL_SetGamepadPlayerIndex
 * SDL_GameControllerSetSensorEnabled => SDL_SetGamepadSensorEnabled
-* SDL_GameControllerTypeForIndex => SDL_GetGamepadTypeForIndex
 * SDL_GameControllerUpdate => SDL_UpdateGamepads
 * SDL_IsGameController => SDL_IsGamepad
 
+The following functions have been removed:
+* SDL_GameControllerNameForIndex() - replaced with SDL_GetGamepadInstanceName()
+* SDL_GameControllerPathForIndex() - replaced with SDL_GetGamepadInstancePath()
+* SDL_GameControllerTypeForIndex() - replaced with SDL_GetGamepadInstanceType()
+* SDL_GameControllerMappingForDeviceIndex() - replaced with SDL_GetGamepadInstanceMapping()
+
 The following symbols have been renamed:
 * SDL_CONTROLLER_AXIS_INVALID => SDL_GAMEPAD_AXIS_INVALID
 * SDL_CONTROLLER_AXIS_LEFTX => SDL_GAMEPAD_AXIS_LEFTX
@@ -260,6 +266,36 @@ The following macros have been renamed:
 
 ## SDL_joystick.h
 
+SDL_JoystickID has changed from Sint32 to Uint32, with an invalid ID being 0.
+
+Rather than iterating over joysticks using device index, there is a new function SDL_GetJoysticks() to get the current list of joysticks, and new functions to get information about joysticks from their instance ID:
+```c
+{
+    if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == 0) {
+        int i, num_joysticks;
+        SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks);
+        if (joysticks) {
+            for (i = 0; i < num_joysticks; ++i) {
+                SDL_JoystickID instance_id = joysticks[i];
+                const char *name = SDL_GetJoystickInstanceName(instance_id);
+                const char *path = SDL_GetJoystickInstancePath(instance_id);
+
+                SDL_Log("Joystick %" SDL_PRIu32 ": %s%s%s VID 0x%.4x, PID 0x%.4x\n",
+                        instance_id, name ? name : "Unknown", path ? ", " : "", path ? path : "", SDL_GetJoystickInstanceVendor(instance_id), SDL_GetJoystickInstanceProduct(instance_id));
+            }
+            SDL_free(joysticks);
+        }
+        SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+    }
+}
+```
+
+The SDL_JOYDEVICEADDED event now provides the joystick instance ID in the `which` member of the jdevice event structure.
+
+The functions SDL_HasJoysticks(), SDL_GetJoysticks(), SDL_GetJoystickInstanceName(), SDL_GetJoystickInstancePath(), SDL_GetJoystickInstancePlayerIndex(), SDL_GetJoystickInstanceGUID(), SDL_GetJoystickInstanceVendor(), SDL_GetJoystickInstanceProduct(), SDL_GetJoystickInstanceProductVersion(), and SDL_GetJoystickInstanceType() have been added to directly query the list of available joysticks.
+
+SDL_AttachVirtualJoystick() and SDL_AttachVirtualJoystickEx() now return the joystick instance ID instead of a device index, and return 0 if there was an error.
+
 The following functions have been renamed:
 * SDL_JoystickAttachVirtual => SDL_AttachVirtualJoystick
 * SDL_JoystickAttachVirtualEx => SDL_AttachVirtualJoystickEx
@@ -273,13 +309,6 @@ The following functions have been renamed:
 * SDL_JoystickGetAxis => SDL_GetJoystickAxis
 * SDL_JoystickGetAxisInitialState => SDL_GetJoystickAxisInitialState
 * SDL_JoystickGetButton => SDL_GetJoystickButton
-* SDL_JoystickGetDeviceGUID => SDL_GetJoystickDeviceGUID
-* SDL_JoystickGetDeviceInstanceID => SDL_GetJoystickDeviceInstanceID
-* SDL_JoystickGetDevicePlayerIndex => SDL_GetJoystickDevicePlayerIndex
-* SDL_JoystickGetDeviceProduct => SDL_GetJoystickDeviceProduct
-* SDL_JoystickGetDeviceProductVersion => SDL_GetJoystickDeviceProductVersion
-* SDL_JoystickGetDeviceType => SDL_GetJoystickDeviceType
-* SDL_JoystickGetDeviceVendor => SDL_GetJoystickDeviceVendor
 * SDL_JoystickGetFirmwareVersion => SDL_GetJoystickFirmwareVersion
 * SDL_JoystickGetGUID => SDL_GetJoystickGUID
 * SDL_JoystickGetGUIDFromString => SDL_GetJoystickGUIDFromString
@@ -294,13 +323,11 @@ The following functions have been renamed:
 * SDL_JoystickInstanceID => SDL_GetJoystickInstanceID
 * SDL_JoystickIsVirtual => SDL_IsJoystickVirtual
 * SDL_JoystickName => SDL_GetJoystickName
-* SDL_JoystickNameForIndex => SDL_GetJoystickNameForIndex
 * SDL_JoystickNumAxes => SDL_GetNumJoystickAxes
 * SDL_JoystickNumButtons => SDL_GetNumJoystickButtons
 * SDL_JoystickNumHats => SDL_GetNumJoystickHats
 * SDL_JoystickOpen => SDL_OpenJoystick
 * SDL_JoystickPath => SDL_GetJoystickPath
-* SDL_JoystickPathForIndex => SDL_GetJoystickPathForIndex
 * SDL_JoystickRumble => SDL_RumbleJoystick
 * SDL_JoystickRumbleTriggers => SDL_RumbleJoystickTriggers
 * SDL_JoystickSendEffect => SDL_SendJoystickEffect
@@ -310,11 +337,22 @@ The following functions have been renamed:
 * SDL_JoystickSetVirtualButton => SDL_SetJoystickVirtualButton
 * SDL_JoystickSetVirtualHat => SDL_SetJoystickVirtualHat
 * SDL_JoystickUpdate => SDL_UpdateJoysticks
-* SDL_NumJoysticks => SDL_GetNumJoysticks
 
 The following symbols have been renamed:
 * SDL_JOYSTICK_TYPE_GAMECONTROLLER => SDL_JOYSTICK_TYPE_GAMEPAD
 
+The following functions have been removed:
+* SDL_NumJoysticks - replaced with SDL_HasJoysticks() and SDL_GetJoysticks()
+* SDL_JoystickGetDeviceGUID() - replaced with SDL_GetJoystickInstanceGUID()
+* SDL_JoystickGetDeviceInstanceID()
+* SDL_JoystickGetDevicePlayerIndex() - replaced with SDL_GetJoystickInstancePlayerIndex()
+* SDL_JoystickGetDeviceProduct() - replaced with SDL_GetJoystickInstanceProduct()
+* SDL_JoystickGetDeviceProductVersion() - replaced with SDL_GetJoystickInstanceProductVersion()
+* SDL_JoystickGetDeviceType() - replaced with SDL_GetJoystickInstanceType()
+* SDL_JoystickGetDeviceVendor() - replaced with SDL_GetJoystickInstanceVendor()
+* SDL_JoystickNameForIndex() - replaced with SDL_GetJoystickInstanceName()
+* SDL_JoystickPathForIndex() - replaced with SDL_GetJoystickInstancePath()
+ 
 ## SDL_keycode.h
 
 The following symbols have been renamed:
@@ -609,18 +647,36 @@ SDL_RWFromFP(void *fp, SDL_bool autoclose)
 
 ## SDL_sensor.h
 
+SDL_SensorID has changed from Sint32 to Uint32, with an invalid ID being 0.
+
+Rather than iterating over sensors using device index, there is a new function SDL_GetSensors() to get the current list of sensors, and new functions to get information about sensors from their instance ID:
+```c
+{
+    if (SDL_InitSubSystem(SDL_INIT_SENSOR) == 0) {
+        int i, num_sensors;
+        SDL_SensorID *sensors = SDL_GetSensors(&num_sensors);
+        if (sensors) {
+            for (i = 0; i < num_sensors; ++i) {
+                SDL_Log("Sensor %" SDL_PRIu32 ": %s, type %d, platform type %d\n",
+                        sensors[i],
+                        SDL_GetSensorInstanceName(sensors[i]),
+                        SDL_GetSensorInstanceType(sensors[i]),
+                        SDL_GetSensorInstanceNonPortableType(sensors[i]));
+            }
+            SDL_free(sensors);
+        }
+        SDL_QuitSubSystem(SDL_INIT_SENSOR);
+    }
+}
+```
+
 Removed SDL_SensorGetDataWithTimestamp(), if you want timestamps for the sensor data, you should use the sensor_timestamp member of SDL_SENSORUPDATE events.
 
 
 The following functions have been renamed:
-* SDL_NumSensors => SDL_GetNumSensors
 * SDL_SensorClose => SDL_CloseSensor
 * SDL_SensorFromInstanceID => SDL_GetSensorFromInstanceID
 * SDL_SensorGetData => SDL_GetSensorData
-* SDL_SensorGetDeviceInstanceID => SDL_GetSensorDeviceInstanceID
-* SDL_SensorGetDeviceName => SDL_GetSensorDeviceName
-* SDL_SensorGetDeviceNonPortableType => SDL_GetSensorDeviceNonPortableType
-* SDL_SensorGetDeviceType => SDL_GetSensorDeviceType
 * SDL_SensorGetInstanceID => SDL_GetSensorInstanceID
 * SDL_SensorGetName => SDL_GetSensorName
 * SDL_SensorGetNonPortableType => SDL_GetSensorNonPortableType
@@ -628,6 +684,15 @@ The following functions have been renamed:
 * SDL_SensorOpen => SDL_OpenSensor
 * SDL_SensorUpdate => SDL_UpdateSensors
 
+The following functions have been removed:
+* SDL_LockSensors()
+* SDL_NumSensors - replaced with SDL_HasSensors() and SDL_GetSensors()
+* SDL_SensorGetDeviceInstanceID()
+* SDL_SensorGetDeviceName() - replaced with SDL_GetSensorInstanceName()
+* SDL_SensorGetDeviceNonPortableType() - replaced with SDL_GetSensorInstanceNonPortableType()
+* SDL_SensorGetDeviceType() - replaced with SDL_GetSensorInstanceType()
+* SDL_UnlockSensors()
+ 
 ## SDL_stdinc.h
 
 The standard C headers like stdio.h and stdlib.h are no longer included, you should include them directly in your project if you use non-SDL C runtime functions.
diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index 94f1bd782451..44923e1ec618 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -397,7 +397,7 @@ typedef struct SDL_JoyDeviceEvent
 {
     Uint32 type;        /**< ::SDL_JOYDEVICEADDED or ::SDL_JOYDEVICEREMOVED */
     Uint64 timestamp;   /**< In nanoseconds, populated using SDL_GetTicksNS() */
-    SDL_JoystickID which;       /**< The joystick device index for the ADDED event, instance id for the REMOVED event */
+    SDL_JoystickID which;       /**< The joystick instance id */
 } SDL_JoyDeviceEvent;
 
 /**
@@ -450,7 +450,7 @@ typedef struct SDL_GamepadDeviceEvent
 {
     Uint32 type;        /**< ::SDL_GAMEPADADDED, ::SDL_GAMEPADREMOVED, or ::SDL_GAMEPADDEVICEREMAPPED */
     Uint64 timestamp;   /**< In nanoseconds, populated using SDL_GetTicksNS() */
-    SDL_JoystickID which;       /**< The joystick device index for the ADDED event, instance id for the REMOVED or REMAPPED event */
+    SDL_JoystickID which;       /**< The joystick instance id */
 } SDL_GamepadDeviceEvent;
 
 /**
diff --git a/include/SDL3/SDL_gamepad.h b/include/SDL3/SDL_gamepad.h
index a22adb4bc8f3..ba84ada1b732 100644
--- a/include/SDL3/SDL_gamepad.h
+++ b/include/SDL3/SDL_gamepad.h
@@ -181,7 +181,7 @@ typedef struct SDL_GamepadBinding
  * \sa SDL_GetGamepadMapping
  * \sa SDL_GetGamepadMappingForGUID
  */
-extern DECLSPEC int SDLCALL SDL_AddGamepadMapping(const char* mappingString);
+extern DECLSPEC int SDLCALL SDL_AddGamepadMapping(const char *mappingString);
 
 /**
  * Load a set of Game Controller mappings from a seekable SDL data stream.
@@ -211,7 +211,7 @@ extern DECLSPEC int SDLCALL SDL_AddGamepadMapping(const char* mappingString);
  * \sa SDL_AddGamepadMappingsFromFile
  * \sa SDL_GetGamepadMappingForGUID
  */
-extern DECLSPEC int SDLCALL SDL_AddGamepadMappingsFromRW(SDL_RWops * rw, int freerw);
+extern DECLSPEC int SDLCALL SDL_AddGamepadMappingsFromRW(SDL_RWops *rw, int freerw);
 
 /**
  *  Load a set of mappings from a file, filtered by the current SDL_GetPlatform()
@@ -250,7 +250,7 @@ extern DECLSPEC char * SDLCALL SDL_GetGamepadMappingForIndex(int mapping_index);
  *
  * \since This function is available since SDL 3.0.0.
  *
- * \sa SDL_GetJoystickDeviceGUID
+ * \sa SDL_GetJoystickInstanceGUID
  * \sa SDL_GetJoystickGUID
  */
 extern DECLSPEC char * SDLCALL SDL_GetGamepadMappingForGUID(SDL_JoystickGUID guid);
@@ -275,13 +275,32 @@ extern DECLSPEC char * SDLCALL SDL_GetGamepadMappingForGUID(SDL_JoystickGUID gui
 extern DECLSPEC char * SDLCALL SDL_GetGamepadMapping(SDL_Gamepad *gamepad);
 
 /**
- * Check if the given joystick is supported by the gamepad interface.
+ * Return whether there are gamepads connected
+ *
+ * \returns SDL_TRUE if there are gamepads connected, SDL_FALSE otherwise.
  *
- * `joystick_index` is the same as the `device_index` passed to
- * SDL_OpenJoystick().
+ * \since This function is available since SDL 3.0.0.
  *
- * \param joystick_index the device_index of a device, up to
- *                       SDL_GetNumJoysticks()
+ * \sa SDL_GetGamepads
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasGamepads(void);
+
+/**
+ * Get a list of currently connected gamepads.
+ *
+ * \param count a pointer filled in with the number of gamepads returned
+ * \returns a 0 terminated array of joystick instance IDs which should be freed with SDL_free(), or NULL on error; call SDL_GetError() for more details.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_OpenGamepad
+ */
+extern DECLSPEC SDL_JoystickID *SDLCALL SDL_GetGamepads(int *count);
+
+/**
+ * Check if the given joystick is supported by the gamepad interface.
+ *
+ * \param instance_id the joystick instance ID
  * \returns SDL_TRUE if the given joystick is supported by the gamepad
  *          interface, SDL_FALSE if it isn't or it's an invalid index.
  *
@@ -290,88 +309,142 @@ extern DECLSPEC char * SDLCALL SDL_GetGamepadMapping(SDL_Gamepad *gamepad);
  * \sa SDL_GetGamepadNameForIndex
  * \sa SDL_OpenGamepad
  */
-extern DECLSPEC SDL_bool SDLCALL SDL_IsGamepad(int joystick_index);
+extern DECLSPEC SDL_bool SDLCALL SDL_IsGamepad(SDL_JoystickID instance_id);
 
 /**
- * Get the implementation dependent name for the gamepad.
- *
- * This function can be called before any gamepads are opened.
+ * Get the implementation dependent name of a gamepad.
  *
- * `joystick_index` is the same as the `device_index` passed to
- * SDL_OpenJoystick().
+ * This can be called before any gamepads are opened.
  *
- * \param joystick_index the device_index of a device, from zero to
- *                       SDL_GetNumJoysticks()-1
- * \returns the implementation-dependent name for the gamepad, or NULL
- *          if there is no name or the index is invalid.
+ * \param instance_id the joystick instance ID
+ * \returns the name of the selected gamepad. If no name can be found, this
+ *          function returns NULL; call SDL_GetError() for more information.
  *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_GetGamepadName
  * \sa SDL_OpenGamepad
- * \sa SDL_IsGamepad
  */
-extern DECLSPEC const char *SDLCALL SDL_GetGamepadNameForIndex(int joystick_index);
+extern DECLSPEC const char *SDLCALL SDL_GetGamepadInstanceName(SDL_JoystickID instance_id);
 
 /**
- * Get the implementation dependent path for the gamepad.
+ * Get the implementation dependent path of a gamepad.
  *
- * This function can be called before any gamepads are opened.
- *
- * `joystick_index` is the same as the `device_index` passed to
- * SDL_OpenJoystick().
+ * This can be called before any gamepads are opened.
  *
- * \param joystick_index the device_index of a device, from zero to
- *                       SDL_GetNumJoysticks()-1
- * \returns the implementation-dependent path for the gamepad, or NULL
- *          if there is no path or the index is invalid.
+ * \param instance_id the joystick instance ID
+ * \returns the path of the selected gamepad. If no path can be found, this
+ *          function returns NULL; call SDL_GetError() for more information.
  *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_GetGamepadPath
+ * \sa SDL_OpenGamepad
+ */
+extern DECLSPEC const char *SDLCALL SDL_GetGamepadInstancePath(SDL_JoystickID instance_id);
+
+/**
+ * Get the player index of a gamepad.
+ *
+ * This can be called before any gamepads are opened.
+ *
+ * \param instance_id the joystick instance ID
+ * \returns the player index of a gamepad, or -1 if it's not available
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetGamepadPlayerIndex
+ * \sa SDL_OpenGamepad
  */
-extern DECLSPEC const char *SDLCALL SDL_GetGamepadPathForIndex(int joystick_index);
+extern DECLSPEC int SDLCALL SDL_GetGamepadInstancePlayerIndex(SDL_JoystickID instance_id);
+
+/**
+ * Get the implementation-dependent GUID of a gamepad.
+ *
+ * This can be called before any gamepads are opened.
+ *
+ * \param instance_id the joystick instance ID
+ * \returns the GUID of the selected gamepad. If called on an invalid index,
+ *          this function returns a zero GUID
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetGamepadGUID
+ * \sa SDL_GetGamepadGUIDString
+ */
+extern DECLSPEC SDL_JoystickGUID SDLCALL SDL_GetGamepadInstanceGUID(SDL_JoystickID instance_id);
+
+/**
+ * Get the USB vendor ID of a gamepad, if available.
+ *
+ * This can be called before any gamepads are opened. If the vendor ID isn't
+ * available this function returns 0.
+ *
+ * \param instance_id the joystick instance ID
+ * \returns the USB vendor ID of the selected gamepad. If called on an
+ *          invalid index, this function returns zero
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC Uint16 SDLCALL SDL_GetGamepadInstanceVendor(SDL_JoystickID instance_id);
+
+/**
+ * Get the USB product ID of a gamepad, if available.
+ *
+ * This can be called before any gamepads are opened. If the product ID isn't
+ * available this function returns 0.
+ *
+ * \param instance_id the joystick instance ID
+ * \returns the USB product ID of the selected gamepad. If called on an
+ *          invalid index, this function returns zero
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC Uint16 SDLCALL SDL_GetGamepadInstanceProduct(SDL_JoystickID instance_id);
+
+/**
+ * Get the product version of a gamepad, if available.
+ *
+ * This can be called before any gamepads are opened. If the product version
+ * isn't available this function returns 0.
+ *
+ * \param instance_id the joystick instance ID
+ * \returns the product version of the selected gamepad. If called on an
+ *          invalid index, this function returns zero
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC Uint16 SDLCALL SDL_GetGamepadInstanceProductVersion(SDL_JoystickID instance_id);
 
 /**
  * Get the type of a gamepad.
  *
  * This can be called before any gamepads are opened.
  *
- * \param joystick_index the device_index of a device, from zero to
- *                       SDL_GetNumJoysticks()-1
+ * \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_GetGamepadTypeForIndex(int joystick_index);
+extern DECLSPEC SDL_GamepadType SDLCALL SDL_GetGamepadInstanceType(SDL_JoystickID instance_id);
 
 /**
  * Get the mapping of a gamepad.
  *
  * This can be called before any gamepads are opened.
  *
- * \param joystick_index the device_index of a device, from zero to
- *                       SDL_GetNumJoysticks()-1
+ * \param instance_id the joystick instance ID
  * \returns the mapping string. Must be freed with SDL_free(). Returns NULL if
  *          no mapping is available.
  *
  * \since This function is available since SDL 3.0.0.
  */
-extern DECLSPEC char *SDLCALL SDL_GetGamepadMappingForDeviceIndex(int joystick_index);
+extern DECLSPEC char *SDLCALL SDL_GetGamepadInstanceMapping(SDL_JoystickID instance_id);
 
 /**
  * Open a gamepad for use.
  *
- * `joystick_index` is the same as the `device_index` passed to
- * SDL_OpenJoystick().
- *
- * The index passed as an argument refers to the N'th gamepad on the
- * system. This index is not the value which will identify this gamepad in
- * future gamepad events. The joystick's instance id (SDL_JoystickID) will
- * be used there instead.
- *
- * \param joystick_index the device_index of a device, up to
- *                       SDL_GetNumJoysticks()
+ * \param instance_id the joystick instance ID
  * \returns a gamepad identifier or NULL if an error occurred; call
  *          SDL_GetError() for more information.
  *
@@ -381,27 +454,23 @@ extern DECLSPEC char *SDLCALL SDL_GetGamepadMappingForDeviceIndex(int joystick_i
  * \sa SDL_GetGamepadNameForIndex
  * \sa SDL_IsGamepad
  */
-extern DECLSPEC SDL_Gamepad *SDLCALL SDL_OpenGamepad(int joystick_index);
+extern DECLSPEC SDL_Gamepad *SDLCALL SDL_OpenGamepad(SDL_JoystickID instance_id);
 
 /**
- * Get the SDL_Gamepad associated with an instance id.
+ * Get the SDL_Gamepad associated with a joystick instance ID.
  *
- * \param joyid the instance id to get the SDL_Gamepad for
+ * \param instance_id the joystick instance ID of the gamepad
  * \returns an SDL_Gamepad on success or NULL on failure; call
  *          SDL_GetError() for more information.
  *
  * \since This function is available since SDL 3.0.0.
  */
-extern DECLSPEC SDL_Gamepad *SDLCALL SDL_GetGamepadFromInstanceID(SDL_JoystickID joyid);
+extern DECLSPEC SDL_Gamepad *SDLCALL SDL_GetGamepadFromInstanceID(SDL_JoystickID instance_id);
 
 /**
  * Get the SDL_Gamepad associated with a player index.
  *
- * Please note that the player index is _not_ the device index, nor is it the
- * instance id!
- *
- * \param player_index the player index, which is not the device index or the
- *                     instance id!
+ * \param player_index the player index, which different from the instance ID
  * \returns the SDL_Gamepad associated with a player index.
  *
  * \since This function is available since SDL 3.0.0.
@@ -442,14 +511,14 @@ extern DECLSPEC const char *SDLCALL SDL_GetGamepadName(SDL_Gamepad *gamepad);
  *
  * \since This function is available since SDL 3.0.0.
  *
- * \sa SDL_GetGamepadPathForIndex
+ * \sa SDL_GetGamepadInstancePath
  */
 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_GetGamepadTypeForIndex(), but
+ * This is the same name as returned by SDL_GetGamepadInstanceType(), but
  * it takes a gamepad identifier instead of the (unstable) device index.
  *
  * \param gamepad the gamepad object to query.
@@ -559,7 +628,7 @@ extern DECLSPEC const char * SDLCALL SDL_GetGamepadSerial(SDL_Gamepad *gamepad);
 extern DECLSPEC SDL_bool SDLCALL SDL_IsGamepadConnected(SDL_Gamepad *gamepad);
 
 /**
- * Get the Joystick ID from a Game Controller.
+ * Get the underlying joystick from a gamepad
  *
  * This function will give you a SDL_Joystick object, which allows you to use
  * the SDL_Joystick functions with a SDL_Gamepad object. This would be
@@ -573,7 +642,7 @@ extern DECLSPEC SDL_bool SDLCALL SDL_IsGamepadConnected(SDL_Gamepad *gamepad);
  *
  * \param gamepad the gamepad object that you want to get a
  *                       joystick from
- * \returns a SDL_Joystick object; call SDL_GetError() for more information.
+ * \returns an SDL_Joystick object; call SDL_GetError() for more information.
  *
  * \since This function is available since SDL 3.0.0.
  */
diff --git a/include/SDL3/SDL_joystick.h b/include/SDL3/SDL_joystick.h
index f212e8c379d6..75d3ec418d65 100644
--- a/include/SDL3/SDL_joystick.h
+++ b/include/SDL3/SDL_joystick.h
@@ -24,9 +24,6 @@
  *
  *  Include file for SDL joystick event handling
  *
- * The term "device_index" identifies currently plugged in joystick devices between 0 and SDL_GetNumJoysticks(), with the exact joystick
- *   behind a device_index changing as joysticks are plugged and unplugged.
- *
  * The term "instance_id" is the current instantiation of a joystick device in the system, if the joystick is removed and then re-inserted
  *   then it will get a new instance_id, instance_id's are monotonically increasing identifiers of a joystick plugged in.
  *
@@ -81,9 +78,9 @@ typedef SDL_GUID SDL_JoystickGUID;
  * and is never reused for the lifetime of the application. If the joystick is
  * disconnected and reconnected, it will get a new ID.
  *
- * The ID value starts at 0 and increments from there. The value -1 is an invalid ID.
+ * The ID value starts at 1 and increments from there. The value 0 is an invalid ID.
  */
-typedef Sint32 SDL_JoystickID;
+typedef Uint32 SDL_JoystickID;
 
 typedef enum
 {
@@ -119,60 +116,52 @@ typedef enum
 /* Function prototypes */
 
 /**
- * Locking for multi-threaded access to the joystick API
- *
- * If you are using the joystick API or handling events from multiple threads
- * you should use these locking functions to protect access to the joysticks.
+ * Locking for atomic access to the joystick API
  *
- * In particular, you are guaranteed that the joystick list won't change, so
- * the API functions that take a joystick index will be valid, and joystick
- * and game controller events will not be delivered.
- *
- * As of SDL 2.26.0, you can take the joystick lock around reinitializing the
- * joystick subsystem, to prevent other threads from seeing joysticks in an
- * uninitialized state. However, all open joysticks will be closed and SDL
- * functions called with them will fail.
+ * The SDL joystick functions are thread-safe, however you can lock the joysticks
+ * while processing to guarantee that the joystick list won't change and joystick
+ * and gamepad events will not be delivered.
  *
  * \since This function is available since SDL 3.0.0.
  */
 extern DECLSPEC void SDLCALL SDL_LockJoysticks(void) SDL_ACQUIRE(SDL_joystick_lock);
 
-
 /**
- * Unlocking for multi-threaded access to the joystick API
+ * Unlocking for atomic access to the joystick API
  *
- * If you are using the joystick API or handling events from multiple threads
- * you should use these locking functions to protect access to the joysticks.
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC void SDLCALL SDL_UnlockJoysticks(void) SDL_RELEASE(SDL_joystick_lock);
+
+/**
+ * Return whether there are joysticks connected
  *
- * In particular, you are guaranteed that the joystick list won't change, so
- * the API functions that take a joystick index will be valid, and joystick
- * and game controller events will not be delivered.
+ * \returns SDL_TRUE if there are joysticks connected, SDL_FALSE otherwise.
  *
  * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetJoysticks
  */
-extern DECLSPEC void SDLCALL SDL_UnlockJoysticks(void) SDL_RELEASE(SDL_joystick_lock);
+extern DECLSPEC SDL_bool SDLCALL SDL_HasJoysticks(void);
 
 /**
- * Count the number of joysticks attached to the system.
+ * Get a list of currently connected joysticks.
  *
- * \returns the 

(Patch may be truncated, please check the link at the top of this post.)