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"