From d6da5a32c921110cc59684f684751502da0ff0dd Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Mon, 10 Mar 2025 16:33:00 +0200 Subject: Fix border/content-mask sized for a CSD client's whole buffer, margin included The previous fix for a stale border size (switching effective_frame_of from dwindow.geometry() to raw dwindow.bbox()) traded one bug for another. bbox() is the window's entire committed buffer; geometry() is that buffer intersected with the client's own xdg_surface:: set_window_geometry hint, which excludes any invisible CSD shadow margin the client reserves around its real visible content. The assumption behind the switch - that sync_geometry's unconditional tiled-state bits make every compliant client reserve no such margin, so nothing would be lost - was wrong: confirmed live via temporary diagnostic logging, Chrome reserves a real, correctly-current 10px margin on all four sides regardless of the tiled hint (Firefox, the window that exposed the original staleness bug, does not - the two disagree on this). Raw bbox() therefore handed the border/content mask Chrome's entire buffer, margin included - 20px wider and taller than its real visible chrome on each axis, with no compensating position shift - so the rounded border curve traced a rectangle Chrome's real content never reached, and its true, still-square corner poked straight through the curve instead of being hidden by it. Reported live as a border not lining up with a window's content and a hard block cutting through an otherwise-rounded corner. Fixed by keeping both properties at once: dwindow.geometry().loc as the margin - assumed symmetric (left == right, top == bottom), which holds for every real CSD shadow margin observed here, since it's a fixed design constant that doesn't scale with window size and so has no equivalent staleness window even while the hint's absolute size does - subtracted from the always-fresh bbox(). Current size, correct visible-content bounds, for a client that reserves a margin (Chrome) and one that doesn't (Firefox, whose hint .loc is always (0, 0), where this reduces to plain bbox()) alike. --- crates/wayland/src/state/geometry.rs | 53 ++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/crates/wayland/src/state/geometry.rs b/crates/wayland/src/state/geometry.rs index 774eabc..b537412 100644 --- a/crates/wayland/src/state/geometry.rs +++ b/crates/wayland/src/state/geometry.rs @@ -84,21 +84,44 @@ impl CompState { // settling and any reasonable commit-throttle window), until an // unrelated maximize/restore cycle on the same window happened to // prompt Firefox into resending a fresh hint and self-correcting. - // `bbox()` - the real bounding box of the window's current surface - // tree - updates on every commit unconditionally, independent of - // whether the client also chose to resend the optional hint, so it - // has no equivalent staleness window. The one thing this gives up - // is excluding a CSD client's own invisible drop-shadow margin from - // the border/content area - but `sync_geometry` already - // unconditionally tells every window it is tiled on all four sides - // specifically so a compliant client (GTK4/Firefox concretely, see - // that function's own doc comment) reserves no such margin at all, - // so a compliant client loses nothing here. A client that ignores - // the tiled hint and reserves one anyway ends up with a border a - // few pixels larger than its real visible chrome - a real but - // strictly smaller, more cosmetic problem than one stuck at the - // wrong size and position indefinitely. - let content = dwindow.bbox(); + // + // A first fix switched outright to `bbox()` - the real bounding + // box of the window's current surface tree, which updates on every + // commit unconditionally - on the reasoning that `sync_geometry` + // already unconditionally tells every window it is tiled on all + // four sides specifically so a compliant client reserves no + // invisible shadow margin, so nothing would be lost by no longer + // excluding one. Wrong: confirmed live via temporary diagnostic + // logging, Chrome reserves a real, correctly-current 10px margin on + // all four sides regardless of the tiled hint (Firefox, the window + // that exposed the original bug, does not - the two disagree on + // this, not just on how quickly they resend the hint). Raw `bbox()` + // gave the border/content mask Chrome's *entire* buffer, margin + // included - 20px wider and taller than its real visible chrome on + // each axis, with no compensating position shift - so the rounded + // border curve traced a rectangle Chrome's real content never + // reached, and its true, still-square corner poked straight through + // the curve instead of being hidden by it. + // + // The fix keeps both properties at once: a margin, `dwindow_ + // geometry.loc`, assumed symmetric (left == right, top == bottom -- + // true of every real CSD shadow margin observed here: a fixed + // design constant, not something that scales with window size) is + // far more durable than the rest of that same hint. A resize + // changes a client's real content size; it does not change how + // large that client's own shadow is, so the margin has no + // equivalent staleness window even while the hint's absolute size + // does. Subtracting it from the always-fresh `bbox()` - instead of + // trusting the hint's own absolute size (stale-prone) or using + // bbox() raw (margin-blind) - gets a content rect that is both + // current and correctly excludes the invisible margin, for a + // client that reserves one (Chrome) and one that doesn't (Firefox, + // where the hint's `.loc` is always `(0, 0)` and this reduces to + // plain `bbox()`) alike. + let dwindow_geometry = dwindow.geometry(); + let bbox = dwindow.bbox(); + let margin = (dwindow_geometry.loc.x.max(0), dwindow_geometry.loc.y.max(0)); + let content = Rectangle::new(bbox.loc, (bbox.size.w - 2 * margin.0, bbox.size.h - 2 * margin.1).into()); if content.size.w <= 0 || content.size.h <= 0 { // No real committed content yet - racing the first commit // right after creation, most likely. Nothing to correct -- cgit v1.2.3