diff options
Diffstat (limited to 'crates/wayland/src/state')
| -rw-r--r-- | crates/wayland/src/state/geometry.rs | 35 | ||||
| -rw-r--r-- | crates/wayland/src/state/lifecycle.rs | 20 | ||||
| -rw-r--r-- | crates/wayland/src/state/mod.rs | 23 |
3 files changed, 65 insertions, 13 deletions
diff --git a/crates/wayland/src/state/geometry.rs b/crates/wayland/src/state/geometry.rs index 5cbece9..d3c7ab8 100644 --- a/crates/wayland/src/state/geometry.rs +++ b/crates/wayland/src/state/geometry.rs @@ -314,6 +314,41 @@ impl CompState { (x, y) } + /// True when this window has something on screen to draw a frame + /// around - it has committed a buffer 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. + /// + /// 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 }; + smithay::backend::renderer::utils::with_renderer_surface_state( + &surface, + |state: &mut smithay::backend::renderer::utils::RendererSurfaceState| state.buffer().is_some(), + ) + .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 163064e..b6c7f09 100644 --- a/crates/wayland/src/state/lifecycle.rs +++ b/crates/wayland/src/state/lifecycle.rs @@ -19,18 +19,13 @@ impl CompState { if wm.window(id).is_some_and(|w| w.size_is_provisional) { self.provisional_size.insert(id); } - // Starts the open-slide tween (see `WindowAnim`'s doc comment): - // the window's first `sync_geometry` call below will see this, - // register the tween, and place it here - a few pixels below - // its resting position - rather than jumping straight to - // `geometry`. Same size throughout, so no extra client configure - // is needed for the tween itself. - if wm.animations_enabled { - if let Some(win) = wm.window_mut(id) { - let g = win.geometry; - win.anim_from = Some(srdwm_core::Rect { y: g.y + OPEN_SLIDE_OFFSET, ..g }); - } - } + // 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`. id }; @@ -383,6 +378,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.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 5c1b02e..44ef772 100644 --- a/crates/wayland/src/state/mod.rs +++ b/crates/wayland/src/state/mod.rs @@ -461,6 +461,27 @@ 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. + /// + /// 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. + /// + /// 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. + /// + /// 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>, 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 @@ -787,7 +808,7 @@ impl WindowAnim { /// resize tween is reserved for maximize/fullscreen, where the client is /// already live and redrawing, not for a window whose first paint may not /// have arrived yet). -const OPEN_SLIDE_OFFSET: i32 = 24; +pub(crate) const OPEN_SLIDE_OFFSET: i32 = 24; /// A held keybinding that is firing repeatedly. /// |