diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 1ff4e1a5e7d..a4ccb13ed48 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -1958,6 +1958,9 @@
Display server supports native color picker. [b]Linux (X11/Wayland)[/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.
@@ -2181,7 +2184,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 an implementation-defined "popup" (usually a floating, borderless, untileable and immovable child window).
+
+
Max value of the [enum WindowFlags].
@@ -2211,6 +2217,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/Popup.xml b/doc/classes/Popup.xml
index 29b44a98f21..a7ca692aa0d 100644
--- a/doc/classes/Popup.xml
+++ b/doc/classes/Popup.xml
@@ -11,6 +11,7 @@
+
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index d6fd57ce443..3e0a26c088d 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -670,6 +670,9 @@
If [code]true[/code], the [Window] will be considered a popup. Popups are sub-windows that don't show as separate windows in system's window manager's window list and will send close request when anything is clicked outside of them (unless [member exclusive] is enabled).
+
+ If [code]true[/code], the [Window] will signal to the window manager that it is supposed to be an implementation-defined "popup" (usually a floating, borderless, untileable and immovable child window).
+
The window's position in pixels.
If [member ProjectSettings.display/window/subwindows/embed_subwindows] is [code]false[/code], the position is in absolute screen coordinates. This typically applies to editor plugins. If the setting is [code]true[/code], the window's position is in the coordinates of its parent [Viewport].
@@ -879,7 +882,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 an implementation-defined "popup" (usually a floating, borderless, untileable and immovable child window).
+
+
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 1d1d9ee33f0..c41c1c594f5 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,58 @@ 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);
+ }
+ }
+ } else {
+ // Send to all windows. Copy all pending callbacks, since callback can erase window.
+ Vector cbs;
+ for (KeyValue &E : windows) {
+ Callable callable = E.value.input_event_callback;
+ if (callable.is_valid()) {
+ cbs.push_back(callable);
+ }
+ }
+
+ for (const Callable &cb : cbs) {
+ cb.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));
- wd.rect.size = p_size;
+ WindowData &wd = windows[p_window_id];
+
+ 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 +156,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 +176,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;
@@ -339,18 +307,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 p_window_id) {
- WindowID window_id = MAIN_WINDOW_ID;
- // TODO: Use window IDs for multiwindow support.
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowID window_id = p_window_id;
+ if (!windows.has(window_id) || window_get_flag(WINDOW_FLAG_POPUP_WM_HINT, 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 p_window_id) {
- WindowID window_id = MAIN_WINDOW_ID;
- // TODO: Use window IDs for multiwindow support.
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowID window_id = p_window_id;
+ if (!windows.has(window_id) || window_get_flag(WINDOW_FLAG_POPUP_WM_HINT, 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);
}
@@ -393,6 +373,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;
}
@@ -453,6 +439,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();
@@ -650,6 +642,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;
}
@@ -668,6 +662,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
@@ -679,11 +674,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);
@@ -735,23 +942,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) {
@@ -762,31 +977,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 {
@@ -801,16 +1026,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) {
@@ -826,7 +1048,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))) {
@@ -836,25 +1059,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) {
@@ -862,7 +1105,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!");
@@ -876,23 +1120,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 {
@@ -901,13 +1158,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;
@@ -919,7 +1178,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;
@@ -937,13 +1197,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: {
@@ -960,7 +1231,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) {
@@ -968,7 +1240,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) {
@@ -980,6 +1252,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;
}
@@ -990,13 +1275,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 {
@@ -1014,13 +1299,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);
}
@@ -1031,9 +1318,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);
}
@@ -1042,7 +1329,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;
}
@@ -1210,13 +1498,22 @@ bool DisplayServerWayland::color_picker(const Callable &p_callback) {
}
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;
}
@@ -1242,14 +1539,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()) {
@@ -1261,16 +1570,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;
@@ -1282,6 +1629,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;
@@ -1291,7 +1639,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);
@@ -1305,6 +1653,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;
@@ -1315,6 +1664,7 @@ void DisplayServerWayland::process_events() {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
}
+ continue;
}
}
@@ -1641,7 +1991,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;
@@ -1650,7 +2000,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) {
@@ -1680,36 +2030,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 0fa4b9d6a61..03a90111da2 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.h
+++ b/platform/linuxbsd/wayland/display_server_wayland.h
@@ -68,7 +68,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;
@@ -76,6 +84,8 @@ class DisplayServerWayland : public DisplayServer {
Rect2i safe_rect;
+ bool emulate_vsync = false;
+
#ifdef GLES3_ENABLED
struct wl_egl_window *wl_egl_window = nullptr;
#endif
@@ -119,17 +129,25 @@ class DisplayServerWayland : public DisplayServer {
HashMap custom_cursors;
- WindowData main_window;
+ HashMap windows;
+ WindowID window_id_counter = MAIN_WINDOW_ID;
+
WaylandThread wayland_thread;
Context context;
bool swap_cancel_ok = false;
+ // 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();
@@ -227,6 +243,14 @@ public:
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 2db257e44a4..0e02656941b 100644
--- a/platform/linuxbsd/wayland/wayland_thread.cpp
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -235,7 +235,7 @@ Ref WaylandThread::_seat_state_get_key_event(SeatState *p_ss, xkb
event.instantiate();
- event->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ event->set_window_id(p_ss->focused_id);
// Set all pressed modifiers.
event->set_shift_pressed(p_ss->shift_pressed);
@@ -727,16 +727,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;
+ 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 (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;
@@ -745,16 +746,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;
@@ -1058,9 +1061,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) {
@@ -1177,7 +1181,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) {
@@ -1223,6 +1227,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);
}
@@ -1258,6 +1263,66 @@ 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));
+
+ double parent_scale = window_state_get_scale_factor(parent);
+
+ ws->rect.position = pos;
+
+ Ref rect_msg;
+ rect_msg.instantiate();
+ rect_msg->id = ws->id;
+ rect_msg->rect.position = scale_vector2i(ws->rect.position, parent_scale);
+ rect_msg->rect.size = scale_vector2i(ws->rect.size, parent_scale);
+
+ 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;
@@ -1340,6 +1405,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);
@@ -1460,73 +1526,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;
}
@@ -1534,11 +1588,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;
@@ -1586,11 +1635,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) {
@@ -1610,19 +1654,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();
@@ -1633,17 +1704,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_button_mask(pd.pressed_button_mask);
- mm->set_position(pd.position);
- mm->set_global_position(pd.position);
+ mm->set_window_id(ws->id);
- Vector2 pos_delta = pd.position - old_pd.position;
+ mm->set_button_mask(pd.pressed_button_mask);
+
+ 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
@@ -1652,7 +1725,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());
@@ -1690,9 +1763,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);
@@ -1732,9 +1805,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
@@ -1760,7 +1833,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);
}
@@ -1782,9 +1855,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);
@@ -1815,11 +1888,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;
}
@@ -1833,11 +1901,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
@@ -1857,11 +1920,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) {
@@ -1902,21 +1960,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);
@@ -1925,16 +2001,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);
@@ -2002,9 +2095,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;
@@ -2021,6 +2121,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;
}
}
@@ -2040,6 +2141,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);
@@ -2056,6 +2158,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) {
@@ -2163,21 +2266,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;
}
@@ -2195,16 +2288,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);
@@ -2212,7 +2317,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));
@@ -2228,15 +2333,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;
@@ -2341,7 +2444,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);
}
@@ -2394,67 +2496,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.
+ // NOTE: Works pretty much like wl_pointer::enter.
+
+ WindowState *ws = wl_surface_get_window_state(surface);
+ if (!ws) {
return;
}
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) {
- return;
- }
-
- WaylandThread *wayland_thread = ss->wayland_thread;
- ERR_FAIL_NULL(wayland_thread);
+ ERR_FAIL_NULL(ts);
ts->data_pending.proximity_serial = serial;
- ts->data_pending.proximal_surface = surface;
- ts->last_surface = surface;
+ ts->data_pending.proximal_id = ws->id;
+ ts->data_pending.last_proximal_id = ws->id;
- Ref msg;
- msg.instantiate();
- msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
- wayland_thread->push_message(msg);
-
- 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.
+ ERR_FAIL_NULL(ts);
+
+ if (ts->data_pending.proximal_id == DisplayServer::INVALID_WINDOW_ID) {
+ // We're probably on a decoration or some other third-party thing.
return;
}
- SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
- if (!ss) {
- return;
- }
+ DisplayServer::WindowID id = ts->data_pending.proximal_id;
- WaylandThread *wayland_thread = ss->wayland_thread;
- ERR_FAIL_NULL(wayland_thread);
+ ts->data_pending.proximal_id = DisplayServer::INVALID_WINDOW_ID;
+ ts->data_pending.pressed_button_mask.clear();
- ts->data_pending.proximal_surface = nullptr;
-
- Ref msg;
- msg.instantiate();
- msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
-
- wayland_thread->push_message(msg);
-
- 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;
@@ -2468,10 +2551,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;
@@ -2483,35 +2567,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) {
+ // NOTE: Works pretty much like wl_pointer::motion.
+
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;
- }
-
- WindowState *ws = wl_surface_get_window_state(ts->data_pending.proximal_surface);
- ERR_FAIL_NULL(ws);
+ 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;
}
@@ -2522,9 +2591,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;
@@ -2545,10 +2612,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;
@@ -2580,15 +2647,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);
@@ -2596,11 +2661,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);
@@ -2610,8 +2708,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.
@@ -2629,10 +2727,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);
@@ -2645,6 +2744,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 }) {
@@ -2660,9 +2761,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);
@@ -2675,7 +2776,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);
}
@@ -2699,6 +2800,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;
}
@@ -2708,17 +2815,19 @@ void WaylandThread::_wp_text_input_on_leave(void *data, struct zwp_text_input_v3
return;
}
+ Ref msg;
+ msg.instantiate();
+ msg->id = ss->ime_window_id;
+ msg->text = String();
+ msg->selection = Vector2i();
+ ss->wayland_thread->push_message(msg);
+
+ ss->ime_window_id = DisplayServer::INVALID_WINDOW_ID;
ss->ime_enabled = false;
ss->ime_active = false;
ss->ime_text = String();
ss->ime_text_commit = String();
ss->ime_cursor = Vector2i();
-
- Ref msg;
- msg.instantiate();
- msg->text = String();
- msg->selection = Vector2i();
- ss->wayland_thread->push_message(msg);
}
void WaylandThread::_wp_text_input_on_preedit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text, int32_t cursor_begin, int32_t cursor_end) {
@@ -2791,15 +2900,18 @@ void WaylandThread::_wp_text_input_on_done(void *data, struct zwp_text_input_v3
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);
}
+
ss->ime_text = String();
ss->ime_text_commit = String();
ss->ime_cursor = Vector2i();
@@ -3025,8 +3137,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;
@@ -3051,7 +3163,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);
}
@@ -3071,7 +3183,8 @@ void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int
#endif
if (size_changed || scale_changed) {
- Size2i scaled_size = scale_vector2i(p_ws->rect.size, window_state_get_scale_factor(p_ws));
+ double win_scale = window_state_get_scale_factor(p_ws);
+ Size2i scaled_size = scale_vector2i(p_ws->rect.size, win_scale);
if (using_fractional) {
DEBUG_LOG_WAYLAND_THREAD(vformat("Resizing the window from %s to %s (fractional scale x%f).", p_ws->rect.size, scaled_size, p_ws->fractional_scale));
@@ -3084,7 +3197,8 @@ void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int
Ref rect_msg;
rect_msg.instantiate();
- rect_msg->rect = p_ws->rect;
+ rect_msg->id = p_ws->id;
+ rect_msg->rect.position = scale_vector2i(p_ws->rect.position, win_scale);
rect_msg->rect.size = scaled_size;
p_ws->wayland_thread->push_message(rect_msg);
}
@@ -3092,6 +3206,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);
}
@@ -3137,12 +3252,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);
@@ -3169,12 +3279,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);
@@ -3319,8 +3424,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;
@@ -3385,15 +3492,152 @@ 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];
+
+ double parent_scale = window_state_get_scale_factor(&parent);
+
+ p_rect.position = scale_vector2i(p_rect.position, 1.0 / parent_scale);
+ p_rect.size = scale_vector2i(p_rect.size, 1.0 / parent_scale);
+
+ 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);
@@ -3401,8 +3645,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) {
@@ -3417,8 +3661,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) {
@@ -3490,9 +3734,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);
@@ -3510,8 +3779,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);
@@ -3529,8 +3798,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: {
@@ -3570,8 +3839,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;
@@ -3697,8 +3966,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) {
@@ -3727,8 +3996,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) {
@@ -3742,8 +4011,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) {
@@ -3759,15 +4028,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.
@@ -3778,8 +4047,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) {
@@ -3795,8 +4064,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;
}
@@ -3815,11 +4084,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;
@@ -3847,7 +4175,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;
@@ -4289,7 +4617,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;
}
@@ -4321,7 +4649,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() {
@@ -4348,9 +4678,9 @@ bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {
MutexLock mutex_lock(mutex);
wl_display_roundtrip(wl_display);
- 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
+ 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;
}
@@ -4376,7 +4706,7 @@ bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {
break;
}
- if (main_window.suspended) {
+ if (is_suspended()) {
return false;
}
@@ -4421,7 +4751,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;
}
@@ -4437,8 +4767,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() {
@@ -4456,34 +4802,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 685828e22ff..815201967de 100644
--- a/platform/linuxbsd/wayland/wayland_thread.h
+++ b/platform/linuxbsd/wayland/wayland_thread.h
@@ -95,8 +95,15 @@ public:
virtual ~Message() = default;
};
+ class WindowMessage : public Message {
+ GDSOFTCLASS(WindowMessage, Message);
+
+ public:
+ DisplayServer::WindowID id = DisplayServer::INVALID_WINDOW_ID;
+ };
+
// Message data for window rect changes.
- class WindowRectMessage : public Message {
+ class WindowRectMessage : public WindowMessage {
GDSOFTCLASS(WindowRectMessage, Message);
public:
@@ -105,7 +112,7 @@ public:
Rect2i rect;
};
- class WindowEventMessage : public Message {
+ class WindowEventMessage : public WindowMessage {
GDSOFTCLASS(WindowEventMessage, Message);
public:
@@ -119,14 +126,14 @@ public:
Ref event;
};
- class DropFilesEventMessage : public Message {
+ class DropFilesEventMessage : public WindowMessage {
GDSOFTCLASS(DropFilesEventMessage, Message);
public:
Vector files;
};
- class IMEUpdateEventMessage : public Message {
+ class IMEUpdateEventMessage : public WindowMessage {
GDSOFTCLASS(IMEUpdateEventMessage, Message);
public:
@@ -134,7 +141,7 @@ public:
Vector2i selection;
};
- class IMECommitEventMessage : public Message {
+ class IMECommitEventMessage : public WindowMessage {
GDSOFTCLASS(IMECommitEventMessage, Message);
public:
@@ -215,7 +222,8 @@ public:
// 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;
@@ -237,6 +245,7 @@ public:
// 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;
@@ -250,6 +259,8 @@ public:
struct zxdg_exported_v2 *xdg_exported_v2 = nullptr;
+ struct xdg_popup *xdg_popup = nullptr;
+
String exported_handle;
// Currently applied buffer scale.
@@ -335,6 +346,9 @@ public:
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;
@@ -364,20 +378,17 @@ public:
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;
@@ -401,9 +412,6 @@ public:
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;
@@ -434,6 +442,9 @@ public:
// 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;
@@ -462,6 +473,7 @@ public:
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;
@@ -486,6 +498,7 @@ public:
// 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;
@@ -516,7 +529,7 @@ private:
Thread events_thread;
ThreadData thread_data;
- WindowState main_window;
+ HashMap windows;
List[> messages;
@@ -628,6 +641,10 @@ private:
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);
@@ -783,6 +800,12 @@ private:
.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,
@@ -968,8 +991,13 @@ public:
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);
@@ -1002,6 +1030,7 @@ public:
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);
@@ -1040,6 +1069,8 @@ public:
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 5b5657d7c05..cb88492d2c3 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() {
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 3b64a6ef6bb..f5e19d2eef7 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -835,6 +835,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;
}
}
@@ -1859,12 +1862,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();
@@ -1892,7 +1902,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);
}
@@ -3095,6 +3105,7 @@ void Window::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "mouse_passthrough"), "set_flag", "get_flag", FLAG_MOUSE_PASSTHROUGH);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "sharp_corners"), "set_flag", "get_flag", FLAG_SHARP_CORNERS);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "exclude_from_capture"), "set_flag", "get_flag", FLAG_EXCLUDE_FROM_CAPTURE);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "popup_wm_hint"), "set_flag", "get_flag", FLAG_POPUP_WM_HINT);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_native"), "set_force_native", "get_force_native");
ADD_GROUP("Limits", "");
@@ -3151,6 +3162,7 @@ void Window::_bind_methods() {
BIND_ENUM_CONSTANT(FLAG_MOUSE_PASSTHROUGH);
BIND_ENUM_CONSTANT(FLAG_SHARP_CORNERS);
BIND_ENUM_CONSTANT(FLAG_EXCLUDE_FROM_CAPTURE);
+ BIND_ENUM_CONSTANT(FLAG_POPUP_WM_HINT);
BIND_ENUM_CONSTANT(FLAG_MAX);
BIND_ENUM_CONSTANT(CONTENT_SCALE_MODE_DISABLED);
diff --git a/scene/main/window.h b/scene/main/window.h
index 1d448a97943..abce073bc51 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -64,6 +64,7 @@ public:
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 a8ae645d894..450ffd2120e 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -1120,6 +1120,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE_MIME);
BIND_ENUM_CONSTANT(FEATURE_EMOJI_AND_SYMBOL_PICKER);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_COLOR_PICKER);
+ BIND_ENUM_CONSTANT(FEATURE_SELF_FITTING_WINDOWS);
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
@@ -1195,6 +1196,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);
@@ -1205,6 +1207,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 e26d6833e0c..075c19ae548 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -166,6 +166,7 @@ public:
FEATURE_NATIVE_DIALOG_FILE_MIME,
FEATURE_EMOJI_AND_SYMBOL_PICKER,
FEATURE_NATIVE_COLOR_PICKER,
+ FEATURE_SELF_FITTING_WINDOWS,
};
virtual bool has_feature(Feature p_feature) const = 0;
@@ -406,6 +407,7 @@ public:
WINDOW_FLAG_MOUSE_PASSTHROUGH,
WINDOW_FLAG_SHARP_CORNERS,
WINDOW_FLAG_EXCLUDE_FROM_CAPTURE,
+ WINDOW_FLAG_POPUP_WM_HINT,
WINDOW_FLAG_MAX,
};
@@ -421,6 +423,7 @@ public:
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);
@@ -449,6 +452,7 @@ public:
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;
]