From 9cdad39b52d5a81274631ccd5917a27202478e8d Mon Sep 17 00:00:00 2001
From: Nintorch <[EMAIL REDACTED]>
Date: Tue, 21 Apr 2026 15:45:33 +0500
Subject: [PATCH] Add trigger rumble support to Emscripten joysticks
This PR adds trigger rumble support to the Emscripten joystick backend.
(cherry picked from commit 651136ac7a461ac55ba07a637efff7cdecf411e2)
---
src/joystick/emscripten/SDL_sysjoystick.c | 72 ++++++++++++---------
src/joystick/emscripten/SDL_sysjoystick_c.h | 6 ++
2 files changed, 49 insertions(+), 29 deletions(-)
diff --git a/src/joystick/emscripten/SDL_sysjoystick.c b/src/joystick/emscripten/SDL_sysjoystick.c
index 5d831bddbba42..15d116f7256fa 100644
--- a/src/joystick/emscripten/SDL_sysjoystick.c
+++ b/src/joystick/emscripten/SDL_sysjoystick.c
@@ -448,7 +448,6 @@ static SDL_JoystickID EMSCRIPTEN_JoystickGetDeviceInstanceID(int device_index)
static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index)
{
SDL_joylist_item *item = JoystickByDeviceIndex(device_index);
- bool rumble_available = false;
if (!item) {
return SDL_SetError("No such device");
@@ -466,22 +465,24 @@ static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index)
joystick->nbuttons = item->nbuttons;
joystick->naxes = item->naxes;
- rumble_available = MAIN_THREAD_EM_ASM_INT({
- let gamepads = navigator['getGamepads']();
- if (!gamepads) {
- return 0;
- }
- let gamepad = gamepads[$0];
- if (!gamepad || !gamepad['vibrationActuator']) {
- return 0;
- }
- return 1;
+ item->rumble_available = MAIN_THREAD_EM_ASM_INT({
+ let gamepad = navigator['getGamepads']()[$0];
+ return gamepad && gamepad['vibrationActuator'] && gamepad['vibrationActuator']['effects']['includes']('dual-rumble');
}, item->index);
- if (rumble_available) {
+ if (item->rumble_available) {
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
}
+ item->trigger_rumble_available = MAIN_THREAD_EM_ASM_INT({
+ let gamepad = navigator['getGamepads']()[$0];
+ return gamepad && gamepad['vibrationActuator'] && gamepad['vibrationActuator']['effects']['includes']('trigger-rumble');
+ }, item->index);
+
+ if (item->trigger_rumble_available) {
+ SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
+ }
+
return true;
}
@@ -582,36 +583,49 @@ static SDL_GUID EMSCRIPTEN_JoystickGetDeviceGUID(int device_index)
return JoystickByDeviceIndex(device_index)->guid;
}
-static bool EMSCRIPTEN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+static bool Emscripten_UpdateRumble(SDL_joylist_item *item)
{
- SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
-
- // clang-format off
bool result = MAIN_THREAD_EM_ASM_INT({
- let gamepads = navigator['getGamepads']();
- if (!gamepads) {
- return 0;
- }
- let gamepad = gamepads[$0];
- if (!gamepad || !gamepad['vibrationActuator']) {
- return 0;
+ let gamepad = navigator['getGamepads']()[$0];
+ if (!gamepad) {
+ return false;
}
-
+ // We check if rumble is available in EMSCRIPTEN_JoystickRumble() and EMSCRIPTEN_JoystickRumbleTriggers().
+ // From my testing using "dual-rumble" here covers both main rumble and trigger rumble.
gamepad['vibrationActuator']['playEffect']('dual-rumble', {
'startDelay': 0,
'duration': 3000,
- 'weakMagnitude': $2 / 0xFFFF,
- 'strongMagnitude': $1 / 0xFFFF,
+ 'weakMagnitude': $1 / 0xFFFF,
+ 'strongMagnitude': $2 / 0xFFFF,
+ 'leftTrigger': $3 / 0xFFFF,
+ 'rightTrigger': $4 / 0xFFFF,
});
- return 1;
- }, item->index, low_frequency_rumble, high_frequency_rumble);
+ return true;
+ }, item->index, item->weak_magnitude_rumble, item->strong_magnitude_rumble, item->left_trigger_rumble, item->right_trigger_rumble);
return result;
}
+static bool EMSCRIPTEN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+{
+ SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
+ if (!item || !item->rumble_available) {
+ return SDL_Unsupported();
+ }
+ item->strong_magnitude_rumble = low_frequency_rumble;
+ item->weak_magnitude_rumble = high_frequency_rumble;
+ return Emscripten_UpdateRumble(item);
+}
+
static bool EMSCRIPTEN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
- return SDL_Unsupported();
+ SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
+ if (!item || !item->trigger_rumble_available) {
+ return SDL_Unsupported();
+ }
+ item->left_trigger_rumble = left_rumble;
+ item->right_trigger_rumble = right_rumble;
+ return Emscripten_UpdateRumble(item);
}
static bool EMSCRIPTEN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
diff --git a/src/joystick/emscripten/SDL_sysjoystick_c.h b/src/joystick/emscripten/SDL_sysjoystick_c.h
index df1fda85ae170..c23d544748e0f 100644
--- a/src/joystick/emscripten/SDL_sysjoystick_c.h
+++ b/src/joystick/emscripten/SDL_sysjoystick_c.h
@@ -46,6 +46,12 @@ typedef struct SDL_joylist_item
double analogButton[64];
EM_BOOL digitalButton[64];
Uint8 hat; // there is (currently) only ever one of these, faked from the d-pad buttons.
+ bool rumble_available;
+ bool trigger_rumble_available;
+ Uint16 weak_magnitude_rumble;
+ Uint16 strong_magnitude_rumble;
+ Uint16 left_trigger_rumble;
+ Uint16 right_trigger_rumble;
struct SDL_joylist_item *next;
} SDL_joylist_item;