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 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); 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(); } 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] 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();