diff options
| author | srdusr <[email protected]> | 2026-07-27 01:25:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-07-27 01:25:00 +0200 |
| commit | e20d49b0ee3dbd83499445d61eb2d65904d74311 (patch) | |
| tree | bec0d976da83c17fef7a68033e4d2b66394421c1 /crates/wayland/src/state/mod.rs | |
| parent | 48ccac7ecaeff0aaa503804bdf8e033e273b9862 (diff) | |
| download | srdwm-e20d49b0ee3dbd83499445d61eb2d65904d74311.tar.gz srdwm-e20d49b0ee3dbd83499445d61eb2d65904d74311.zip | |
Hide a window only when srdwm knows it has not drawn, not when a lookup says so
Regression I introduced two commits ago, reported live: "I can click close
where the button would normally be and it does close, but it is still
invisible."
The gate that stops an empty frame being drawn before a client paints asked
the renderer, from inside the render loop, whether a window's surface had a
buffer attached right now - and treated "no" as "do not draw". That
question is only meaningful for a native xdg-shell toplevel. An XWayland
window's surface state does not describe it the same way, so the answer came
back no on every frame and the window was never drawn again, while srdwm's
own hit-testing carried on working perfectly: an invisible window that still
takes clicks, which is a worse failure than the empty frame it was meant to
prevent.
Inverted to the fail-safe direction. `new_managed_window` - the one path
that creates a native toplevel - puts the window into
`awaiting_first_buffer`, and `commit` takes it out on the first commit that
carries a buffer. The render and capture paths test that set and nothing
else. A window is now hidden only when srdwm itself put it there, so no
window whose plumbing works differently can be hidden by a lookup that did
not apply to it: the XWayland map path never touches the set, and neither
can anything else.
The buffer question still gets asked, but only in `commit`, about a surface
it was just handed, where it is the right question.
Verified both halves: an ordinary spawn still shows no frame before content
(26 captured frames with content, 0 without), and the only way into the set
is one line in one function.
Diffstat (limited to 'crates/wayland/src/state/mod.rs')
| -rw-r--r-- | crates/wayland/src/state/mod.rs | 45 |
1 files changed, 27 insertions, 18 deletions
diff --git a/crates/wayland/src/state/mod.rs b/crates/wayland/src/state/mod.rs index 44ef772..64990c3 100644 --- a/crates/wayland/src/state/mod.rs +++ b/crates/wayland/src/state/mod.rs @@ -461,27 +461,36 @@ pub(crate) struct CompState { /// this set is ever consulted, so a surface only reaches the unmap path /// once it has legitimately shown something. pub(crate) layer_surfaces_shown_once: HashSet<WlSurface>, - /// Windows whose surface has committed a buffer at least once. + /// Native Wayland toplevels that srdwm has created but whose client + /// has not yet drawn anything. /// - /// A toplevel exists, and is placed and decorated, from the moment its - /// role is created - which is well before the client has drawn - /// anything. Rendering it at that point paints a border and a titlebar - /// around empty desktop: an empty frame stands there on its own, then - /// snaps to a different size once the real buffer arrives and the - /// guessed `800x600` placeholder (`Window::size_is_provisional`) is - /// replaced. Measured in a nested session: the frame was drawn ~800ms - /// before any content, one full `TITLEBAR_HEIGHT` too tall, which is - /// what "the border corners look funny before a window spawns" is. + /// A toplevel is placed and decorated from the moment its role is + /// created, which is well before the client paints. Rendering it then + /// paints a border and a titlebar around empty desktop: an empty frame + /// stands there on its own, then snaps to a different size once the + /// real buffer arrives and the guessed `800x600` placeholder + /// (`Window::size_is_provisional`) is replaced. Measured in a nested + /// session: ~800ms of empty frame, one full `TITLEBAR_HEIGHT` too tall. /// - /// So this gates two things: nothing is drawn for a window that has - /// never had a buffer, and the open-slide starts at the first buffer - /// rather than at role creation, so the animation plays where it can - /// actually be seen instead of finishing against an empty frame. + /// This gates two things: nothing is drawn for a window still in this + /// set, and the open-slide starts when a window leaves it, so the + /// animation plays where it can be seen instead of finishing against + /// an empty frame. /// - /// Same shape, and the same reason, as `layer_surfaces_shown_once` - /// above: a window that has legitimately shown something once is never - /// hidden again by this, however its buffer state changes afterward. - pub(crate) windows_shown_once: HashSet<WindowId>, + /// Membership is the fail-safe direction, and that is the whole point + /// of the design. A window is hidden only when srdwm itself put it + /// here - `new_managed_window`, the one path that creates a native + /// toplevel - and `CompositorHandler::commit` takes it out again on + /// the first commit that carries a buffer. Nothing else can ever land + /// in it, so no window whose plumbing works differently can be hidden + /// by a lookup that did not apply to it. The first version of this + /// asked the renderer "does this surface have a buffer right now" from + /// inside the render loop, defaulting to hidden when the answer was + /// no; an XWayland window, whose surface state that lookup does not + /// describe, went invisible in the owner's live session while staying + /// clickable - reported as "I can click close where the button would + /// be and it does close, but it is still invisible". + pub(crate) awaiting_first_buffer: HashSet<WindowId>, pub(crate) decorations: HashMap<WindowId, MemoryRenderBuffer>, /// The top border strip's rounded-corner bitmap, cached the same way /// and at the same trigger points as `decorations` (built in |