Wayland: Implement native sub-windows

The backend is now mature enough to not explode with multiple windows
but the `DisplayServer` API still cannot meet some guarantees required
by the various Wayland protocols we use. To meet those guarantees this
patch adds three new elements to the DisplayServer API, with relative
handling logic for `Window` and `Popup` nodes:

 - `WINDOW_EVENT_FORCE_CLOSE`, which tells a window to *forcefully*
close itself and ensure a proper cleanup of its references, as Wayland
enforces this behavior;

 - `WINDOW_FLAG_POPUP_WM_HINT`, which explicitly declares a window as a
"popup", as Wayland enforces this distinction and heuristics are not
reliable enough;

 - `FEATURE_SELF_FITTING_WINDOWS`, which signals that the compositor can
fit windows to the screen automatically and that nodes should not do
that themselves.

Given the size of this feature, this patch also includes various
`WaylandThread` reworks and fixes including:

 - Improvements to frame wait logic, with fixes to various stalls and a
configurable (through a `#define`) timeout amount;

 - A proper implementation of `window_can_draw`;

 - Complete overhaul of pointer and tablet handling. Now everything is
always accumulated and handled only on each respective `frame` event.
This makes their logic simpler and more robust.

 - Better handling of pointer leaving and pointer enter/exit event
sending;

 - Keyboard focus tracking;

 - More solid window references using IDs instead of raw pointers as
windows can be deleted at any time;

 - More aggressive messaging to window nodes to enforce rects imposed by
the compositor.
This commit is contained in:
Riteo
2024-09-27 13:59:48 +02:00
committed by Riteo Siuga
parent 27b2ba667c
commit 84d3adcf2f
12 changed files with 1263 additions and 475 deletions

View File

@ -95,8 +95,15 @@ public:
virtual ~Message() = default;
};
class WindowMessage : public Message {
GDSOFTCLASS(WindowMessage, Message);
public:
DisplayServer::WindowID id = DisplayServer::INVALID_WINDOW_ID;
};
// Message data for window rect changes.
class WindowRectMessage : public Message {
class WindowRectMessage : public WindowMessage {
GDSOFTCLASS(WindowRectMessage, Message);
public:
@ -105,7 +112,7 @@ public:
Rect2i rect;
};
class WindowEventMessage : public Message {
class WindowEventMessage : public WindowMessage {
GDSOFTCLASS(WindowEventMessage, Message);
public:
@ -119,14 +126,14 @@ public:
Ref<InputEvent> event;
};
class DropFilesEventMessage : public Message {
class DropFilesEventMessage : public WindowMessage {
GDSOFTCLASS(DropFilesEventMessage, Message);
public:
Vector<String> files;
};
class IMEUpdateEventMessage : public Message {
class IMEUpdateEventMessage : public WindowMessage {
GDSOFTCLASS(IMEUpdateEventMessage, Message);
public:
@ -134,7 +141,7 @@ public:
Vector2i selection;
};
class IMECommitEventMessage : public Message {
class IMECommitEventMessage : public WindowMessage {
GDSOFTCLASS(IMECommitEventMessage, Message);
public:
@ -215,7 +222,8 @@ public:
// TODO: Make private?
struct WindowState {
DisplayServer::WindowID id;
DisplayServer::WindowID id = DisplayServer::INVALID_WINDOW_ID;
DisplayServer::WindowID parent_id = DisplayServer::INVALID_WINDOW_ID;
Rect2i rect;
DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED;
@ -237,6 +245,7 @@ public:
// be called even after being destroyed, pointing to probably invalid window
// data by then and segfaulting hard.
struct wl_callback *frame_callback = nullptr;
uint64_t last_frame_time = 0;
struct wl_surface *wl_surface = nullptr;
struct xdg_surface *xdg_surface = nullptr;
@ -250,6 +259,8 @@ public:
struct zxdg_exported_v2 *xdg_exported_v2 = nullptr;
struct xdg_popup *xdg_popup = nullptr;
String exported_handle;
// Currently applied buffer scale.
@ -335,6 +346,9 @@ public:
MouseButton last_button_pressed = MouseButton::NONE;
Point2 last_pressed_position;
DisplayServer::WindowID pointed_id = DisplayServer::INVALID_WINDOW_ID;
DisplayServer::WindowID last_pointed_id = DisplayServer::INVALID_WINDOW_ID;
// This is needed to check for a new double click every time.
bool double_click_begun = false;
@ -364,20 +378,17 @@ public:
bool double_click_begun = false;
// Note: the protocol doesn't have it (I guess that this isn't really meant to
// be used as a mouse...), but we'll hack one in with the current ticks.
uint64_t button_time = 0;
uint64_t motion_time = 0;
DisplayServer::WindowID proximal_id = DisplayServer::INVALID_WINDOW_ID;
DisplayServer::WindowID last_proximal_id = DisplayServer::INVALID_WINDOW_ID;
uint32_t proximity_serial = 0;
struct wl_surface *proximal_surface = nullptr;
};
struct TabletToolState {
struct wl_seat *wl_seat = nullptr;
struct wl_surface *last_surface = nullptr;
bool is_eraser = false;
TabletToolData data_pending;
@ -401,9 +412,6 @@ public:
uint32_t pointer_enter_serial = 0;
struct wl_surface *pointed_surface = nullptr;
struct wl_surface *last_pointed_surface = nullptr;
struct zwp_relative_pointer_v1 *wp_relative_pointer = nullptr;
struct zwp_locked_pointer_v1 *wp_locked_pointer = nullptr;
struct zwp_confined_pointer_v1 *wp_confined_pointer = nullptr;
@ -434,6 +442,9 @@ public:
// Keyboard.
struct wl_keyboard *wl_keyboard = nullptr;
// For key events.
DisplayServer::WindowID focused_id = DisplayServer::INVALID_WINDOW_ID;
struct xkb_context *xkb_context = nullptr;
struct xkb_keymap *xkb_keymap = nullptr;
struct xkb_state *xkb_state = nullptr;
@ -462,6 +473,7 @@ public:
struct wl_data_device *wl_data_device = nullptr;
// Drag and drop.
DisplayServer::WindowID dnd_id = DisplayServer::INVALID_WINDOW_ID;
struct wl_data_offer *wl_data_offer_dnd = nullptr;
uint32_t dnd_enter_serial = 0;
@ -486,6 +498,7 @@ public:
// IME.
struct zwp_text_input_v3 *wp_text_input = nullptr;
DisplayServer::WindowID ime_window_id = DisplayServer::INVALID_WINDOW_ID;
bool ime_enabled = false;
bool ime_active = false;
String ime_text;
@ -516,7 +529,7 @@ private:
Thread events_thread;
ThreadData thread_data;
WindowState main_window;
HashMap<DisplayServer::WindowID, WindowState> windows;
List<Ref<Message>> messages;
@ -628,6 +641,10 @@ private:
static void _xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height);
static void _xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities);
static void _xdg_popup_on_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height);
static void _xdg_popup_on_popup_done(void *data, struct xdg_popup *xdg_popup);
static void _xdg_popup_on_repositioned(void *data, struct xdg_popup *xdg_popup, uint32_t token);
// wayland-protocols event handlers.
static void _wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale);
@ -783,6 +800,12 @@ private:
.wm_capabilities = _xdg_toplevel_on_wm_capabilities,
};
static constexpr struct xdg_popup_listener xdg_popup_listener = {
.configure = _xdg_popup_on_configure,
.popup_done = _xdg_popup_on_popup_done,
.repositioned = _xdg_popup_on_repositioned,
};
// wayland-protocols event listeners.
static constexpr struct wp_fractional_scale_v1_listener wp_fractional_scale_listener = {
.preferred_scale = _wp_fractional_scale_on_preferred_scale,
@ -968,8 +991,13 @@ public:
void beep() const;
void window_create(DisplayServer::WindowID p_window_id, int p_width, int p_height);
void window_create_popup(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id, Rect2i p_rect);
void window_destroy(DisplayServer::WindowID p_window_Id);
void window_set_parent(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id);
struct wl_surface *window_get_wl_surface(DisplayServer::WindowID p_window_id) const;
WindowState *window_get_state(DisplayServer::WindowID p_window_id);
void window_start_resize(DisplayServer::WindowResizeEdge p_edge, DisplayServer::WindowID p_window);
@ -1002,6 +1030,7 @@ public:
void pointer_set_hint(const Point2i &p_hint);
PointerConstraint pointer_get_constraint() const;
DisplayServer::WindowID pointer_get_pointed_window_id() const;
DisplayServer::WindowID pointer_get_last_pointed_window_id() const;
BitField<MouseButtonMask> pointer_get_button_mask() const;
void cursor_set_visible(bool p_visible);
@ -1040,6 +1069,8 @@ public:
bool get_reset_frame();
bool wait_frame_suspend_ms(int p_timeout);
uint64_t window_get_last_frame_time(DisplayServer::WindowID p_window_id) const;
bool window_is_suspended(DisplayServer::WindowID p_window_id) const;
bool is_suspended() const;
Error init();