atagen

monolith

public
Star0
Watch0

Commit 4e490b7685b8

tarball

atagen <boss@atagen.co>2026-10-19 10:50 UTC

4 files changed+342-317
windows: move size negotiation and ghost capture into their own modules

windows.rs mixed mapping, natural-size negotiation, rule placement and frame capture in 700 lines. The moved code is unchanged apart from two visibility widenings.
Added file: crates/monolith/src/ghosts.rs+119 additions
@@ -0,0 +1,119 @@
1+//! The last frames windows leave behind when they close or hide to a tray, so
2+//! their animations have something to show.
3+
4+use crate::anim::Ghost;
5+use crate::state::{State, now_ns};
6+use crate::wm::{LastFrame, root};
7+use monolith_layout::{OutputId, Rect, WindowId};
8+use monolith_render_vk::VulkanTexture;
9+use smithay::backend::renderer::Renderer;
10+use smithay::backend::renderer::utils::{import_surface_tree, with_renderer_surface_state};
11+use smithay::desktop::Window;
12+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
13+use smithay::wayland::compositor::{BufferAssignment, SurfaceAttributes, with_states};
14+
15+impl State {
16+ /// Called before a commit applies. A null-buffer commit makes smithay
17+ /// drop the surface's textures before `on_buffer` runs, so the frame a
18+ /// hiding window leaves behind has to be taken here.
19+ pub fn keep_frame_for_hide(&mut self, surface: &WlSurface) {
20+ let Some(id) = self.window_id(surface) else {
21+ return;
22+ };
23+ let removes = with_states(surface, |s| {
24+ let mut attrs = s.cached_state.get::<SurfaceAttributes>();
25+ matches!(attrs.pending().buffer, Some(BufferAssignment::Removed))
26+ });
27+ let Some(s) = self.wm.get(id).filter(|s| removes && s.drawn) else {
28+ return;
29+ };
30+ let window = s.window.clone();
31+ let frame = self.last_frame(&window);
32+ if let Some(s) = self.wm.get_mut(id) {
33+ s.hide_frame = frame;
34+ }
35+ }
36+
37+ /// Keeps a visible tiled window's last frame shrinking to its seed while
38+ /// its neighbours close over it.
39+ pub(crate) fn leave_ghost(
40+ &mut self,
41+ id: WindowId,
42+ window: &Window,
43+ saved: Option<LastFrame>,
44+ opacity: f32,
45+ ) {
46+ let Some(o) = self.wm.layout.window(id).and_then(|w| w.output) else {
47+ return;
48+ };
49+ let tiled = self
50+ .wm
51+ .layout
52+ .output(o)
53+ .is_some_and(|out| out.tree.contains(id));
54+ if !tiled || !self.wm.layout.visible(id) {
55+ return;
56+ }
57+ let Some(seed) = self
58+ .wm
59+ .layout
60+ .arrange_hiding(o, &[id])
61+ .tiled
62+ .into_iter()
63+ .find(|p| p.window == id)
64+ else {
65+ return;
66+ };
67+ if let Some(frame) = saved.or_else(|| self.last_frame(window)) {
68+ self.wm.last_frames.insert(id, frame);
69+ self.wm.anim.add_ghost(Ghost {
70+ id,
71+ output: o,
72+ seed: seed.rect,
73+ born: now_ns(),
74+ fixed: None,
75+ opacity,
76+ });
77+ }
78+ }
79+
80+ /// Fades a window that just left the screen where it was, like a
81+ /// dismissed scratchpad.
82+ pub fn fade_out(&mut self, id: WindowId, rect: Rect, output: OutputId) {
83+ let Some(window) = self.wm.window(id).cloned() else {
84+ return;
85+ };
86+ let opacity = self.wm.get(id).and_then(|s| s.rule.opacity).unwrap_or(1.0);
87+ if let Some(frame) = self.last_frame(&window) {
88+ self.wm.last_frames.insert(id, frame);
89+ self.wm.anim.add_ghost(Ghost {
90+ id,
91+ output,
92+ seed: rect,
93+ born: now_ns(),
94+ fixed: Some(rect),
95+ opacity,
96+ });
97+ }
98+ }
99+
100+ fn last_frame(&mut self, window: &Window) -> Option<LastFrame> {
101+ let surface = root(window)?;
102+ let _ = import_surface_tree(&mut self.renderer, &surface);
103+ let context = self.renderer.context_id();
104+ // The geometry lookup locks the surface's state, which the closure
105+ // below already holds.
106+ let geometry = window.geometry();
107+ with_renderer_surface_state(&surface, |s| {
108+ let texture = s.texture::<VulkanTexture>(context.clone())?.clone();
109+ Some(LastFrame {
110+ texture,
111+ scale: s.buffer_scale(),
112+ transform: s.buffer_transform(),
113+ geometry,
114+ view: s.view()?,
115+ })
116+ })
117+ .flatten()
118+ }
119+}
Modified file: crates/monolith/src/main.rs+2 additions
@@ -11,6 +11,7 @@ mod drm;
1111 mod fifo;
1212 mod focus;
1313 mod frame;
14+mod ghosts;
1415 mod globals;
1516 mod idle;
1617 mod import;
@@ -30,6 +31,7 @@ mod scale;
3031 mod scene;
3132 mod seat;
3233 mod signals;
34+mod sizing;
3335 mod state;
3436 mod stats;
3537 mod timing;
Added file: crates/monolith/src/sizing.rs+217 additions
@@ -0,0 +1,217 @@
1+//! Sizing floating windows. Clients are asked what size they want, and rules
2+//! can pick the size and spot.
3+
4+use crate::state::{State, now_ns};
5+use crate::wm::Kind;
6+use monolith_config::At;
7+use monolith_layout::{Rect, WindowId, clamp_float};
8+use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
9+use smithay::utils::Serial;
10+use std::time::Duration;
11+
12+impl State {
13+ /// Gives floats that landed on the layout's default rect their own size,
14+ /// as when a tiled window is toggled. The client is told to pick one, and
15+ /// its next commit after acking says which.
16+ pub fn size_new_floats(&mut self) {
17+ let stale: Vec<WindowId> = self
18+ .wm
19+ .iter()
20+ .filter(|(id, s)| {
21+ s.natural_size && !self.wm.layout.window(*id).is_some_and(|w| w.floating)
22+ })
23+ .map(|(id, _)| id)
24+ .collect();
25+ for id in stale {
26+ if let Some(s) = self.wm.get_mut(id) {
27+ s.natural_size = false;
28+ s.natural_after = None;
29+ }
30+ }
31+ let defaults: Vec<WindowId> = self
32+ .wm
33+ .layout
34+ .windows()
35+ .filter(|(_, w)| w.floating && w.float_default && w.scratchpad.is_none() && !w.hidden)
36+ .map(|(id, _)| id)
37+ .collect();
38+ for id in defaults {
39+ self.ask_natural_size(id);
40+ }
41+ }
42+
43+ fn ask_natural_size(&mut self, id: WindowId) {
44+ let Some(window) = self
45+ .wm
46+ .get(id)
47+ .filter(|s| !s.natural_size)
48+ .map(|s| s.window.clone())
49+ else {
50+ return;
51+ };
52+ let geometry = window.geometry().size;
53+ let current = (geometry.w > 0 && geometry.h > 0).then_some(geometry);
54+ let fixed = window.toplevel().and_then(crate::xdg::fixed_size);
55+ let size = match (window.x11_surface(), window.toplevel()) {
56+ (Some(x), _) => crate::xwayland::fixed_size(x),
57+ (_, Some(_)) if fixed.is_some() => fixed,
58+ (_, Some(t)) => {
59+ let held = self.visual_rect(id);
60+ let serial = crate::wm::configure_free(t);
61+ if let Some(w) = self.wm.get_mut(id)
62+ && let Some(serial) = serial
63+ {
64+ w.natural_size = true;
65+ w.natural_after = Some(serial);
66+ if let Some(rect) = held {
67+ self.wm.layout.hold_float_rect(id, rect);
68+ }
69+ self.await_natural_size(id, serial);
70+ return;
71+ }
72+ current
73+ }
74+ _ => None,
75+ };
76+ match size {
77+ Some(size) => self.float_at_natural_size(id, f64::from(size.w), f64::from(size.h)),
78+ None => {
79+ let rect = self.wm.layout.window(id).and_then(|w| w.float_rect);
80+ if let Some(rect) = rect {
81+ self.wm.layout.set_float_rect(id, rect);
82+ }
83+ }
84+ }
85+ }
86+
87+ /// Where a placed window is drawn right now.
88+ fn visual_rect(&self, id: WindowId) -> Option<Rect> {
89+ let now = now_ns();
90+ let (kind, rect, o) = self.wm.get(id)?.placed?;
91+ self.wm
92+ .anim
93+ .gliding(id, now)
94+ .or_else(|| {
95+ (kind == Kind::Tiled)
96+ .then(|| self.wm.anim.tiled(o, id, now))
97+ .flatten()
98+ })
99+ .or(Some(rect))
100+ }
101+
102+ /// A client that never answers a request for its size takes whatever
103+ /// size it has.
104+ fn await_natural_size(&mut self, id: WindowId, serial: Serial) {
105+ let timeout = Timer::from_duration(Duration::from_millis(300));
106+ let _ = self.core.handle.insert_source(timeout, move |_, _, state| {
107+ state.give_up_natural_size(id, serial);
108+ TimeoutAction::Drop
109+ });
110+ }
111+
112+ fn give_up_natural_size(&mut self, id: WindowId, serial: Serial) {
113+ let Some(w) = self.wm.get_mut(id) else {
114+ return;
115+ };
116+ if !w.natural_size || w.natural_after != Some(serial) {
117+ return;
118+ }
119+ w.natural_size = false;
120+ w.natural_after = None;
121+ let size = w.window.geometry().size;
122+ if size.w > 0 && size.h > 0 {
123+ self.float_at_natural_size(id, f64::from(size.w), f64::from(size.h));
124+ } else if let Some(rect) = self.wm.layout.window(id).and_then(|w| w.float_rect) {
125+ self.wm.layout.set_float_rect(id, rect);
126+ }
127+ self.apply_layout();
128+ }
129+
130+ /// Gives a floating window the size and spot its rules ask for, keeping
131+ /// whichever of the two they leave out. One still waiting for its client
132+ /// to pick a size gets its spot when the size arrives, unless the rule
133+ /// has a size to give it right away.
134+ pub(crate) fn float_by_rule(&mut self, id: WindowId) {
135+ let Some(win) = self.wm.layout.window(id).filter(|win| win.floating) else {
136+ return;
137+ };
138+ let (held, fullscreen) = (win.float_rect.map(|r| (r.w, r.h)), win.fullscreen);
139+ let Some(s) = self.wm.get_mut(id) else {
140+ return;
141+ };
142+ let rule = &s.rule;
143+ let size = rule.size.map(|(w, h)| (f64::from(w), f64::from(h)));
144+ let shaped = rule.at.is_some() || rule.min_size.is_some() || rule.max_size.is_some();
145+ if size.is_some() {
146+ s.natural_size = false;
147+ s.natural_after = None;
148+ } else if !shaped || s.natural_size || fullscreen {
149+ return;
150+ }
151+ let Some((w, h)) = size.or(held) else {
152+ return;
153+ };
154+ if let Some(rect) = self.float_rect_for(id, w, h) {
155+ self.wm.layout.set_float_rect(id, rect);
156+ }
157+ }
158+
159+ /// Where a floating window of this size goes. A rule can pick the spot,
160+ /// otherwise it is centred over its parent, or else its output, and kept
161+ /// clear of the bars.
162+ fn float_rect_for(&self, id: WindowId, w: f64, h: f64) -> Option<Rect> {
163+ let rule = self.wm.get(id).map(|s| &s.rule);
164+ let (w, h) = rule.map_or((w, h), |r| r.clamp_size(w, h));
165+ let parent = self
166+ .wm
167+ .layout
168+ .window(id)
169+ .and_then(|win| win.parent)
170+ .and_then(|p| self.wm.get(p)?.placed)
171+ .map(|(_, r, _)| r);
172+ let output = self
173+ .wm
174+ .layout
175+ .window(id)
176+ .and_then(|win| win.output)
177+ .and_then(|o| self.wm.layout.output(o))?;
178+ let usable = output.usable();
179+ let at = rule.and_then(|r| r.at);
180+ let around = if at.is_some() {
181+ usable
182+ } else {
183+ parent.unwrap_or(usable)
184+ };
185+ let (x, y) = match at {
186+ Some(At::Position(x, y)) => {
187+ return Some(Rect::new(
188+ output.area.x + f64::from(x),
189+ output.area.y + f64::from(y),
190+ w,
191+ h,
192+ ));
193+ }
194+ _ => (
195+ around.x + (around.w - w) / 2.0,
196+ around.y + (around.h - h) / 2.0,
197+ ),
198+ };
199+ Some(clamp_float(Rect::new(x, y, w, h), usable))
200+ }
201+
202+ /// Sizes a window to what its client picked. One already floating glides
203+ /// there from where it is drawn.
204+ pub(crate) fn float_at_natural_size(&mut self, id: WindowId, w: f64, h: f64) {
205+ if let Some(rect) = self.float_rect_for(id, w, h) {
206+ self.wm.layout.set_float_rect(id, rect);
207+ if let Some((Kind::Floating, from, o)) = self.wm.get(id).and_then(|s| s.placed) {
208+ let now = now_ns();
209+ let from = self.wm.anim.gliding(id, now).unwrap_or(from);
210+ self.wm.anim.glide(id, from, rect, now);
211+ if let Some(s) = self.wm.get_mut(id) {
212+ s.placed = Some((Kind::Floating, rect, o));
213+ }
214+ }
215+ }
216+ }
217+}
Modified file: crates/monolith/src/windows.rs+4 additions−317 deletions
@@ -1,18 +1,14 @@
1-use crate::anim::Ghost;
21 use crate::state::{State, now_ns};
3-use crate::wm::{Kind, LastFrame, identity, root};
4-use monolith_config::At;
5-use monolith_layout::{OutputId, Rect, TagId, WindowId, clamp_float};
6-use monolith_render_vk::VulkanTexture;
7-use smithay::backend::renderer::Renderer;
8-use smithay::backend::renderer::utils::{import_surface_tree, with_renderer_surface_state};
2+use crate::wm::{identity, root};
3+use monolith_layout::{OutputId, Rect, TagId, WindowId};
4+use smithay::backend::renderer::utils::with_renderer_surface_state;
95 use smithay::desktop::Window;
106 use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
117 use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState;
128 use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
139 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
1410 use smithay::utils::{Logical, Point, Rectangle, Serial};
15-use smithay::wayland::compositor::{BufferAssignment, SurfaceAttributes, with_states};
11+use smithay::wayland::compositor::with_states;
1612 use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
1713 use std::time::Duration;
1814
@@ -239,27 +235,6 @@ impl State {
239235 self.apply_layout();
240236 }
241237
242- /// Called before a commit applies. A null-buffer commit makes smithay
243- /// drop the surface's textures before `on_buffer` runs, so the frame a
244- /// hiding window leaves behind has to be taken here.
245- pub fn keep_frame_for_hide(&mut self, surface: &WlSurface) {
246- let Some(id) = self.window_id(surface) else {
247- return;
248- };
249- let removes = with_states(surface, |s| {
250- let mut attrs = s.cached_state.get::<SurfaceAttributes>();
251- matches!(attrs.pending().buffer, Some(BufferAssignment::Removed))
252- });
253- let Some(s) = self.wm.get(id).filter(|s| removes && s.drawn) else {
254- return;
255- };
256- let window = s.window.clone();
257- let frame = self.last_frame(&window);
258- if let Some(s) = self.wm.get_mut(id) {
259- s.hide_frame = frame;
260- }
261- }
262-
263238 fn first_buffer(&mut self, id: WindowId) {
264239 let Some(w) = self.wm.get_mut(id) else {
265240 return;
@@ -324,211 +299,6 @@ impl State {
324299 true
325300 }
326301
327- /// Gives floats that landed on the layout's default rect their own size,
328- /// as when a tiled window is toggled. The client is told to pick one, and
329- /// its next commit after acking says which.
330- pub fn size_new_floats(&mut self) {
331- let stale: Vec<WindowId> = self
332- .wm
333- .iter()
334- .filter(|(id, s)| {
335- s.natural_size && !self.wm.layout.window(*id).is_some_and(|w| w.floating)
336- })
337- .map(|(id, _)| id)
338- .collect();
339- for id in stale {
340- if let Some(s) = self.wm.get_mut(id) {
341- s.natural_size = false;
342- s.natural_after = None;
343- }
344- }
345- let defaults: Vec<WindowId> = self
346- .wm
347- .layout
348- .windows()
349- .filter(|(_, w)| w.floating && w.float_default && w.scratchpad.is_none() && !w.hidden)
350- .map(|(id, _)| id)
351- .collect();
352- for id in defaults {
353- self.ask_natural_size(id);
354- }
355- }
356-
357- fn ask_natural_size(&mut self, id: WindowId) {
358- let Some(window) = self
359- .wm
360- .get(id)
361- .filter(|s| !s.natural_size)
362- .map(|s| s.window.clone())
363- else {
364- return;
365- };
366- let geometry = window.geometry().size;
367- let current = (geometry.w > 0 && geometry.h > 0).then_some(geometry);
368- let fixed = window.toplevel().and_then(crate::xdg::fixed_size);
369- let size = match (window.x11_surface(), window.toplevel()) {
370- (Some(x), _) => crate::xwayland::fixed_size(x),
371- (_, Some(_)) if fixed.is_some() => fixed,
372- (_, Some(t)) => {
373- let held = self.visual_rect(id);
374- let serial = crate::wm::configure_free(t);
375- if let Some(w) = self.wm.get_mut(id)
376- && let Some(serial) = serial
377- {
378- w.natural_size = true;
379- w.natural_after = Some(serial);
380- if let Some(rect) = held {
381- self.wm.layout.hold_float_rect(id, rect);
382- }
383- self.await_natural_size(id, serial);
384- return;
385- }
386- current
387- }
388- _ => None,
389- };
390- match size {
391- Some(size) => self.float_at_natural_size(id, f64::from(size.w), f64::from(size.h)),
392- None => {
393- let rect = self.wm.layout.window(id).and_then(|w| w.float_rect);
394- if let Some(rect) = rect {
395- self.wm.layout.set_float_rect(id, rect);
396- }
397- }
398- }
399- }
400-
401- /// Where a placed window is drawn right now.
402- fn visual_rect(&self, id: WindowId) -> Option<Rect> {
403- let now = now_ns();
404- let (kind, rect, o) = self.wm.get(id)?.placed?;
405- self.wm
406- .anim
407- .gliding(id, now)
408- .or_else(|| {
409- (kind == Kind::Tiled)
410- .then(|| self.wm.anim.tiled(o, id, now))
411- .flatten()
412- })
413- .or(Some(rect))
414- }
415-
416- /// A client that never answers a request for its size takes whatever
417- /// size it has.
418- fn await_natural_size(&mut self, id: WindowId, serial: Serial) {
419- let timeout = Timer::from_duration(Duration::from_millis(300));
420- let _ = self.core.handle.insert_source(timeout, move |_, _, state| {
421- state.give_up_natural_size(id, serial);
422- TimeoutAction::Drop
423- });
424- }
425-
426- fn give_up_natural_size(&mut self, id: WindowId, serial: Serial) {
427- let Some(w) = self.wm.get_mut(id) else {
428- return;
429- };
430- if !w.natural_size || w.natural_after != Some(serial) {
431- return;
432- }
433- w.natural_size = false;
434- w.natural_after = None;
435- let size = w.window.geometry().size;
436- if size.w > 0 && size.h > 0 {
437- self.float_at_natural_size(id, f64::from(size.w), f64::from(size.h));
438- } else if let Some(rect) = self.wm.layout.window(id).and_then(|w| w.float_rect) {
439- self.wm.layout.set_float_rect(id, rect);
440- }
441- self.apply_layout();
442- }
443-
444- /// Gives a floating window the size and spot its rules ask for, keeping
445- /// whichever of the two they leave out. One still waiting for its client
446- /// to pick a size gets its spot when the size arrives, unless the rule
447- /// has a size to give it right away.
448- pub(crate) fn float_by_rule(&mut self, id: WindowId) {
449- let Some(win) = self.wm.layout.window(id).filter(|win| win.floating) else {
450- return;
451- };
452- let (held, fullscreen) = (win.float_rect.map(|r| (r.w, r.h)), win.fullscreen);
453- let Some(s) = self.wm.get_mut(id) else {
454- return;
455- };
456- let rule = &s.rule;
457- let size = rule.size.map(|(w, h)| (f64::from(w), f64::from(h)));
458- let shaped = rule.at.is_some() || rule.min_size.is_some() || rule.max_size.is_some();
459- if size.is_some() {
460- s.natural_size = false;
461- s.natural_after = None;
462- } else if !shaped || s.natural_size || fullscreen {
463- return;
464- }
465- let Some((w, h)) = size.or(held) else {
466- return;
467- };
468- if let Some(rect) = self.float_rect_for(id, w, h) {
469- self.wm.layout.set_float_rect(id, rect);
470- }
471- }
472-
473- /// Where a floating window of this size goes. A rule can pick the spot,
474- /// otherwise it is centred over its parent, or else its output, and kept
475- /// clear of the bars.
476- fn float_rect_for(&self, id: WindowId, w: f64, h: f64) -> Option<Rect> {
477- let rule = self.wm.get(id).map(|s| &s.rule);
478- let (w, h) = rule.map_or((w, h), |r| r.clamp_size(w, h));
479- let parent = self
480- .wm
481- .layout
482- .window(id)
483- .and_then(|win| win.parent)
484- .and_then(|p| self.wm.get(p)?.placed)
485- .map(|(_, r, _)| r);
486- let output = self
487- .wm
488- .layout
489- .window(id)
490- .and_then(|win| win.output)
491- .and_then(|o| self.wm.layout.output(o))?;
492- let usable = output.usable();
493- let at = rule.and_then(|r| r.at);
494- let around = if at.is_some() {
495- usable
496- } else {
497- parent.unwrap_or(usable)
498- };
499- let (x, y) = match at {
500- Some(At::Position(x, y)) => {
501- return Some(Rect::new(
502- output.area.x + f64::from(x),
503- output.area.y + f64::from(y),
504- w,
505- h,
506- ));
507- }
508- _ => (
509- around.x + (around.w - w) / 2.0,
510- around.y + (around.h - h) / 2.0,
511- ),
512- };
513- Some(clamp_float(Rect::new(x, y, w, h), usable))
514- }
515-
516- /// Sizes a window to what its client picked. One already floating glides
517- /// there from where it is drawn.
518- fn float_at_natural_size(&mut self, id: WindowId, w: f64, h: f64) {
519- if let Some(rect) = self.float_rect_for(id, w, h) {
520- self.wm.layout.set_float_rect(id, rect);
521- if let Some((Kind::Floating, from, o)) = self.wm.get(id).and_then(|s| s.placed) {
522- let now = now_ns();
523- let from = self.wm.anim.gliding(id, now).unwrap_or(from);
524- self.wm.anim.glide(id, from, rect, now);
525- if let Some(s) = self.wm.get_mut(id) {
526- s.placed = Some((Kind::Floating, rect, o));
527- }
528- }
529- }
530- }
531-
532302 pub fn unmap_window(&mut self, surface: &WlSurface) {
533303 self.wm
534304 .unmapped
@@ -550,89 +320,6 @@ impl State {
550320 state.map(|s| s.window)
551321 }
552322
553- /// Keeps a visible tiled window's last frame shrinking to its seed while
554- /// its neighbours close over it.
555- fn leave_ghost(
556- &mut self,
557- id: WindowId,
558- window: &Window,
559- saved: Option<LastFrame>,
560- opacity: f32,
561- ) {
562- let Some(o) = self.wm.layout.window(id).and_then(|w| w.output) else {
563- return;
564- };
565- let tiled = self
566- .wm
567- .layout
568- .output(o)
569- .is_some_and(|out| out.tree.contains(id));
570- if !tiled || !self.wm.layout.visible(id) {
571- return;
572- }
573- let Some(seed) = self
574- .wm
575- .layout
576- .arrange_hiding(o, &[id])
577- .tiled
578- .into_iter()
579- .find(|p| p.window == id)
580- else {
581- return;
582- };
583- if let Some(frame) = saved.or_else(|| self.last_frame(window)) {
584- self.wm.last_frames.insert(id, frame);
585- self.wm.anim.add_ghost(Ghost {
586- id,
587- output: o,
588- seed: seed.rect,
589- born: now_ns(),
590- fixed: None,
591- opacity,
592- });
593- }
594- }
595-
596- /// Fades a window that just left the screen where it was, like a
597- /// dismissed scratchpad.
598- pub fn fade_out(&mut self, id: WindowId, rect: Rect, output: OutputId) {
599- let Some(window) = self.wm.window(id).cloned() else {
600- return;
601- };
602- let opacity = self.wm.get(id).and_then(|s| s.rule.opacity).unwrap_or(1.0);
603- if let Some(frame) = self.last_frame(&window) {
604- self.wm.last_frames.insert(id, frame);
605- self.wm.anim.add_ghost(Ghost {
606- id,
607- output,
608- seed: rect,
609- born: now_ns(),
610- fixed: Some(rect),
611- opacity,
612- });
613- }
614- }
615-
616- fn last_frame(&mut self, window: &Window) -> Option<LastFrame> {
617- let surface = root(window)?;
618- let _ = import_surface_tree(&mut self.renderer, &surface);
619- let context = self.renderer.context_id();
620- // The geometry lookup locks the surface's state, which the closure
621- // below already holds.
622- let geometry = window.geometry();
623- with_renderer_surface_state(&surface, |s| {
624- let texture = s.texture::<VulkanTexture>(context.clone())?.clone();
625- Some(LastFrame {
626- texture,
627- scale: s.buffer_scale(),
628- transform: s.buffer_transform(),
629- geometry,
630- view: s.view()?,
631- })
632- })
633- .flatten()
634- }
635-
636323 /// Picks up a changed title or app id, which clients send at any time.
637324 pub fn refresh_identity(&mut self, id: WindowId) {
638325 let Some(w) = self.wm.get_mut(id) else {