mirror of
https://github.com/godotengine/godot.git
synced 2026-05-12 22:35:35 +00:00
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.
This commit is contained in:
@@ -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<InputEvent> &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.
|
||||
|
||||
@@ -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<InputEvent> &p_event);
|
||||
void _dispatch_input_event(const Ref<InputEvent> &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();
|
||||
|
||||
@@ -199,6 +199,21 @@ Vector<uint8_t> WaylandThread::_wp_primary_selection_offer_read(struct wl_displa
|
||||
return Vector<uint8_t>();
|
||||
}
|
||||
|
||||
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<InputEventKey> WaylandThread::_seat_state_get_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed) {
|
||||
Ref<InputEventKey> 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;
|
||||
}
|
||||
|
||||
@@ -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<uint8_t> _wl_data_offer_read(struct wl_display *wl_display, const char *p_mime, struct wl_data_offer *wl_data_offer);
|
||||
static Vector<uint8_t> _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<InputEventKey> _seat_state_get_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed);
|
||||
static Ref<InputEventKey> _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();
|
||||
|
||||
Reference in New Issue
Block a user