From 84d3adcf2f6544d30a5f832311ccc79b9412f11f Mon Sep 17 00:00:00 2001 From: Riteo Date: Fri, 27 Sep 2024 13:59:48 +0200 Subject: [PATCH] Wayland: Implement native sub-windows The backend is now mature enough to not explode with multiple windows but the `DisplayServer` API still cannot meet some guarantees required by the various Wayland protocols we use. To meet those guarantees this patch adds three new elements to the DisplayServer API, with relative handling logic for `Window` and `Popup` nodes: - `WINDOW_EVENT_FORCE_CLOSE`, which tells a window to *forcefully* close itself and ensure a proper cleanup of its references, as Wayland enforces this behavior; - `WINDOW_FLAG_POPUP_WM_HINT`, which explicitly declares a window as a "popup", as Wayland enforces this distinction and heuristics are not reliable enough; - `FEATURE_SELF_FITTING_WINDOWS`, which signals that the compositor can fit windows to the screen automatically and that nodes should not do that themselves. Given the size of this feature, this patch also includes various `WaylandThread` reworks and fixes including: - Improvements to frame wait logic, with fixes to various stalls and a configurable (through a `#define`) timeout amount; - A proper implementation of `window_can_draw`; - Complete overhaul of pointer and tablet handling. Now everything is always accumulated and handled only on each respective `frame` event. This makes their logic simpler and more robust. - Better handling of pointer leaving and pointer enter/exit event sending; - Keyboard focus tracking; - More solid window references using IDs instead of raw pointers as windows can be deleted at any time; - More aggressive messaging to window nodes to enforce rects imposed by the compositor. --- doc/classes/DisplayServer.xml | 12 +- doc/classes/Popup.xml | 1 + doc/classes/Window.xml | 8 +- .../wayland/display_server_wayland.cpp | 655 ++++++++++--- .../linuxbsd/wayland/display_server_wayland.h | 38 +- platform/linuxbsd/wayland/wayland_thread.cpp | 927 ++++++++++++------ platform/linuxbsd/wayland/wayland_thread.h | 61 +- scene/gui/popup.cpp | 6 + scene/main/window.cpp | 22 +- scene/main/window.h | 1 + servers/display_server.cpp | 3 + servers/display_server.h | 4 + 12 files changed, 1263 insertions(+), 475 deletions(-) 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;