srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/wayland/src/protocols/compositor.rs18
-rw-r--r--crates/wayland/src/state/geometry.rs35
-rw-r--r--crates/wayland/src/state/lifecycle.rs20
-rw-r--r--crates/wayland/src/state/mod.rs23
-rw-r--r--crates/wayland/src/udev/capture.rs5
-rw-r--r--crates/wayland/src/udev/platform.rs1
-rw-r--r--crates/wayland/src/udev/render.rs13
-rw-r--r--crates/wayland/src/winit/capture.rs5
-rw-r--r--crates/wayland/src/winit/connect.rs1
-rw-r--r--crates/wayland/src/winit/render.rs4
10 files changed, 112 insertions, 13 deletions
diff --git a/crates/wayland/src/protocols/compositor.rs b/crates/wayland/src/protocols/compositor.rs
index e2868e4..7be5d7e 100644
--- a/crates/wayland/src/protocols/compositor.rs
+++ b/crates/wayland/src/protocols/compositor.rs
@@ -89,6 +89,24 @@ impl CompositorHandler for CompState {
// own first real size a chance to adopt it from what `on_commit`
// just recomputed, rather than keep rendering/configuring
// against the guessed placeholder for one more round-trip.
+ // The first commit that actually carries a buffer is when this
+ // window becomes visible, so it is also when the open-slide
+ // should start - see `windows_shown_once`. Registered here
+ // rather than in `new_managed_window` because a role is created
+ // well before a client paints (measured at ~800ms for a cold
+ // terminal), which is long enough for the whole tween to finish
+ // against an empty frame and for the window to simply appear,
+ // already at rest, with no animation at all.
+ if !self.windows_shown_once.contains(&id) && self.window_has_content(id) {
+ self.windows_shown_once.insert(id);
+ let mut wm = self.wm.borrow_mut();
+ 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 + crate::state::OPEN_SLIDE_OFFSET, ..g });
+ }
+ }
+ }
self.adopt_provisional_size(id);
// See `content_epoch`'s doc comment: this is the only per-commit
// signal the udev backend's rounded-corner mask cache has to
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.
///
diff --git a/crates/wayland/src/udev/capture.rs b/crates/wayland/src/udev/capture.rs
index a64a73c..287c003 100644
--- a/crates/wayland/src/udev/capture.rs
+++ b/crates/wayland/src/udev/capture.rs
@@ -69,6 +69,11 @@ impl CompState {
let Some(udev) = self.udev.as_mut() else { return Err("no udev backend".to_string()) };
let mut elements: Vec<crate::elements::OverlayElement<PixmanRenderer>> = Vec::new();
for id in ids {
+ // Matches the render loops: a capture must not show a frame the
+ // screen does not (see `window_has_content`).
+ if !Self::has_content(&self.windows_shown_once, &self.id_to_window, id) {
+ continue;
+ }
let Some(w) = self.id_to_window.get(&id) else { continue };
let Some(surface) = crate::input::dwindow_wl_surface(w) else { continue };
let Some(geom) = self.wm.borrow().window(id).map(|w| w.geometry) else { continue };
diff --git a/crates/wayland/src/udev/platform.rs b/crates/wayland/src/udev/platform.rs
index 8dad7c3..86307dc 100644
--- a/crates/wayland/src/udev/platform.rs
+++ b/crates/wayland/src/udev/platform.rs
@@ -256,6 +256,7 @@ impl UdevPlatform {
dead_layer_surfaces: HashSet::new(),
hidden_layer_surfaces: HashMap::new(),
layer_surfaces_shown_once: HashSet::new(),
+ windows_shown_once: HashSet::new(),
decorations: HashMap::new(),
border_top_decorations: HashMap::new(),
border_bottom_decorations: HashMap::new(),
diff --git a/crates/wayland/src/udev/render.rs b/crates/wayland/src/udev/render.rs
index 9992b2c..c2899ae 100644
--- a/crates/wayland/src/udev/render.rs
+++ b/crates/wayland/src/udev/render.rs
@@ -303,6 +303,14 @@ impl CompState {
let mut elements: Vec<crate::elements::OverlayElement<smithay::backend::renderer::gles::GlesRenderer>> =
crate::cursor::render_elements(&cursor_status, &cursor_buffers, &mut gpu.renderer, udev.pointer_pos, origin, udev.heads[index].size);
for &id in &ids {
+ // Nothing is drawn for a window that has never committed a
+ // buffer - otherwise its border, titlebar and shadow
+ // stand around empty desktop until the client paints,
+ // then jump when the placeholder size is replaced. See
+ // `window_has_content`.
+ if !Self::has_content(&self.windows_shown_once, &self.id_to_window, id) {
+ continue;
+ }
let Some(w) = self.wm.borrow().window(id).cloned() else { continue };
let Some(dwindow) = self.id_to_window.get(&id) else { continue };
let Some(surface) = crate::elements::window_wl_surface(dwindow) else { continue };
@@ -541,6 +549,11 @@ impl CompState {
// window is stacked in front.
let mut occluders: Vec<srdwm_core::Rect> = Vec::with_capacity(ids.len());
for &id in &ids {
+ // See the GPU loop above: a window with no buffer yet has
+ // nothing for a frame to go around.
+ if !Self::has_content(&self.windows_shown_once, &self.id_to_window, id) {
+ continue;
+ }
let Some(w) = self.wm.borrow().window(id).cloned() else { continue };
// `w.geometry` is the animation's *target*, not
// necessarily where the window is actually drawn this
diff --git a/crates/wayland/src/winit/capture.rs b/crates/wayland/src/winit/capture.rs
index 3813559..6e03dd8 100644
--- a/crates/wayland/src/winit/capture.rs
+++ b/crates/wayland/src/winit/capture.rs
@@ -98,6 +98,11 @@ impl WaylandPlatform {
let mut occluders: Vec<srdwm_core::Rect> = Vec::new();
let focused = self.wm.borrow().focused_id();
for id in self.wm.borrow().visible_windows_front_to_back().map(|w| w.id).collect::<Vec<_>>() {
+ // Matches the render loops: a capture must not show a frame the
+ // screen does not (see `window_has_content`).
+ if !crate::state::CompState::has_content(&self.state.windows_shown_once, &self.state.id_to_window, id) {
+ continue;
+ }
let Some(w) = self.wm.borrow().window(id).cloned() else { continue };
if let Some(deco) = self.state.decorations.get(&id) {
if let Ok(elem) = MemoryRenderBufferRenderElement::from_buffer(renderer, (w.geometry.x as f64, w.geometry.y as f64), deco, None, None, None, Kind::Unspecified) {
diff --git a/crates/wayland/src/winit/connect.rs b/crates/wayland/src/winit/connect.rs
index aa59d9d..cca991c 100644
--- a/crates/wayland/src/winit/connect.rs
+++ b/crates/wayland/src/winit/connect.rs
@@ -168,6 +168,7 @@ impl WaylandPlatform {
dead_layer_surfaces: HashSet::new(),
hidden_layer_surfaces: HashMap::new(),
layer_surfaces_shown_once: HashSet::new(),
+ windows_shown_once: HashSet::new(),
decorations: HashMap::new(),
border_top_decorations: HashMap::new(),
border_bottom_decorations: HashMap::new(),
diff --git a/crates/wayland/src/winit/render.rs b/crates/wayland/src/winit/render.rs
index 432ebba..d49351f 100644
--- a/crates/wayland/src/winit/render.rs
+++ b/crates/wayland/src/winit/render.rs
@@ -236,6 +236,10 @@ impl WaylandPlatform {
let monitor_bounds: Vec<srdwm_core::Rect> = self.wm.borrow().monitors().iter().map(|m| m.full_geometry).collect();
let mut occluders: Vec<srdwm_core::Rect> = Vec::with_capacity(ids.len());
for id in ids {
+ // See `window_has_content`: no buffer yet means no frame yet.
+ if !crate::state::CompState::has_content(&self.state.windows_shown_once, &self.state.id_to_window, id) {
+ continue;
+ }
let Some(w) = self.wm.borrow().window(id).cloned() else { continue };
// `w.geometry` is the animation's target, not necessarily where
// the window is actually drawn this frame - see the matching