layer layout: reserve only exclusive-zone surfaces (two-pass, labwc-style)

Replace the layer_reserve_box self-reservation heuristic with labwc's
layers_arrange(): configure exclusive-zone surfaces first (highest layer
down) so they reserve, then the zero-exclusive-zone remainder (lowest
layer up) positioned within the residue without shrinking it.  A transient
zero-ez surface (e.g. a panel notification) no longer resizes the tabs.
This commit is contained in:
Andrew Hamilton 2026-09-01 22:31:04 +10:00
parent bc98746207
commit 5dc0c76cd1

126
kick.c
View file

@ -4363,15 +4363,19 @@ found:
Layer-shell surfaces are never tabs: each renders into server.layer_tree, a
dedicated child of server.scene->tree that sits above every window tree and
is never toggled by show_window()/set_active_tab(), so a bar stays visible
across all tabs. The bar's exclusive zone shrinks the usable area the
maximized tabs are sized into, mirroring sway (tabs never overlap the bar).
across all tabs. Only a layer surface's own exclusive_zone shrinks the
usable area the maximized tabs are sized into (delegated to wlroots' scene
helper), mirroring sway and labwc: a transient zero-exclusive-zone surface
(e.g. a panel notification) is positioned but never resizes the tabs.
wlroots' own scene helper (wlr_scene_layer_surface_v1_*) does the heavy
lifting for us: it computes the surface's anchored/centered geometry from
its pending state and, for mapped surfaces with a positive exclusive zone,
shrinks a caller-supplied usable area for the next layer surface. We just
drive that per-surface and then size every maximized tab to the residual
usable box. */
shrinks a caller-supplied usable area for the next layer surface. We drive
that in two passes -- exclusive-zone surfaces first (highest layer first)
so they always get preferred placement regardless of launch order, then the
zero-exclusive-zone remainder within the residue (lowest layer first) --
and size every maximized tab to the residual usable box. */
struct kick_layer {
struct wl_list link;
@ -4431,78 +4435,56 @@ static void layer_raise_above_windows(void) {
wlr_scene_node_raise_to_top(&server.layer_tree->node);
}
/* Run the layer layout pass: configure every layer surface against the given
full area, letting each reservation shrink the shared usable box, then size
all maximized tabs into the residual area. */
/* Reserve the bar's own footprint along its anchored edge, independent of the
client's exclusive_zone (which a panel like waybar may never send). The
bar's rendered height is taken from its requested size, falling back to the
mapped surface box. Only a mapped, positively-sized bar reserves space, so
a still-starting client doesn't collapse the tabs. */
static void layer_reserve_box(struct wlr_layer_surface_v1 *layer_surface,
struct wlr_box *usable) {
struct wlr_surface *s = layer_surface->surface;
if (!s || !s->mapped) return;
/* When the client already requests an exclusive zone, wlroots shrank
usable by it during wlr_scene_layer_surface_v1_configure; reserving the
footprint here too would double-shrink. Only self-reserve bars that do
not ask for an exclusive zone (e.g. waybar defaulting to exclusive_zone
0 while still occupying the edge). */
if (layer_surface->current.exclusive_zone > 0) return;
uint32_t anchor = layer_surface->current.anchor;
int h = (int)layer_surface->current.desired_height;
int w = (int)layer_surface->current.desired_width;
if (h == 0 && s->current.buffer_height > 0) {
int scale = s->current.scale > 0 ? s->current.scale : 1;
h = s->current.buffer_height / scale;
}
if (w == 0 && s->current.buffer_width > 0) {
int scale = s->current.scale > 0 ? s->current.scale : 1;
w = s->current.buffer_width / scale;
}
if (anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP && h > 0) {
usable->y += h;
if (usable->height >= h)
usable->height -= h;
else
usable->height = 0;
} else if (anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM && h > 0) {
if (usable->height >= h)
usable->height -= h;
else
usable->height = 0;
} else if (anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT && w > 0) {
usable->x += w;
if (usable->width >= w)
usable->width -= w;
else
usable->width = 0;
} else if (anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT && w > 0) {
if (usable->width >= w)
usable->width -= w;
else
usable->width = 0;
}
}
static void layer_apply_layout_box(const struct wlr_box *full) {
struct wlr_box usable = *full;
/* Layer orders for the two configure passes, mirroring labwc's
layers_arrange(): exclusive-zone surfaces run from the highest layer down
so they always get preferred placement, the zero-exclusive-zone remainder
runs from the lowest layer up inside the residue. */
static const enum zwlr_layer_shell_v1_layer layer_order_exclusive[] = {
ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
ZWLR_LAYER_SHELL_V1_LAYER_TOP,
ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM,
ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
};
static const enum zwlr_layer_shell_v1_layer layer_order_rest[] = {
ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM,
ZWLR_LAYER_SHELL_V1_LAYER_TOP,
ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
};
/* Configure every layer surface on `want` whose exclusive-zone class matches:
exclusive=true means a non-zero exclusive_zone (including -1, the full-area
request), exclusive=false the zero-exclusive-zone remainder. wlroots' scene
helper shrinks the shared usable box inside the call for mapped surfaces
with a positive exclusive zone, and positions everything else within the
current residue without shrinking it. */
static void layer_configure_class(enum zwlr_layer_shell_v1_layer want,
bool exclusive, const struct wlr_box *full,
struct wlr_box *usable) {
struct kick_layer *sl;
wl_list_for_each(sl, &server.layer_surfaces, link) {
if (!sl->scene || !sl->seen_init) continue;
wlr_scene_layer_surface_v1_configure(sl->scene, full, &usable);
}
/* Reserve the bars' footprint (see layer_reserve_box). This runs after
wlroots has shrunk usable by any explicit exclusive_zone, but our own
reservation is what actually guarantees tabs clear a bar that never
requests an exclusive zone. */
wl_list_for_each(sl, &server.layer_surfaces, link) {
if (!sl->scene) continue;
layer_reserve_box(sl->surface, &usable);
if (sl->surface->current.layer != want) continue;
if (!!sl->surface->current.exclusive_zone != exclusive) continue;
wlr_scene_layer_surface_v1_configure(sl->scene, full, usable);
}
}
/* Run the layer layout pass: configure every layer surface against the given
full area -- exclusive-zone surfaces first so they reserve, then the
zero-exclusive-zone remainder within the residue -- and size all maximized
tabs into the final usable box. */
static void layer_apply_layout_box(const struct wlr_box *full) {
struct wlr_box usable = *full;
for (size_t i = 0;
i < sizeof(layer_order_exclusive) / sizeof(layer_order_exclusive[0]);
i++)
layer_configure_class(layer_order_exclusive[i], true, full, &usable);
for (size_t i = 0;
i < sizeof(layer_order_rest) / sizeof(layer_order_rest[0]); i++)
layer_configure_class(layer_order_rest[i], false, full, &usable);
server.layout_x = usable.x;
server.layout_y = usable.y;