SDL: Added Emscripten support for Sensor API

From 33f1008d01dcfefcb43657e0e9b28a640cbfb0b9 Mon Sep 17 00:00:00 2001
From: antonegas <[EMAIL REDACTED]>
Date: Sun, 26 Oct 2025 01:52:55 +0200
Subject: [PATCH] Added Emscripten support for Sensor API

---
 CMakeLists.txt                                |   9 +
 include/build_config/SDL_build_config.h.cmake |   1 +
 src/sensor/SDL_sensor.c                       |   3 +
 src/sensor/SDL_syssensor.h                    |   1 +
 src/sensor/emscripten/SDL_emscriptensensor.c  | 195 ++++++++++++++++++
 src/sensor/emscripten/SDL_emscriptensensor.h  |  21 ++
 6 files changed, 230 insertions(+)
 create mode 100644 src/sensor/emscripten/SDL_emscriptensensor.c
 create mode 100644 src/sensor/emscripten/SDL_emscriptensensor.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8c9b8e7dabb56..2ec62ddc5eb64 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1715,6 +1715,15 @@ elseif(EMSCRIPTEN)
     set(HAVE_CLOCK_GETTIME 1)
   endif()
 
+  if(SDL_SENSOR)
+    set(SDL_SENSOR_EMSCRIPTEN 1)
+    set(HAVE_SDL_SENSORS TRUE)
+    sdl_glob_sources(
+      "${SDL3_SOURCE_DIR}/src/sensor/emscripten/*.c"
+      "${SDL3_SOURCE_DIR}/src/sensor/emscripten/*.h"
+    )
+  endif()
+
   if(SDL_VIDEO)
     set(SDL_VIDEO_DRIVER_EMSCRIPTEN 1)
     sdl_glob_sources(
diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake
index 36d642e705bc1..35560da940507 100644
--- a/include/build_config/SDL_build_config.h.cmake
+++ b/include/build_config/SDL_build_config.h.cmake
@@ -344,6 +344,7 @@
 #cmakedefine SDL_SENSOR_DUMMY 1
 #cmakedefine SDL_SENSOR_VITA 1
 #cmakedefine SDL_SENSOR_N3DS 1
+#cmakedefine SDL_SENSOR_EMSCRIPTEN 1
 
 #cmakedefine SDL_SENSOR_PRIVATE 1
 
diff --git a/src/sensor/SDL_sensor.c b/src/sensor/SDL_sensor.c
index 917b039f5c9e2..f829c92a58d8f 100644
--- a/src/sensor/SDL_sensor.c
+++ b/src/sensor/SDL_sensor.c
@@ -43,6 +43,9 @@ static SDL_SensorDriver *SDL_sensor_drivers[] = {
 #ifdef SDL_SENSOR_N3DS
     &SDL_N3DS_SensorDriver,
 #endif
+#ifdef SDL_SENSOR_EMSCRIPTEN
+    &SDL_EMSCRIPTEN_SensorDriver,
+#endif
 #if defined(SDL_SENSOR_DUMMY) || defined(SDL_SENSOR_DISABLED)
     &SDL_DUMMY_SensorDriver
 #endif
diff --git a/src/sensor/SDL_syssensor.h b/src/sensor/SDL_syssensor.h
index 1ce63e5d7fa34..a1a360ae082e2 100644
--- a/src/sensor/SDL_syssensor.h
+++ b/src/sensor/SDL_syssensor.h
@@ -106,5 +106,6 @@ extern SDL_SensorDriver SDL_WINDOWS_SensorDriver;
 extern SDL_SensorDriver SDL_DUMMY_SensorDriver;
 extern SDL_SensorDriver SDL_VITA_SensorDriver;
 extern SDL_SensorDriver SDL_N3DS_SensorDriver;
+extern SDL_SensorDriver SDL_EMSCRIPTEN_SensorDriver;
 
 #endif // SDL_syssensor_h_
diff --git a/src/sensor/emscripten/SDL_emscriptensensor.c b/src/sensor/emscripten/SDL_emscriptensensor.c
new file mode 100644
index 0000000000000..b718fda717149
--- /dev/null
+++ b/src/sensor/emscripten/SDL_emscriptensensor.c
@@ -0,0 +1,195 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifdef SDL_SENSOR_EMSCRIPTEN
+
+#include "../SDL_syssensor.h"
+#include "SDL_emscriptensensor.h"
+#include <emscripten/html5.h>
+
+#define EMSCRIPTEN_SENSOR_COUNT 2
+
+typedef struct
+{
+    SDL_SensorType type;
+    SDL_SensorID instance_id;
+    float data[3];
+    bool new_data;
+} SDL_EmscriptenSensor;
+
+static SDL_EmscriptenSensor SDL_sensors[EMSCRIPTEN_SENSOR_COUNT];
+
+static void SDL_EMSCRIPTEN_AccelerometerCallback(const EmscriptenDeviceMotionEvent *event)
+{
+    double total_gravity;
+    double gravity[3];
+
+    // Convert from browser specific gravity constant to SDL_STANDARD_GRAVITY.
+    total_gravity = 0.0;
+    total_gravity += fabs(event->accelerationIncludingGravityX - event->accelerationX);
+    total_gravity += fabs(event->accelerationIncludingGravityY - event->accelerationY);
+    total_gravity += fabs(event->accelerationIncludingGravityZ - event->accelerationZ);
+
+    gravity[0] = (event->accelerationIncludingGravityX - event->accelerationX) / total_gravity;
+    gravity[1] = (event->accelerationIncludingGravityY - event->accelerationY) / total_gravity;
+    gravity[2] = (event->accelerationIncludingGravityZ - event->accelerationZ) / total_gravity;
+
+    SDL_sensors[0].data[0] = (float)(event->accelerationX + gravity[0] * SDL_STANDARD_GRAVITY);
+    SDL_sensors[0].data[1] = (float)(event->accelerationY + gravity[1] * SDL_STANDARD_GRAVITY);
+    SDL_sensors[0].data[2] = (float)(event->accelerationZ + gravity[2] * SDL_STANDARD_GRAVITY);
+    SDL_sensors[0].new_data = true;
+}
+
+static void SDL_EMSCRIPTEN_GyroscopeCallback(const EmscriptenDeviceMotionEvent *event)
+{
+    SDL_sensors[1].data[0] = (float)event->rotationRateAlpha * SDL_PI_F / 180.0f;
+    SDL_sensors[1].data[1] = (float)event->rotationRateBeta * SDL_PI_F / 180.0f;
+    SDL_sensors[1].data[2] = (float)event->rotationRateGamma * SDL_PI_F / 180.0f;
+    SDL_sensors[1].new_data = true;
+}
+
+static int SDL_EMSCRIPTEN_SensorCallback(int event_type, const EmscriptenDeviceMotionEvent *event, void *user_data)
+{
+    SDL_EMSCRIPTEN_AccelerometerCallback(event);
+    SDL_EMSCRIPTEN_GyroscopeCallback(event);
+
+    return true;
+}
+
+static bool SDL_EMSCRIPTEN_SensorInit(void)
+{
+    emscripten_set_devicemotion_callback((void *)0, false, &SDL_EMSCRIPTEN_SensorCallback);
+
+    SDL_sensors[0].type = SDL_SENSOR_ACCEL;
+    SDL_sensors[0].instance_id = SDL_GetNextObjectID();
+    SDL_sensors[0].new_data = false;
+    SDL_sensors[1].type = SDL_SENSOR_GYRO;
+    SDL_sensors[1].instance_id = SDL_GetNextObjectID();
+    SDL_sensors[1].new_data = false;
+
+    return true;
+}
+
+static int SDL_EMSCRIPTEN_SensorGetCount(void)
+{
+    return EMSCRIPTEN_SENSOR_COUNT;
+}
+
+static void SDL_EMSCRIPTEN_SensorDetect(void)
+{
+}
+
+static const char *SDL_EMSCRIPTEN_SensorGetDeviceName(int device_index)
+{
+    if (device_index < EMSCRIPTEN_SENSOR_COUNT) {
+        switch (SDL_sensors[device_index].type) {
+        case SDL_SENSOR_ACCEL:
+            return "Accelerometer";
+        case SDL_SENSOR_GYRO:
+            return "Gyroscope";
+        default:
+            return "Unknown";
+        }
+    }
+
+    return NULL;
+}
+
+static SDL_SensorType SDL_EMSCRIPTEN_SensorGetDeviceType(int device_index)
+{
+    if (device_index < EMSCRIPTEN_SENSOR_COUNT) {
+        return SDL_sensors[device_index].type;
+    }
+
+    return SDL_SENSOR_INVALID;
+}
+
+static int SDL_EMSCRIPTEN_SensorGetDeviceNonPortableType(int device_index)
+{
+    if (device_index < EMSCRIPTEN_SENSOR_COUNT) {
+        return SDL_sensors[device_index].type;
+    }
+
+    return -1;
+}
+
+static SDL_SensorID SDL_EMSCRIPTEN_SensorGetDeviceInstanceID(int device_index)
+{
+    if (device_index < EMSCRIPTEN_SENSOR_COUNT) {
+        return SDL_sensors[device_index].instance_id;
+    }
+
+    return -1;
+}
+
+static bool SDL_EMSCRIPTEN_SensorOpen(SDL_Sensor *sensor, int device_index)
+{
+    return true;
+}
+
+static void SDL_EMSCRIPTEN_SensorUpdate(SDL_Sensor *sensor)
+{
+    Uint64 timestamp;
+
+    switch (sensor->type) {
+    case SDL_SENSOR_ACCEL:
+        if (SDL_sensors[0].new_data) {
+            SDL_sensors[0].new_data = false;
+            timestamp = SDL_GetTicksNS();
+            SDL_SendSensorUpdate(timestamp, sensor, timestamp, SDL_sensors[0].data, SDL_arraysize(SDL_sensors[0].data));
+        }
+        break;
+    case SDL_SENSOR_GYRO:
+        if (SDL_sensors[1].new_data) {
+            SDL_sensors[1].new_data = false;
+            timestamp = SDL_GetTicksNS();
+            SDL_SendSensorUpdate(timestamp, sensor, timestamp, SDL_sensors[1].data, SDL_arraysize(SDL_sensors[1].data));
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+static void SDL_EMSCRIPTEN_SensorClose(SDL_Sensor *sensor)
+{
+}
+
+static void SDL_EMSCRIPTEN_SensorQuit(void)
+{
+}
+
+SDL_SensorDriver SDL_EMSCRIPTEN_SensorDriver = {
+    SDL_EMSCRIPTEN_SensorInit,
+    SDL_EMSCRIPTEN_SensorGetCount,
+    SDL_EMSCRIPTEN_SensorDetect,
+    SDL_EMSCRIPTEN_SensorGetDeviceName,
+    SDL_EMSCRIPTEN_SensorGetDeviceType,
+    SDL_EMSCRIPTEN_SensorGetDeviceNonPortableType,
+    SDL_EMSCRIPTEN_SensorGetDeviceInstanceID,
+    SDL_EMSCRIPTEN_SensorOpen,
+    SDL_EMSCRIPTEN_SensorUpdate,
+    SDL_EMSCRIPTEN_SensorClose,
+    SDL_EMSCRIPTEN_SensorQuit,
+};
+
+#endif // SDL_SENSOR_EMSCRIPTEN
diff --git a/src/sensor/emscripten/SDL_emscriptensensor.h b/src/sensor/emscripten/SDL_emscriptensensor.h
new file mode 100644
index 0000000000000..4b0c6f8fcc833
--- /dev/null
+++ b/src/sensor/emscripten/SDL_emscriptensensor.h
@@ -0,0 +1,21 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"