diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 6089f85bc774..fd1ce9839ab7 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -1952,6 +1952,9 @@
Display server supports system emoji and symbol picker. [b]Windows, macOS[/b]
+
+ Display server automatically fits popups according to the screen boundaries. Window nodes should not attempt to do that themselves.
+
Makes the mouse cursor visible if it is hidden.
@@ -2176,7 +2179,10 @@
[b]Note:[/b] This flag is implemented on macOS and Windows.
[b]Note:[/b] Setting this flag will [b]NOT[/b] prevent other apps from capturing an image, it should not be used as a security measure.
-
+
+ Signals the window manager that this window is supposed to be a window-manager-defined "popup" (usually a floating, borderless, immovable child window).
+
+
Max value of the [enum WindowFlags].
@@ -2206,6 +2212,10 @@
Sent when the window title bar decoration is changed (e.g. [constant WINDOW_FLAG_EXTEND_TO_TITLE] is set or window entered/exited full screen mode).
[b]Note:[/b] This flag is implemented only on macOS.
+
+ Sent when the window has been forcibly closed by the Display Server. The window shall immediately hide and clean any internal rendering references.
+ [b]Note:[/b] This flag is implemented only on Linux (Wayland).
+
Top-left edge of a window.
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index 90eba0bdea85..52fa38f88b21 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -879,7 +879,7 @@
[b]Note:[/b] This flag is implemented on macOS and Windows.
[b]Note:[/b] Setting this flag will [b]NOT[/b] prevent other apps from capturing an image, it should not be used as a security measure.
-
+
Max value of the [enum Flags].
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index 6d7d2125c682..824e0fc4b120 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -50,6 +50,8 @@
#include "wayland/egl_manager_wayland_gles.h"
#endif
+#define WAYLAND_MAX_FRAME_TIME_US (1'000'000)
+
String DisplayServerWayland::_get_app_id_from_context(Context p_context) {
String app_id;
@@ -76,8 +78,10 @@ String DisplayServerWayland::_get_app_id_from_context(Context p_context) {
return app_id;
}
-void DisplayServerWayland::_send_window_event(WindowEvent p_event) {
- WindowData &wd = main_window;
+void DisplayServerWayland::_send_window_event(WindowEvent p_event, WindowID p_window_id) {
+ ERR_FAIL_COND(!windows.has(p_window_id));
+
+ WindowData &wd = windows[p_window_id];
if (wd.window_event_callback.is_valid()) {
Variant event = int(p_event);
@@ -86,24 +90,45 @@ void DisplayServerWayland::_send_window_event(WindowEvent p_event) {
}
void DisplayServerWayland::dispatch_input_events(const Ref &p_event) {
- ((DisplayServerWayland *)(get_singleton()))->_dispatch_input_event(p_event);
+ static_cast(get_singleton())->_dispatch_input_event(p_event);
}
void DisplayServerWayland::_dispatch_input_event(const Ref &p_event) {
- Callable callable = main_window.input_event_callback;
- if (callable.is_valid()) {
- callable.call(p_event);
+ Ref event_from_window = p_event;
+
+ if (event_from_window.is_valid()) {
+ WindowID window_id = event_from_window->get_window_id();
+
+ Ref key_event = p_event;
+ if (!popup_menu_list.is_empty() && key_event.is_valid()) {
+ // Redirect to the highest popup menu.
+ window_id = popup_menu_list.back()->get();
+ }
+
+ // Send to a single window.
+ if (windows.has(window_id)) {
+ Callable callable = windows[window_id].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call(p_event);
+ }
+ }
}
}
-void DisplayServerWayland::_resize_window(const Size2i &p_size) {
- WindowData &wd = main_window;
+void DisplayServerWayland::_update_window_rect(const Rect2i &p_rect, WindowID p_window_id) {
+ ERR_FAIL_COND(!windows.has(p_window_id));
+
+ WindowData &wd = windows[p_window_id];
- wd.rect.size = p_size;
+ if (wd.rect == p_rect) {
+ return;
+ }
+
+ wd.rect = p_rect;
#ifdef RD_ENABLED
if (wd.visible && rendering_context) {
- rendering_context->window_set_size(MAIN_WINDOW_ID, wd.rect.size.width, wd.rect.size.height);
+ rendering_context->window_set_size(p_window_id, wd.rect.size.width, wd.rect.size.height);
}
#endif
@@ -118,78 +143,6 @@ void DisplayServerWayland::_resize_window(const Size2i &p_size) {
}
}
-void DisplayServerWayland::_show_window() {
- MutexLock mutex_lock(wayland_thread.mutex);
-
- WindowData &wd = main_window;
-
- if (!wd.visible) {
- DEBUG_LOG_WAYLAND("Showing window.");
-
- // 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.
- // TODO: Fix/Port/Move/Whatever to `WaylandThread` APIs.
- WindowMode setup_mode = wd.mode;
-
- wayland_thread.window_create(MAIN_WINDOW_ID, wd.rect.size.width, wd.rect.size.height);
- wayland_thread.window_set_min_size(MAIN_WINDOW_ID, wd.min_size);
- wayland_thread.window_set_max_size(MAIN_WINDOW_ID, wd.max_size);
- wayland_thread.window_set_app_id(MAIN_WINDOW_ID, _get_app_id_from_context(context));
- wayland_thread.window_set_borderless(MAIN_WINDOW_ID, window_get_flag(WINDOW_FLAG_BORDERLESS));
-
- // 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
- // graphics context window creation logic here.
-#ifdef RD_ENABLED
- if (rendering_context) {
- union {
-#ifdef VULKAN_ENABLED
- RenderingContextDriverVulkanWayland::WindowPlatformData vulkan;
-#endif
- } wpd;
-#ifdef VULKAN_ENABLED
- if (rendering_driver == "vulkan") {
- wpd.vulkan.surface = wayland_thread.window_get_wl_surface(wd.id);
- wpd.vulkan.display = wayland_thread.get_wl_display();
- }
-#endif
- Error err = rendering_context->window_create(wd.id, &wpd);
- ERR_FAIL_COND_MSG(err != OK, vformat("Can't create a %s window", rendering_driver));
-
- rendering_context->window_set_size(wd.id, wd.rect.size.width, wd.rect.size.height);
- rendering_context->window_set_vsync_mode(wd.id, wd.vsync_mode);
-
- emulate_vsync = (rendering_context->window_get_vsync_mode(wd.id) == DisplayServer::VSYNC_ENABLED);
-
- if (emulate_vsync) {
- print_verbose("VSYNC: manually throttling frames using MAILBOX.");
- rendering_context->window_set_vsync_mode(wd.id, DisplayServer::VSYNC_MAILBOX);
- }
- }
-#endif
-
-#ifdef GLES3_ENABLED
- if (egl_manager) {
- struct wl_surface *wl_surface = wayland_thread.window_get_wl_surface(wd.id);
- wd.wl_egl_window = wl_egl_window_create(wl_surface, wd.rect.size.width, wd.rect.size.height);
-
- Error err = egl_manager->window_create(MAIN_WINDOW_ID, wayland_thread.get_wl_display(), wd.wl_egl_window, wd.rect.size.width, wd.rect.size.height);
- ERR_FAIL_COND_MSG(err == ERR_CANT_CREATE, "Can't show a GLES3 window.");
-
- window_set_vsync_mode(wd.vsync_mode, MAIN_WINDOW_ID);
- }
-#endif
- // NOTE: The public window-handling methods might depend on this flag being
- // set. Ensure to not make any of these calls before this assignment.
- wd.visible = true;
-
- // Actually try to apply the window's mode now that it's visible.
- window_set_mode(setup_mode);
-
- wayland_thread.window_set_title(MAIN_WINDOW_ID, wd.title);
- }
-}
// Interface methods.
bool DisplayServerWayland::has_feature(Feature p_feature) const {
@@ -210,7 +163,9 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const {
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_IME:
case FEATURE_WINDOW_DRAG:
- case FEATURE_CLIPBOARD_PRIMARY: {
+ case FEATURE_CLIPBOARD_PRIMARY:
+ case FEATURE_SUBWINDOWS:
+ case FEATURE_SELF_FITTING_WINDOWS: {
return true;
} break;
@@ -307,18 +262,30 @@ void DisplayServerWayland::set_system_theme_change_callback(const Callable &p_ca
}
Error DisplayServerWayland::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector &p_filters, const Callable &p_callback) {
- WindowID window_id = MAIN_WINDOW_ID;
- // TODO: Use window IDs for multiwindow support.
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowID window_id = wayland_thread.pointer_get_last_pointed_window_id();
+ if (!windows.has(window_id)) {
+ window_id = MAIN_WINDOW_ID;
+ }
+
+ WaylandThread::WindowState *ws = wayland_thread.window_get_state(window_id);
+ ERR_FAIL_NULL_V(ws, ERR_BUG);
- WaylandThread::WindowState *ws = wayland_thread.wl_surface_get_window_state(wayland_thread.window_get_wl_surface(window_id));
return portal_desktop->file_dialog_show(window_id, (ws ? ws->exported_handle : String()), p_title, p_current_directory, String(), p_filename, p_mode, p_filters, TypedArray(), p_callback, false);
}
Error DisplayServerWayland::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector &p_filters, const TypedArray &p_options, const Callable &p_callback) {
- WindowID window_id = MAIN_WINDOW_ID;
- // TODO: Use window IDs for multiwindow support.
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowID window_id = wayland_thread.pointer_get_last_pointed_window_id();
+ if (!windows.has(window_id)) {
+ window_id = MAIN_WINDOW_ID;
+ }
+
+ WaylandThread::WindowState *ws = wayland_thread.window_get_state(window_id);
+ ERR_FAIL_NULL_V(ws, ERR_BUG);
- WaylandThread::WindowState *ws = wayland_thread.wl_surface_get_window_state(wayland_thread.window_get_wl_surface(window_id));
return portal_desktop->file_dialog_show(window_id, (ws ? ws->exported_handle : String()), p_title, p_current_directory, p_root, p_filename, p_mode, p_filters, p_options, p_callback, true);
}
@@ -361,6 +328,12 @@ void DisplayServerWayland::_mouse_update_mode() {
wayland_thread.pointer_set_constraint(constraint);
+ if (wanted_mouse_mode == DisplayServer::MOUSE_MODE_CAPTURED) {
+ WindowData *pointed_win = windows.getptr(wayland_thread.pointer_get_pointed_window_id());
+ ERR_FAIL_NULL(pointed_win);
+ wayland_thread.pointer_set_hint(pointed_win->rect.size / 2);
+ }
+
mouse_mode = wanted_mouse_mode;
}
@@ -421,6 +394,12 @@ void DisplayServerWayland::warp_mouse(const Point2i &p_to) {
Point2i DisplayServerWayland::mouse_get_position() const {
MutexLock mutex_lock(wayland_thread.mutex);
+ WindowID pointed_id = wayland_thread.pointer_get_pointed_window_id();
+
+ if (pointed_id != INVALID_WINDOW_ID) {
+ return Input::get_singleton()->get_mouse_position() + windows[pointed_id].rect.position;
+ }
+
// We can't properly implement this method by design.
// This is the best we can do unfortunately.
return Input::get_singleton()->get_mouse_position();
@@ -618,6 +597,8 @@ float DisplayServerWayland::screen_get_refresh_rate(int p_screen) const {
void DisplayServerWayland::screen_set_keep_on(bool p_enable) {
MutexLock mutex_lock(wayland_thread.mutex);
+ // FIXME: For some reason this does not also windows from the wayland thread.
+
if (screen_is_kept_on() == p_enable) {
return;
}
@@ -636,6 +617,7 @@ void DisplayServerWayland::screen_set_keep_on(bool p_enable) {
}
bool DisplayServerWayland::screen_is_kept_on() const {
+ // FIXME: Multiwindow support.
#ifdef DBUS_ENABLED
return wayland_thread.window_get_idle_inhibition(MAIN_WINDOW_ID) || screensaver_inhibited;
#else
@@ -647,11 +629,223 @@ Vector DisplayServerWayland::get_window_list() const {
MutexLock mutex_lock(wayland_thread.mutex);
Vector ret;
- ret.push_back(MAIN_WINDOW_ID);
-
+ for (const KeyValue &E : windows) {
+ ret.push_back(E.key);
+ }
return ret;
}
+DisplayServer::WindowID DisplayServerWayland::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent) {
+ WindowID id = ++window_id_counter;
+ WindowData &wd = windows[id];
+
+ wd.id = id;
+ wd.mode = p_mode;
+ wd.flags = p_flags;
+ wd.vsync_mode = p_vsync_mode;
+
+ // NOTE: Remember to clear its position if this window will be a toplevel. We
+ // can only know once we show it.
+ wd.rect = p_rect;
+
+ wd.title = "Godot";
+ wd.parent_id = p_transient_parent;
+ return id;
+}
+
+void DisplayServerWayland::show_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];
+
+ if (!wd.visible) {
+ 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.
+ // TODO: Fix/Port/Move/Whatever to `WaylandThread` APIs.
+ WindowMode setup_mode = wd.mode;
+
+ // Let's determine the closest toplevel. For toplevels it will be themselves,
+ // for popups the first toplevel ancestor it finds.
+ WindowID root_id = wd.id;
+ while (root_id != INVALID_WINDOW_ID && window_get_flag(WINDOW_FLAG_POPUP_WM_HINT, root_id)) {
+ root_id = windows[root_id].parent_id;
+ }
+ ERR_FAIL_COND(root_id == INVALID_WINDOW_ID);
+
+ wd.root_id = root_id;
+
+ if (!window_get_flag(WINDOW_FLAG_POPUP_WM_HINT, p_window_id)) {
+ // NOTE: DO **NOT** KEEP THE POSITION SET FOR TOPLEVELS. Wayland does not
+ // track them and we're gonna get our events transformed in unexpected ways.
+ wd.rect.position = Point2i();
+
+ DEBUG_LOG_WAYLAND(vformat("Creating regular window of size %s", wd.rect.size));
+ wayland_thread.window_create(p_window_id, wd.rect.size.width, wd.rect.size.height);
+ wayland_thread.window_set_min_size(p_window_id, wd.min_size);
+ wayland_thread.window_set_max_size(p_window_id, wd.max_size);
+ wayland_thread.window_set_app_id(p_window_id, _get_app_id_from_context(context));
+ wayland_thread.window_set_borderless(p_window_id, window_get_flag(WINDOW_FLAG_BORDERLESS, p_window_id));
+
+ if (wd.parent_id != INVALID_WINDOW_ID) {
+ wayland_thread.window_set_parent(wd.id, wd.parent_id);
+ }
+
+ // Since it can't have a position. Let's tell the window node the news by
+ // the actual rect to it.
+ if (wd.rect_changed_callback.is_valid()) {
+ wd.rect_changed_callback.call(wd.rect);
+ }
+ } else {
+ DEBUG_LOG_WAYLAND("!!!!! Making popup !!!!!");
+
+ windows[root_id].popup_stack.push_back(p_window_id);
+
+ if (window_get_flag(WINDOW_FLAG_POPUP, p_window_id)) {
+ // Reroutes all input to it.
+ popup_menu_list.push_back(p_window_id);
+ }
+
+ wayland_thread.window_create_popup(p_window_id, wd.parent_id, wd.rect);
+ }
+
+ // 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
+ // graphics context window creation logic here.
+#ifdef RD_ENABLED
+ if (rendering_context) {
+ union {
+#ifdef VULKAN_ENABLED
+ RenderingContextDriverVulkanWayland::WindowPlatformData vulkan;
+#endif
+ } wpd;
+#ifdef VULKAN_ENABLED
+ if (rendering_driver == "vulkan") {
+ wpd.vulkan.surface = wayland_thread.window_get_wl_surface(wd.id);
+ wpd.vulkan.display = wayland_thread.get_wl_display();
+ }
+#endif
+ Error err = rendering_context->window_create(wd.id, &wpd);
+ ERR_FAIL_COND_MSG(err != OK, vformat("Can't create a %s window", rendering_driver));
+
+ rendering_context->window_set_size(wd.id, wd.rect.size.width, wd.rect.size.height);
+
+ // NOTE: Looks like we have to set the vsync mode before creating the screen
+ // or it won't work. Resist any temptation.
+ window_set_vsync_mode(wd.vsync_mode, p_window_id);
+ }
+
+ if (rendering_device) {
+ rendering_device->screen_create(wd.id);
+ }
+#endif
+
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ struct wl_surface *wl_surface = wayland_thread.window_get_wl_surface(wd.id);
+ wd.wl_egl_window = wl_egl_window_create(wl_surface, wd.rect.size.width, wd.rect.size.height);
+
+ Error err = egl_manager->window_create(p_window_id, wayland_thread.get_wl_display(), wd.wl_egl_window, wd.rect.size.width, wd.rect.size.height);
+ ERR_FAIL_COND_MSG(err == ERR_CANT_CREATE, "Can't show a GLES3 window.");
+
+ window_set_vsync_mode(wd.vsync_mode, p_window_id);
+ }
+#endif
+
+ // NOTE: The public window-handling methods might depend on this flag being
+ // set. Ensure to not make any of these calls before this assignment.
+ wd.visible = true;
+
+ // Actually try to apply the window's mode now that it's visible.
+ window_set_mode(setup_mode, wd.id);
+
+ wayland_thread.window_set_title(p_window_id, wd.title);
+ }
+}
+
+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(!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();
+ }
+
+ 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));
+}
+
+DisplayServer::WindowID DisplayServerWayland::window_get_active_popup() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (!popup_menu_list.is_empty()) {
+ return popup_menu_list.back()->get();
+ }
+
+ return INVALID_WINDOW_ID;
+}
+
+void DisplayServerWayland::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ ERR_FAIL_COND(!windows.has(p_window));
+
+ windows[p_window].safe_rect = p_rect;
+}
+
+Rect2i DisplayServerWayland::window_get_popup_safe_rect(WindowID p_window) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());
+
+ return windows[p_window].safe_rect;
+}
+
int64_t DisplayServerWayland::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
MutexLock mutex_lock(wayland_thread.mutex);
@@ -703,23 +897,31 @@ DisplayServer::WindowID DisplayServerWayland::get_window_at_screen_position(cons
void DisplayServerWayland::window_attach_instance_id(ObjectID p_instance, WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- main_window.instance_id = p_instance;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+
+ windows[p_window_id].instance_id = p_instance;
}
ObjectID DisplayServerWayland::window_get_attached_instance_id(WindowID p_window_id) const {
MutexLock mutex_lock(wayland_thread.mutex);
- return main_window.instance_id;
+ ERR_FAIL_COND_V(!windows.has(p_window_id), ObjectID());
+
+ return windows[p_window_id].instance_id;
}
void DisplayServerWayland::window_set_title(const String &p_title, DisplayServer::WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- WindowData &wd = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+
+ WindowData &wd = windows[p_window_id];
wd.title = p_title;
- wayland_thread.window_set_title(MAIN_WINDOW_ID, wd.title);
+ if (wd.visible) {
+ wayland_thread.window_set_title(p_window_id, wd.title);
+ }
}
void DisplayServerWayland::window_set_mouse_passthrough(const Vector &p_region, DisplayServer::WindowID p_window_id) {
@@ -730,31 +932,41 @@ void DisplayServerWayland::window_set_mouse_passthrough(const Vector &p
void DisplayServerWayland::window_set_rect_changed_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- main_window.rect_changed_callback = p_callable;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+
+ windows[p_window_id].rect_changed_callback = p_callable;
}
void DisplayServerWayland::window_set_window_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- main_window.window_event_callback = p_callable;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+
+ windows[p_window_id].window_event_callback = p_callable;
}
void DisplayServerWayland::window_set_input_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- main_window.input_event_callback = p_callable;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+
+ windows[p_window_id].input_event_callback = p_callable;
}
void DisplayServerWayland::window_set_input_text_callback(const Callable &p_callable, WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- main_window.input_text_callback = p_callable;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+
+ windows[p_window_id].input_text_callback = p_callable;
}
void DisplayServerWayland::window_set_drop_files_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- main_window.drop_files_callback = p_callable;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+
+ windows[p_window_id].drop_files_callback = p_callable;
}
int DisplayServerWayland::window_get_current_screen(DisplayServer::WindowID p_window_id) const {
@@ -769,16 +981,13 @@ void DisplayServerWayland::window_set_current_screen(int p_screen, DisplayServer
Point2i DisplayServerWayland::window_get_position(DisplayServer::WindowID p_window_id) const {
MutexLock mutex_lock(wayland_thread.mutex);
- // We can't know the position of toplevels with the standard protocol.
- return Point2i();
+ return windows[p_window_id].rect.position;
}
Point2i DisplayServerWayland::window_get_position_with_decorations(DisplayServer::WindowID p_window_id) const {
MutexLock mutex_lock(wayland_thread.mutex);
- // We can't know the position of toplevels with the standard protocol, nor can
- // we get information about the decorations, at least with SSDs.
- return Point2i();
+ return windows[p_window_id].rect.position;
}
void DisplayServerWayland::window_set_position(const Point2i &p_position, DisplayServer::WindowID p_window_id) {
@@ -794,7 +1003,8 @@ void DisplayServerWayland::window_set_max_size(const Size2i p_size, DisplayServe
ERR_FAIL_MSG("Maximum window size can't be negative!");
}
- WindowData &wd = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowData &wd = windows[p_window_id];
// FIXME: Is `p_size.x < wd.min_size.x || p_size.y < wd.min_size.y` == `p_size < wd.min_size`?
if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
@@ -804,25 +1014,45 @@ void DisplayServerWayland::window_set_max_size(const Size2i p_size, DisplayServe
wd.max_size = p_size;
- wayland_thread.window_set_max_size(MAIN_WINDOW_ID, p_size);
+ if (wd.visible) {
+ wayland_thread.window_set_max_size(p_window_id, p_size);
+ }
}
Size2i DisplayServerWayland::window_get_max_size(DisplayServer::WindowID p_window_id) const {
MutexLock mutex_lock(wayland_thread.mutex);
- return main_window.max_size;
+ ERR_FAIL_COND_V(!windows.has(p_window_id), Size2i());
+ return windows[p_window_id].max_size;
}
void DisplayServerWayland::gl_window_make_current(DisplayServer::WindowID p_window_id) {
#ifdef GLES3_ENABLED
if (egl_manager) {
- egl_manager->window_make_current(MAIN_WINDOW_ID);
+ egl_manager->window_make_current(p_window_id);
}
#endif
}
void DisplayServerWayland::window_set_transient(WindowID p_window_id, WindowID p_parent) {
- // Currently unsupported.
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowData &wd = windows[p_window_id];
+
+ ERR_FAIL_COND(wd.parent_id == p_parent);
+
+ if (p_parent != INVALID_WINDOW_ID) {
+ ERR_FAIL_COND(!windows.has(p_parent));
+ ERR_FAIL_COND_MSG(wd.parent_id != INVALID_WINDOW_ID, "Window already has a transient parent");
+ wd.parent_id = p_parent;
+
+ // NOTE: Looks like live unparenting is not really practical unfortunately.
+ // See WaylandThread::window_set_parent for more info.
+ if (wd.visible) {
+ wayland_thread.window_set_parent(p_window_id, p_parent);
+ }
+ }
}
void DisplayServerWayland::window_set_min_size(const Size2i p_size, DisplayServer::WindowID p_window_id) {
@@ -830,7 +1060,8 @@ void DisplayServerWayland::window_set_min_size(const Size2i p_size, DisplayServe
DEBUG_LOG_WAYLAND(vformat("window minsize set to %s", p_size));
- WindowData &wd = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowData &wd = windows[p_window_id];
if (p_size.x < 0 || p_size.y < 0) {
ERR_FAIL_MSG("Minimum window size can't be negative!");
@@ -844,23 +1075,36 @@ void DisplayServerWayland::window_set_min_size(const Size2i p_size, DisplayServe
wd.min_size = p_size;
- wayland_thread.window_set_min_size(MAIN_WINDOW_ID, p_size);
+ if (wd.visible) {
+ wayland_thread.window_set_min_size(p_window_id, p_size);
+ }
}
Size2i DisplayServerWayland::window_get_min_size(DisplayServer::WindowID p_window_id) const {
MutexLock mutex_lock(wayland_thread.mutex);
- return main_window.min_size;
+ ERR_FAIL_COND_V(!windows.has(p_window_id), Size2i());
+ return windows[p_window_id].min_size;
}
void DisplayServerWayland::window_set_size(const Size2i p_size, DisplayServer::WindowID p_window_id) {
- // The XDG spec doesn't allow non-interactive resizes.
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowData &wd = windows[p_window_id];
+
+ // The XDG spec doesn't allow non-interactive resizes. Let's update the
+ // window's internal representation to account for that.
+ if (wd.rect_changed_callback.is_valid()) {
+ wd.rect_changed_callback.call(wd.rect);
+ }
}
Size2i DisplayServerWayland::window_get_size(DisplayServer::WindowID p_window_id) const {
MutexLock mutex_lock(wayland_thread.mutex);
- return main_window.rect.size;
+ ERR_FAIL_COND_V(!windows.has(p_window_id), Size2i());
+ return windows[p_window_id].rect.size;
}
Size2i DisplayServerWayland::window_get_size_with_decorations(DisplayServer::WindowID p_window_id) const {
@@ -869,13 +1113,15 @@ Size2i DisplayServerWayland::window_get_size_with_decorations(DisplayServer::Win
// I don't think there's a way of actually knowing the size of the window
// decoration in Wayland, at least in the case of SSDs, nor that it would be
// that useful in this case. We'll just return the main window's size.
- return main_window.rect.size;
+ ERR_FAIL_COND_V(!windows.has(p_window_id), Size2i());
+ return windows[p_window_id].rect.size;
}
void DisplayServerWayland::window_set_mode(WindowMode p_mode, DisplayServer::WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- WindowData &wd = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowData &wd = windows[p_window_id];
if (!wd.visible) {
return;
@@ -887,7 +1133,8 @@ void DisplayServerWayland::window_set_mode(WindowMode p_mode, DisplayServer::Win
DisplayServer::WindowMode DisplayServerWayland::window_get_mode(DisplayServer::WindowID p_window_id) const {
MutexLock mutex_lock(wayland_thread.mutex);
- const WindowData &wd = main_window;
+ ERR_FAIL_COND_V(!windows.has(p_window_id), WINDOW_MODE_WINDOWED);
+ const WindowData &wd = windows[p_window_id];
if (!wd.visible) {
return WINDOW_MODE_WINDOWED;
@@ -905,13 +1152,24 @@ bool DisplayServerWayland::window_is_maximize_allowed(DisplayServer::WindowID p_
void DisplayServerWayland::window_set_flag(WindowFlags p_flag, bool p_enabled, DisplayServer::WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- WindowData &wd = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowData &wd = windows[p_window_id];
DEBUG_LOG_WAYLAND(vformat("Window set flag %d", p_flag));
switch (p_flag) {
case WINDOW_FLAG_BORDERLESS: {
- wayland_thread.window_set_borderless(MAIN_WINDOW_ID, p_enabled);
+ wayland_thread.window_set_borderless(p_window_id, p_enabled);
+ } break;
+
+ 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, "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, "Popup hint can't changed while window is opened.");
} break;
default: {
@@ -928,7 +1186,8 @@ void DisplayServerWayland::window_set_flag(WindowFlags p_flag, bool p_enabled, D
bool DisplayServerWayland::window_get_flag(WindowFlags p_flag, DisplayServer::WindowID p_window_id) const {
MutexLock mutex_lock(wayland_thread.mutex);
- return main_window.flags & (1 << p_flag);
+ ERR_FAIL_COND_V(!windows.has(p_window_id), false);
+ return windows[p_window_id].flags & (1 << p_flag);
}
void DisplayServerWayland::window_request_attention(DisplayServer::WindowID p_window_id) {
@@ -936,7 +1195,7 @@ void DisplayServerWayland::window_request_attention(DisplayServer::WindowID p_wi
DEBUG_LOG_WAYLAND("Requested attention.");
- wayland_thread.window_request_attention(MAIN_WINDOW_ID);
+ wayland_thread.window_request_attention(p_window_id);
}
void DisplayServerWayland::window_move_to_foreground(DisplayServer::WindowID p_window_id) {
@@ -948,6 +1207,19 @@ bool DisplayServerWayland::window_is_focused(WindowID p_window_id) const {
}
bool DisplayServerWayland::window_can_draw(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ uint64_t last_frame_time = wayland_thread.window_get_last_frame_time(p_window_id);
+ uint64_t time_since_frame = OS::get_singleton()->get_ticks_usec() - last_frame_time;
+
+ if (time_since_frame > WAYLAND_MAX_FRAME_TIME_US) {
+ return false;
+ }
+
+ if (wayland_thread.window_is_suspended(p_window_id)) {
+ return false;
+ }
+
return suspend_state == SuspendState::NONE;
}
@@ -958,13 +1230,13 @@ bool DisplayServerWayland::can_any_window_draw() const {
void DisplayServerWayland::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- wayland_thread.window_set_ime_active(p_active, MAIN_WINDOW_ID);
+ wayland_thread.window_set_ime_active(p_active, p_window_id);
}
void DisplayServerWayland::window_set_ime_position(const Point2i &p_pos, DisplayServer::WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
- wayland_thread.window_set_ime_position(p_pos, MAIN_WINDOW_ID);
+ wayland_thread.window_set_ime_position(p_pos, p_window_id);
}
Point2i DisplayServerWayland::ime_get_selection() const {
@@ -982,13 +1254,15 @@ String DisplayServerWayland::ime_get_text() const {
void DisplayServerWayland::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, DisplayServer::WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
+ WindowData &wd = windows[p_window_id];
+
#ifdef RD_ENABLED
if (rendering_context) {
rendering_context->window_set_vsync_mode(p_window_id, p_vsync_mode);
- emulate_vsync = (rendering_context->window_get_vsync_mode(p_window_id) == DisplayServer::VSYNC_ENABLED);
+ wd.emulate_vsync = (rendering_context->window_get_vsync_mode(p_window_id) == DisplayServer::VSYNC_ENABLED);
- if (emulate_vsync) {
+ if (wd.emulate_vsync) {
print_verbose("VSYNC: manually throttling frames using MAILBOX.");
rendering_context->window_set_vsync_mode(p_window_id, DisplayServer::VSYNC_MAILBOX);
}
@@ -999,9 +1273,9 @@ void DisplayServerWayland::window_set_vsync_mode(DisplayServer::VSyncMode p_vsyn
if (egl_manager) {
egl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
- emulate_vsync = egl_manager->is_using_vsync();
+ wd.emulate_vsync = egl_manager->is_using_vsync();
- if (emulate_vsync) {
+ if (wd.emulate_vsync) {
print_verbose("VSYNC: manually throttling frames with swap delay 0.");
egl_manager->set_use_vsync(false);
}
@@ -1010,7 +1284,8 @@ void DisplayServerWayland::window_set_vsync_mode(DisplayServer::VSyncMode p_vsyn
}
DisplayServer::VSyncMode DisplayServerWayland::window_get_vsync_mode(DisplayServer::WindowID p_window_id) const {
- if (emulate_vsync) {
+ const WindowData &wd = windows[p_window_id];
+ if (wd.emulate_vsync) {
return DisplayServer::VSYNC_ENABLED;
}
@@ -1166,13 +1441,22 @@ Key DisplayServerWayland::keyboard_get_keycode_from_physical(Key p_keycode) cons
}
void DisplayServerWayland::try_suspend() {
+ bool must_emulate = false;
+
+ for (KeyValue &pair : windows) {
+ if (pair.value.emulate_vsync) {
+ must_emulate = true;
+ break;
+ }
+ }
+
// Due to various reasons, we manually handle display synchronization by
// waiting for a frame event (request to draw) or, if available, the actual
// window's suspend status. When a window is suspended, we can avoid drawing
// altogether, either because the compositor told us that we don't need to or
// because the pace of the frame events became unreliable.
- if (emulate_vsync) {
- bool frame = wayland_thread.wait_frame_suspend_ms(1000);
+ if (must_emulate) {
+ bool frame = wayland_thread.wait_frame_suspend_ms(WAYLAND_MAX_FRAME_TIME_US / 1000);
if (!frame) {
suspend_state = SuspendState::TIMEOUT;
}
@@ -1198,14 +1482,26 @@ void DisplayServerWayland::process_events() {
while (wayland_thread.has_message()) {
Ref msg = wayland_thread.pop_message();
+ // Generic check. Not actual message handling.
+ Ref win_msg = msg;
+ if (win_msg.is_valid()) {
+ ERR_CONTINUE_MSG(win_msg->id == INVALID_WINDOW_ID, "Invalid window ID received from Wayland thread.");
+
+ if (!windows.has(win_msg->id)) {
+ // Window got probably deleted.
+ continue;
+ }
+ }
+
Ref winrect_msg = msg;
if (winrect_msg.is_valid()) {
- _resize_window(winrect_msg->rect.size);
+ _update_window_rect(winrect_msg->rect, winrect_msg->id);
+ continue;
}
Ref winev_msg = msg;
if (winev_msg.is_valid()) {
- _send_window_event(winev_msg->event);
+ _send_window_event(winev_msg->event, winev_msg->id);
if (winev_msg->event == WINDOW_EVENT_FOCUS_IN) {
if (OS::get_singleton()->get_main_loop()) {
@@ -1217,16 +1513,54 @@ void DisplayServerWayland::process_events() {
}
Input::get_singleton()->release_pressed_events();
}
+ continue;
}
Ref inputev_msg = msg;
if (inputev_msg.is_valid()) {
- Input::get_singleton()->parse_input_event(inputev_msg->event);
+ Ref mb = inputev_msg->event;
+
+ bool handled = false;
+ if (!popup_menu_list.is_empty() && mb.is_valid()) {
+ // Popup menu handling.
+
+ BitField mouse_mask = mb->get_button_mask();
+ if (mouse_mask != last_mouse_monitor_mask && mb->is_pressed()) {
+ List::Element *E = popup_menu_list.back();
+ List::Element *C = nullptr;
+
+ // Looking for the oldest popup to close.
+ while (E) {
+ WindowData &wd = windows[E->get()];
+ Point2 global_pos = mb->get_position() + window_get_position(mb->get_window_id());
+ if (wd.rect.has_point(global_pos)) {
+ break;
+ } else if (wd.safe_rect.has_point(global_pos)) {
+ break;
+ }
+
+ C = E;
+ E = E->prev();
+ }
+
+ if (C) {
+ handled = true;
+ _send_window_event(WINDOW_EVENT_CLOSE_REQUEST, C->get());
+ }
+ }
+
+ last_mouse_monitor_mask = mouse_mask;
+ }
+
+ if (!handled) {
+ Input::get_singleton()->parse_input_event(inputev_msg->event);
+ }
+ continue;
}
Ref dropfiles_msg = msg;
if (dropfiles_msg.is_valid()) {
- WindowData wd = main_window;
+ WindowData wd = windows[dropfiles_msg->id];
if (wd.drop_files_callback.is_valid()) {
Variant v_files = dropfiles_msg->files;
@@ -1238,6 +1572,7 @@ void DisplayServerWayland::process_events() {
ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(wd.drop_files_callback, v_args, 1, ce)));
}
}
+ continue;
}
Ref ime_commit_msg = msg;
@@ -1247,7 +1582,7 @@ void DisplayServerWayland::process_events() {
Ref ke;
ke.instantiate();
- ke->set_window_id(MAIN_WINDOW_ID);
+ ke->set_window_id(ime_commit_msg->id);
ke->set_pressed(true);
ke->set_echo(false);
ke->set_keycode(Key::NONE);
@@ -1261,6 +1596,7 @@ void DisplayServerWayland::process_events() {
ime_selection = Vector2i();
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
+ continue;
}
Ref ime_update_msg = msg;
@@ -1271,6 +1607,7 @@ void DisplayServerWayland::process_events() {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
}
+ continue;
}
}
@@ -1317,6 +1654,7 @@ void DisplayServerWayland::process_events() {
DEBUG_LOG_WAYLAND("Unsuspending from timeout.");
}
}
+ wayland_thread.commit_surfaces();
}
#ifdef DBUS_ENABLED
@@ -1586,7 +1924,7 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
cursor_set_shape(CURSOR_BUSY);
- WindowData &wd = main_window;
+ WindowData &wd = windows[MAIN_WINDOW_ID];
wd.id = MAIN_WINDOW_ID;
wd.mode = p_mode;
@@ -1595,7 +1933,7 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
wd.rect.size = p_resolution;
wd.title = "Godot";
- _show_window();
+ show_window(MAIN_WINDOW_ID);
#ifdef RD_ENABLED
if (rendering_context) {
@@ -1625,36 +1963,27 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
}
DisplayServerWayland::~DisplayServerWayland() {
- // TODO: Multiwindow support.
-
if (native_menu) {
memdelete(native_menu);
native_menu = nullptr;
}
- if (main_window.visible) {
-#ifdef VULKAN_ENABLED
- if (rendering_device) {
- rendering_device->screen_free(MAIN_WINDOW_ID);
- }
+ // Iterating on the window map while we delete stuff from it is a bit
+ // uncomfortable, plus we can't even delete /all/ windows in an arbitrary order
+ // (due to popups).
+ List toplevels;
- if (rendering_context) {
- rendering_context->window_destroy(MAIN_WINDOW_ID);
- }
-#endif
+ for (const KeyValue &pair : windows) {
+ WindowID id = pair.key;
-#ifdef GLES3_ENABLED
- if (egl_manager) {
- egl_manager->window_destroy(MAIN_WINDOW_ID);
+ if (!window_get_flag(WINDOW_FLAG_POPUP_WM_HINT, id)) {
+ toplevels.push_back(id);
}
-#endif
}
-#ifdef GLES3_ENABLED
- if (main_window.wl_egl_window) {
- wl_egl_window_destroy(main_window.wl_egl_window);
+ for (WindowID &id : toplevels) {
+ delete_sub_window(id);
}
-#endif
wayland_thread.destroy();
diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h
index 4e21aea6a553..acfdc63f3b3c 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.h
+++ b/platform/linuxbsd/wayland/display_server_wayland.h
@@ -69,7 +69,15 @@
class DisplayServerWayland : public DisplayServer {
// No need to register with GDCLASS, it's platform-specific and nothing is added.
struct WindowData {
- WindowID id;
+ WindowID id = INVALID_WINDOW_ID;
+
+ WindowID parent_id = INVALID_WINDOW_ID;
+
+ // For popups.
+ WindowID root_id = INVALID_WINDOW_ID;
+
+ // For toplevels.
+ List popup_stack;
Rect2i rect;
Size2i max_size;
@@ -77,6 +85,8 @@ class DisplayServerWayland : public DisplayServer {
Rect2i safe_rect;
+ bool emulate_vsync = false;
+
#ifdef GLES3_ENABLED
struct wl_egl_window *wl_egl_window = nullptr;
#endif
@@ -120,16 +130,24 @@ class DisplayServerWayland : public DisplayServer {
HashMap custom_cursors;
- WindowData main_window;
+ HashMap windows;
+ WindowID window_id_counter = MAIN_WINDOW_ID;
+
WaylandThread wayland_thread;
Context context;
+ // NOTE: These are the based on WINDOW_FLAG_POPUP, which does NOT imply what it
+ // seems. It's particularly confusing for our usecase, but just know that these
+ // are the "take all input thx" windows while the `popup_stack` variable keeps
+ // track of all the generic floating window concept.
+ List popup_menu_list;
+ BitField last_mouse_monitor_mask;
+
String ime_text;
Vector2i ime_selection;
SuspendState suspend_state = SuspendState::NONE;
- bool emulate_vsync = false;
String rendering_driver;
@@ -155,14 +173,12 @@ class DisplayServerWayland : public DisplayServer {
#endif
static String _get_app_id_from_context(Context p_context);
- void _send_window_event(WindowEvent p_event);
+ void _send_window_event(WindowEvent p_event, WindowID p_window_id = MAIN_WINDOW_ID);
static void dispatch_input_events(const Ref &p_event);
void _dispatch_input_event(const Ref &p_event);
- void _resize_window(const Size2i &p_size);
-
- virtual void _show_window();
+ void _update_window_rect(const Rect2i &p_rect, WindowID p_window_id = MAIN_WINDOW_ID);
void try_suspend();
@@ -224,6 +240,14 @@ class DisplayServerWayland : public DisplayServer {
virtual Vector get_window_list() const override;
+ virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i(), bool p_exclusive = false, WindowID p_transient_parent = INVALID_WINDOW_ID) override;
+ virtual void show_window(WindowID p_id) override;
+ virtual void delete_sub_window(WindowID p_id) override;
+
+ virtual WindowID window_get_active_popup() const override;
+ virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override;
+ virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const override;
+
virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp
index 6155d9e3675d..f0b0b74b7e79 100644
--- a/platform/linuxbsd/wayland/wayland_thread.cpp
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -227,7 +227,7 @@ bool WaylandThread::_seat_state_configure_key_event(SeatState &p_ss, Refset_window_id(DisplayServer::MAIN_WINDOW_ID);
+ p_event->set_window_id(p_ss.focused_id);
// Set all pressed modifiers.
p_event->set_shift_pressed(p_ss.shift_pressed);
@@ -692,16 +692,17 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry
}
if (name == registry->wp_viewporter_name) {
- WindowState *ws = ®istry->wayland_thread->main_window;
-
- if (registry->wp_viewporter) {
- wp_viewporter_destroy(registry->wp_viewporter);
- registry->wp_viewporter = nullptr;
- }
+ for (KeyValue &pair : registry->wayland_thread->windows) {
+ WindowState ws = pair.value;
+ if (registry->wp_viewporter) {
+ wp_viewporter_destroy(registry->wp_viewporter);
+ registry->wp_viewporter = nullptr;
+ }
- if (ws->wp_viewport) {
- wp_viewport_destroy(ws->wp_viewport);
- ws->wp_viewport = nullptr;
+ if (ws.wp_viewport) {
+ wp_viewport_destroy(ws.wp_viewport);
+ ws.wp_viewport = nullptr;
+ }
}
registry->wp_viewporter_name = 0;
@@ -710,16 +711,18 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry
}
if (name == registry->wp_fractional_scale_manager_name) {
- WindowState *ws = ®istry->wayland_thread->main_window;
+ for (KeyValue &pair : registry->wayland_thread->windows) {
+ WindowState ws = pair.value;
- if (registry->wp_fractional_scale_manager) {
- wp_fractional_scale_manager_v1_destroy(registry->wp_fractional_scale_manager);
- registry->wp_fractional_scale_manager = nullptr;
- }
+ if (registry->wp_fractional_scale_manager) {
+ wp_fractional_scale_manager_v1_destroy(registry->wp_fractional_scale_manager);
+ registry->wp_fractional_scale_manager = nullptr;
+ }
- if (ws->wp_fractional_scale) {
- wp_fractional_scale_v1_destroy(ws->wp_fractional_scale);
- ws->wp_fractional_scale = nullptr;
+ if (ws.wp_fractional_scale) {
+ wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);
+ ws.wp_fractional_scale = nullptr;
+ }
}
registry->wp_fractional_scale_manager_name = 0;
@@ -1023,9 +1026,10 @@ void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *w
ERR_FAIL_NULL(ws->wayland_thread);
ERR_FAIL_NULL(ws->wl_surface);
+ ws->last_frame_time = OS::get_singleton()->get_ticks_usec();
ws->wayland_thread->set_frame();
- ws->frame_callback = wl_surface_frame(ws->wl_surface),
+ ws->frame_callback = wl_surface_frame(ws->wl_surface);
wl_callback_add_listener(ws->frame_callback, &frame_wl_callback_listener, ws);
if (ws->wl_surface && ws->buffer_scale_changed) {
@@ -1140,7 +1144,7 @@ void WaylandThread::_xdg_surface_on_configure(void *data, struct xdg_surface *xd
WindowState *ws = (WindowState *)data;
ERR_FAIL_NULL(ws);
- DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure width %d height %d", ws->rect.size.width, ws->rect.size.height));
+ DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure rect %s", ws->rect));
}
void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) {
@@ -1186,6 +1190,7 @@ void WaylandThread::_xdg_toplevel_on_close(void *data, struct xdg_toplevel *xdg_
Ref msg;
msg.instantiate();
+ msg->id = ws->id;
msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;
ws->wayland_thread->push_message(msg);
}
@@ -1221,6 +1226,63 @@ void WaylandThread::_xdg_toplevel_on_wm_capabilities(void *data, struct xdg_topl
}
}
+void WaylandThread::_xdg_popup_on_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ if (width != 0 && height != 0) {
+ window_state_update_size(ws, width, height);
+ }
+
+ WindowState *parent = ws->wayland_thread->window_get_state(ws->parent_id);
+ ERR_FAIL_NULL(parent);
+
+ Point2i pos = Point2i(x, y);
+ if (parent->libdecor_frame) {
+ int translated_x = x;
+ int translated_y = y;
+ libdecor_frame_translate_coordinate(parent->libdecor_frame, x, y, &translated_x, &translated_y);
+
+ pos.x = translated_x;
+ pos.y = translated_y;
+ }
+
+ // Looks like the position returned here is relative to the parent. We have to
+ // accumulate it or there's gonna be a lot of confusion godot-side.
+ pos += parent->rect.position;
+
+ if (ws->rect.position != pos) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Repositioning popup %d from %s to %s", ws->id, ws->rect.position, pos));
+
+ ws->rect.position = pos;
+
+ Ref rect_msg;
+ rect_msg.instantiate();
+ rect_msg->id = ws->id;
+ rect_msg->rect = ws->rect;
+
+ ws->wayland_thread->push_message(rect_msg);
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("xdg popup on configure x%d y%d w%d h%d", x, y, width, height));
+}
+
+void WaylandThread::_xdg_popup_on_popup_done(void *data, struct xdg_popup *xdg_popup) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ Ref ev_msg;
+ ev_msg.instantiate();
+ ev_msg->id = ws->id;
+ ev_msg->event = DisplayServer::WINDOW_EVENT_FORCE_CLOSE;
+
+ ws->wayland_thread->push_message(ev_msg);
+}
+
+void WaylandThread::_xdg_popup_on_repositioned(void *data, struct xdg_popup *xdg_popup, uint32_t token) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("stub xdg popup repositioned %x", token));
+}
+
// NOTE: Deprecated.
void WaylandThread::_xdg_exported_v1_on_handle(void *data, zxdg_exported_v1 *exported, const char *handle) {
WindowState *ws = (WindowState *)data;
@@ -1303,6 +1365,7 @@ void WaylandThread::libdecor_frame_on_close(struct libdecor_frame *frame, void *
Ref winevent_msg;
winevent_msg.instantiate();
+ winevent_msg->id = ws->id;
winevent_msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;
ws->wayland_thread->push_message(winevent_msg);
@@ -1423,73 +1486,61 @@ void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callbac
}
void WaylandThread::_wl_pointer_on_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
- if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) {
+ WindowState *ws = wl_surface_get_window_state(surface);
+ if (!ws) {
return;
}
- DEBUG_LOG_WAYLAND_THREAD("Pointing window.");
-
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
ERR_FAIL_NULL(ss->cursor_surface);
+
+ PointerData &pd = ss->pointer_data_buffer;
+
ss->pointer_enter_serial = serial;
- ss->pointed_surface = surface;
- ss->last_pointed_surface = surface;
+ pd.pointed_id = ws->id;
+ pd.last_pointed_id = ws->id;
+ pd.position.x = wl_fixed_to_double(surface_x);
+ pd.position.y = wl_fixed_to_double(surface_y);
seat_state_update_cursor(ss);
- Ref msg;
- msg.instantiate();
- msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
-
- ss->wayland_thread->push_message(msg);
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Pointer entered window %d.", ws->id));
}
void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {
- if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) {
- return;
- }
-
- DEBUG_LOG_WAYLAND_THREAD("Left window.");
+ // NOTE: `surface` will probably be null when the surface is destroyed.
+ // See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/366
+ // See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/465
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- WaylandThread *wayland_thread = ss->wayland_thread;
- ERR_FAIL_NULL(wayland_thread);
+ PointerData &pd = ss->pointer_data_buffer;
- ss->pointed_surface = nullptr;
+ if (pd.pointed_id == DisplayServer::INVALID_WINDOW_ID) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
- ss->pointer_data_buffer.pressed_button_mask.clear();
+ DisplayServer::WindowID id = pd.pointed_id;
- Ref msg;
- msg.instantiate();
- msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
+ pd.pointed_id = DisplayServer::INVALID_WINDOW_ID;
+ pd.pressed_button_mask.clear();
- wayland_thread->push_message(msg);
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Pointer left window %d.", id));
}
void WaylandThread::_wl_pointer_on_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- if (!ss->pointed_surface) {
- // We're probably on a decoration or some other third-party thing.
- return;
- }
-
- WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
- ERR_FAIL_NULL(ws);
-
PointerData &pd = ss->pointer_data_buffer;
- // TODO: Scale only when sending the Wayland message.
pd.position.x = wl_fixed_to_double(surface_x);
pd.position.y = wl_fixed_to_double(surface_y);
- pd.position *= window_state_get_scale_factor(ws);
-
pd.motion_time = time;
}
@@ -1497,11 +1548,6 @@ void WaylandThread::_wl_pointer_on_button(void *data, struct wl_pointer *wl_poin
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- if (!ss->pointed_surface) {
- // We're probably on a decoration or some other third-party thing.
- return;
- }
-
PointerData &pd = ss->pointer_data_buffer;
MouseButton button_pressed = MouseButton::NONE;
@@ -1549,11 +1595,6 @@ void WaylandThread::_wl_pointer_on_axis(void *data, struct wl_pointer *wl_pointe
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- if (!ss->pointed_surface) {
- // We're probably on a decoration or some other third-party thing.
- return;
- }
-
PointerData &pd = ss->pointer_data_buffer;
switch (axis) {
@@ -1573,19 +1614,46 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- if (!ss->pointed_surface) {
- // We're probably on a decoration or some other third-party thing.
- return;
- }
-
WaylandThread *wayland_thread = ss->wayland_thread;
ERR_FAIL_NULL(wayland_thread);
- wayland_thread->_set_current_seat(ss->wl_seat);
-
PointerData &old_pd = ss->pointer_data;
PointerData &pd = ss->pointer_data_buffer;
+ if (pd.pointed_id != old_pd.pointed_id) {
+ if (old_pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {
+ Ref msg;
+ msg.instantiate();
+ msg->id = old_pd.pointed_id;
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
+
+ wayland_thread->push_message(msg);
+ }
+
+ if (pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {
+ Ref msg;
+ msg.instantiate();
+ msg->id = pd.pointed_id;
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
+
+ wayland_thread->push_message(msg);
+ }
+ }
+
+ if (pd.pointed_id == DisplayServer::INVALID_WINDOW_ID) {
+ // We're probably on a decoration or some other third-party thing. Let's
+ // "commit" the data and call it a day.
+ old_pd = pd;
+ return;
+ }
+
+ WindowState *ws = ss->wayland_thread->window_get_state(pd.pointed_id);
+ ERR_FAIL_NULL(ws);
+
+ double scale = window_state_get_scale_factor(ws);
+
+ wayland_thread->_set_current_seat(ss->wl_seat);
+
if (old_pd.motion_time != pd.motion_time || old_pd.relative_motion_time != pd.relative_motion_time) {
Ref mm;
mm.instantiate();
@@ -1596,17 +1664,19 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
mm->set_alt_pressed(ss->alt_pressed);
mm->set_meta_pressed(ss->meta_pressed);
- mm->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ mm->set_window_id(ws->id);
+
mm->set_button_mask(pd.pressed_button_mask);
- mm->set_position(pd.position);
- mm->set_global_position(pd.position);
- Vector2 pos_delta = pd.position - old_pd.position;
+ mm->set_position(pd.position * scale);
+ mm->set_global_position(pd.position * scale);
+
+ Vector2 pos_delta = (pd.position - old_pd.position) * scale;
if (old_pd.relative_motion_time != pd.relative_motion_time) {
uint32_t time_delta = pd.relative_motion_time - old_pd.relative_motion_time;
- mm->set_relative(pd.relative_motion);
+ mm->set_relative(pd.relative_motion * scale);
mm->set_velocity((Vector2)pos_delta / time_delta);
} else {
// The spec includes the possibility of having motion events without an
@@ -1615,7 +1685,7 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
// relative speed anymore though.
uint32_t time_delta = pd.motion_time - old_pd.motion_time;
- mm->set_relative(pd.position - old_pd.position);
+ mm->set_relative(pos_delta);
mm->set_velocity((Vector2)pos_delta / time_delta);
}
mm->set_relative_screen_position(mm->get_relative());
@@ -1653,9 +1723,9 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
pg->set_alt_pressed(ss->alt_pressed);
pg->set_meta_pressed(ss->meta_pressed);
- pg->set_position(pd.position);
+ pg->set_position(pd.position * scale);
- pg->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ pg->set_window_id(ws->id);
pg->set_delta(pd.scroll_vector);
@@ -1695,9 +1765,9 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
mb->set_alt_pressed(ss->alt_pressed);
mb->set_meta_pressed(ss->meta_pressed);
- mb->set_window_id(DisplayServer::MAIN_WINDOW_ID);
- mb->set_position(pd.position);
- mb->set_global_position(pd.position);
+ mb->set_window_id(ws->id);
+ mb->set_position(pd.position * scale);
+ mb->set_global_position(pd.position * scale);
if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN) {
// If this is a discrete scroll, specify how many "clicks" it did for this
@@ -1723,7 +1793,7 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
pd.last_pressed_position = pd.position;
}
- if (old_pd.double_click_begun && mb->is_pressed() && pd.last_button_pressed == old_pd.last_button_pressed && (pd.button_time - old_pd.button_time) < 400 && Vector2(old_pd.last_pressed_position).distance_to(Vector2(pd.last_pressed_position)) < 5) {
+ if (old_pd.double_click_begun && mb->is_pressed() && pd.last_button_pressed == old_pd.last_button_pressed && (pd.button_time - old_pd.button_time) < 400 && Vector2(old_pd.last_pressed_position * scale).distance_to(Vector2(pd.last_pressed_position * scale)) < 5) {
pd.double_click_begun = false;
mb->set_double_click(true);
}
@@ -1745,9 +1815,9 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
Ref wh_up;
wh_up.instantiate();
- wh_up->set_window_id(DisplayServer::MAIN_WINDOW_ID);
- wh_up->set_position(pd.position);
- wh_up->set_global_position(pd.position);
+ wh_up->set_window_id(ws->id);
+ wh_up->set_position(pd.position * scale);
+ wh_up->set_global_position(pd.position * scale);
// We have to unset the button to avoid it getting stuck.
pd.pressed_button_mask.clear_flag(test_button_mask);
@@ -1778,11 +1848,6 @@ void WaylandThread::_wl_pointer_on_axis_source(void *data, struct wl_pointer *wl
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- if (!ss->pointed_surface) {
- // We're probably on a decoration or some other third-party thing.
- return;
- }
-
ss->pointer_data_buffer.scroll_type = axis_source;
}
@@ -1796,11 +1861,6 @@ void WaylandThread::_wl_pointer_on_axis_discrete(void *data, struct wl_pointer *
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- if (!ss->pointed_surface) {
- // We're probably on a decoration or some other third-party thing.
- return;
- }
-
PointerData &pd = ss->pointer_data_buffer;
// NOTE: We can allow ourselves to not accumulate this data (and thus just
@@ -1820,11 +1880,6 @@ void WaylandThread::_wl_pointer_on_axis_value120(void *data, struct wl_pointer *
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- if (!ss->pointed_surface) {
- // We're probably on a decoration or some other third-party thing.
- return;
- }
-
PointerData &pd = ss->pointer_data_buffer;
if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
@@ -1865,21 +1920,39 @@ void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_ke
}
void WaylandThread::_wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
+ WindowState *ws = wl_surface_get_window_state(surface);
+ if (!ws) {
+ return;
+ }
+
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
WaylandThread *wayland_thread = ss->wayland_thread;
ERR_FAIL_NULL(wayland_thread);
+ ss->focused_id = ws->id;
+
wayland_thread->_set_current_seat(ss->wl_seat);
Ref msg;
msg.instantiate();
+ msg->id = ws->id;
msg->event = DisplayServer::WINDOW_EVENT_FOCUS_IN;
wayland_thread->push_message(msg);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Keyboard focused window %d.", ws->id));
}
void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) {
+ // NOTE: `surface` will probably be null when the surface is destroyed.
+ // See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/366
+ // See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/465
+
+ if (surface && !wl_proxy_is_godot((struct wl_proxy *)surface)) {
+ return;
+ }
+
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
@@ -1888,16 +1961,33 @@ void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_key
ss->repeating_keycode = XKB_KEYCODE_INVALID;
+ if (ss->focused_id == DisplayServer::INVALID_WINDOW_ID) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ WindowState *ws = wayland_thread->window_get_state(ss->focused_id);
+ ERR_FAIL_NULL(ws);
+
+ ss->focused_id = DisplayServer::INVALID_WINDOW_ID;
+
Ref msg;
msg.instantiate();
+ msg->id = ws->id;
msg->event = DisplayServer::WINDOW_EVENT_FOCUS_OUT;
wayland_thread->push_message(msg);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Keyboard unfocused window %d.", ws->id));
}
void WaylandThread::_wl_keyboard_on_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
+ if (ss->focused_id == DisplayServer::INVALID_WINDOW_ID) {
+ return;
+ }
+
WaylandThread *wayland_thread = ss->wayland_thread;
ERR_FAIL_NULL(wayland_thread);
@@ -1959,9 +2049,16 @@ void WaylandThread::_wl_data_device_on_data_offer(void *data, struct wl_data_dev
}
void WaylandThread::_wl_data_device_on_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
+ WindowState *ws = wl_surface_get_window_state(surface);
+ if (!ws) {
+ return;
+ }
+
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
+ ss->dnd_id = ws->id;
+
ss->dnd_enter_serial = serial;
ss->wl_data_offer_dnd = id;
@@ -1978,6 +2075,7 @@ void WaylandThread::_wl_data_device_on_leave(void *data, struct wl_data_device *
memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));
wl_data_offer_destroy(ss->wl_data_offer_dnd);
ss->wl_data_offer_dnd = nullptr;
+ ss->dnd_id = DisplayServer::INVALID_WINDOW_ID;
}
}
@@ -1997,6 +2095,7 @@ void WaylandThread::_wl_data_device_on_drop(void *data, struct wl_data_device *w
if (os) {
Ref msg;
msg.instantiate();
+ msg->id = ss->dnd_id;
Vector list_data = _wl_data_offer_read(wayland_thread->wl_display, "text/uri-list", ss->wl_data_offer_dnd);
@@ -2013,6 +2112,7 @@ void WaylandThread::_wl_data_device_on_drop(void *data, struct wl_data_device *w
memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));
wl_data_offer_destroy(ss->wl_data_offer_dnd);
ss->wl_data_offer_dnd = nullptr;
+ ss->dnd_id = DisplayServer::INVALID_WINDOW_ID;
}
void WaylandThread::_wl_data_device_on_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
@@ -2120,21 +2220,11 @@ void WaylandThread::_wp_relative_pointer_on_relative_motion(void *data, struct z
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
- if (!ss->pointed_surface) {
- // We're probably on a decoration or some other third-party thing.
- return;
- }
-
PointerData &pd = ss->pointer_data_buffer;
- WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
- ERR_FAIL_NULL(ws);
-
pd.relative_motion.x = wl_fixed_to_double(dx);
pd.relative_motion.y = wl_fixed_to_double(dy);
- pd.relative_motion *= window_state_get_scale_factor(ws);
-
pd.relative_motion_time = uptime_lo;
}
@@ -2152,16 +2242,28 @@ void WaylandThread::_wp_pointer_gesture_pinch_on_update(void *data, struct zwp_p
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
+ // NOTE: From what I can tell, this and all other pointer gestures are separate
+ // from the "frame" mechanism of regular pointers. Thus, let's just assume we
+ // can read from the "committed" state.
+ const PointerData &pd = ss->pointer_data;
+
WaylandThread *wayland_thread = ss->wayland_thread;
ERR_FAIL_NULL(wayland_thread);
- PointerData &pd = ss->pointer_data_buffer;
+ WindowState *ws = wayland_thread->window_get_state(pd.pointed_id);
+ ERR_FAIL_NULL(ws);
+
+ double win_scale = window_state_get_scale_factor(ws);
if (ss->active_gesture == Gesture::MAGNIFY) {
Ref mg;
mg.instantiate();
- mg->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ mg->set_window_id(pd.pointed_id);
+
+ if (ws) {
+ mg->set_window_id(ws->id);
+ }
// Set all pressed modifiers.
mg->set_shift_pressed(ss->shift_pressed);
@@ -2169,7 +2271,7 @@ void WaylandThread::_wp_pointer_gesture_pinch_on_update(void *data, struct zwp_p
mg->set_alt_pressed(ss->alt_pressed);
mg->set_meta_pressed(ss->meta_pressed);
- mg->set_position(pd.position);
+ mg->set_position(pd.position * win_scale);
wl_fixed_t scale_delta = scale - ss->old_pinch_scale;
mg->set_factor(1 + wl_fixed_to_double(scale_delta));
@@ -2185,15 +2287,13 @@ void WaylandThread::_wp_pointer_gesture_pinch_on_update(void *data, struct zwp_p
Ref pg;
pg.instantiate();
- pg->set_window_id(DisplayServer::MAIN_WINDOW_ID);
-
// Set all pressed modifiers.
pg->set_shift_pressed(ss->shift_pressed);
pg->set_ctrl_pressed(ss->ctrl_pressed);
pg->set_alt_pressed(ss->alt_pressed);
pg->set_meta_pressed(ss->meta_pressed);
- pg->set_position(pd.position);
+ pg->set_position(pd.position * win_scale);
pg->set_delta(Vector2(wl_fixed_to_double(dx), wl_fixed_to_double(dy)));
Ref pan_msg;
@@ -2298,7 +2398,6 @@ void WaylandThread::_wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_
wl_proxy_tag_godot((struct wl_proxy *)id);
zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, state);
-
ss->tablet_tools.push_back(id);
}
@@ -2351,67 +2450,48 @@ void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_too
}
void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) {
- if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) {
- // We're probably on a decoration or something.
- return;
- }
+ // NOTE: Works pretty much like wl_pointer::enter.
- TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
- if (!ts) {
- return;
- }
-
- SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
- if (!ss) {
+ WindowState *ws = wl_surface_get_window_state(surface);
+ if (!ws) {
return;
}
- WaylandThread *wayland_thread = ss->wayland_thread;
- ERR_FAIL_NULL(wayland_thread);
+ TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
+ ERR_FAIL_NULL(ts);
ts->data_pending.proximity_serial = serial;
- ts->data_pending.proximal_surface = surface;
- ts->last_surface = surface;
-
- Ref msg;
- msg.instantiate();
- msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
- wayland_thread->push_message(msg);
+ ts->data_pending.proximal_id = ws->id;
+ ts->data_pending.last_proximal_id = ws->id;
- DEBUG_LOG_WAYLAND_THREAD("Tablet tool entered window.");
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Tablet tool entered window %d.", ts->data_pending.proximal_id));
}
void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {
+ // NOTE: Works pretty much like wl_pointer::leave.
+
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
- if (!ts || !ts->data_pending.proximal_surface) {
- // Not our stuff, we don't care.
- return;
- }
+ ERR_FAIL_NULL(ts);
- SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
- if (!ss) {
+ if (ts->data_pending.proximal_id == DisplayServer::INVALID_WINDOW_ID) {
+ // We're probably on a decoration or some other third-party thing.
return;
}
- WaylandThread *wayland_thread = ss->wayland_thread;
- ERR_FAIL_NULL(wayland_thread);
-
- ts->data_pending.proximal_surface = nullptr;
-
- Ref msg;
- msg.instantiate();
- msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
+ DisplayServer::WindowID id = ts->data_pending.proximal_id;
- wayland_thread->push_message(msg);
+ ts->data_pending.proximal_id = DisplayServer::INVALID_WINDOW_ID;
+ ts->data_pending.pressed_button_mask.clear();
- DEBUG_LOG_WAYLAND_THREAD("Tablet tool left window.");
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Tablet tool left window %d.", id));
}
void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial) {
+ // NOTE: Works pretty much like wl_pointer::button but only for a pressed left
+ // button.
+
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
- if (!ts) {
- return;
- }
+ ERR_FAIL_NULL(ts);
TabletToolData &td = ts->data_pending;
@@ -2425,10 +2505,11 @@ void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v
}
void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {
+ // NOTE: Works pretty much like wl_pointer::button but only for a released left
+ // button.
+
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
- if (!ts) {
- return;
- }
+ ERR_FAIL_NULL(ts);
TabletToolData &td = ts->data_pending;
@@ -2440,35 +2521,20 @@ void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2
}
void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y) {
- TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
- if (!ts) {
- return;
- }
-
- if (!ts->data_pending.proximal_surface) {
- // We're probably on a decoration or some other third-party thing.
- return;
- }
+ // NOTE: Works pretty much like wl_pointer::motion.
- WindowState *ws = wl_surface_get_window_state(ts->data_pending.proximal_surface);
- ERR_FAIL_NULL(ws);
+ TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
+ ERR_FAIL_NULL(ts);
TabletToolData &td = ts->data_pending;
- double scale_factor = window_state_get_scale_factor(ws);
-
td.position.x = wl_fixed_to_double(x);
td.position.y = wl_fixed_to_double(y);
- td.position *= scale_factor;
-
- td.motion_time = OS::get_singleton()->get_ticks_msec();
}
void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t pressure) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
- if (!ts) {
- return;
- }
+ ERR_FAIL_NULL(ts);
ts->data_pending.pressure = pressure;
}
@@ -2479,9 +2545,7 @@ void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_to
void WaylandThread::_wp_tablet_tool_on_tilt(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
- if (!ts) {
- return;
- }
+ ERR_FAIL_NULL(ts);
TabletToolData &td = ts->data_pending;
@@ -2502,10 +2566,10 @@ void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_
}
void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state) {
+ // NOTE: Works pretty much like wl_pointer::button.
+
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
- if (!ts) {
- return;
- }
+ ERR_FAIL_NULL(ts);
TabletToolData &td = ts->data_pending;
@@ -2537,15 +2601,13 @@ void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool
}
void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t time) {
+ // NOTE: Works pretty much like wl_pointer::frame.
+
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
- if (!ts) {
- return;
- }
+ ERR_FAIL_NULL(ts);
SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
- if (!ss) {
- return;
- }
+ ERR_FAIL_NULL(ss);
WaylandThread *wayland_thread = ss->wayland_thread;
ERR_FAIL_NULL(wayland_thread);
@@ -2553,11 +2615,44 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_
TabletToolData &old_td = ts->data;
TabletToolData &td = ts->data_pending;
+ if (td.proximal_id != old_td.proximal_id) {
+ if (old_td.proximal_id != DisplayServer::INVALID_WINDOW_ID) {
+ Ref msg;
+ msg.instantiate();
+ msg->id = old_td.proximal_id;
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
+
+ wayland_thread->push_message(msg);
+ }
+
+ if (td.proximal_id != DisplayServer::INVALID_WINDOW_ID) {
+ Ref msg;
+ msg.instantiate();
+ msg->id = td.proximal_id;
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
+
+ wayland_thread->push_message(msg);
+ }
+ }
+
+ if (td.proximal_id == DisplayServer::INVALID_WINDOW_ID) {
+ // We're probably on a decoration or some other third-party thing. Let's
+ // "commit" the data and call it a day.
+ old_td = td;
+ return;
+ }
+
+ WindowState *ws = wayland_thread->window_get_state(td.proximal_id);
+ ERR_FAIL_NULL(ws);
+
+ double scale = window_state_get_scale_factor(ws);
if (old_td.position != td.position || old_td.tilt != td.tilt || old_td.pressure != td.pressure) {
+ td.motion_time = time;
+
Ref mm;
mm.instantiate();
- mm->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ mm->set_window_id(td.proximal_id);
// Set all pressed modifiers.
mm->set_shift_pressed(ss->shift_pressed);
@@ -2567,8 +2662,8 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_
mm->set_button_mask(td.pressed_button_mask);
- mm->set_position(td.position);
- mm->set_global_position(td.position);
+ mm->set_global_position(td.position * scale);
+ mm->set_position(td.position * scale);
// NOTE: The Godot API expects normalized values and we store them raw,
// straight from the compositor, so we have to normalize them here.
@@ -2586,10 +2681,11 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_
mm->set_pen_inverted(ts->is_eraser);
- mm->set_relative(td.position - old_td.position);
- mm->set_relative_screen_position(mm->get_relative());
+ Vector2 pos_delta = (td.position - old_td.position) * scale;
+
+ mm->set_relative(pos_delta);
+ mm->set_relative_screen_position(pos_delta);
- Vector2 pos_delta = td.position - old_td.position;
uint32_t time_delta = td.motion_time - old_td.motion_time;
mm->set_velocity((Vector2)pos_delta / time_delta);
@@ -2602,6 +2698,8 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_
}
if (old_td.pressed_button_mask != td.pressed_button_mask) {
+ td.button_time = time;
+
BitField pressed_mask_delta = BitField((int64_t)old_td.pressed_button_mask ^ (int64_t)td.pressed_button_mask);
for (MouseButton test_button : { MouseButton::LEFT, MouseButton::RIGHT }) {
@@ -2617,9 +2715,9 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_
mb->set_alt_pressed(ss->alt_pressed);
mb->set_meta_pressed(ss->meta_pressed);
- mb->set_window_id(DisplayServer::MAIN_WINDOW_ID);
- mb->set_position(td.position);
- mb->set_global_position(td.position);
+ mb->set_window_id(td.proximal_id);
+ mb->set_position(td.position * scale);
+ mb->set_global_position(td.position * scale);
mb->set_button_mask(td.pressed_button_mask);
mb->set_button_index(test_button);
@@ -2632,7 +2730,7 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_
td.last_pressed_position = td.position;
}
- if (old_td.double_click_begun && mb->is_pressed() && td.last_button_pressed == old_td.last_button_pressed && (td.button_time - old_td.button_time) < 400 && Vector2(td.last_pressed_position).distance_to(Vector2(old_td.last_pressed_position)) < 5) {
+ if (old_td.double_click_begun && mb->is_pressed() && td.last_button_pressed == old_td.last_button_pressed && (td.button_time - old_td.button_time) < 400 && Vector2(td.last_pressed_position * scale).distance_to(Vector2(old_td.last_pressed_position * scale)) < 5) {
td.double_click_begun = false;
mb->set_double_click(true);
}
@@ -2656,6 +2754,12 @@ void WaylandThread::_wp_text_input_on_enter(void *data, struct zwp_text_input_v3
return;
}
+ WindowState *ws = wl_surface_get_window_state(surface);
+ if (!ws) {
+ return;
+ }
+
+ ss->ime_window_id = ws->id;
ss->ime_enabled = true;
}
@@ -2665,6 +2769,12 @@ void WaylandThread::_wp_text_input_on_leave(void *data, struct zwp_text_input_v3
return;
}
+ WindowState *ws = wl_surface_get_window_state(surface);
+ if (!ws) {
+ return;
+ }
+
+ ss->ime_window_id = DisplayServer::INVALID_WINDOW_ID;
ss->ime_enabled = false;
ss->ime_active = false;
ss->ime_text = String();
@@ -2745,14 +2855,18 @@ void WaylandThread::_wp_text_input_on_done(void *data, struct zwp_text_input_v3
return;
}
+ ss->ime_window_id = DisplayServer::INVALID_WINDOW_ID;
+
if (!ss->ime_text_commit.is_empty()) {
Ref msg;
msg.instantiate();
+ msg->id = ss->ime_window_id;
msg->text = ss->ime_text_commit;
ss->wayland_thread->push_message(msg);
} else {
Ref msg;
msg.instantiate();
+ msg->id = ss->ime_window_id;
msg->text = ss->ime_text;
msg->selection = ss->ime_cursor;
ss->wayland_thread->push_message(msg);
@@ -2982,8 +3096,8 @@ void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int
bool using_fractional = p_ws->preferred_fractional_scale > 0;
// If neither is true we no-op.
- bool scale_changed = false;
- bool size_changed = false;
+ bool scale_changed = true;
+ bool size_changed = true;
if (p_ws->rect.size.width != p_width || p_ws->rect.size.height != p_height) {
p_ws->rect.size.width = p_width;
@@ -3008,7 +3122,7 @@ void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int
}
}
- if (p_ws->wl_surface && (size_changed || scale_changed)) {
+ if (p_ws->wl_surface) {
if (p_ws->wp_viewport) {
wp_viewport_set_destination(p_ws->wp_viewport, p_width, p_height);
}
@@ -3041,6 +3155,7 @@ void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int
Ref rect_msg;
rect_msg.instantiate();
+ rect_msg->id = p_ws->id;
rect_msg->rect = p_ws->rect;
rect_msg->rect.size = scaled_size;
p_ws->wayland_thread->push_message(rect_msg);
@@ -3049,6 +3164,7 @@ void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int
if (scale_changed) {
Ref dpi_msg;
dpi_msg.instantiate();
+ dpi_msg->id = p_ws->id;
dpi_msg->event = DisplayServer::WINDOW_EVENT_DPI_CHANGE;
p_ws->wayland_thread->push_message(dpi_msg);
}
@@ -3094,12 +3210,7 @@ void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) {
}
if (p_ss->wp_locked_pointer == nullptr) {
- struct wl_surface *locked_surface = p_ss->last_pointed_surface;
-
- if (locked_surface == nullptr) {
- locked_surface = window_get_wl_surface(DisplayServer::MAIN_WINDOW_ID);
- }
-
+ struct wl_surface *locked_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);
ERR_FAIL_NULL(locked_surface);
p_ss->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer(registry.wp_pointer_constraints, locked_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
@@ -3126,12 +3237,7 @@ void WaylandThread::seat_state_confine_pointer(SeatState *p_ss) {
}
if (p_ss->wp_confined_pointer == nullptr) {
- struct wl_surface *confined_surface = p_ss->last_pointed_surface;
-
- if (confined_surface == nullptr) {
- confined_surface = window_get_wl_surface(DisplayServer::MAIN_WINDOW_ID);
- }
-
+ struct wl_surface *confined_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);
ERR_FAIL_NULL(confined_surface);
p_ss->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer(registry.wp_pointer_constraints, confined_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
@@ -3273,8 +3379,10 @@ Ref WaylandThread::pop_message() {
}
void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_width, int p_height) {
- // TODO: Implement multi-window support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
+
+ ws.id = p_window_id;
ws.registry = ®istry;
ws.wayland_thread = this;
@@ -3339,15 +3447,147 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_wid
// 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) {
+ ERR_FAIL_COND(windows.has(p_window_id));
+ ERR_FAIL_COND(!windows.has(p_parent_id));
+
+ WindowState &ws = windows[p_window_id];
+ WindowState &parent = windows[p_parent_id];
+
+ ws.id = p_window_id;
+ ws.parent_id = p_parent_id;
+ ws.registry = ®istry;
+ ws.wayland_thread = this;
+
+ ws.rect = p_rect;
+
+ ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor);
+ wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface);
+ wl_surface_add_listener(ws.wl_surface, &wl_surface_listener, &ws);
+
+ if (registry.wp_viewporter) {
+ ws.wp_viewport = wp_viewporter_get_viewport(registry.wp_viewporter, ws.wl_surface);
+
+ if (registry.wp_fractional_scale_manager) {
+ ws.wp_fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(registry.wp_fractional_scale_manager, ws.wl_surface);
+ wp_fractional_scale_v1_add_listener(ws.wp_fractional_scale, &wp_fractional_scale_listener, &ws);
+ }
+ }
+
+ ws.xdg_surface = xdg_wm_base_get_xdg_surface(registry.xdg_wm_base, ws.wl_surface);
+ xdg_surface_add_listener(ws.xdg_surface, &xdg_surface_listener, &ws);
+
+ Rect2i positioner_rect;
+ positioner_rect.size = parent.rect.size;
+ struct xdg_surface *parent_xdg_surface = parent.xdg_surface;
+
+ Point2i offset = ws.rect.position - parent.rect.position;
+
+#ifdef LIBDECOR_ENABLED
+ if (!parent_xdg_surface && parent.libdecor_frame) {
+ parent_xdg_surface = libdecor_frame_get_xdg_surface(parent.libdecor_frame);
+
+ int corner_x = 0;
+ int corner_y = 0;
+ libdecor_frame_translate_coordinate(parent.libdecor_frame, 0, 0, &corner_x, &corner_y);
+
+ positioner_rect.position.x = corner_x;
+ positioner_rect.position.y = corner_y;
+
+ positioner_rect.size.width -= corner_x;
+ positioner_rect.size.height -= corner_y;
+ }
+#endif
+
+ ERR_FAIL_NULL(parent_xdg_surface);
+
+ struct xdg_positioner *xdg_positioner = xdg_wm_base_create_positioner(registry.xdg_wm_base);
+ xdg_positioner_set_size(xdg_positioner, ws.rect.size.width, ws.rect.size.height);
+ xdg_positioner_set_anchor(xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
+ xdg_positioner_set_gravity(xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
+ xdg_positioner_set_constraint_adjustment(xdg_positioner, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y);
+ xdg_positioner_set_anchor_rect(xdg_positioner, positioner_rect.position.x, positioner_rect.position.y, positioner_rect.size.width, positioner_rect.size.height);
+ xdg_positioner_set_offset(xdg_positioner, offset.x, offset.y);
+
+ ws.xdg_popup = xdg_surface_get_popup(ws.xdg_surface, parent_xdg_surface, xdg_positioner);
+ xdg_popup_add_listener(ws.xdg_popup, &xdg_popup_listener, &ws);
+
+ xdg_positioner_destroy(xdg_positioner);
+
+ ws.frame_callback = wl_surface_frame(ws.wl_surface);
+ 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) {
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
+
+ if (ws.xdg_popup) {
+ xdg_popup_destroy(ws.xdg_popup);
+ }
+
+ if (ws.xdg_toplevel_decoration) {
+ zxdg_toplevel_decoration_v1_destroy(ws.xdg_toplevel_decoration);
+ }
+
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_destroy(ws.xdg_toplevel);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_unref(ws.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+
+ if (ws.wp_fractional_scale) {
+ wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);
+ }
+
+ if (ws.wp_viewport) {
+ wp_viewport_destroy(ws.wp_viewport);
+ }
+
+ if (ws.frame_callback) {
+ wl_callback_destroy(ws.frame_callback);
+ }
+
+ if (ws.xdg_surface) {
+ xdg_surface_destroy(ws.xdg_surface);
+ }
+
+ if (ws.wl_surface) {
+ wl_surface_destroy(ws.wl_surface);
+ }
+
+ // Before continuing, let's handle any leftover event that might still refer to
+ // this window.
+ wl_display_roundtrip(wl_display);
+
+ // We can already clean up here, we're done.
+ windows.erase(p_window_id);
}
struct wl_surface *WaylandThread::window_get_wl_surface(DisplayServer::WindowID p_window_id) const {
- // TODO: Use window IDs for multiwindow support.
- const WindowState &ws = main_window;
+ ERR_FAIL_COND_V(!windows.has(p_window_id), nullptr);
+ const WindowState &ws = windows[p_window_id];
return ws.wl_surface;
}
+WaylandThread::WindowState *WaylandThread::window_get_state(DisplayServer::WindowID p_window_id) {
+ return windows.getptr(p_window_id);
+}
+
void WaylandThread::beep() const {
if (registry.xdg_system_bell) {
xdg_system_bell_v1_ring(registry.xdg_system_bell, nullptr);
@@ -3355,8 +3595,8 @@ void WaylandThread::beep() const {
}
void WaylandThread::window_start_drag(DisplayServer::WindowID p_window_id) {
- // TODO: Use window IDs for multiwindow support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
if (ss && ws.xdg_toplevel) {
@@ -3371,8 +3611,8 @@ void WaylandThread::window_start_drag(DisplayServer::WindowID p_window_id) {
}
void WaylandThread::window_start_resize(DisplayServer::WindowResizeEdge p_edge, DisplayServer::WindowID p_window) {
- // TODO: Use window IDs for multiwindow support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowState &ws = windows[p_window];
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
if (ss && ws.xdg_toplevel) {
@@ -3444,9 +3684,34 @@ void WaylandThread::window_start_resize(DisplayServer::WindowResizeEdge p_edge,
#endif
}
+void WaylandThread::window_set_parent(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id) {
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ ERR_FAIL_COND(!windows.has(p_parent_id));
+
+ WindowState &child = windows[p_window_id];
+ child.parent_id = p_parent_id;
+
+ WindowState &parent = windows[p_parent_id];
+
+ // NOTE: We can't really unparent as, at the time of writing, libdecor
+ // segfaults when trying to set a null parent. Hopefully unparenting is not
+ // that common. Bummer.
+
+#ifdef LIBDECOR_ENABLED
+ if (child.libdecor_frame && parent.libdecor_frame) {
+ libdecor_frame_set_parent(child.libdecor_frame, parent.libdecor_frame);
+ return;
+ }
+#endif
+
+ if (child.xdg_toplevel && parent.xdg_toplevel) {
+ xdg_toplevel_set_parent(child.xdg_toplevel, parent.xdg_toplevel);
+ }
+}
+
void WaylandThread::window_set_max_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {
- // TODO: Use window IDs for multiwindow support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
Vector2i logical_max_size = p_size / window_state_get_scale_factor(&ws);
@@ -3464,8 +3729,8 @@ void WaylandThread::window_set_max_size(DisplayServer::WindowID p_window_id, con
}
void WaylandThread::window_set_min_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {
- // TODO: Use window IDs for multiwindow support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
Size2i logical_min_size = p_size / window_state_get_scale_factor(&ws);
@@ -3483,8 +3748,8 @@ void WaylandThread::window_set_min_size(DisplayServer::WindowID p_window_id, con
}
bool WaylandThread::window_can_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) const {
- // TODO: Use window IDs for multiwindow support.
- const WindowState &ws = main_window;
+ ERR_FAIL_COND_V(!windows.has(p_window_id), false);
+ const WindowState &ws = windows[p_window_id];
switch (p_window_mode) {
case DisplayServer::WINDOW_MODE_WINDOWED: {
@@ -3524,8 +3789,8 @@ bool WaylandThread::window_can_set_mode(DisplayServer::WindowID p_window_id, Dis
}
void WaylandThread::window_try_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) {
- // TODO: Use window IDs for multiwindow support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
if (ws.mode == p_window_mode) {
return;
@@ -3651,8 +3916,8 @@ void WaylandThread::window_try_set_mode(DisplayServer::WindowID p_window_id, Dis
}
void WaylandThread::window_set_borderless(DisplayServer::WindowID p_window_id, bool p_borderless) {
- // TODO: Use window IDs for multiwindow support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
if (ws.xdg_toplevel_decoration) {
if (p_borderless) {
@@ -3681,8 +3946,8 @@ void WaylandThread::window_set_borderless(DisplayServer::WindowID p_window_id, b
}
void WaylandThread::window_set_title(DisplayServer::WindowID p_window_id, const String &p_title) {
- // TODO: Use window IDs for multiwindow support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
#ifdef LIBDECOR_ENABLED
if (ws.libdecor_frame) {
@@ -3696,8 +3961,8 @@ void WaylandThread::window_set_title(DisplayServer::WindowID p_window_id, const
}
void WaylandThread::window_set_app_id(DisplayServer::WindowID p_window_id, const String &p_app_id) {
- // TODO: Use window IDs for multiwindow support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
#ifdef LIBDECOR_ENABLED
if (ws.libdecor_frame) {
@@ -3713,15 +3978,15 @@ void WaylandThread::window_set_app_id(DisplayServer::WindowID p_window_id, const
}
DisplayServer::WindowMode WaylandThread::window_get_mode(DisplayServer::WindowID p_window_id) const {
- // TODO: Use window IDs for multiwindow support.
- const WindowState &ws = main_window;
+ ERR_FAIL_COND_V(!windows.has(p_window_id), DisplayServer::WINDOW_MODE_WINDOWED);
+ const WindowState &ws = windows[p_window_id];
return ws.mode;
}
void WaylandThread::window_request_attention(DisplayServer::WindowID p_window_id) {
- // TODO: Use window IDs for multiwindow support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
if (registry.xdg_activation) {
// Window attention requests are done through the XDG activation protocol.
@@ -3732,8 +3997,8 @@ void WaylandThread::window_request_attention(DisplayServer::WindowID p_window_id
}
void WaylandThread::window_set_idle_inhibition(DisplayServer::WindowID p_window_id, bool p_enable) {
- // TODO: Use window IDs for multiwindow support.
- WindowState &ws = main_window;
+ ERR_FAIL_COND(!windows.has(p_window_id));
+ WindowState &ws = windows[p_window_id];
if (p_enable) {
if (ws.registry->wp_idle_inhibit_manager && !ws.wp_idle_inhibitor) {
@@ -3749,8 +4014,8 @@ void WaylandThread::window_set_idle_inhibition(DisplayServer::WindowID p_window_
}
bool WaylandThread::window_get_idle_inhibition(DisplayServer::WindowID p_window_id) const {
- // TODO: Use window IDs for multiwindow support.
- const WindowState &ws = main_window;
+ ERR_FAIL_COND_V(!windows.has(p_window_id), false);
+ const WindowState &ws = windows[p_window_id];
return ws.wp_idle_inhibitor != nullptr;
}
@@ -3769,11 +4034,70 @@ DisplayServer::WindowID WaylandThread::pointer_get_pointed_window_id() const {
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
if (ss) {
- WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+ // Let's determine the most recently used tablet tool.
+ TabletToolState *max_ts = nullptr;
+ for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ TabletToolState *ts = wp_tablet_tool_get_state(tool);
+ ERR_CONTINUE(ts == nullptr);
- if (ws) {
- return ws->id;
+ TabletToolData &td = ts->data;
+
+ if (!max_ts) {
+ max_ts = ts;
+ continue;
+ }
+
+ if (MAX(td.button_time, td.motion_time) > MAX(max_ts->data.button_time, max_ts->data.motion_time)) {
+ max_ts = ts;
+ }
}
+
+ const PointerData &pd = ss->pointer_data;
+
+ if (max_ts) {
+ TabletToolData &td = max_ts->data;
+ if (MAX(td.button_time, td.motion_time) > MAX(pd.button_time, pd.motion_time)) {
+ return td.proximal_id;
+ }
+ }
+
+ return ss->pointer_data.pointed_id;
+ }
+
+ return DisplayServer::INVALID_WINDOW_ID;
+}
+DisplayServer::WindowID WaylandThread::pointer_get_last_pointed_window_id() const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ // Let's determine the most recently used tablet tool.
+ TabletToolState *max_ts = nullptr;
+ for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ TabletToolState *ts = wp_tablet_tool_get_state(tool);
+ ERR_CONTINUE(ts == nullptr);
+
+ TabletToolData &td = ts->data;
+
+ if (!max_ts) {
+ max_ts = ts;
+ continue;
+ }
+
+ if (MAX(td.button_time, td.motion_time) > MAX(max_ts->data.button_time, max_ts->data.motion_time)) {
+ max_ts = ts;
+ }
+ }
+
+ const PointerData &pd = ss->pointer_data;
+
+ if (max_ts) {
+ TabletToolData &td = max_ts->data;
+ if (MAX(td.button_time, td.motion_time) > MAX(pd.button_time, pd.motion_time)) {
+ return td.last_proximal_id;
+ }
+ }
+
+ return ss->pointer_data.last_pointed_id;
}
return DisplayServer::INVALID_WINDOW_ID;
@@ -3801,7 +4125,7 @@ void WaylandThread::pointer_set_hint(const Point2i &p_hint) {
return;
}
- WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+ WindowState *ws = window_get_state(ss->pointer_data.pointed_id);
int hint_x = 0;
int hint_y = 0;
@@ -4246,7 +4570,7 @@ void WaylandThread::primary_set_text(const String &p_text) {
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
if (registry.wp_primary_selection_device_manager == nullptr) {
- DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, protocol not available");
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, protocol not available.");
return;
}
@@ -4278,7 +4602,9 @@ void WaylandThread::primary_set_text(const String &p_text) {
}
void WaylandThread::commit_surfaces() {
- wl_surface_commit(main_window.wl_surface);
+ for (KeyValue &pair : windows) {
+ wl_surface_commit(pair.value.wl_surface);
+ }
}
void WaylandThread::set_frame() {
@@ -4295,9 +4621,19 @@ bool WaylandThread::get_reset_frame() {
// 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) {
- if (main_window.suspended) {
- // The window is suspended! The compositor is telling us _explicitly_ that we
- // don't need to draw, without letting us guess through the frame event's
+ // This is a bit of a chicken and egg thing... Looks like the main event loop
+ // has to call its rightfully forever-blocking poll right in between
+ // `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 we the mutex is locked (it should) and unblock the
+ // main thread with a roundtrip!
+ MutexLock mutex_lock(mutex);
+ wl_display_roundtrip(wl_display);
+
+ if (is_suspended()) {
+ // All windows are suspended! The compositor is telling us _explicitly_ that
+ // we don't need to draw, without letting us guess through the frame event's
// timing and stuff like that. Our job here is done.
return false;
}
@@ -4323,7 +4659,7 @@ bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {
break;
}
- if (main_window.suspended) {
+ if (is_suspended()) {
return false;
}
@@ -4368,7 +4704,7 @@ bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {
// Let's try dispatching now...
wl_display_dispatch_pending(wl_display);
- if (main_window.suspended) {
+ if (is_suspended()) {
return false;
}
@@ -4384,8 +4720,24 @@ bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {
return false;
}
+uint64_t WaylandThread::window_get_last_frame_time(DisplayServer::WindowID p_window_id) const {
+ ERR_FAIL_COND_V(!windows.has(p_window_id), false);
+ return windows[p_window_id].last_frame_time;
+}
+
+bool WaylandThread::window_is_suspended(DisplayServer::WindowID p_window_id) const {
+ ERR_FAIL_COND_V(!windows.has(p_window_id), false);
+ return windows[p_window_id].suspended;
+}
+
bool WaylandThread::is_suspended() const {
- return main_window.suspended;
+ for (const KeyValue &E : windows) {
+ if (!E.value.suspended) {
+ return false;
+ }
+ }
+
+ return true;
}
void WaylandThread::destroy() {
@@ -4403,34 +4755,37 @@ void WaylandThread::destroy() {
events_thread.wait_to_finish();
}
- if (main_window.wp_fractional_scale) {
- wp_fractional_scale_v1_destroy(main_window.wp_fractional_scale);
- }
+ for (KeyValue &pair : windows) {
+ WindowState &ws = pair.value;
+ if (ws.wp_fractional_scale) {
+ wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);
+ }
- if (main_window.wp_viewport) {
- wp_viewport_destroy(main_window.wp_viewport);
- }
+ if (ws.wp_viewport) {
+ wp_viewport_destroy(ws.wp_viewport);
+ }
- if (main_window.frame_callback) {
- wl_callback_destroy(main_window.frame_callback);
- }
+ if (ws.frame_callback) {
+ wl_callback_destroy(ws.frame_callback);
+ }
#ifdef LIBDECOR_ENABLED
- if (main_window.libdecor_frame) {
- libdecor_frame_close(main_window.libdecor_frame);
- }
+ if (ws.libdecor_frame) {
+ libdecor_frame_close(ws.libdecor_frame);
+ }
#endif // LIBDECOR_ENABLED
- if (main_window.xdg_toplevel) {
- xdg_toplevel_destroy(main_window.xdg_toplevel);
- }
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_destroy(ws.xdg_toplevel);
+ }
- if (main_window.xdg_surface) {
- xdg_surface_destroy(main_window.xdg_surface);
- }
+ if (ws.xdg_surface) {
+ xdg_surface_destroy(ws.xdg_surface);
+ }
- if (main_window.wl_surface) {
- wl_surface_destroy(main_window.wl_surface);
+ if (ws.wl_surface) {
+ wl_surface_destroy(ws.wl_surface);
+ }
}
for (struct wl_seat *wl_seat : registry.wl_seats) {
diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h
index 5172bf4087b7..0d554fc70f5c 100644
--- a/platform/linuxbsd/wayland/wayland_thread.h
+++ b/platform/linuxbsd/wayland/wayland_thread.h
@@ -94,15 +94,20 @@ class WaylandThread {
virtual ~Message() = default;
};
+ class WindowMessage : public Message {
+ public:
+ DisplayServer::WindowID id = DisplayServer::INVALID_WINDOW_ID;
+ };
+
// Message data for window rect changes.
- class WindowRectMessage : public Message {
+ class WindowRectMessage : public WindowMessage {
public:
// NOTE: This is in "scaled" terms. For example, if there's a 1920x1080 rect
// with a scale factor of 2, the actual value of `rect` will be 3840x2160.
Rect2i rect;
};
- class WindowEventMessage : public Message {
+ class WindowEventMessage : public WindowMessage {
public:
DisplayServer::WindowEvent event;
};
@@ -112,18 +117,18 @@ class WaylandThread {
Ref event;
};
- class DropFilesEventMessage : public Message {
+ class DropFilesEventMessage : public WindowMessage {
public:
Vector files;
};
- class IMEUpdateEventMessage : public Message {
+ class IMEUpdateEventMessage : public WindowMessage {
public:
String text;
Vector2i selection;
};
- class IMECommitEventMessage : public Message {
+ class IMECommitEventMessage : public WindowMessage {
public:
String text;
};
@@ -202,7 +207,8 @@ class WaylandThread {
// TODO: Make private?
struct WindowState {
- DisplayServer::WindowID id;
+ DisplayServer::WindowID id = DisplayServer::INVALID_WINDOW_ID;
+ DisplayServer::WindowID parent_id = DisplayServer::INVALID_WINDOW_ID;
Rect2i rect;
DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED;
@@ -224,6 +230,7 @@ class WaylandThread {
// be called even after being destroyed, pointing to probably invalid window
// data by then and segfaulting hard.
struct wl_callback *frame_callback = nullptr;
+ uint64_t last_frame_time = 0;
struct wl_surface *wl_surface = nullptr;
struct xdg_surface *xdg_surface = nullptr;
@@ -237,6 +244,8 @@ class WaylandThread {
struct zxdg_exported_v2 *xdg_exported_v2 = nullptr;
+ struct xdg_popup *xdg_popup = nullptr;
+
String exported_handle;
// Currently applied buffer scale.
@@ -322,6 +331,9 @@ class WaylandThread {
MouseButton last_button_pressed = MouseButton::NONE;
Point2 last_pressed_position;
+ DisplayServer::WindowID pointed_id = DisplayServer::INVALID_WINDOW_ID;
+ DisplayServer::WindowID last_pointed_id = DisplayServer::INVALID_WINDOW_ID;
+
// This is needed to check for a new double click every time.
bool double_click_begun = false;
@@ -351,20 +363,17 @@ class WaylandThread {
bool double_click_begun = false;
- // Note: the protocol doesn't have it (I guess that this isn't really meant to
- // be used as a mouse...), but we'll hack one in with the current ticks.
uint64_t button_time = 0;
-
uint64_t motion_time = 0;
+ DisplayServer::WindowID proximal_id = DisplayServer::INVALID_WINDOW_ID;
+ DisplayServer::WindowID last_proximal_id = DisplayServer::INVALID_WINDOW_ID;
uint32_t proximity_serial = 0;
- struct wl_surface *proximal_surface = nullptr;
};
struct TabletToolState {
struct wl_seat *wl_seat = nullptr;
- struct wl_surface *last_surface = nullptr;
bool is_eraser = false;
TabletToolData data_pending;
@@ -388,9 +397,6 @@ class WaylandThread {
uint32_t pointer_enter_serial = 0;
- struct wl_surface *pointed_surface = nullptr;
- struct wl_surface *last_pointed_surface = nullptr;
-
struct zwp_relative_pointer_v1 *wp_relative_pointer = nullptr;
struct zwp_locked_pointer_v1 *wp_locked_pointer = nullptr;
struct zwp_confined_pointer_v1 *wp_confined_pointer = nullptr;
@@ -421,6 +427,9 @@ class WaylandThread {
// Keyboard.
struct wl_keyboard *wl_keyboard = nullptr;
+ // For key events.
+ DisplayServer::WindowID focused_id = DisplayServer::INVALID_WINDOW_ID;
+
struct xkb_context *xkb_context = nullptr;
struct xkb_keymap *xkb_keymap = nullptr;
struct xkb_state *xkb_state = nullptr;
@@ -447,6 +456,7 @@ class WaylandThread {
struct wl_data_device *wl_data_device = nullptr;
// Drag and drop.
+ DisplayServer::WindowID dnd_id = DisplayServer::INVALID_WINDOW_ID;
struct wl_data_offer *wl_data_offer_dnd = nullptr;
uint32_t dnd_enter_serial = 0;
@@ -471,6 +481,7 @@ class WaylandThread {
// IME.
struct zwp_text_input_v3 *wp_text_input = nullptr;
+ DisplayServer::WindowID ime_window_id = DisplayServer::INVALID_WINDOW_ID;
bool ime_enabled = false;
bool ime_active = false;
String ime_text;
@@ -501,7 +512,7 @@ class WaylandThread {
Thread events_thread;
ThreadData thread_data;
- WindowState main_window;
+ HashMap windows;
List[> messages;
@@ -613,6 +624,10 @@ class WaylandThread {
static void _xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height);
static void _xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities);
+ static void _xdg_popup_on_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height);
+ static void _xdg_popup_on_popup_done(void *data, struct xdg_popup *xdg_popup);
+ static void _xdg_popup_on_repositioned(void *data, struct xdg_popup *xdg_popup, uint32_t token);
+
// wayland-protocols event handlers.
static void _wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale);
@@ -768,6 +783,12 @@ class WaylandThread {
.wm_capabilities = _xdg_toplevel_on_wm_capabilities,
};
+ static constexpr struct xdg_popup_listener xdg_popup_listener = {
+ .configure = _xdg_popup_on_configure,
+ .popup_done = _xdg_popup_on_popup_done,
+ .repositioned = _xdg_popup_on_repositioned,
+ };
+
// wayland-protocols event listeners.
static constexpr struct wp_fractional_scale_v1_listener wp_fractional_scale_listener = {
.preferred_scale = _wp_fractional_scale_on_preferred_scale,
@@ -952,8 +973,13 @@ class WaylandThread {
void beep() const;
void window_create(DisplayServer::WindowID p_window_id, int p_width, int p_height);
+ void window_create_popup(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id, Rect2i p_rect);
+ void window_destroy(DisplayServer::WindowID p_window_Id);
+
+ void window_set_parent(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id);
struct wl_surface *window_get_wl_surface(DisplayServer::WindowID p_window_id) const;
+ WindowState *window_get_state(DisplayServer::WindowID p_window_id);
void window_start_resize(DisplayServer::WindowResizeEdge p_edge, DisplayServer::WindowID p_window);
@@ -986,6 +1012,7 @@ class WaylandThread {
void pointer_set_hint(const Point2i &p_hint);
PointerConstraint pointer_get_constraint() const;
DisplayServer::WindowID pointer_get_pointed_window_id() const;
+ DisplayServer::WindowID pointer_get_last_pointed_window_id() const;
BitField pointer_get_button_mask() const;
void cursor_set_visible(bool p_visible);
@@ -1024,6 +1051,8 @@ class WaylandThread {
bool get_reset_frame();
bool wait_frame_suspend_ms(int p_timeout);
+ uint64_t window_get_last_frame_time(DisplayServer::WindowID p_window_id) const;
+ bool window_is_suspended(DisplayServer::WindowID p_window_id) const;
bool is_suspended() const;
Error init();
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index 94d7303ae504..6f5f5d361c95 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -167,6 +167,11 @@ Rect2i Popup::_popup_adjust_rect() const {
Rect2i current(get_position(), get_size());
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SELF_FITTING_WINDOWS)) {
+ // We're fine as is, the Display Server will take care of that for us.
+ return current;
+ }
+
if (current.position.x + current.size.x > parent_rect.position.x + parent_rect.size.x) {
current.position.x = parent_rect.position.x + parent_rect.size.x - current.size.x;
}
@@ -219,6 +224,7 @@ Popup::Popup() {
set_flag(FLAG_BORDERLESS, true);
set_flag(FLAG_RESIZE_DISABLED, true);
set_flag(FLAG_POPUP, true);
+ set_flag(FLAG_POPUP_WM_HINT, true);
}
Popup::~Popup() {
@@ -419,6 +425,7 @@ void PopupPanel::_bind_methods() {
PopupPanel::PopupPanel() {
set_flag(FLAG_TRANSPARENT, true);
+ set_flag(FLAG_POPUP_WM_HINT, true);
panel = memnew(Panel);
panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 1bb01313e721..8897fbb3dfd5 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -836,6 +836,9 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) {
case DisplayServer::WINDOW_EVENT_TITLEBAR_CHANGE: {
emit_signal(SNAME("titlebar_changed"));
} break;
+ case DisplayServer::WINDOW_EVENT_FORCE_CLOSE: {
+ hide();
+ } break;
}
}
@@ -1860,12 +1863,19 @@ void Window::popup(const Rect2i &p_screen_rect) {
// Update window size to calculate the actual window size based on contents minimum size and minimum size.
_update_window_size();
+ bool should_fit = !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SELF_FITTING_WINDOWS);
+
if (p_screen_rect != Rect2i()) {
set_position(p_screen_rect.position);
- int screen_id = DisplayServer::get_singleton()->get_screen_from_rect(p_screen_rect);
- Size2i screen_size = DisplayServer::get_singleton()->screen_get_usable_rect(screen_id).size;
- Size2i new_size = p_screen_rect.size.min(screen_size);
- set_size(new_size);
+
+ if (should_fit) {
+ int screen_id = DisplayServer::get_singleton()->get_screen_from_rect(p_screen_rect);
+ Size2i screen_size = DisplayServer::get_singleton()->screen_get_usable_rect(screen_id).size;
+ Size2i new_size = p_screen_rect.size.min(screen_size);
+ set_size(new_size);
+ } else {
+ set_size(p_screen_rect.size);
+ }
}
Rect2i adjust = _popup_adjust_rect();
@@ -1893,7 +1903,7 @@ void Window::popup(const Rect2i &p_screen_rect) {
int screen_id = DisplayServer::get_singleton()->window_get_current_screen(get_window_id());
parent_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen_id);
}
- if (parent_rect != Rect2i() && !parent_rect.intersects(Rect2i(position, size))) {
+ if (should_fit && parent_rect != Rect2i() && !parent_rect.intersects(Rect2i(position, size))) {
ERR_PRINT(vformat("Window %d spawned at invalid position: %s.", get_window_id(), position));
set_position((parent_rect.size - size) / 2);
}
diff --git a/scene/main/window.h b/scene/main/window.h
index 285fd95db103..53d51b521281 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -65,6 +65,7 @@ class Window : public Viewport {
FLAG_MOUSE_PASSTHROUGH = DisplayServer::WINDOW_FLAG_MOUSE_PASSTHROUGH,
FLAG_SHARP_CORNERS = DisplayServer::WINDOW_FLAG_SHARP_CORNERS,
FLAG_EXCLUDE_FROM_CAPTURE = DisplayServer::WINDOW_FLAG_EXCLUDE_FROM_CAPTURE,
+ FLAG_POPUP_WM_HINT = DisplayServer::WINDOW_FLAG_POPUP_WM_HINT,
FLAG_MAX = DisplayServer::WINDOW_FLAG_MAX,
};
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index bf3d428420a7..aacdb4ad800f 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -1108,6 +1108,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_WINDOW_EMBEDDING);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE_MIME);
BIND_ENUM_CONSTANT(FEATURE_EMOJI_AND_SYMBOL_PICKER);
+ BIND_ENUM_CONSTANT(FEATURE_SELF_FITTING_WINDOWS);
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
@@ -1183,6 +1184,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(WINDOW_FLAG_MOUSE_PASSTHROUGH);
BIND_ENUM_CONSTANT(WINDOW_FLAG_SHARP_CORNERS);
BIND_ENUM_CONSTANT(WINDOW_FLAG_EXCLUDE_FROM_CAPTURE);
+ BIND_ENUM_CONSTANT(WINDOW_FLAG_POPUP_WM_HINT);
BIND_ENUM_CONSTANT(WINDOW_FLAG_MAX);
BIND_ENUM_CONSTANT(WINDOW_EVENT_MOUSE_ENTER);
@@ -1193,6 +1195,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(WINDOW_EVENT_GO_BACK_REQUEST);
BIND_ENUM_CONSTANT(WINDOW_EVENT_DPI_CHANGE);
BIND_ENUM_CONSTANT(WINDOW_EVENT_TITLEBAR_CHANGE);
+ BIND_ENUM_CONSTANT(WINDOW_EVENT_FORCE_CLOSE);
BIND_ENUM_CONSTANT(WINDOW_EDGE_TOP_LEFT);
BIND_ENUM_CONSTANT(WINDOW_EDGE_TOP);
diff --git a/servers/display_server.h b/servers/display_server.h
index 30bb533a5d39..18f68b3924b8 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -162,6 +162,7 @@ class DisplayServer : public Object {
FEATURE_WINDOW_EMBEDDING,
FEATURE_NATIVE_DIALOG_FILE_MIME,
FEATURE_EMOJI_AND_SYMBOL_PICKER,
+ FEATURE_SELF_FITTING_WINDOWS,
};
virtual bool has_feature(Feature p_feature) const = 0;
@@ -402,6 +403,7 @@ class DisplayServer : public Object {
WINDOW_FLAG_MOUSE_PASSTHROUGH,
WINDOW_FLAG_SHARP_CORNERS,
WINDOW_FLAG_EXCLUDE_FROM_CAPTURE,
+ WINDOW_FLAG_POPUP_WM_HINT,
WINDOW_FLAG_MAX,
};
@@ -417,6 +419,7 @@ class DisplayServer : public Object {
WINDOW_FLAG_MOUSE_PASSTHROUGH_BIT = (1 << WINDOW_FLAG_MOUSE_PASSTHROUGH),
WINDOW_FLAG_SHARP_CORNERS_BIT = (1 << WINDOW_FLAG_SHARP_CORNERS),
WINDOW_FLAG_EXCLUDE_FROM_CAPTURE_BIT = (1 << WINDOW_FLAG_EXCLUDE_FROM_CAPTURE),
+ WINDOW_FLAG_POPUP_WM_HINT_BIT = (1 << WINDOW_FLAG_POPUP_WM_HINT),
};
virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i(), bool p_exclusive = false, WindowID p_transient_parent = INVALID_WINDOW_ID);
@@ -445,6 +448,7 @@ class DisplayServer : public Object {
WINDOW_EVENT_GO_BACK_REQUEST,
WINDOW_EVENT_DPI_CHANGE,
WINDOW_EVENT_TITLEBAR_CHANGE,
+ WINDOW_EVENT_FORCE_CLOSE,
};
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0;
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0;
]