diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 49138717377..684281876a4 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -84,6 +84,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(DisplayServerEnums::Context p_context) { String app_id; @@ -161,6 +162,62 @@ void DisplayServerWayland::_dispatch_input_event(const Ref &p_event) } } +void DisplayServerWayland::_delete_window(DisplayServerEnums::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(DisplayServerEnums::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(DisplayServerEnums::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(); + } + + AccessibilityServer::get_singleton()->window_destroy(p_window_id); + + 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, DisplayServerEnums::WindowID p_window_id) { ERR_FAIL_COND(!windows.has(p_window_id)); @@ -765,7 +822,7 @@ void DisplayServerWayland::show_window(DisplayServerEnums::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 +852,7 @@ void DisplayServerWayland::show_window(DisplayServerEnums::WindowID p_window_id) wayland_thread.window_set_borderless(p_window_id, window_get_flag(DisplayServerEnums::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 +869,16 @@ void DisplayServerWayland::show_window(DisplayServerEnums::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(DisplayServerEnums::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,57 +940,9 @@ void DisplayServerWayland::show_window(DisplayServerEnums::WindowID p_window_id) void DisplayServerWayland::delete_sub_window(DisplayServerEnums::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 == DisplayServerEnums::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 `DisplayServerEnums::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(DisplayServerEnums::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(DisplayServerEnums::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(); - } - - AccessibilityServer::get_singleton()->window_destroy(p_window_id); - - 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); } DisplayServerEnums::WindowID DisplayServerWayland::window_get_active_popup() const { @@ -1025,7 +1044,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); } } @@ -1121,7 +1140,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); } } @@ -1156,7 +1175,7 @@ void DisplayServerWayland::window_set_transient(DisplayServerEnums::WindowID p_w // 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); } } @@ -1182,7 +1201,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); } } @@ -1205,7 +1224,9 @@ void DisplayServerWayland::window_set_size(const Size2i p_size, DisplayServerEnu } 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); } @@ -1244,7 +1265,7 @@ void DisplayServerWayland::window_set_mode(DisplayServerEnums::WindowMode p_mode ERR_FAIL_COND(!windows.has(p_window_id)); WindowData &wd = windows[p_window_id]; - if (!wd.visible) { + if (!wd.created) { return; } @@ -1257,7 +1278,7 @@ DisplayServerEnums::WindowMode DisplayServerWayland::window_get_mode(DisplayServ ERR_FAIL_COND_V(!windows.has(p_window_id), DisplayServerEnums::WINDOW_MODE_WINDOWED); const WindowData &wd = windows[p_window_id]; - if (!wd.visible) { + if (!wd.created) { return DisplayServerEnums::WINDOW_MODE_WINDOWED; } @@ -1285,12 +1306,12 @@ void DisplayServerWayland::window_set_flag(DisplayServerEnums::WindowFlags p_fla case DisplayServerEnums::WINDOW_FLAG_POPUP: { ERR_FAIL_COND_MSG(p_window_id == DisplayServerEnums::MAIN_WINDOW_ID, "Main window can't be popup."); - ERR_FAIL_COND_MSG(wd.visible && (wd.flags & DisplayServerEnums::WINDOW_FLAG_POPUP_BIT) != p_enabled, "Popup flag can't changed while window is opened."); + ERR_FAIL_COND_MSG(wd.created && (wd.flags & DisplayServerEnums::WINDOW_FLAG_POPUP_BIT) != p_enabled, "Popup flag can't changed while window is opened."); } break; case DisplayServerEnums::WINDOW_FLAG_POPUP_WM_HINT: { ERR_FAIL_COND_MSG(p_window_id == DisplayServerEnums::MAIN_WINDOW_ID, "Main window can't have popup hint."); - ERR_FAIL_COND_MSG(wd.visible && (wd.flags & DisplayServerEnums::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 & DisplayServerEnums::WINDOW_FLAG_POPUP_WM_HINT_BIT) != p_enabled, "Popup hint can't changed while window is opened."); } break; default: { @@ -2016,6 +2037,8 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Dis 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) { @@ -2221,6 +2244,12 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Dis show_window(DisplayServerEnums::MAIN_WINDOW_ID); + if (!windows.has(DisplayServerEnums::MAIN_WINDOW_ID) || !windows[DisplayServerEnums::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); @@ -2272,6 +2301,8 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Dis } DisplayServerWayland::~DisplayServerWayland() { + wayland_thread.mutex.lock(); + if (native_menu) { memdelete(native_menu); native_menu = nullptr; @@ -2293,10 +2324,13 @@ DisplayServerWayland::~DisplayServerWayland() { } for (DisplayServerEnums::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 d2f63d7c598..1eadf93783f 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -85,6 +85,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; @@ -174,6 +177,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(DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID); void _update_window_rect(const Rect2i &p_rect, DisplayServerEnums::WindowID p_window_id = DisplayServerEnums::MAIN_WINDOW_ID); void try_suspend(); diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index c8e98639f53..315a7e40e43 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -199,6 +199,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; @@ -1361,6 +1376,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)); } @@ -1368,6 +1385,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 = DisplayServerEnums::WINDOW_MODE_WINDOWED; @@ -1423,9 +1448,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)); } @@ -1476,10 +1499,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); @@ -1571,6 +1600,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; @@ -3361,19 +3392,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); @@ -3545,6 +3564,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; @@ -3858,7 +3881,7 @@ void WaylandThread::window_create(DisplayServerEnums::WindowID p_window_id, cons 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); @@ -3932,11 +3955,6 @@ void WaylandThread::window_create(DisplayServerEnums::WindowID p_window_id, cons } 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(DisplayServerEnums::WindowID p_window_id, DisplayServerEnums::WindowID p_parent_id, Rect2i p_rect) { @@ -3950,7 +3968,6 @@ void WaylandThread::window_create_popup(DisplayServerEnums::WindowID p_window_id 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; @@ -4021,9 +4038,6 @@ void WaylandThread::window_create_popup(DisplayServerEnums::WindowID p_window_id 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(DisplayServerEnums::WindowID p_window_id) { @@ -4142,7 +4156,7 @@ Size2i WaylandThread::window_set_size(DisplayServerEnums::WindowID p_window_id, 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 { @@ -4408,8 +4422,8 @@ void WaylandThread::window_try_set_mode(DisplayServerEnums::WindowID p_window_id #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 != DisplayServerEnums::WINDOW_MODE_WINDOWED) { @@ -4910,6 +4924,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."); @@ -4946,6 +4961,7 @@ Error WaylandThread::init() { } // Wait for seat capabilities. + // TODO: Async? wl_display_roundtrip(wl_display); #ifdef LIBDECOR_ENABLED @@ -5368,6 +5384,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) { @@ -5376,9 +5427,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()) { @@ -5394,65 +5457,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; @@ -5494,6 +5504,43 @@ bool WaylandThread::is_suspended() const { return true; } +bool WaylandThread::window_wait_ready(DisplayServerEnums::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 2df57dd502a..8a3a3579955 100644 --- a/platform/linuxbsd/wayland/wayland_thread.h +++ b/platform/linuxbsd/wayland/wayland_thread.h @@ -256,6 +256,8 @@ public: Rect2i rect; DisplayServerEnums::WindowMode mode = DisplayServerEnums::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. @@ -1057,6 +1059,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); @@ -1193,6 +1198,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); @@ -1202,6 +1210,8 @@ public: bool window_is_suspended(DisplayServerEnums::WindowID p_window_id) const; bool is_suspended() const; + bool window_wait_ready(DisplayServerEnums::WindowID p_window_id, int p_timeout_ms); + struct godot_embedding_compositor *get_embedding_compositor(); ProcessID embedded_compositor_get_focused_pid();