Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions doc/classes/Input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,14 @@
<param index="0" name="device" type="int" />
<description>
Returns a dictionary with extra platform-specific information about the device, e.g. the raw gamepad name from the OS or the Steam Input index.
On Windows, Linux, and macOS, the dictionary contains the following fields:
On Windows, Linux, macOS, and Web, the dictionary contains the following fields:
[code]raw_name[/code]: The name of the controller as it came from the OS, before getting renamed by the controller database.
[code]vendor_id[/code]: The USB vendor ID of the device.
[code]product_id[/code]: The USB product ID of the device.
[code]steam_input_index[/code]: The Steam Input gamepad index, if the device is not a Steam Input device this key won't be present.
On Windows, the dictionary can have an additional field:
[code]xinput_index[/code]: The index of the controller in the XInput system. This key won't be present for devices not handled by XInput.
[b]Note:[/b] The returned dictionary is always empty on Android, iOS, visionOS, and Web.
The dictionary can also include the following fields under selected platforms:
[code]steam_input_index[/code]: The Steam Input gamepad index (Windows, Linux, and macOS only). If the device is not a Steam Input device this key won't be present.
[code]xinput_index[/code]: The index of the controller in the XInput system (Windows only). This key won't be present for devices not handled by XInput.
[b]Note:[/b] The returned dictionary is always empty on Android, iOS, and visionOS.
</description>
</method>
<method name="get_joy_name">
Expand Down
4 changes: 2 additions & 2 deletions drivers/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ if env["metal"]:
SConscript("metal/SCsub")

# Input drivers
if env["sdl"] and env["platform"] in ["linuxbsd", "macos", "windows"]:
# TODO: Evaluate support for Android, iOS, and Web.
if env["sdl"] and env["platform"] in ["linuxbsd", "macos", "windows", "web"]:
# TODO: Evaluate support for Android, iOS, and visionOS.
SConscript("sdl/SCsub")

# Core dependencies
Expand Down
17 changes: 17 additions & 0 deletions drivers/sdl/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,23 @@ if env["builtin_sdl"]:
"timer/windows/SDL_systimer.c",
]

elif env["platform"] == "web":
env_sdl.Append(CPPDEFINES=["SDL_PLATFORM_EMSCRIPTEN"])
thirdparty_sources += [
"core/unix/SDL_appid.c",
"core/unix/SDL_poll.c",
"haptic/dummy/SDL_syshaptic.c",
"joystick/emscripten/SDL_sysjoystick.c",
"loadso/dummy/SDL_sysloadso.c",
"thread/generic/SDL_syscond.c",
"thread/generic/SDL_sysmutex.c",
"thread/generic/SDL_sysrwlock.c",
"thread/generic/SDL_syssem.c",
"thread/generic/SDL_systhread.c",
"thread/generic/SDL_systls.c",
"timer/unix/SDL_systimer.c",
]

thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]

env_thirdparty = env_sdl.Clone()
Expand Down
15 changes: 15 additions & 0 deletions drivers/sdl/SDL_build_config_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,21 @@
#define SDL_THREAD_PTHREAD 1
#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1

// Emscripten (web) defines
#elif defined(SDL_PLATFORM_EMSCRIPTEN)

#define SDL_PLATFORM_PRIVATE_NAME "Emscripten"
#define SDL_PLATFORM_UNIX 1
#define HAVE_STDIO_H 1
#define HAVE_LIBC 1

#define SDL_HAPTIC_DUMMY 1
#define SDL_JOYSTICK_EMSCRIPTEN 1

#define SDL_LOADSO_DUMMY 1
#define SDL_THREADS_DISABLED 1
#define SDL_TIMER_UNIX 1

// Other platforms are not supported (for now)
#else
#error "No SDL build config was found for this platform. Setup one before compiling the engine."
Expand Down
30 changes: 30 additions & 0 deletions drivers/sdl/joypad_sdl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
#include <SDL3/SDL_iostream.h>
#include <SDL3/SDL_joystick.h>

#ifdef WEB_ENABLED
#include <emscripten/html5.h>
#endif

JoypadSDL *JoypadSDL::singleton = nullptr;

// Macro to skip the SDL joystick event handling if the device is an SDL gamepad, because
Expand Down Expand Up @@ -172,13 +176,20 @@ void JoypadSDL::process_events() {

sdl_instance_id_to_joypad_id.insert(sdl_event.jdevice.which, joy_id);

if (should_ignore_joypad(sdl_event.jdevice.which)) {
close_joypad(joy_id);
continue;
}

Dictionary joypad_info;
// Skip Godot's mapping system if SDL already handles the joypad's mapping.
joypad_info["mapping_handled"] = SDL_IsGamepad(sdl_event.jdevice.which);
joypad_info["raw_name"] = String(SDL_GetJoystickName(joy));
joypad_info["vendor_id"] = itos(SDL_GetJoystickVendor(joy));
joypad_info["product_id"] = itos(SDL_GetJoystickProduct(joy));

#if defined(WINDOWS_ENABLED) || defined(LINUXBSD_ENABLED) || defined(MACOS_ENABLED)
// These properties only make sense on desktop platforms.
const uint64_t steam_handle = SDL_GetGamepadSteamHandle(gamepad);
if (steam_handle != 0) {
joypad_info["steam_input_index"] = itos(steam_handle);
Expand All @@ -189,6 +200,7 @@ void JoypadSDL::process_events() {
// For XInput controllers SDL_GetJoystickPlayerIndex returns the XInput user index.
joypad_info["xinput_index"] = itos(player_index);
}
#endif

Input::get_singleton()->joy_connection_changed(
joy_id,
Expand All @@ -197,7 +209,9 @@ void JoypadSDL::process_events() {
joypads[joy_id].guid,
joypad_info);

#ifndef WEB_ENABLED // Joypad features are not supported on the web.
Input::get_singleton()->set_joy_features(joy_id, &joypads[joy_id]);
#endif
}
// An event for an attached joypad
} else if (sdl_event.type >= SDL_EVENT_JOYSTICK_AXIS_MOTION && sdl_event.type < SDL_EVENT_FINGER_DOWN && sdl_instance_id_to_joypad_id.has(sdl_event.jdevice.which)) {
Expand Down Expand Up @@ -310,4 +324,20 @@ SDL_Gamepad *JoypadSDL::Joypad::get_sdl_gamepad() const {
return SDL_GetGamepadFromID(sdl_instance_idx);
}

bool JoypadSDL::should_ignore_joypad(SDL_JoystickID p_joy_id) {
SDL_Joystick *joy = SDL_GetJoystickFromID(p_joy_id);
String joy_name_lower = String(SDL_GetJoystickName(joy)).to_lower();

#ifdef WEB_ENABLED
// DualSense on Firefox works very badly (input lag, no dpad, wrong face buttons, no vibration),
// I'm not sure it's fixable in Godot, so we just ignore it.
bool is_firefox = EM_ASM_INT({ return navigator.userAgent.toLowerCase().includes('firefox') });
if (is_firefox && joy_name_lower.contains("dualsense")) {
return true;
}
#endif

return false;
}

#endif // SDL_ENABLED
1 change: 1 addition & 0 deletions drivers/sdl/joypad_sdl.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,5 @@ class JoypadSDL {
HashMap<SDL_JoystickID, int> sdl_instance_id_to_joypad_id;

void close_joypad(int p_pad_idx);
bool should_ignore_joypad(SDL_JoystickID p_joy_id);
};
7 changes: 7 additions & 0 deletions platform/web/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,13 @@ def configure(env: "SConsEnvironment"):
if env["wasm_simd"]:
env.Append(CCFLAGS=["-msimd128"])

if env["sdl"]:
if env["builtin_sdl"]:
env.Append(CPPDEFINES=["SDL_ENABLED"])
else:
print_warning("`builtin_sdl` was explicitly disabled. Disabling SDL input driver support.")
env["sdl"] = False

# Reduce code size by generating less support code (e.g. skip NodeJS support).
env.Append(LINKFLAGS=["-sENVIRONMENT=web,worker"])

Expand Down
67 changes: 0 additions & 67 deletions platform/web/display_server_web.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -837,36 +837,6 @@ void DisplayServerWeb::_window_blur_callback() {
Input::get_singleton()->release_pressed_events();
}

// Gamepad
void DisplayServerWeb::gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid) {
String id = p_id;
String guid = p_guid;

#ifdef PROXY_TO_PTHREAD_ENABLED
if (!Thread::is_main_thread()) {
callable_mp_static(DisplayServerWeb::_gamepad_callback).call_deferred(p_index, p_connected, id, guid);
return;
}
#endif

_gamepad_callback(p_index, p_connected, id, guid);
}

void DisplayServerWeb::_gamepad_callback(int p_index, int p_connected, const String &p_id, const String &p_guid) {
if (p_connected) {
DisplayServerWeb::get_singleton()->gamepad_count += 1;
} else {
DisplayServerWeb::get_singleton()->gamepad_count -= 1;
}

Input *input = Input::get_singleton();
if (p_connected) {
input->joy_connection_changed(p_index, true, p_id, p_guid);
} else {
input->joy_connection_changed(p_index, false, "");
}
}

// IME.
void DisplayServerWeb::ime_callback(int p_type, const char *p_text) {
String text = String::utf8(p_text);
Expand Down Expand Up @@ -964,36 +934,6 @@ String DisplayServerWeb::ime_get_text() const {
return ime_text;
}

void DisplayServerWeb::process_joypads() {
Input *input = Input::get_singleton();
int32_t pads = godot_js_input_gamepad_sample_count();
int32_t s_btns_num = 0;
int32_t s_axes_num = 0;
int32_t s_standard = 0;
float s_btns[16];
float s_axes[10];
for (int idx = 0; idx < pads; idx++) {
int err = godot_js_input_gamepad_sample_get(idx, s_btns, &s_btns_num, s_axes, &s_axes_num, &s_standard);
if (err) {
continue;
}
for (int b = 0; b < s_btns_num; b++) {
// Buttons 6 and 7 in the standard mapping need to be
// axis to be handled as JoyAxis::TRIGGER by Godot.
if (s_standard && (b == 6)) {
input->joy_axis(idx, JoyAxis::TRIGGER_LEFT, s_btns[b]);
} else if (s_standard && (b == 7)) {
input->joy_axis(idx, JoyAxis::TRIGGER_RIGHT, s_btns[b]);
} else {
input->joy_button(idx, (JoyButton)b, s_btns[b]);
}
}
for (int a = 0; a < s_axes_num; a++) {
input->joy_axis(idx, (JoyAxis)a, s_axes[a]);
}
}
}

Vector<String> DisplayServerWeb::get_rendering_drivers_func() {
Vector<String> drivers;
#ifdef GLES3_ENABLED
Expand Down Expand Up @@ -1162,7 +1102,6 @@ DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode
godot_js_input_key_cb(&DisplayServerWeb::key_callback, key_event.code, key_event.key);
godot_js_input_paste_cb(&DisplayServerWeb::update_clipboard_callback);
godot_js_input_drop_files_cb(&DisplayServerWeb::drop_files_js_callback);
godot_js_input_gamepad_cb(&DisplayServerWeb::gamepad_callback);
godot_js_set_ime_cb(&DisplayServerWeb::ime_callback, &DisplayServerWeb::key_callback, key_event.code, key_event.key);

// JS Display interface (js/libs/library_godot_display.js)
Expand Down Expand Up @@ -1462,12 +1401,6 @@ DisplayServer::VSyncMode DisplayServerWeb::window_get_vsync_mode(WindowID p_vsyn
void DisplayServerWeb::process_events() {
process_keys();
Input::get_singleton()->flush_buffered_events();

if (gamepad_count > 0) {
if (godot_js_input_gamepad_sample() == OK) {
process_joypads();
}
}
}

void DisplayServerWeb::process_keys() {
Expand Down
5 changes: 0 additions & 5 deletions platform/web/display_server_web.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,6 @@ class DisplayServerWeb : public DisplayServer {
bool swap_cancel_ok = false;
NativeMenu *native_menu = nullptr;

int gamepad_count = 0;

MouseMode mouse_mode_base = MOUSE_MODE_VISIBLE;
MouseMode mouse_mode_override = MOUSE_MODE_VISIBLE;
bool mouse_mode_override_enabled = false;
Expand All @@ -130,8 +128,6 @@ class DisplayServerWeb : public DisplayServer {
static void _key_callback(const String &p_key_event_code, const String &p_key_event_key, int p_pressed, int p_repeat, int p_modifiers);
WASM_EXPORT static void vk_input_text_callback(const char *p_text, int p_cursor);
static void _vk_input_text_callback(const String &p_text, int p_cursor);
WASM_EXPORT static void gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid);
static void _gamepad_callback(int p_index, int p_connected, const String &p_id, const String &p_guid);
WASM_EXPORT static void js_utterance_callback(int p_event, int64_t p_id, int p_pos);
static void _js_utterance_callback(int p_event, int64_t p_id, int p_pos);
WASM_EXPORT static void ime_callback(int p_type, const char *p_text);
Expand All @@ -149,7 +145,6 @@ class DisplayServerWeb : public DisplayServer {
WASM_EXPORT static void drop_files_js_callback(const char **p_filev, int p_filec);
static void _drop_files_js_callback(const Vector<String> &p_files);

void process_joypads();
void process_keys();

static Vector<String> get_rendering_drivers_func();
Expand Down
5 changes: 0 additions & 5 deletions platform/web/godot_js.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ extern void godot_js_set_ime_position(int p_x, int p_y);
extern void godot_js_set_ime_cb(void (*p_input)(int p_type, const char *p_text), void (*p_callback)(int p_type, int p_repeat, int p_modifiers), char r_code[32], char r_key[32]);
extern int godot_js_is_ime_focused();

// Input gamepad
extern void godot_js_input_gamepad_cb(void (*p_on_change)(int p_index, int p_connected, const char *p_id, const char *p_guid));
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_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));

Expand Down
Loading