diff options
Diffstat (limited to 'crates/wayland/src/state')
| -rw-r--r-- | crates/wayland/src/state/geometry.rs | 42 | ||||
| -rw-r--r-- | crates/wayland/src/state/lifecycle.rs | 18 | ||||
| -rw-r--r-- | crates/wayland/src/state/mod.rs | 45 |
3 files changed, 54 insertions, 51 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) } diff --git a/crates/wayland/src/state/lifecycle.rs b/crates/wayland/src/state/lifecycle.rs index b6c7f09..f2cddd9 100644 --- a/crates/wayland/src/state/lifecycle.rs +++ b/crates/wayland/src/state/lifecycle.rs @@ -19,13 +19,15 @@ impl CompState { if wm.window(id).is_some_and(|w| w.size_is_provisional) { self.provisional_size.insert(id); } - // The open-slide tween is NOT started here, deliberately. - // A toplevel role exists well before its client paints - // anything, so starting it here ran the animation against an - // empty frame and left the window simply appearing, already at - // rest. `CompositorHandler::commit` starts it at the first - // commit that carries a buffer instead - see - // `windows_shown_once`. + // Nothing is drawn for this window until its client paints, + // and the open-slide tween starts then rather than here - a + // toplevel role exists well before a client's first buffer, so + // starting the animation here ran it against an empty frame and + // left the window simply appearing, already at rest. + // `CompositorHandler::commit` does both. This is the only place + // anything is ever put into `awaiting_first_buffer`; see that + // field for why that matters. + self.awaiting_first_buffer.insert(id); id }; @@ -378,7 +380,7 @@ impl CompState { self.shadow_buffers.remove(&id); self.border_side_buffers.remove(&id); self.decoration_signatures.remove(&id); - self.windows_shown_once.remove(&id); + self.awaiting_first_buffer.remove(&id); self.last_synced_size.remove(&id); self.content_epoch.remove(&id); self.rounded_content_buffers.remove(&id); 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 |