From 1fedf8d1c2678bc3462789d4f46ff67ed580958c Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Tue, 16 Jun 2026 01:48:16 -0700 Subject: [PATCH 1/3] window: Add decoration-extents API for server-side decorations Add meta_window_{set,get}_decoration_extents() to reserve an additive strip around the client rectangle (logical pixels, Wayland-only, a no-op for X11), intended for a compositor- or shell-drawn title bar. mutter does not implement xdg-decoration itself; this is a building block for a shell-side implementation. The model is the inverse of custom_frame_extents: buffer = frame + custom_frame_extents - decoration_extents Folding the extents into the client_rect<->frame_rect converters makes placement, on-screen constraints, maximization, tiling and work-area computation reserve the strip automatically, and sizes the client to sit below it. meta-window-wayland.c accounts for it when configuring the surface (skipping zero dimensions so the client still picks its own initial size), computing the buffer size and position, and clamping the min/max size hints. The setter grows the frame by the delta so the client size stays constant as the strip is toggled. Assisted-By: Claude Opus 4.8 --- src/core/window-private.h | 15 +++ src/core/window.c | 150 ++++++++++++++++++++++++++++-- src/meta/window.h | 14 +++ src/wayland/meta-window-wayland.c | 115 ++++++++++++++++++----- 4 files changed, 262 insertions(+), 32 deletions(-) diff --git a/src/core/window-private.h b/src/core/window-private.h index 80c30e03013..a42fe876cda 100644 --- a/src/core/window-private.h +++ b/src/core/window-private.h @@ -322,6 +322,14 @@ struct _MetaWindow MetaFrameBorder custom_frame_extents; + /* Server-side decoration extents reserved around the client, in logical + * pixels (e.g. a compositor-drawn title bar). Unlike custom_frame_extents, + * which carves client-drawn shadow margins *out* of the buffer, these are + * added *around* the client so the window manager reserves space for them + * during placement, constraints, maximization and tiling. Only used for + * Wayland windows; X11 windows have real frames instead. */ + MetaFrameBorder decoration_extents; + /* The rectangles here are in "frame rect" coordinates. See the * comment at the top of meta_window_move_resize_internal() for more * information. */ @@ -726,6 +734,13 @@ void meta_window_recalc_features (MetaWindow *window); void meta_window_frame_size_changed (MetaWindow *window); +/* Decoration extents (see the field documentation above) scaled from logical + * pixels into the window's buffer/frame pixel space, ready to combine with + * custom_frame_extents and the frame rect. Returns zero borders for windows + * that have no server-side decorations. */ +void meta_window_get_decoration_extents_scaled (MetaWindow *window, + MetaFrameBorder *extents); + gboolean meta_window_is_in_stack (MetaWindow *window); void meta_window_stack_just_below (MetaWindow *window, diff --git a/src/core/window.c b/src/core/window.c index 7f186fdf38d..7823397f94c 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -4760,13 +4760,20 @@ meta_window_client_rect_to_frame_rect (MetaWindow *window, #endif { const MetaFrameBorder *extents = &window->custom_frame_extents; + MetaFrameBorder deco; - frame_rect->x += extents->left; - frame_rect->y += extents->top; + /* buffer = frame + custom_frame_extents - decoration_extents, hence + * frame = buffer - custom_frame_extents + decoration_extents. */ + meta_window_get_decoration_extents_scaled (window, &deco); + + frame_rect->x += extents->left - deco.left; + frame_rect->y += extents->top - deco.top; if (frame_rect->width != G_MAXINT) - frame_rect->width -= extents->left + extents->right; + frame_rect->width -= (extents->left + extents->right) - + (deco.left + deco.right); if (frame_rect->height != G_MAXINT) - frame_rect->height -= extents->top + extents->bottom; + frame_rect->height -= (extents->top + extents->bottom) - + (deco.top + deco.bottom); } } @@ -4806,14 +4813,141 @@ meta_window_frame_rect_to_client_rect (MetaWindow *window, #endif { const MetaFrameBorder *extents = &window->custom_frame_extents; + MetaFrameBorder deco; + + /* buffer = frame + custom_frame_extents - decoration_extents. */ + meta_window_get_decoration_extents_scaled (window, &deco); - client_rect->x -= extents->left; - client_rect->y -= extents->top; - client_rect->width += extents->left + extents->right; - client_rect->height += extents->top + extents->bottom; + client_rect->x -= extents->left - deco.left; + client_rect->y -= extents->top - deco.top; + client_rect->width += (extents->left + extents->right) - + (deco.left + deco.right); + client_rect->height += (extents->top + extents->bottom) - + (deco.top + deco.bottom); } } +void +meta_window_get_decoration_extents_scaled (MetaWindow *window, + MetaFrameBorder *extents) +{ + int scale = 1; + + *extents = window->decoration_extents; + + if (META_IS_WINDOW_WAYLAND (window)) + scale = meta_window_wayland_get_geometry_scale (window); + + extents->left *= scale; + extents->right *= scale; + extents->top *= scale; + extents->bottom *= scale; +} + +/** + * meta_window_set_decoration_extents: + * @window: a #MetaWindow + * @left: width of the left decoration, in logical pixels + * @right: width of the right decoration, in logical pixels + * @top: height of the top decoration, in logical pixels + * @bottom: height of the bottom decoration, in logical pixels + * + * Reserves space around the client area of @window for compositor-drawn + * server-side decorations (for example, a title bar drawn by the shell). + * + * The window manager treats the reserved strip as part of the window for + * placement, on-screen constraints, maximization and tiling, and the client + * is configured with the reduced size so its content does not draw under the + * decorations. The decorations themselves are not drawn by mutter; the caller + * is responsible for painting them in the reserved area, which lies between + * the window's frame rect and its buffer rect. + * + * This only has an effect on Wayland windows; X11 windows use real frames. + */ +void +meta_window_set_decoration_extents (MetaWindow *window, + int left, + int right, + int top, + int bottom) +{ + MetaFrameBorder old_scaled, new_scaled; + MetaFrameBorder extents; + MtkRectangle rect; + + g_return_if_fail (META_IS_WINDOW (window)); + + extents.left = left; + extents.right = right; + extents.top = top; + extents.bottom = bottom; + + if (memcmp (&window->decoration_extents, &extents, + sizeof (MetaFrameBorder)) == 0) + return; + + meta_window_get_decoration_extents_scaled (window, &old_scaled); + window->decoration_extents = extents; + meta_window_get_decoration_extents_scaled (window, &new_scaled); + + if (window->unmanaging) + return; + + /* A window that is still negotiating its initial size has no frame rect yet; + * leave it untouched. The extents are folded into its first configure by the + * commit path (frame = client geometry + extents), so the client keeps the + * size it chooses and the frame simply grows to include the strip. Forcing a + * move/resize here would instead push a size onto a not-yet-sized window and + * collapse it to its minimum. */ + meta_window_get_frame_rect (window, &rect); + if (mtk_rectangle_is_empty (&rect)) + return; + + /* For an already-sized window, grow (or shrink) the frame by the change so + * the client keeps its current size — the decorations are added around it + * rather than carved out of it. Constraints then clamp it on screen and only + * resize the client where it no longer fits, e.g. when maximized. */ + rect.x -= new_scaled.left - old_scaled.left; + rect.y -= new_scaled.top - old_scaled.top; + rect.width += (new_scaled.left - old_scaled.left) + + (new_scaled.right - old_scaled.right); + rect.height += (new_scaled.top - old_scaled.top) + + (new_scaled.bottom - old_scaled.bottom); + + meta_window_move_resize_frame (window, FALSE, + rect.x, rect.y, rect.width, rect.height); +} + +/** + * meta_window_get_decoration_extents: + * @window: a #MetaWindow + * @left: (out) (optional): location for the left decoration width + * @right: (out) (optional): location for the right decoration width + * @top: (out) (optional): location for the top decoration height + * @bottom: (out) (optional): location for the bottom decoration height + * + * Retrieves the server-side decoration extents most recently set with + * meta_window_set_decoration_extents(), in logical pixels. + */ +void +meta_window_get_decoration_extents (MetaWindow *window, + int *left, + int *right, + int *top, + int *bottom) +{ + g_return_if_fail (META_IS_WINDOW (window)); + + if (left) + *left = window->decoration_extents.left; + if (right) + *right = window->decoration_extents.right; + if (top) + *top = window->decoration_extents.top; + if (bottom) + *bottom = window->decoration_extents.bottom; +} + /** * meta_window_get_frame_rect: * @window: a #MetaWindow diff --git a/src/meta/window.h b/src/meta/window.h index 7ad6a71cff1..3ea407563d9 100644 --- a/src/meta/window.h +++ b/src/meta/window.h @@ -137,6 +137,20 @@ void meta_window_frame_rect_to_client_rect (MetaWindow *window, MtkRectangle *frame_rect, MtkRectangle *client_rect); +META_EXPORT +void meta_window_set_decoration_extents (MetaWindow *window, + int left, + int right, + int top, + int bottom); + +META_EXPORT +void meta_window_get_decoration_extents (MetaWindow *window, + int *left, + int *right, + int *top, + int *bottom); + META_EXPORT MetaDisplay *meta_window_get_display (MetaWindow *window); diff --git a/src/wayland/meta-window-wayland.c b/src/wayland/meta-window-wayland.c index db06823d4d8..a5025763334 100644 --- a/src/wayland/meta-window-wayland.c +++ b/src/wayland/meta-window-wayland.c @@ -287,20 +287,29 @@ surface_state_changed (MetaWindow *window) meta_compositor_get_current_window_drag (window->display->compositor); if (is_drag_resizing_window (window_drag, window)) { + MetaFrameBorder deco; + + meta_window_get_decoration_extents_scaled (window, &deco); + configuration->has_size = TRUE; meta_window_drag_calculate_window_size (window_drag, &configuration->width, &configuration->height); + configuration->width -= deco.left + deco.right; + configuration->height -= deco.top + deco.bottom; } else if (is_configuration_up_to_date && last_sent_configuration->config && meta_window_config_is_floating (last_sent_configuration->config)) { MtkRectangle frame_rect = meta_window_config_get_rect (window->config); + MetaFrameBorder deco; + + meta_window_get_decoration_extents_scaled (window, &deco); configuration->has_size = TRUE; - configuration->width = frame_rect.width; - configuration->height = frame_rect.height; + configuration->width = frame_rect.width - (deco.left + deco.right); + configuration->height = frame_rect.height - (deco.top + deco.bottom); } meta_window_wayland_configure (wl_window, configuration); @@ -335,9 +344,18 @@ should_configure (MetaWindow *window, MetaWaylandWindowConfiguration *last_sent_configuration = wl_window->last_sent_configuration; MtkRectangle frame_rect; + MetaFrameBorder deco; + int configured_width, configured_height; frame_rect = meta_window_config_get_rect (window->config); + /* last_sent_configuration->width/height is the client size we asked for, + * i.e. the frame size minus the reserved decoration area; compare like for + * like. */ + meta_window_get_decoration_extents_scaled (window, &deco); + configured_width = constrained_rect.width - (deco.left + deco.right); + configured_height = constrained_rect.height - (deco.top + deco.bottom); + /* Initial configuration, always need to configure. */ if (!last_sent_configuration) return TRUE; @@ -346,8 +364,8 @@ should_configure (MetaWindow *window, * configure the new size. */ if (last_sent_configuration->has_size && flags & META_MOVE_RESIZE_RESIZE_ACTION && - (constrained_rect.width != last_sent_configuration->width || - constrained_rect.height != last_sent_configuration->height)) + (configured_width != last_sent_configuration->width || + configured_height != last_sent_configuration->height)) return TRUE; /* Something wants to resize our mapped window. */ @@ -356,6 +374,16 @@ should_configure (MetaWindow *window, constrained_rect.height != frame_rect.height)) return TRUE; + /* The reserved decoration area changed (e.g. a title bar was toggled), so + * the client size changes even though the frame size did not. Only relevant + * once the window has a buffer; an unsized window keeps choosing its own + * size and folds in the extents on its next commit. */ + if (meta_wayland_surface_get_buffer (wl_window->surface) && + last_sent_configuration->has_size && + (configured_width != last_sent_configuration->width || + configured_height != last_sent_configuration->height)) + return TRUE; + /* The state was changed, or the change was explicitly marked as a configure * request. */ if (flags & META_MOVE_RESIZE_STATE_CHANGED || @@ -415,6 +443,7 @@ meta_window_wayland_move_resize_internal (MetaWindow *window, gboolean can_move_now = FALSE; MtkRectangle configured_rect; MtkRectangle frame_rect; + MetaFrameBorder deco; MetaGravity gravity; int geometry_scale; int new_x; @@ -438,9 +467,18 @@ meta_window_wayland_move_resize_internal (MetaWindow *window, * to the Wayland surface. */ geometry_scale = meta_window_wayland_get_geometry_scale (window); frame_rect = meta_window_config_get_rect (window->config); + meta_window_get_decoration_extents_scaled (window, &deco); + /* The client fills the frame rect minus the area reserved for server-side + * decorations. Only subtract from a real size: a zero dimension means "the + * client picks its own size" (e.g. the initial configure) and must stay + * zero — clamping it to 1 would force the client to a 1px size. */ configured_rect.width = constrained_rect.width; configured_rect.height = constrained_rect.height; + if (configured_rect.width > 0) + configured_rect.width = MAX (configured_rect.width - (deco.left + deco.right), 1); + if (configured_rect.height > 0) + configured_rect.height = MAX (configured_rect.height - (deco.top + deco.bottom), 1); /* The size is determined by the client, except when the client is explicitly * fullscreen, in which case the compositor compensates for the size @@ -485,11 +523,13 @@ meta_window_wayland_move_resize_internal (MetaWindow *window, window->buffer_rect.width = frame_rect.width + window->custom_frame_extents.left + - window->custom_frame_extents.right; + window->custom_frame_extents.right - + (deco.left + deco.right); window->buffer_rect.height = frame_rect.height + window->custom_frame_extents.top + - window->custom_frame_extents.bottom; + window->custom_frame_extents.bottom - + (deco.top + deco.bottom); /* This is a commit of an attach. We should move the window to match the * new position the client wants. */ @@ -641,8 +681,8 @@ meta_window_wayland_move_resize_internal (MetaWindow *window, window->placement.current.rel_y = rel_y; } - new_buffer_x = new_x - window->custom_frame_extents.left; - new_buffer_y = new_y - window->custom_frame_extents.top; + new_buffer_x = new_x - window->custom_frame_extents.left + deco.left; + new_buffer_y = new_y - window->custom_frame_extents.top + deco.top; if (new_buffer_x != window->buffer_rect.x || new_buffer_y != window->buffer_rect.y) @@ -833,12 +873,19 @@ meta_window_wayland_main_monitor_changed (MetaWindow *window, window->custom_frame_extents.bottom = (int)(scale_factor * window->custom_frame_extents.bottom); - /* Buffer rect. */ - scale_rect_size (&window->buffer_rect, scale_factor); - window->buffer_rect.x = - frame_rect.x - window->custom_frame_extents.left; - window->buffer_rect.y = - frame_rect.y - window->custom_frame_extents.top; + /* Buffer rect. The decoration extents are stored in logical pixels, so they + * need no rescaling here; the buffer size scaled above already accounts for + * them. */ + { + MetaFrameBorder deco; + + meta_window_get_decoration_extents_scaled (window, &deco); + scale_rect_size (&window->buffer_rect, scale_factor); + window->buffer_rect.x = + frame_rect.x - window->custom_frame_extents.left + deco.left; + window->buffer_rect.y = + frame_rect.y - window->custom_frame_extents.top + deco.top; + } meta_compositor_sync_window_geometry (window->display->compositor, window, @@ -1588,6 +1635,7 @@ meta_window_wayland_finish_move_resize (MetaWindow *window, gboolean is_client_resize; MetaWindowDrag *window_drag; MtkRectangle frame_rect; + MetaFrameBorder deco; MetaWindowActor *window_actor; MetaWaylandToplevelDrag *toplevel_drag; MetaPlaceFlag place_flags = META_PLACE_FLAG_NONE; @@ -1672,12 +1720,15 @@ meta_window_wayland_finish_move_resize (MetaWindow *window, window_drag = meta_compositor_get_current_window_drag (display->compositor); is_window_being_resized = is_drag_resizing_window (window_drag, window); + /* new_geom is the client's window geometry; the frame additionally covers + * the reserved server-side decoration area. */ + meta_window_get_decoration_extents_scaled (window, &deco); frame_rect = meta_window_config_get_rect (window->config); rect = (MtkRectangle) { .x = frame_rect.x, .y = frame_rect.y, - .width = new_geom.width, - .height = new_geom.height + .width = new_geom.width + deco.left + deco.right, + .height = new_geom.height + deco.top + deco.bottom }; if (!is_window_being_resized) @@ -1840,6 +1891,7 @@ meta_window_wayland_set_min_size (MetaWindow *window, int height) { gint64 new_width, new_height; + MetaFrameBorder deco; float scale; meta_topic (META_DEBUG_GEOMETRY, "Window %s sets min size %d x %d", @@ -1857,10 +1909,13 @@ meta_window_wayland_set_min_size (MetaWindow *window, scale = (float) meta_window_wayland_get_geometry_scale (window); scale_size (&width, &height, scale); + meta_window_get_decoration_extents_scaled (window, &deco); new_width = width + (window->custom_frame_extents.left + - window->custom_frame_extents.right); + window->custom_frame_extents.right) + + (deco.left + deco.right); new_height = height + (window->custom_frame_extents.top + - window->custom_frame_extents.bottom); + window->custom_frame_extents.bottom) + + (deco.top + deco.bottom); window->size_hints.min_width = (int) MIN (new_width, G_MAXINT); window->size_hints.min_height = (int) MIN (new_height, G_MAXINT); @@ -1874,6 +1929,7 @@ meta_window_wayland_set_max_size (MetaWindow *window, { gint64 new_width, new_height; + MetaFrameBorder deco; float scale; meta_topic (META_DEBUG_GEOMETRY, "Window %s sets max size %d x %d", @@ -1891,10 +1947,13 @@ meta_window_wayland_set_max_size (MetaWindow *window, scale = (float) meta_window_wayland_get_geometry_scale (window); scale_size (&width, &height, scale); + meta_window_get_decoration_extents_scaled (window, &deco); new_width = width + (window->custom_frame_extents.left + - window->custom_frame_extents.right); + window->custom_frame_extents.right) + + (deco.left + deco.right); new_height = height + (window->custom_frame_extents.top + - window->custom_frame_extents.bottom); + window->custom_frame_extents.bottom) + + (deco.top + deco.bottom); window->size_hints.max_width = (int) ((new_width > 0 && new_width < G_MAXINT) ? new_width : G_MAXINT); @@ -1909,6 +1968,7 @@ meta_window_wayland_get_min_size (MetaWindow *window, int *height) { gint64 current_width, current_height; + MetaFrameBorder deco; float scale; if (!(window->size_hints.flags & META_SIZE_HINTS_PROGRAM_MIN_SIZE)) @@ -1920,12 +1980,15 @@ meta_window_wayland_get_min_size (MetaWindow *window, return; } + meta_window_get_decoration_extents_scaled (window, &deco); current_width = window->size_hints.min_width - (window->custom_frame_extents.left + - window->custom_frame_extents.right); + window->custom_frame_extents.right) - + (deco.left + deco.right); current_height = window->size_hints.min_height - (window->custom_frame_extents.top + - window->custom_frame_extents.bottom); + window->custom_frame_extents.bottom) - + (deco.top + deco.bottom); *width = MAX (current_width, 0); *height = MAX (current_height, 0); @@ -1941,6 +2004,7 @@ meta_window_wayland_get_max_size (MetaWindow *window, { gint64 current_width = 0; gint64 current_height = 0; + MetaFrameBorder deco; float scale; if (!(window->size_hints.flags & META_SIZE_HINTS_PROGRAM_MAX_SIZE)) @@ -1952,15 +2016,18 @@ meta_window_wayland_get_max_size (MetaWindow *window, return; } + meta_window_get_decoration_extents_scaled (window, &deco); if (window->size_hints.max_width < G_MAXINT) current_width = window->size_hints.max_width - (window->custom_frame_extents.left + - window->custom_frame_extents.right); + window->custom_frame_extents.right) - + (deco.left + deco.right); if (window->size_hints.max_height < G_MAXINT) current_height = window->size_hints.max_height - (window->custom_frame_extents.top + - window->custom_frame_extents.bottom); + window->custom_frame_extents.bottom) - + (deco.top + deco.bottom); *width = CLAMP (current_width, 0, G_MAXINT); *height = CLAMP (current_height, 0, G_MAXINT); -- GitLab From df0d9dfd70ad33f2a24e879f8b23676e23652550 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sun, 21 Jun 2026 20:52:11 -0700 Subject: [PATCH 2/3] wayland: Add meta_object_from_wl_resource() Resolve a surface-bearing protocol resource (a wl_surface, xdg_surface or xdg_toplevel) to its MetaWindow wrapped as a GObject, or NULL. This lets a compositor-side protocol implementation -- such as a server-side decoration manager -- find the window backing the objects it receives as request arguments, with the caller downcasting from GObject to MetaWindow. Assisted-By: Claude Opus 4.8 --- src/meta/meta-wayland-surface.h | 17 +++++++++++++++++ src/wayland/meta-wayland-xdg-shell.c | 25 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/meta/meta-wayland-surface.h b/src/meta/meta-wayland-surface.h index 487dc579b59..3340bdb31de 100644 --- a/src/meta/meta-wayland-surface.h +++ b/src/meta/meta-wayland-surface.h @@ -20,6 +20,8 @@ #include #include +struct wl_resource; + G_BEGIN_DECLS #define META_TYPE_WAYLAND_SURFACE (meta_wayland_surface_get_type ()) @@ -32,4 +34,19 @@ G_DECLARE_FINAL_TYPE (MetaWaylandSurface, META_EXPORT MetaWindow *meta_wayland_surface_get_window (MetaWaylandSurface *surface); +/** + * meta_object_from_wl_resource: (skip) + * @resource: a wl_surface, xdg_surface or xdg_toplevel resource + * + * Resolves a Wayland protocol resource that is backed by a surface to its + * window, if any. Intended for compositor-side protocol implementations (such + * as a server-side decoration manager) that receive surface-bearing objects as + * request arguments. + * + * Returns: (transfer none) (nullable) (type name (MetaWindow)): the #MetaWindow + * wrapped as a #GObject, or %NULL + */ +META_EXPORT +GObject *meta_object_from_wl_resource (struct wl_resource *resource); + G_END_DECLS diff --git a/src/wayland/meta-wayland-xdg-shell.c b/src/wayland/meta-wayland-xdg-shell.c index d91b08a1af0..b12e4a1c502 100644 --- a/src/wayland/meta-wayland-xdg-shell.c +++ b/src/wayland/meta-wayland-xdg-shell.c @@ -190,6 +190,31 @@ surface_from_xdg_toplevel_resource (struct wl_resource *resource) return surface_from_xdg_surface_resource (resource); } +GObject * +meta_object_from_wl_resource (struct wl_resource *resource) +{ + void *user_data; + MetaWaylandSurface *surface = NULL; + MetaWindow *window = NULL; + + if (!resource) + return NULL; + + user_data = wl_resource_get_user_data (resource); + if (!user_data) + return NULL; + + if (META_IS_WAYLAND_SURFACE (user_data)) + surface = user_data; + else if (META_IS_WAYLAND_SURFACE_ROLE (user_data)) + surface = meta_wayland_surface_role_get_surface (user_data); + + if (surface) + window = meta_wayland_surface_get_window (surface); + + return window ? G_OBJECT (window) : NULL; +} + static MetaDisplay * display_from_surface (MetaWaylandSurface *surface) { -- GitLab From 76c0a352d4af17b31e96f5390491b16d3a366447 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Tue, 16 Jun 2026 01:48:44 -0700 Subject: [PATCH 3/3] wayland: Anchor popups to the client area under reserved decorations An xdg_positioner anchors a popup relative to its parent's window geometry (the client area), but a reserved server-side decoration strip grows the parent's frame rect beyond that client area. Translate the parent frame rect to the client-area origin (subtracting the decoration extents) in the three places that derive popup position from it: the positioner-to-placement conversion, the reposition-on-ack path in finish_move_resize, and constrain_custom_rule (which re-reads the parent config rect and would otherwise undo the other two). Without decoration extents all three are no-ops; with them, menus stay aligned with the parent's content instead of riding up into the decoration strip. Assisted-By: Claude Opus 4.8 --- src/core/constraints.c | 17 +++++++++++++++++ src/wayland/meta-wayland-xdg-shell.c | 13 +++++++++++++ src/wayland/meta-window-wayland.c | 11 +++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/core/constraints.c b/src/core/constraints.c index 54046edb6d6..7cb0bedafd7 100644 --- a/src/core/constraints.c +++ b/src/core/constraints.c @@ -951,6 +951,23 @@ constrain_custom_rule (MetaWindow *window, parent = meta_window_get_transient_for (window); parent_rect = meta_window_config_get_rect (parent->config); + + /* The placement rule's anchor/offsets are relative to the parent's window + * geometry (its client area). When the parent reserves a server-side + * decoration strip, its frame rect (the config rect) is grown beyond the + * client area by the decoration extents, so translate it back to the + * client-area rect. Otherwise popups (e.g. menus) are offset by the reserved + * strip. No-op for windows without decoration extents. */ + { + MetaFrameBorder deco; + + meta_window_get_decoration_extents_scaled (parent, &deco); + parent_rect.x += deco.left; + parent_rect.y += deco.top; + parent_rect.width -= deco.left + deco.right; + parent_rect.height -= deco.top + deco.bottom; + } + if (window->placement.state == META_PLACEMENT_STATE_CONSTRAINED_FINISHED) { placement_rule->parent_rect.x = parent_rect.x; diff --git a/src/wayland/meta-wayland-xdg-shell.c b/src/wayland/meta-wayland-xdg-shell.c index b12e4a1c502..3cd567dd4d7 100644 --- a/src/wayland/meta-wayland-xdg-shell.c +++ b/src/wayland/meta-wayland-xdg-shell.c @@ -2506,9 +2506,22 @@ meta_wayland_xdg_positioner_to_placement (MetaWaylandXdgPositioner *xdg_position MetaWindow *parent_window) { MtkRectangle parent_rect; + MetaFrameBorder deco; meta_window_get_frame_rect (parent_window, &parent_rect); + /* The positioner's anchor rect is relative to the parent's window geometry + * (its client area). When the parent reserves a server-side decoration strip, + * its frame rect is grown beyond the client area by the decoration extents, + * so translate the frame rect back to the client-area rect. Otherwise popups + * (e.g. menus) are offset by the reserved strip. No-op for windows without + * decoration extents. */ + meta_window_get_decoration_extents_scaled (parent_window, &deco); + parent_rect.x += deco.left; + parent_rect.y += deco.top; + parent_rect.width -= deco.left + deco.right; + parent_rect.height -= deco.top + deco.bottom; + if (xdg_positioner->acked_parent_configure) { MetaWindowWayland *parent_wl_window = META_WINDOW_WAYLAND (parent_window); diff --git a/src/wayland/meta-window-wayland.c b/src/wayland/meta-window-wayland.c index a5025763334..d96d47ab3fa 100644 --- a/src/wayland/meta-window-wayland.c +++ b/src/wayland/meta-window-wayland.c @@ -1739,9 +1739,20 @@ meta_window_wayland_finish_move_resize (MetaWindow *window, { MetaWindow *parent; MtkRectangle parent_rect; + MetaFrameBorder parent_deco; parent = meta_window_get_transient_for (window); parent_rect = meta_window_config_get_rect (parent->config); + + /* rel_x/rel_y are relative to the parent's window geometry (its + * client area), but the parent's config rect is the frame rect, + * which a reserved decoration strip grows beyond the client area. + * Translate to the client-area origin so the popup stays aligned + * with the parent's content. No-op without decoration extents. */ + meta_window_get_decoration_extents_scaled (parent, &parent_deco); + parent_rect.x += parent_deco.left; + parent_rect.y += parent_deco.top; + rect.x = parent_rect.x + acked_configuration->rel_x; rect.y = parent_rect.y + acked_configuration->rel_y; } -- GitLab