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/geometry.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/geometry.rs')
| -rw-r--r-- | crates/wayland/src/state/geometry.rs | 42 |
1 files changed, 17 insertions, 25 deletions
diff --git a/crates/wayland/src/state/geometry.rs b/crates/wayland/src/state/geometry.rs index d3c7ab8..9780ae3 100644 --- a/crates/wayland/src/state/geometry.rs +++ b/crates/wayland/src/state/geometry.rs @@ -315,29 +315,25 @@ impl CompState { } /// True when this window has something on screen to draw a frame - /// around - it has committed a buffer at least once. + /// around, i.e. its client has painted at least once. /// - /// A toplevel is placed and decorated the moment its role is created, - /// which is well before the client paints. Drawing it then puts a - /// border, a titlebar and a shadow around bare desktop, at the guessed - /// placeholder size (`Window::size_is_provisional`), and that empty - /// frame then jumps when the real buffer arrives at the real size. - /// - /// A window that cannot be resolved to a surface at all counts as - /// drawable, deliberately: this hides a window only on positive - /// evidence that it has never drawn, so nothing whose surface plumbing - /// works differently (an XWayland window, say) can be hidden by a - /// lookup that simply did not apply to it. - /// - /// See `windows_shown_once` for why the answer latches once true. + /// Reads one set and nothing else - see `awaiting_first_buffer` for + /// why the answer must never depend on a lookup that can fail. Takes + /// the set rather than `&self` so a render loop can call it while it + /// already holds `self.udev` mutably borrowed. + pub(crate) fn has_content(awaiting_first_buffer: &HashSet<WindowId>, id: WindowId) -> bool { + !awaiting_first_buffer.contains(&id) + } + + /// Whether this window's own surface has a buffer attached right now. /// - /// Takes the two maps rather than `&self` so a render loop can call it - /// while it already holds `self.udev` mutably borrowed. - pub(crate) fn has_content(shown_once: &HashSet<WindowId>, id_to_window: &HashMap<WindowId, DWindow>, id: WindowId) -> bool { - if shown_once.contains(&id) { - return true; - } - let Some(surface) = id_to_window.get(&id).and_then(crate::elements::window_wl_surface) else { return true }; + /// Only `CompositorHandler::commit` asks this, about a surface it was + /// just handed, to decide whether a window can stop + /// `awaiting_first_buffer`. Nothing on the render path may ask it: a + /// window whose surface state this does not describe would answer + /// "no" and be hidden for ever. + pub(crate) fn surface_has_buffer(&self, id: WindowId) -> bool { + let Some(surface) = self.id_to_window.get(&id).and_then(crate::elements::window_wl_surface) else { return false }; smithay::backend::renderer::utils::with_renderer_surface_state( &surface, |state: &mut smithay::backend::renderer::utils::RendererSurfaceState| state.buffer().is_some(), @@ -345,10 +341,6 @@ impl CompState { .unwrap_or(false) } - pub(crate) fn window_has_content(&self, id: WindowId) -> bool { - Self::has_content(&self.windows_shown_once, &self.id_to_window, id) - } - pub(crate) fn effective_frame(&self, id: WindowId, geom: srdwm_core::Rect) -> srdwm_core::Rect { Self::effective_frame_of(&self.wm, &self.id_to_window, &self.pending_size_configure, id, geom) } |