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:
File diff suppressed because it is too large
Load Diff
@ -68,7 +68,15 @@
|
||||
class DisplayServerWayland : public DisplayServer {
|
||||
// No need to register with GDCLASS, it's platform-specific and nothing is added.
|
||||
struct WindowData {
|
||||
WindowID id;
|
||||
WindowID id = INVALID_WINDOW_ID;
|
||||
|
||||
WindowID parent_id = INVALID_WINDOW_ID;
|
||||
|
||||
// For popups.
|
||||
WindowID root_id = INVALID_WINDOW_ID;
|
||||
|
||||
// For toplevels.
|
||||
List<WindowID> popup_stack;
|
||||
|
||||
Rect2i rect;
|
||||
Size2i max_size;
|
||||
@ -76,6 +84,8 @@ class DisplayServerWayland : public DisplayServer {
|
||||
|
||||
Rect2i safe_rect;
|
||||
|
||||
bool emulate_vsync = false;
|
||||
|
||||
#ifdef GLES3_ENABLED
|
||||
struct wl_egl_window *wl_egl_window = nullptr;
|
||||
#endif
|
||||
@ -119,17 +129,25 @@ class DisplayServerWayland : public DisplayServer {
|
||||
|
||||
HashMap<CursorShape, CustomCursor> custom_cursors;
|
||||
|
||||
WindowData main_window;
|
||||
HashMap<WindowID, WindowData> windows;
|
||||
WindowID window_id_counter = MAIN_WINDOW_ID;
|
||||
|
||||
WaylandThread wayland_thread;
|
||||
|
||||
Context context;
|
||||
bool swap_cancel_ok = false;
|
||||
|
||||
// NOTE: These are the based on WINDOW_FLAG_POPUP, which does NOT imply what it
|
||||
// seems. It's particularly confusing for our usecase, but just know that these
|
||||
// are the "take all input thx" windows while the `popup_stack` variable keeps
|
||||
// track of all the generic floating window concept.
|
||||
List<WindowID> popup_menu_list;
|
||||
BitField<MouseButtonMask> 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<InputEvent> &p_event);
|
||||
void _dispatch_input_event(const Ref<InputEvent> &p_event);
|
||||
|
||||
void _resize_window(const Size2i &p_size);
|
||||
|
||||
virtual void _show_window();
|
||||
void _update_window_rect(const Rect2i &p_rect, WindowID p_window_id = MAIN_WINDOW_ID);
|
||||
|
||||
void try_suspend();
|
||||
|
||||
@ -227,6 +243,14 @@ public:
|
||||
|
||||
virtual Vector<DisplayServer::WindowID> 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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
|
||||
Reference in New Issue
Block a user