From 34fb2bfb5b1dbc3cfa205cad7d84ecbb8d7e1351 Mon Sep 17 00:00:00 2001
From: Nintorch <92302738+Nintorch@users.noreply.github.com>
Date: Fri, 26 Dec 2025 16:43:04 +0500
Subject: [PATCH] Add support for web joypad vibration
---
doc/classes/Input.xml | 2 +-
platform/web/display_server_web.cpp | 2 ++
platform/web/godot_js.h | 1 +
platform/web/js/libs/library_godot_input.js | 30 +++++++++++++++++++++
4 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml
index 805576f1701..74e54a89ded 100644
--- a/doc/classes/Input.xml
+++ b/doc/classes/Input.xml
@@ -433,7 +433,7 @@
Starts to vibrate the joypad. Joypads usually come with two rumble motors, a strong and a weak one. [param weak_magnitude] is the strength of the weak motor (between 0 and 1) and [param strong_magnitude] is the strength of the strong motor (between 0 and 1). [param duration] is the duration of the effect in seconds (a duration of 0 will try to play the vibration indefinitely). The vibration can be stopped early by calling [method stop_joy_vibration].
[b]Note:[/b] Not every hardware is compatible with long effect durations; it is recommended to restart an effect if it has to be played for more than a few seconds.
- [b]Note:[/b] For macOS, vibration is only supported in macOS 11 and later.
+ [b]Note:[/b] For Android and web browsers running on Android, joypad vibration is not supported due to OS restrictions. For macOS, vibration is only supported in macOS 11 and later. For the web browsers running on macOS, vibration only works in Safari.
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index eda004f908d..47e299beec6 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -991,6 +991,8 @@ void DisplayServerWeb::process_joypads() {
for (int a = 0; a < s_axes_num; a++) {
input->joy_axis(idx, (JoyAxis)a, s_axes[a]);
}
+ Vector2 vibration_strength = input->get_joy_vibration_strength(idx);
+ godot_js_input_gamepad_process_vibration(idx, input->get_joy_vibration_timestamp(idx), vibration_strength.x, vibration_strength.y, input->get_joy_vibration_duration(idx));
}
}
diff --git a/platform/web/godot_js.h b/platform/web/godot_js.h
index 29b291d566a..8d9ea49b6dc 100644
--- a/platform/web/godot_js.h
+++ b/platform/web/godot_js.h
@@ -76,6 +76,7 @@ extern void godot_js_input_gamepad_cb(void (*p_on_change)(int p_index, int p_con
extern int godot_js_input_gamepad_sample();
extern int godot_js_input_gamepad_sample_count();
extern int godot_js_input_gamepad_sample_get(int p_idx, float r_btns[16], int32_t *r_btns_num, float r_axes[10], int32_t *r_axes_num, int32_t *r_standard);
+extern void godot_js_input_gamepad_process_vibration(int p_index, double p_timestamp, float p_weak_magnitude, float p_strong_magnitude, float p_duration);
extern void godot_js_input_paste_cb(void (*p_callback)(const char *p_text));
extern void godot_js_input_drop_files_cb(void (*p_callback)(const char **p_filev, int p_filec));
diff --git a/platform/web/js/libs/library_godot_input.js b/platform/web/js/libs/library_godot_input.js
index 576d1a7e219..d4ab2fb9c0e 100644
--- a/platform/web/js/libs/library_godot_input.js
+++ b/platform/web/js/libs/library_godot_input.js
@@ -177,6 +177,7 @@ const GodotInputGamepads = {
$GodotInputGamepads__deps: ['$GodotRuntime', '$GodotEventListeners'],
$GodotInputGamepads: {
samples: [],
+ vibration_timestamp: [],
get_pads: function () {
try {
@@ -228,6 +229,29 @@ const GodotInputGamepads = {
GodotInputGamepads.samples = samples;
},
+ process_vibration: function (index, timestamp, weak_magnitude, strong_magnitude, duration) {
+ const pad = GodotInputGamepads.get_pads()[index];
+ if (!pad) {
+ return;
+ }
+ const actuator = pad['vibrationActuator'];
+ if (!actuator) {
+ return;
+ }
+ const prev_timestamp = GodotInputGamepads.vibration_timestamp[index];
+ if (prev_timestamp >= timestamp) {
+ return;
+ }
+ GodotInputGamepads.vibration_timestamp[index] = timestamp;
+
+ actuator['playEffect']('dual-rumble', {
+ 'startDelay': 0,
+ 'duration': duration * 1000,
+ 'weakMagnitude': weak_magnitude,
+ 'strongMagnitude': strong_magnitude,
+ });
+ },
+
init: function (onchange) {
GodotInputGamepads.samples = [];
function add(pad) {
@@ -679,6 +703,12 @@ const GodotInput = {
return 0;
},
+ godot_js_input_gamepad_process_vibration__proxy: 'sync',
+ godot_js_input_gamepad_process_vibration__sig: 'viffff',
+ godot_js_input_gamepad_process_vibration: function (p_index, p_timestamp, p_weak_magnitude, p_strong_magnitude, p_duration) {
+ GodotInputGamepads.process_vibration(p_index, p_timestamp, p_weak_magnitude, p_strong_magnitude, p_duration);
+ },
+
/*
* Drag/Drop API
*/