Wayland: Allow non-interactive window resizing

Despite what I thought in the past, it is allowed, as long as we follow
certain limitations depending on the toplevel's state.

As usual I peppered the code with comments expaining what those
limitations are.

Regarding popups, AFAICT there are no major limitations, although we
should eventually use the new `reposition` method, which autoadjusts the
popup to follow the screen's borders and whatnot. I didn't do that in
this patch as it requires some elbow grease, especially if we want to do
it synchronously.
This commit is contained in:
Dery Almas
2025-12-15 23:13:38 +01:00
parent 481f36ed20
commit 8a448032c2
3 changed files with 137 additions and 11 deletions

View File

@@ -1204,11 +1204,12 @@ void DisplayServerWayland::window_set_size(const Size2i p_size, DisplayServer::W
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 new_size = p_size;
if (wd.visible) {
new_size = wayland_thread.window_set_size(p_window_id, p_size);
}
_update_window_rect(Rect2i(wd.rect.position, new_size), p_window_id);
}
Size2i DisplayServerWayland::window_get_size(DisplayServer::WindowID p_window_id) const {

View File

@@ -1348,6 +1348,13 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *
// Expect the window to be in a plain state. It will get properly set if the
// compositor reports otherwise below.
ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;
ws->maximized = false;
ws->fullscreen = false;
ws->resizing = false;
ws->tiled_left = false;
ws->tiled_right = false;
ws->tiled_top = false;
ws->tiled_bottom = false;
ws->suspended = false;
uint32_t *state = nullptr;
@@ -1355,10 +1362,32 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *
switch (*state) {
case XDG_TOPLEVEL_STATE_MAXIMIZED: {
ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;
ws->maximized = true;
} break;
case XDG_TOPLEVEL_STATE_FULLSCREEN: {
ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;
ws->fullscreen = true;
} break;
case XDG_TOPLEVEL_STATE_RESIZING: {
ws->resizing = true;
} break;
case XDG_TOPLEVEL_STATE_TILED_LEFT: {
ws->tiled_left = true;
} break;
case XDG_TOPLEVEL_STATE_TILED_RIGHT: {
ws->tiled_right = true;
} break;
case XDG_TOPLEVEL_STATE_TILED_TOP: {
ws->tiled_top = true;
} break;
case XDG_TOPLEVEL_STATE_TILED_BOTTOM: {
ws->tiled_bottom = true;
} break;
case XDG_TOPLEVEL_STATE_SUSPENDED: {
@@ -1537,15 +1566,42 @@ void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, st
// Expect the window to be in a plain state. It will get properly set if the
// compositor reports otherwise below.
ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;
ws->maximized = false;
ws->fullscreen = false;
ws->resizing = false;
ws->tiled_left = false;
ws->tiled_right = false;
ws->tiled_top = false;
ws->tiled_bottom = false;
ws->suspended = false;
if (libdecor_configuration_get_window_state(configuration, &window_state)) {
if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {
ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;
ws->maximized = true;
}
if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) {
ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;
ws->fullscreen = true;
}
// libdecor doesn't have the resizing state for whatever reason.
if (window_state & LIBDECOR_WINDOW_STATE_TILED_LEFT) {
ws->tiled_left = true;
}
if (window_state & LIBDECOR_WINDOW_STATE_TILED_RIGHT) {
ws->tiled_right = true;
}
if (window_state & LIBDECOR_WINDOW_STATE_TILED_TOP) {
ws->tiled_top = true;
}
if (window_state & LIBDECOR_WINDOW_STATE_TILED_BOTTOM) {
ws->tiled_bottom = true;
}
if (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) {
@@ -3997,6 +4053,58 @@ const WaylandThread::WindowState *WaylandThread::window_get_state(DisplayServer:
return windows.getptr(p_window_id);
}
Size2i WaylandThread::window_set_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {
ERR_FAIL_COND_V(!windows.has(p_window_id), p_size);
WindowState &ws = windows[p_window_id];
double window_scale = window_state_get_scale_factor(&ws);
if (ws.maximized) {
// Can't do anything.
return scale_vector2i(ws.rect.size, window_scale);
}
Size2i new_size = scale_vector2i(p_size, 1 / window_scale);
if (ws.tiled_left && ws.tiled_right) {
// Tiled left and right, we shouldn't change from our current width or else
// it'll look wonky.
new_size.width = ws.rect.size.width;
}
if (ws.tiled_top && ws.tiled_bottom) {
// Tiled top and bottom. Same as above, but for the height.
new_size.height = ws.rect.size.height;
}
if (ws.resizing && ws.rect.size.width > 0 && ws.rect.size.height > 0) {
// The spec says that we shall not resize further than the config size. We can
// resize less than that though.
new_size = new_size.min(ws.rect.size);
}
// NOTE: Older versions of libdecor (~2022) do not have a way to get the max
// content size. Let's also check for its pointer so that we can preserve
// compatibility with older distros.
if (ws.libdecor_frame && libdecor_frame_get_max_content_size) {
int max_width = new_size.width;
int max_height = new_size.height;
// NOTE: Max content size is dynamic on libdecor, as plugins can override it
// to accommodate their decorations.
libdecor_frame_get_max_content_size(ws.libdecor_frame, &max_width, &max_height);
if (max_width > 0 && max_height > 0) {
new_size.width = MIN(new_size.width, max_width);
new_size.height = MIN(new_size.height, max_height);
}
}
window_state_update_size(&ws, new_size.width, new_size.height);
return scale_vector2i(new_size, window_scale);
}
void WaylandThread::beep() const {
if (registry.xdg_system_bell) {
xdg_system_bell_v1_ring(registry.xdg_system_bell, nullptr);
@@ -4177,8 +4285,12 @@ bool WaylandThread::window_can_set_mode(DisplayServer::WindowID p_window_id, Dis
};
case DisplayServer::WINDOW_MODE_MAXIMIZED: {
// NOTE: libdecor doesn't seem to have a maximize capability query?
// The fact that there's a fullscreen one makes me suspicious.
if (ws.libdecor_frame) {
// NOTE: libdecor doesn't seem to have a maximize capability query?
// The fact that there's a fullscreen one makes me suspicious. Anyways,
// let's act as if we always can.
return true;
}
return ws.can_maximize;
};
@@ -5436,7 +5548,9 @@ void WaylandThread::destroy() {
zwp_tablet_tool_v2_destroy(tool);
}
zwp_text_input_v3_destroy(ss->wp_text_input);
if (ss->wp_text_input) {
zwp_text_input_v3_destroy(ss->wp_text_input);
}
memdelete(ss);
}

View File

@@ -247,15 +247,25 @@ public:
Rect2i rect;
DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED;
bool suspended = false;
// Toplevel states.
bool maximized = false; // MUST obey configure size.
bool fullscreen = false; // Can be smaller than configure size.
bool resizing = false; // Configure size is a max.
// No need for `activated` (yet)
bool tiled_left = false;
bool tiled_right = false;
bool tiled_top = false;
bool tiled_bottom = false;
bool suspended = false; // We can stop drawing.
// These are true by default as it isn't guaranteed that we'll find an
// xdg-shell implementation with wm_capabilities available. If and once we
// receive a wm_capabilities event these will get reset and updated with
// whatever the compositor says.
bool can_minimize = false;
bool can_maximize = false;
bool can_fullscreen = false;
bool can_minimize = true;
bool can_maximize = true;
bool can_fullscreen = true;
HashSet<struct wl_output *> wl_outputs;
@@ -1101,6 +1111,7 @@ public:
struct wl_surface *window_get_wl_surface(DisplayServer::WindowID p_window_id) const;
WindowState *window_get_state(DisplayServer::WindowID p_window_id);
const WindowState *window_get_state(DisplayServer::WindowID p_window_id) const;
Size2i window_set_size(DisplayServer::WindowID p_window_id, const Size2i &p_size);
void window_start_resize(DisplayServer::WindowResizeEdge p_edge, DisplayServer::WindowID p_window);