From e2fce9597197d96144079c8fec4470b14916e9b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:13:07 +0200 Subject: [PATCH 1/5] Fix theme item inspector tooltips for Window subclasses. (cherry picked from commit 432214a29edbed233dd80e635806467c1795fbe3) --- editor/inspector/editor_inspector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/inspector/editor_inspector.cpp b/editor/inspector/editor_inspector.cpp index 434c850c607..18b952eeef4 100644 --- a/editor/inspector/editor_inspector.cpp +++ b/editor/inspector/editor_inspector.cpp @@ -4442,7 +4442,7 @@ void EditorInspector::update_tree() { bool found = false; // Small hack for theme_overrides. They are listed under Control, but come from another class. - if (classname == "Control" && p.name.begins_with("theme_override_")) { + if ((classname == "Control" || classname == "Window") && p.name.begins_with("theme_override_")) { classname = get_edited_object()->get_class(); } From 12d5ed791e93a88a6de32d1c6dbb9aa998c1fe25 Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Thu, 19 Mar 2026 08:54:44 +0100 Subject: [PATCH 2/5] Revert "[.NET] Remove EFS update on reloading assemblies" but with deferred call It turns out this call was needed after all, it ensures new scripts are included in the global class list by updating EFS for them. In my previous PR I must've only tested scripts that have been opened or loaded by the editor at some point so I didn't encounter the bug. This reverts the previous PR but with a deferred call to avoid reintroducing the bug that the PR fixed. Updating EFS here is still too early, so we defer the call to ensure the type info is available. (cherry picked from commit 37481d661358766ab407d2365a2f4ed3a948e0f4) --- .../GodotSharp/Core/Bridge/ScriptManagerBridge.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index 3ee4853340b..342a49e70a0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -356,6 +356,20 @@ namespace Godot.Bridge } } } + + // This method may be called before initialization. + if (NativeFuncs.godotsharp_dotnet_module_is_initialized().ToBool() && Engine.IsEditorHint()) + { + if (_pathTypeBiMap.Paths.Count > 0) + { + Callable.From(() => + { + string[] scriptPaths = _pathTypeBiMap.Paths.ToArray(); + using godot_packed_string_array scriptPathsNative = Marshaling.ConvertSystemArrayToNativePackedStringArray(scriptPaths); + NativeFuncs.godotsharp_internal_editor_file_system_update_files(scriptPathsNative); + }).CallDeferred(); + } + } } [UnmanagedCallersOnly] From 3da104e06a34f0ae876d651f9577605c3fb06ab9 Mon Sep 17 00:00:00 2001 From: rinevard Date: Thu, 19 Mar 2026 22:18:41 +0800 Subject: [PATCH 3/5] Fix timeline cursor following mouse during marker selection (cherry picked from commit ffb697f9671d578d92d21cb3733b649088a42f2c) --- editor/animation/animation_track_editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/animation/animation_track_editor.cpp b/editor/animation/animation_track_editor.cpp index 47503d06ad8..aca00dca5b5 100644 --- a/editor/animation/animation_track_editor.cpp +++ b/editor/animation/animation_track_editor.cpp @@ -9092,6 +9092,7 @@ void AnimationMarkerEdit::gui_input(const Ref &p_event) { float offset = (pos.x - timeline->get_name_limit()) / timeline->get_zoom_scale(); if (!read_only) { bool selected = _try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), false); + moving_selection_attempt = false; menu->clear(); menu->add_icon_item(get_editor_theme_icon(SNAME("Key")), TTR("Insert Marker..."), MENU_KEY_INSERT); From b107463c7e1d56b620458e19ec22e0a108479e7e Mon Sep 17 00:00:00 2001 From: Goldenlion5648 Date: Sun, 15 Mar 2026 21:53:28 -0400 Subject: [PATCH 4/5] stop autocomplete from eating words by default (cherry picked from commit e6d5e532e99c9e3765a102cd5a85330da39b78b3) --- core/input/input_map.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 3b1db681333..d1f2aad9cd9 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -543,16 +543,16 @@ const HashMap>> &InputMap::get_builtins() { inputs.push_back(InputEventKey::create_reference(Key::SPACE | KeyModifierMask::CTRL)); default_builtin_cache.insert("ui_text_completion_query", inputs); - inputs = List>(); - inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::TAB)); - inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::ENTER)); - inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::KP_ENTER)); - default_builtin_cache.insert("ui_text_completion_accept", inputs); - inputs = List>(); inputs.push_back(InputEventKey::create_reference(Key::TAB)); inputs.push_back(InputEventKey::create_reference(Key::ENTER)); inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER)); + default_builtin_cache.insert("ui_text_completion_accept", inputs); + + inputs = List>(); + inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::TAB)); + inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::ENTER)); + inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::KP_ENTER)); default_builtin_cache.insert("ui_text_completion_replace", inputs); // Newlines From c563a1f251083b4f29cdfa0297a0fe5dbba75e17 Mon Sep 17 00:00:00 2001 From: Dery Almas Date: Wed, 4 Mar 2026 02:23:32 +0100 Subject: [PATCH 5/5] Wayland: Improve mapping robustness and synchronization We assumed that a window will be ready after a single roundtrip but that's actually not guaranteed by the XDG shell protocol. This patch waits for an arbitrary timeout (10s) before erroring out and forcefully closing the window. Since this exercised quite some code paths, it also reworks some window handling logic and fixes a bunch of synchronization issues. (cherry picked from commit eb680bc8c3e4a811dc8736716fd9cf57567c21dd) --- .../wayland/display_server_wayland.cpp | 166 ++++++++----- .../linuxbsd/wayland/display_server_wayland.h | 4 + platform/linuxbsd/wayland/wayland_thread.cpp | 227 +++++++++++------- platform/linuxbsd/wayland/wayland_thread.h | 10 + 4 files changed, 251 insertions(+), 156 deletions(-) diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index fa57d5adfe8..2e3881eb89f 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -67,6 +67,7 @@ #endif #define WAYLAND_MAX_FRAME_TIME_US (1'000'000) +#define WINDOW_READY_TIMEOUT_MS (10'000) String DisplayServerWayland::_get_app_id_from_context(Context p_context) { String app_id; @@ -144,6 +145,66 @@ void DisplayServerWayland::_dispatch_input_event(const Ref &p_event) } } +void DisplayServerWayland::_delete_window(WindowID p_window_id) { + ERR_FAIL_COND(!windows.has(p_window_id)); + WindowData &wd = windows[p_window_id]; + + ERR_FAIL_COND(!windows.has(wd.root_id)); + WindowData &root_wd = windows[wd.root_id]; + + // NOTE: By the time the Wayland thread will send a `WINDOW_EVENT_MOUSE_EXIT` + // the window will be gone and the message will be discarded, confusing the + // engine. We thus have to send it ourselves. + if (wayland_thread.pointer_get_pointed_window_id() == p_window_id) { + _send_window_event(WINDOW_EVENT_MOUSE_EXIT, p_window_id); + } + + // The XDG shell specification requires us to clear all popups in reverse order. + while (!root_wd.popup_stack.is_empty() && root_wd.popup_stack.back()->get() != p_window_id) { + _send_window_event(WINDOW_EVENT_FORCE_CLOSE, root_wd.popup_stack.back()->get()); + } + + if (root_wd.popup_stack.back() && root_wd.popup_stack.back()->get() == p_window_id) { + root_wd.popup_stack.pop_back(); + } + + if (popup_menu_list.back() && popup_menu_list.back()->get() == p_window_id) { + popup_menu_list.pop_back(); + } + +#ifdef ACCESSKIT_ENABLED + if (accessibility_driver) { + accessibility_driver->window_destroy(p_window_id); + } +#endif + + if (wd.visible) { +#ifdef VULKAN_ENABLED + if (rendering_device) { + rendering_device->screen_free(p_window_id); + } + + if (rendering_context) { + rendering_context->window_destroy(p_window_id); + } +#endif + +#ifdef GLES3_ENABLED + if (egl_manager) { + egl_manager->window_destroy(p_window_id); + } +#endif + } + + if (wd.created) { + wayland_thread.window_destroy(p_window_id); + } + + windows.erase(p_window_id); + + DEBUG_LOG_WAYLAND(vformat("Destroyed window %d", p_window_id)); +} + void DisplayServerWayland::_update_window_rect(const Rect2i &p_rect, WindowID p_window_id) { ERR_FAIL_COND(!windows.has(p_window_id)); @@ -765,7 +826,7 @@ void DisplayServerWayland::show_window(WindowID p_window_id) { WindowData &wd = windows[p_window_id]; - if (!wd.visible) { + if (!wd.created) { DEBUG_LOG_WAYLAND(vformat("Showing window %d", p_window_id)); // Showing this window will reset its mode with whatever the compositor // reports. We'll save the mode beforehand so that we can reapply it later. @@ -795,7 +856,7 @@ void DisplayServerWayland::show_window(WindowID p_window_id) { wayland_thread.window_set_borderless(p_window_id, window_get_flag(WINDOW_FLAG_BORDERLESS, p_window_id)); // Since it can't have a position. Let's tell the window node the news by - // the actual rect to it. + // sending the actual rect to it. if (wd.rect_changed_callback.is_valid()) { wd.rect_changed_callback.call(wd.rect); } @@ -812,6 +873,16 @@ void DisplayServerWayland::show_window(WindowID p_window_id) { wayland_thread.window_create_popup(p_window_id, wd.parent_id, wd.rect); } + wd.created = true; + + bool ready = wayland_thread.window_wait_ready(p_window_id, WINDOW_READY_TIMEOUT_MS); + if (!ready) { + ERR_PRINT(vformat("Could not create window %d: timeout.", p_window_id)); + _send_window_event(WINDOW_EVENT_FORCE_CLOSE, p_window_id); + + return; + } + // NOTE: The XDG shell protocol is built in a way that causes the window to // be immediately shown as soon as a valid buffer is assigned to it. Hence, // the only acceptable way of implementing window showing is to move the @@ -873,61 +944,9 @@ void DisplayServerWayland::show_window(WindowID p_window_id) { void DisplayServerWayland::delete_sub_window(WindowID p_window_id) { MutexLock mutex_lock(wayland_thread.mutex); - ERR_FAIL_COND(!windows.has(p_window_id)); - WindowData &wd = windows[p_window_id]; + ERR_FAIL_COND_MSG(p_window_id == MAIN_WINDOW_ID, "Main window can't be deleted"); - ERR_FAIL_COND(!windows.has(wd.root_id)); - WindowData &root_wd = windows[wd.root_id]; - - // NOTE: By the time the Wayland thread will send a `WINDOW_EVENT_MOUSE_EXIT` - // the window will be gone and the message will be discarded, confusing the - // engine. We thus have to send it ourselves. - if (wayland_thread.pointer_get_pointed_window_id() == p_window_id) { - _send_window_event(WINDOW_EVENT_MOUSE_EXIT, p_window_id); - } - - // The XDG shell specification requires us to clear all popups in reverse order. - while (!root_wd.popup_stack.is_empty() && root_wd.popup_stack.back()->get() != p_window_id) { - _send_window_event(WINDOW_EVENT_FORCE_CLOSE, root_wd.popup_stack.back()->get()); - } - - if (root_wd.popup_stack.back() && root_wd.popup_stack.back()->get() == p_window_id) { - root_wd.popup_stack.pop_back(); - } - - if (popup_menu_list.back() && popup_menu_list.back()->get() == p_window_id) { - popup_menu_list.pop_back(); - } - -#ifdef ACCESSKIT_ENABLED - if (accessibility_driver) { - accessibility_driver->window_destroy(p_window_id); - } -#endif - - if (wd.visible) { -#ifdef VULKAN_ENABLED - if (rendering_device) { - rendering_device->screen_free(p_window_id); - } - - if (rendering_context) { - rendering_context->window_destroy(p_window_id); - } -#endif - -#ifdef GLES3_ENABLED - if (egl_manager) { - egl_manager->window_destroy(p_window_id); - } -#endif - - wayland_thread.window_destroy(p_window_id); - } - - windows.erase(p_window_id); - - DEBUG_LOG_WAYLAND(vformat("Destroyed window %d", p_window_id)); + _delete_window(p_window_id); } DisplayServer::WindowID DisplayServerWayland::window_get_active_popup() const { @@ -1029,7 +1048,7 @@ void DisplayServerWayland::window_set_title(const String &p_title, DisplayServer wd.title = p_title; - if (wd.visible) { + if (wd.created) { wayland_thread.window_set_title(p_window_id, wd.title); } } @@ -1125,7 +1144,7 @@ void DisplayServerWayland::window_set_max_size(const Size2i p_size, DisplayServe wd.max_size = p_size; - if (wd.visible) { + if (wd.created) { wayland_thread.window_set_max_size(p_window_id, p_size); } } @@ -1160,7 +1179,7 @@ void DisplayServerWayland::window_set_transient(WindowID p_window_id, WindowID p // NOTE: Looks like live unparenting is not really practical unfortunately. // See WaylandThread::window_set_parent for more info. - if (wd.visible) { + if (wd.created) { wayland_thread.window_set_parent(p_window_id, p_parent); } } @@ -1186,7 +1205,7 @@ void DisplayServerWayland::window_set_min_size(const Size2i p_size, DisplayServe wd.min_size = p_size; - if (wd.visible) { + if (wd.created) { wayland_thread.window_set_min_size(p_window_id, p_size); } } @@ -1209,7 +1228,9 @@ void DisplayServerWayland::window_set_size(const Size2i p_size, DisplayServer::W } Size2i new_size = p_size; - if (wd.visible) { + new_size = p_size.maxi(1); + + if (wd.created) { new_size = wayland_thread.window_set_size(p_window_id, p_size); } @@ -1248,7 +1269,7 @@ void DisplayServerWayland::window_set_mode(WindowMode p_mode, DisplayServer::Win ERR_FAIL_COND(!windows.has(p_window_id)); WindowData &wd = windows[p_window_id]; - if (!wd.visible) { + if (!wd.created) { return; } @@ -1261,7 +1282,7 @@ DisplayServer::WindowMode DisplayServerWayland::window_get_mode(DisplayServer::W ERR_FAIL_COND_V(!windows.has(p_window_id), WINDOW_MODE_WINDOWED); const WindowData &wd = windows[p_window_id]; - if (!wd.visible) { + if (!wd.created) { return WINDOW_MODE_WINDOWED; } @@ -1289,12 +1310,12 @@ void DisplayServerWayland::window_set_flag(WindowFlags p_flag, bool p_enabled, D case WINDOW_FLAG_POPUP: { ERR_FAIL_COND_MSG(p_window_id == MAIN_WINDOW_ID, "Main window can't be popup."); - ERR_FAIL_COND_MSG(wd.visible && (wd.flags & WINDOW_FLAG_POPUP_BIT) != p_enabled, "Popup flag can't changed while window is opened."); + ERR_FAIL_COND_MSG(wd.created && (wd.flags & WINDOW_FLAG_POPUP_BIT) != p_enabled, "Popup flag can't changed while window is opened."); } break; case WINDOW_FLAG_POPUP_WM_HINT: { ERR_FAIL_COND_MSG(p_window_id == MAIN_WINDOW_ID, "Main window can't have popup hint."); - ERR_FAIL_COND_MSG(wd.visible && (wd.flags & WINDOW_FLAG_POPUP_WM_HINT_BIT) != p_enabled, "Popup hint can't changed while window is opened."); + ERR_FAIL_COND_MSG(wd.created && (wd.flags & WINDOW_FLAG_POPUP_WM_HINT_BIT) != p_enabled, "Popup hint can't changed while window is opened."); } break; default: { @@ -2028,6 +2049,8 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win String session_desk = OS::get_singleton()->get_environment("XDG_SESSION_DESKTOP").to_lower(); swap_cancel_ok = (current_desk.contains("kde") || session_desk.contains("kde") || current_desk.contains("lxqt") || session_desk.contains("lxqt")); + MutexLock mutex_lock(wayland_thread.mutex); + Error thread_err = wayland_thread.init(); if (thread_err != OK) { @@ -2247,6 +2270,12 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win show_window(MAIN_WINDOW_ID); + if (!windows.has(MAIN_WINDOW_ID) || !windows[MAIN_WINDOW_ID].visible) { + ERR_PRINT("Could not map the main window."); + r_error = ERR_CANT_CREATE; + return; + } + #ifdef RD_ENABLED if (rendering_context) { rendering_device = memnew(RenderingDevice); @@ -2298,6 +2327,8 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win } DisplayServerWayland::~DisplayServerWayland() { + wayland_thread.mutex.lock(); + if (native_menu) { memdelete(native_menu); native_menu = nullptr; @@ -2321,10 +2352,13 @@ DisplayServerWayland::~DisplayServerWayland() { } for (WindowID &id : toplevels) { - delete_sub_window(id); + _delete_window(id); } windows.clear(); + // The thread needs the mutex to clean up. We're not going to touch Wayland + // stuff anymore from now on anyways. + wayland_thread.mutex.unlock(); wayland_thread.destroy(); // Destroy all drivers. diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h index e86ca338c72..dab330d3b83 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -92,6 +92,9 @@ class DisplayServerWayland : public DisplayServer { struct wl_egl_window *wl_egl_window = nullptr; #endif + // Whether a `WaylandThread` equivalent exists or not. + bool created = false; + // Flags whether we have allocated a buffer through the video drivers. bool visible = false; @@ -181,6 +184,7 @@ class DisplayServerWayland : public DisplayServer { static void dispatch_input_events(const Ref &p_event); void _dispatch_input_event(const Ref &p_event); + void _delete_window(WindowID p_window_id = MAIN_WINDOW_ID); void _update_window_rect(const Rect2i &p_rect, WindowID p_window_id = MAIN_WINDOW_ID); void try_suspend(); diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 0223ff0e1d0..0a06021ef23 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -197,6 +197,21 @@ Vector WaylandThread::_wp_primary_selection_offer_read(struct wl_displa return Vector(); } +void WaylandThread::_wl_display_check_error(struct wl_display *wl_display) { + int werror = wl_display_get_error(wl_display); + if (werror) { + if (werror == EPROTO) { + struct wl_interface *wl_interface = nullptr; + uint32_t id = 0; + + int error_code = wl_display_get_protocol_error(wl_display, (const struct wl_interface **)&wl_interface, &id); + CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id)); + } else { + CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror)); + } + } +} + Ref WaylandThread::_seat_state_get_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed) { Ref event; @@ -1342,6 +1357,8 @@ void WaylandThread::_xdg_surface_on_configure(void *data, struct xdg_surface *xd WindowState *ws = (WindowState *)data; ERR_FAIL_NULL(ws); + ws->ready = true; + DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure rect %s", ws->rect)); } @@ -1349,6 +1366,14 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel * WindowState *ws = (WindowState *)data; ERR_FAIL_NULL(ws); + if (width == 0) { + width = ws->rect.size.width; + } + + if (height == 0) { + height = ws->rect.size.height; + } + // Expect the window to be in a plain state. It will get properly set if the // compositor reports otherwise below. ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; @@ -1404,9 +1429,7 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel * } } - if (width != 0 && height != 0) { - window_state_update_size(ws, width, height); - } + window_state_update_size(ws, width, height); DEBUG_LOG_WAYLAND_THREAD(vformat("XDG toplevel on configure width %d height %d.", width, height)); } @@ -1457,10 +1480,16 @@ void WaylandThread::_xdg_popup_on_configure(void *data, struct xdg_popup *xdg_po WindowState *ws = (WindowState *)data; ERR_FAIL_NULL(ws); - if (width != 0 && height != 0) { - window_state_update_size(ws, width, height); + if (width == 0) { + width = ws->rect.size.width; } + if (height == 0) { + height = ws->rect.size.width; + } + + window_state_update_size(ws, width, height); + WindowState *parent = ws->wayland_thread->window_get_state(ws->parent_id); ERR_FAIL_NULL(parent); @@ -1552,6 +1581,8 @@ void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, st WindowState *ws = (WindowState *)user_data; ERR_FAIL_NULL(ws); + ws->ready = true; + int width = 0; int height = 0; @@ -3342,19 +3373,7 @@ void WaylandThread::_poll_events_thread(void *p_data) { } } - int werror = wl_display_get_error(data->wl_display); - - if (werror) { - if (werror == EPROTO) { - struct wl_interface *wl_interface = nullptr; - uint32_t id = 0; - - int error_code = wl_display_get_protocol_error(data->wl_display, (const struct wl_interface **)&wl_interface, &id); - CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id)); - } else { - CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror)); - } - } + _wl_display_check_error(data->wl_display); wl_display_flush(data->wl_display); @@ -3526,6 +3545,10 @@ double WaylandThread::window_state_get_scale_factor(const WindowState *p_ws) { void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int p_height) { ERR_FAIL_NULL(p_ws); + // Failsafe. + p_width = MAX(p_width, 1); + p_height = MAX(p_height, 1); + int preferred_buffer_scale = window_state_get_preferred_buffer_scale(p_ws); bool using_fractional = p_ws->preferred_fractional_scale > 0; @@ -3824,7 +3847,7 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, const Siz ws.registry = ®istry; ws.wayland_thread = this; - ws.rect.size = p_size; + ws.rect.size = p_size.maxi(1); ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor); wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface); @@ -3898,11 +3921,6 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, const Siz } wl_surface_commit(ws.wl_surface); - - // Wait for the surface to be configured before continuing. - wl_display_roundtrip(wl_display); - - window_state_update_size(&ws, ws.rect.size.width, ws.rect.size.height); } void WaylandThread::window_create_popup(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id, Rect2i p_rect) { @@ -3916,7 +3934,6 @@ void WaylandThread::window_create_popup(DisplayServer::WindowID p_window_id, Dis p_rect.position = scale_vector2i(p_rect.position, 1.0 / parent_scale); p_rect.size = scale_vector2i(p_rect.size, 1.0 / parent_scale); - // We manually scaled based on the parent. If we don't set the relevant fields, // the resizing routines will get confused and scale once more. ws.preferred_fractional_scale = parent.preferred_fractional_scale; @@ -3987,9 +4004,6 @@ void WaylandThread::window_create_popup(DisplayServer::WindowID p_window_id, Dis wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws); wl_surface_commit(ws.wl_surface); - - // Wait for the surface to be configured before continuing. - wl_display_roundtrip(wl_display); } void WaylandThread::window_destroy(DisplayServer::WindowID p_window_id) { @@ -4108,7 +4122,7 @@ Size2i WaylandThread::window_set_size(DisplayServer::WindowID p_window_id, const window_state_update_size(&ws, new_size.width, new_size.height); - return scale_vector2i(new_size, window_scale); + return scale_vector2i(new_size, window_scale).maxi(1); } void WaylandThread::beep() const { @@ -4374,8 +4388,8 @@ void WaylandThread::window_try_set_mode(DisplayServer::WindowID p_window_id, Dis #endif // LIBDECOR_ENABLED } break; } - - // Wait for a configure event and hope that something changed. + // Roundtrip and hope that something changed. + // TODO: Async? wl_display_roundtrip(wl_display); if (ws.mode != DisplayServer::WINDOW_MODE_WINDOWED) { @@ -4841,6 +4855,7 @@ Error WaylandThread::init() { wl_registry_add_listener(wl_registry, &wl_registry_listener, ®istry); // Wait for registry to get notified from the compositor. + // TODO: Async? wl_display_roundtrip(wl_display); ERR_FAIL_NULL_V_MSG(registry.wl_shm, ERR_UNAVAILABLE, "Can't obtain the Wayland shared memory global."); @@ -4877,6 +4892,7 @@ Error WaylandThread::init() { } // Wait for seat capabilities. + // TODO: Async? wl_display_roundtrip(wl_display); #ifdef LIBDECOR_ENABLED @@ -5299,6 +5315,41 @@ bool WaylandThread::get_reset_frame() { return old_frame; } +// Wraps around `wl_display_dispatch_pending`. Judging from the docs, this +// should work pretty much like `wl_display_dispatch_timeout`, which is only +// available on somewhat recent versions of libwayland. +// +// This implementation is NOT based on libwayland's code. +int WaylandThread::wait_events(int p_timeout_ms) { + struct pollfd poll_fd; + poll_fd.fd = wl_display_get_fd(wl_display); + poll_fd.events = POLLIN | POLLHUP; + + while (wl_display_prepare_read(wl_display) != 0) { + // Event queue got already something. + return wl_display_dispatch_pending(wl_display); + } + + wl_display_flush(wl_display); + + // Wait for the event file descriptor to have new data. + poll(&poll_fd, 1, p_timeout_ms); + + if (poll_fd.revents | POLLIN) { + // Load the queues with fresh new data. + wl_display_read_events(wl_display); + } else { + // Oh well... Stop signaling that we want to read. + wl_display_cancel_read(wl_display); + + // We've got no new events :( + return 0; + } + + // Let's try dispatching now... + return wl_display_dispatch_pending(wl_display); +} + // Dispatches events until a frame event is received, a window is reported as // suspended or the timeout expires. bool WaylandThread::wait_frame_suspend_ms(int p_timeout) { @@ -5307,9 +5358,21 @@ bool WaylandThread::wait_frame_suspend_ms(int p_timeout) { // `wl_display_prepare_read` and `wl_display_read`. This means, that it will // basically be guaranteed to stay stuck in a "prepare read" state, where it // will block any other attempt at reading the display fd, such as ours. The - // solution? Let's make sure the mutex is locked (it should) and unblock the - // main thread with a roundtrip! + // solution? Let's make sure the mutex is locked (it should)... MutexLock mutex_lock(mutex); + + if (is_suspended()) { + return false; + } + + if (frame) { + frame = false; + return true; + } + + ERR_FAIL_COND_V(Thread::get_caller_id() == events_thread.get_id(), false); + + // ...and unblock the main thread with a roundtrip! wl_display_roundtrip(wl_display); if (is_suspended()) { @@ -5325,65 +5388,12 @@ bool WaylandThread::wait_frame_suspend_ms(int p_timeout) { return true; } - struct pollfd poll_fd; - poll_fd.fd = wl_display_get_fd(wl_display); - poll_fd.events = POLLIN | POLLHUP; + for (int remaining_ms = p_timeout; remaining_ms > 0;) { + int begin_ms = OS::get_singleton()->get_ticks_msec(); - int begin_ms = OS::get_singleton()->get_ticks_msec(); - int remaining_ms = p_timeout; + wait_events(remaining_ms); - while (remaining_ms > 0) { - // Empty the event queue while it's full. - while (wl_display_prepare_read(wl_display) != 0) { - if (wl_display_dispatch_pending(wl_display) == -1) { - // Oh no. We'll check and handle any display error below. - break; - } - - if (is_suspended()) { - return false; - } - - if (frame) { - // We had a frame event in the queue :D - frame = false; - return true; - } - } - - int werror = wl_display_get_error(wl_display); - - if (werror) { - if (werror == EPROTO) { - struct wl_interface *wl_interface = nullptr; - uint32_t id = 0; - - int error_code = wl_display_get_protocol_error(wl_display, (const struct wl_interface **)&wl_interface, &id); - CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id)); - } else { - CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror)); - } - } - - wl_display_flush(wl_display); - - // Wait for the event file descriptor to have new data. - poll(&poll_fd, 1, remaining_ms); - - if (poll_fd.revents | POLLIN) { - // Load the queues with fresh new data. - wl_display_read_events(wl_display); - } else { - // Oh well... Stop signaling that we want to read. - wl_display_cancel_read(wl_display); - - // We've got no new events :( - // We won't even bother with checking the frame flag. - return false; - } - - // Let's try dispatching now... - wl_display_dispatch_pending(wl_display); + _wl_display_check_error(wl_display); if (is_suspended()) { return false; @@ -5425,6 +5435,43 @@ bool WaylandThread::is_suspended() const { return true; } +bool WaylandThread::window_wait_ready(DisplayServer::WindowID p_window_id, int p_timeout_ms) { + MutexLock mutex_lock(mutex); + + WindowState *ws = windows.getptr(p_window_id); + ERR_FAIL_NULL_V(ws, false); + + if (ws->ready) { + return true; + } + + // See _poll_events_thread. + ERR_FAIL_COND_V(Thread::get_caller_id() == events_thread.get_id(), ws->ready); + + // Unlock the thread. + wl_display_roundtrip(wl_display); + + if (ws->ready) { + return true; + } + + for (int remaining_ms = p_timeout_ms; remaining_ms > 0;) { + int begin_ms = OS::get_singleton()->get_ticks_msec(); + + wait_events(remaining_ms); + + _wl_display_check_error(wl_display); + + if (ws->ready) { + return true; + } + + remaining_ms -= OS::get_singleton()->get_ticks_msec() - begin_ms; + } + + return false; +} + struct godot_embedding_compositor *WaylandThread::get_embedding_compositor() { return registry.godot_embedding_compositor; } diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h index d589b5f5841..64eb673f467 100644 --- a/platform/linuxbsd/wayland/wayland_thread.h +++ b/platform/linuxbsd/wayland/wayland_thread.h @@ -248,6 +248,8 @@ public: Rect2i rect; DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED; + bool ready = false; // Is configured or otherwise ready to be mapped. + // Toplevel states. bool maximized = false; // MUST obey configure size. bool fullscreen = false; // Can be smaller than configure size. @@ -1049,6 +1051,9 @@ private: static Vector _wl_data_offer_read(struct wl_display *wl_display, const char *p_mime, struct wl_data_offer *wl_data_offer); static Vector _wp_primary_selection_offer_read(struct wl_display *wl_display, const char *p_mime, struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer); + // Crashes if there's an error in the display and crashes if so. + static void _wl_display_check_error(struct wl_display *wl_display); + static void _seat_state_set_current(WaylandThread::SeatState &p_ss); static Ref _seat_state_get_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed); static Ref _seat_state_get_unstuck_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed, Key p_key); @@ -1183,6 +1188,9 @@ public: void commit_surfaces(); + // Returns handled events. + int wait_events(int p_timeout = -1); + void set_frame(); bool get_reset_frame(); bool wait_frame_suspend_ms(int p_timeout); @@ -1192,6 +1200,8 @@ public: bool window_is_suspended(DisplayServer::WindowID p_window_id) const; bool is_suspended() const; + bool window_wait_ready(DisplayServer::WindowID p_window_id, int p_timeout_ms); + struct godot_embedding_compositor *get_embedding_compositor(); OS::ProcessID embedded_compositor_get_focused_pid();