SDL: joystick: Expose sensor data at the SDL_Joystick level.

From 4f3d7017b0c380f06cce5febfd5f79f2be6a0ce8 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 27 Aug 2026 10:18:03 -0400
Subject: [PATCH] joystick: Expose sensor data at the SDL_Joystick level.

Previously this was only available through the gamepad API, but this state has
always actually lived on the lower-level joystick objects, so there's no sense
in not offering a public API for that level, as well.

Closes #16202.
---
 include/SDL3/SDL_joystick.h       |  87 +++++++++++++++
 src/dynapi/SDL_dynapi.exports     |   5 +
 src/dynapi/SDL_dynapi.sym         |   5 +
 src/dynapi/SDL_dynapi_overrides.h |   5 +
 src/dynapi/SDL_dynapi_procs.h     |   5 +
 src/joystick/SDL_gamepad.c        | 166 +----------------------------
 src/joystick/SDL_joystick.c       | 171 ++++++++++++++++++++++++++++++
 7 files changed, 283 insertions(+), 161 deletions(-)

diff --git a/include/SDL3/SDL_joystick.h b/include/SDL3/SDL_joystick.h
index e3bf5988c27ff..be9a8817346da 100644
--- a/include/SDL3/SDL_joystick.h
+++ b/include/SDL3/SDL_joystick.h
@@ -1250,6 +1250,93 @@ extern SDL_DECLSPEC Uint8 SDLCALL SDL_GetJoystickHat(SDL_Joystick *joystick, int
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_GetJoystickButton(SDL_Joystick *joystick, int button);
 
+/**
+ * Return whether a joystick has a particular sensor.
+ *
+ * Sensors are disabled by default and SDL_SetJoystickSensorEnabled() is used
+ * to enable them.
+ *
+ * \param joystick the joystick to query.
+ * \param type the type of sensor to query.
+ * \returns true if the sensor exists, false otherwise.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.2.0.
+ *
+ * \sa SDL_GetJoystickSensorData
+ * \sa SDL_GetJoystickSensorDataRate
+ * \sa SDL_SetJoystickSensorEnabled
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_JoystickHasSensor(SDL_Joystick *joystick, SDL_SensorType type);
+
+/**
+ * Set whether data reporting for a joystick sensor is enabled.
+ *
+ * Sensors are disabled by default and this function is used to enable them.
+ *
+ * \param joystick the joystick to update.
+ * \param type the type of sensor to enable/disable.
+ * \param enabled whether data reporting should be enabled.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ *          information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.2.0.
+ *
+ * \sa SDL_JoystickHasSensor
+ * \sa SDL_JoystickSensorEnabled
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SetJoystickSensorEnabled(SDL_Joystick *joystick, SDL_SensorType type, bool enabled);
+
+/**
+ * Query whether sensor data reporting is enabled for a joystick.
+ *
+ * \param joystick the joystick to query.
+ * \param type the type of sensor to query.
+ * \returns true if the sensor is enabled, false otherwise.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.2.0.
+ *
+ * \sa SDL_SetJoystickSensorEnabled
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_JoystickSensorEnabled(SDL_Joystick *joystick, SDL_SensorType type);
+
+/**
+ * Get the data rate (number of events per second) of a joystick sensor.
+ *
+ * \param joystick the joystick to query.
+ * \param type the type of sensor to query.
+ * \returns the data rate, or 0.0f if the data rate is not available.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.2.0.
+ */
+extern SDL_DECLSPEC float SDLCALL SDL_GetJoystickSensorDataRate(SDL_Joystick *joystick, SDL_SensorType type);
+
+/**
+ * Get the current state of a joystick sensor.
+ *
+ * The number of values and interpretation of the data is sensor dependent.
+ * See the remarks in SDL_SensorType for details for each type of sensor.
+ *
+ * \param joystick the joystick to query.
+ * \param type the type of sensor to query.
+ * \param data a pointer filled with the current sensor state.
+ * \param num_values the number of values to write to data.
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ *          information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.2.0.
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_GetJoystickSensorData(SDL_Joystick *joystick, SDL_SensorType type, float *data, int num_values);
+
 /**
  * Start a rumble effect.
  *
diff --git a/src/dynapi/SDL_dynapi.exports b/src/dynapi/SDL_dynapi.exports
index f0c685c4be7d5..a06f0f21d6434 100644
--- a/src/dynapi/SDL_dynapi.exports
+++ b/src/dynapi/SDL_dynapi.exports
@@ -1302,3 +1302,8 @@ _SDL_GetDeviceFormFactor
 _SDL_GetDeviceFormFactorName
 _SDL_IsUbuntuTouch
 _SDL_GetNumProperties
+_SDL_JoystickHasSensor
+_SDL_SetJoystickSensorEnabled
+_SDL_JoystickSensorEnabled
+_SDL_GetJoystickSensorDataRate
+_SDL_GetJoystickSensorData
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index f30ea16eb5c00..6e0354148122d 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1303,6 +1303,11 @@ SDL3_0.0.0 {
     SDL_GetDeviceFormFactorName;
     SDL_IsUbuntuTouch;
     SDL_GetNumProperties;
+    SDL_JoystickHasSensor;
+    SDL_SetJoystickSensorEnabled;
+    SDL_JoystickSensorEnabled;
+    SDL_GetJoystickSensorDataRate;
+    SDL_GetJoystickSensorData;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index c7ce948a96c86..1f8ba18a01e59 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1329,3 +1329,8 @@
 #define SDL_GetDeviceFormFactorName SDL_GetDeviceFormFactorName_REAL
 #define SDL_IsUbuntuTouch SDL_IsUbuntuTouch_REAL
 #define SDL_GetNumProperties SDL_GetNumProperties_REAL
+#define SDL_JoystickHasSensor SDL_JoystickHasSensor_REAL
+#define SDL_SetJoystickSensorEnabled SDL_SetJoystickSensorEnabled_REAL
+#define SDL_JoystickSensorEnabled SDL_JoystickSensorEnabled_REAL
+#define SDL_GetJoystickSensorDataRate SDL_GetJoystickSensorDataRate_REAL
+#define SDL_GetJoystickSensorData SDL_GetJoystickSensorData_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 299f77f2e89cd..6bf988441a21b 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1337,3 +1337,8 @@ SDL_DYNAPI_PROC(SDL_FormFactor,SDL_GetDeviceFormFactor,(void),(),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetDeviceFormFactorName,(SDL_FormFactor a),(a),return)
 SDL_DYNAPI_PROC(bool,SDL_IsUbuntuTouch,(void),(),return)
 SDL_DYNAPI_PROC(int,SDL_GetNumProperties,(SDL_PropertiesID a),(a),return)
+SDL_DYNAPI_PROC(bool,SDL_JoystickHasSensor,(SDL_Joystick *a,SDL_SensorType b),(a,b),return)
+SDL_DYNAPI_PROC(bool,SDL_SetJoystickSensorEnabled,(SDL_Joystick *a,SDL_SensorType b,bool c),(a,b,c),return)
+SDL_DYNAPI_PROC(bool,SDL_JoystickSensorEnabled,(SDL_Joystick *a,SDL_SensorType b),(a,b),return)
+SDL_DYNAPI_PROC(float,SDL_GetJoystickSensorDataRate,(SDL_Joystick *a,SDL_SensorType b),(a,b),return)
+SDL_DYNAPI_PROC(bool,SDL_GetJoystickSensorData,(SDL_Joystick *a,SDL_SensorType b,float *c,int d),(a,b,c,d),return)
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index afcf6c5d1800e..5065e277266dd 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -3780,185 +3780,29 @@ bool SDL_GetGamepadTouchpadFinger(SDL_Gamepad *gamepad, int touchpad, int finger
     return result;
 }
 
-/**
- *  Return whether a gamepad has a particular sensor.
- */
 bool SDL_GamepadHasSensor(SDL_Gamepad *gamepad, SDL_SensorType type)
 {
-    bool result = false;
-
-    SDL_LockJoysticks();
-    {
-        SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
-        if (joystick) {
-            int i;
-            for (i = 0; i < joystick->nsensors; ++i) {
-                if (joystick->sensors[i].type == type) {
-                    result = true;
-                    break;
-                }
-            }
-        }
-    }
-    SDL_UnlockJoysticks();
-
-    return result;
+    return SDL_JoystickHasSensor(SDL_GetGamepadJoystick(gamepad), type);
 }
 
-/*
- *  Set whether data reporting for a gamepad sensor is enabled
- */
 bool SDL_SetGamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type, bool enabled)
 {
-    SDL_LockJoysticks();
-    {
-        SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
-        if (joystick) {
-            int i;
-            for (i = 0; i < joystick->nsensors; ++i) {
-                SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];
-
-                if (sensor->type == type) {
-                    if (sensor->enabled == (enabled != false)) {
-                        SDL_UnlockJoysticks();
-                        return true;
-                    }
-
-                    if (type == SDL_SENSOR_ACCEL && joystick->accel_sensor) {
-                        if (enabled) {
-                            joystick->accel = SDL_OpenSensor(joystick->accel_sensor);
-                            if (!joystick->accel) {
-                                SDL_UnlockJoysticks();
-                                return false;
-                            }
-                        } else {
-                            if (joystick->accel) {
-                                SDL_CloseSensor(joystick->accel);
-                                joystick->accel = NULL;
-                            }
-                        }
-                    } else if (type == SDL_SENSOR_GYRO && joystick->gyro_sensor) {
-                        if (enabled) {
-                            joystick->gyro = SDL_OpenSensor(joystick->gyro_sensor);
-                            if (!joystick->gyro) {
-                                SDL_UnlockJoysticks();
-                                return false;
-                            }
-                        } else {
-                            if (joystick->gyro) {
-                                SDL_CloseSensor(joystick->gyro);
-                                joystick->gyro = NULL;
-                            }
-                        }
-                    } else {
-                        if (enabled) {
-                            if (joystick->nsensors_enabled == 0) {
-                                if (!joystick->driver->SetSensorsEnabled(joystick, true)) {
-                                    SDL_UnlockJoysticks();
-                                    return false;
-                                }
-                            }
-                            ++joystick->nsensors_enabled;
-                        } else {
-                            if (joystick->nsensors_enabled == 1) {
-                                if (!joystick->driver->SetSensorsEnabled(joystick, false)) {
-                                    SDL_UnlockJoysticks();
-                                    return false;
-                                }
-                            }
-                            --joystick->nsensors_enabled;
-                        }
-                    }
-
-                    sensor->enabled = enabled;
-                    SDL_UnlockJoysticks();
-                    return true;
-                }
-            }
-        }
-    }
-    SDL_UnlockJoysticks();
-
-    return SDL_Unsupported();
+    return SDL_SetJoystickSensorEnabled(SDL_GetGamepadJoystick(gamepad), type, enabled);
 }
 
-/*
- *  Query whether sensor data reporting is enabled for a gamepad
- */
 bool SDL_GamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type)
 {
-    bool result = false;
-
-    SDL_LockJoysticks();
-    {
-        SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
-        if (joystick) {
-            int i;
-            for (i = 0; i < joystick->nsensors; ++i) {
-                if (joystick->sensors[i].type == type) {
-                    result = joystick->sensors[i].enabled;
-                    break;
-                }
-            }
-        }
-    }
-    SDL_UnlockJoysticks();
-
-    return result;
+    return SDL_JoystickSensorEnabled(SDL_GetGamepadJoystick(gamepad), type);
 }
 
-/*
- *  Get the data rate of a gamepad sensor.
- */
 float SDL_GetGamepadSensorDataRate(SDL_Gamepad *gamepad, SDL_SensorType type)
 {
-    float result = 0.0f;
-
-    SDL_LockJoysticks();
-    {
-        SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
-        if (joystick) {
-            int i;
-            for (i = 0; i < joystick->nsensors; ++i) {
-                SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];
-
-                if (sensor->type == type) {
-                    result = sensor->rate;
-                    break;
-                }
-            }
-        }
-    }
-    SDL_UnlockJoysticks();
-
-    return result;
+    return SDL_GetJoystickSensorDataRate(SDL_GetGamepadJoystick(gamepad), type);
 }
 
-/*
- *  Get the current state of a gamepad sensor.
- */
 bool SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *data, int num_values)
 {
-    SDL_LockJoysticks();
-    {
-        SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
-        if (joystick) {
-            int i;
-            for (i = 0; i < joystick->nsensors; ++i) {
-                SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];
-
-                if (sensor->type == type) {
-                    num_values = SDL_min(num_values, SDL_arraysize(sensor->data));
-                    SDL_memcpy(data, sensor->data, num_values * sizeof(*data));
-                    SDL_UnlockJoysticks();
-                    return true;
-                }
-            }
-        }
-    }
-    SDL_UnlockJoysticks();
-
-    return SDL_Unsupported();
+    return SDL_GetJoystickSensorData(SDL_GetGamepadJoystick(gamepad), type, data, num_values);
 }
 
 bool SDL_GamepadHasCapSense(SDL_Gamepad *gamepad, SDL_GamepadCapSenseType type)
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index af4211dcd5f9c..4fbe70b0abc94 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -1889,6 +1889,177 @@ bool SDL_GetJoystickButton(SDL_Joystick *joystick, int button)
     return down;
 }
 
+static bool ErrorNoSuchSensor(void)
+{
+    return SDL_SetError("No such sensor on this device");
+}
+
+/**
+ *  Return whether a joystick has a particular sensor.
+ */
+bool SDL_JoystickHasSensor(SDL_Joystick *joystick, SDL_SensorType type)
+{
+    bool result = false;
+
+    SDL_LockJoysticks();
+    {
+        CHECK_JOYSTICK_MAGIC(joystick, false);
+        for (int i = 0; i < joystick->nsensors; ++i) {
+            if (joystick->sensors[i].type == type) {
+                result = true;
+                break;
+            }
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    return result;
+}
+
+/*
+ *  Set whether data reporting for a joystick sensor is enabled
+ */
+bool SDL_SetJoystickSensorEnabled(SDL_Joystick *joystick, SDL_SensorType type, bool enabled)
+{
+    SDL_LockJoysticks();
+    {
+        CHECK_JOYSTICK_MAGIC(joystick, false);
+        for (int i = 0; i < joystick->nsensors; ++i) {
+            SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];
+
+            if (sensor->type == type) {
+                if (sensor->enabled == (enabled != false)) {
+                    SDL_UnlockJoysticks();
+                    return true;
+                }
+
+                if (type == SDL_SENSOR_ACCEL && joystick->accel_sensor) {
+                    if (enabled) {
+                        joystick->accel = SDL_OpenSensor(joystick->accel_sensor);
+                        if (!joystick->accel) {
+                            SDL_UnlockJoysticks();
+                            return false;
+                        }
+                    } else {
+                        if (joystick->accel) {
+                            SDL_CloseSensor(joystick->accel);
+                            joystick->accel = NULL;
+                        }
+                    }
+                } else if (type == SDL_SENSOR_GYRO && joystick->gyro_sensor) {
+                    if (enabled) {
+                        joystick->gyro = SDL_OpenSensor(joystick->gyro_sensor);
+                        if (!joystick->gyro) {
+                            SDL_UnlockJoysticks();
+                            return false;
+                        }
+                    } else {
+                        if (joystick->gyro) {
+                            SDL_CloseSensor(joystick->gyro);
+                            joystick->gyro = NULL;
+                        }
+                    }
+                } else {
+                    if (enabled) {
+                        if (joystick->nsensors_enabled == 0) {
+                            if (!joystick->driver->SetSensorsEnabled(joystick, true)) {
+                                SDL_UnlockJoysticks();
+                                return false;
+                            }
+                        }
+                        ++joystick->nsensors_enabled;
+                    } else {
+                        if (joystick->nsensors_enabled == 1) {
+                            if (!joystick->driver->SetSensorsEnabled(joystick, false)) {
+                                SDL_UnlockJoysticks();
+                                return false;
+                            }
+                        }
+                        --joystick->nsensors_enabled;
+                    }
+                }
+
+                sensor->enabled = enabled;
+                SDL_UnlockJoysticks();
+                return true;
+            }
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    return ErrorNoSuchSensor();
+}
+
+/*
+ *  Query whether sensor data reporting is enabled for a joystick
+ */
+bool SDL_JoystickSensorEnabled(SDL_Joystick *joystick, SDL_SensorType type)
+{
+    bool result = false;
+
+    SDL_LockJoysticks();
+    {
+        CHECK_JOYSTICK_MAGIC(joystick, false);
+        for (int i = 0; i < joystick->nsensors; ++i) {
+            if (joystick->sensors[i].type == type) {
+                result = joystick->sensors[i].enabled;
+                break;
+            }
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    return result;
+}
+
+/*
+ *  Get the data rate of a joystick sensor.
+ */
+float SDL_GetJoystickSensorDataRate(SDL_Joystick *joystick, SDL_SensorType type)
+{
+    float result = 0.0f;
+
+    SDL_LockJoysticks();
+    {
+        CHECK_JOYSTICK_MAGIC(joystick, 0.0f);
+        for (int i = 0; i < joystick->nsensors; ++i) {
+            SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];
+
+            if (sensor->type == type) {
+                result = sensor->rate;
+                break;
+            }
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    return result;
+}
+
+/*
+ *  Get the current state of a joystick sensor.
+ */
+bool SDL_GetJoystickSensorData(SDL_Joystick *joystick, SDL_SensorType type, float *data, int num_values)
+{
+    SDL_LockJoysticks();
+    {
+        CHECK_JOYSTICK_MAGIC(joystick, false);
+        for (int i = 0; i < joystick->nsensors; ++i) {
+            SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];
+
+            if (sensor->type == type) {
+                num_values = SDL_min(num_values, SDL_arraysize(sensor->data));
+                SDL_memcpy(data, sensor->data, num_values * sizeof(*data));
+                SDL_UnlockJoysticks();
+                return true;
+            }
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    return ErrorNoSuchSensor();
+}
+
 /*
  * Return if the joystick in question is currently attached to the system,
  *  \return false if not plugged in, true if still present.