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