atagen

monolith

public
Star0
Watch0

Commit b75c2f9a2eee

tarball

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

3 files changed+51-2
frame: send frame callbacks to x11 popups and client cursors

send_frame_callbacks only walked mapped windows and layers, so animated client cursors froze and override-redirect menus got no callbacks. Unmanaged surfaces are drawn in front of a fullscreen window, so they are never skipped by the covering check.
Modified file: crates/monolith/src/drm.rs+10 additions−1 deletions
@@ -1076,7 +1076,16 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
10761076 surface.failures = if failed { surface.failures + 1 } else { 0 };
10771077 surface.redraw = animating || failed;
10781078 let elapsed = core.start.elapsed();
1079- crate::frame::send_frame_callbacks(wm, lock, surface.id, &surface.output, now, elapsed);
1079+ crate::frame::send_frame_callbacks(
1080+ wm,
1081+ lock,
1082+ xwayland.as_ref().map_or(&[], |x| &x.unmanaged),
1083+ surface.id,
1084+ &surface.output,
1085+ now,
1086+ elapsed,
1087+ );
1088+ crate::frame::send_cursor_frames(wm, input, surface.id, &surface.output, elapsed);
10801089 (animating || failed) && !surface.queued
10811090 }
10821091
Modified file: crates/monolith/src/frame.rs+32 additions
@@ -8,6 +8,8 @@ use crate::wm::Wm;
88 use monolith_config::Decoration;
99 use monolith_layout::OutputId;
1010 use monolith_render_vk::VulkanRenderer;
11+use smithay::desktop::utils::send_frames_surface_tree;
12+use smithay::input::pointer::CursorImageStatus;
1113 use smithay::output::Output;
1214 use smithay::utils::Point;
1315 use smithay::xwayland::X11Surface;
@@ -68,6 +70,7 @@ pub fn compose(
6870 pub fn send_frame_callbacks(
6971 wm: &Wm,
7072 lock: &Lock,
73+ unmanaged: &[X11Surface],
7174 o: OutputId,
7275 output: &Output,
7376 now: u64,
@@ -92,4 +95,33 @@ pub fn send_frame_callbacks(
9295 });
9396 }
9497 crate::layers::send_frames(output, elapsed, covering.is_some());
98+ if let Some(geo) = wm.space.output_geometry(output) {
99+ for surface in unmanaged
100+ .iter()
101+ .filter(|x| geo.overlaps(x.geometry()))
102+ .filter_map(|x| x.wl_surface())
103+ {
104+ send_frames_surface_tree(&surface, output, elapsed, Some(Duration::ZERO), |_, _| {
105+ Some(output.clone())
106+ });
107+ }
108+ }
109+}
110+
111+/// Lets a client that set its own cursor image draw the next frame of it
112+/// while the pointer is on `output`, so animated cursors keep moving.
113+pub fn send_cursor_frames(wm: &Wm, input: &Input, o: OutputId, output: &Output, elapsed: Duration) {
114+ let CursorImageStatus::Surface(surface) = &input.cursor_status else {
115+ return;
116+ };
117+ let pos = input.pointer_pos;
118+ if wm
119+ .layout
120+ .output(o)
121+ .is_some_and(|out| out.area.contains(pos.x, pos.y))
122+ {
123+ send_frames_surface_tree(surface, output, elapsed, Some(Duration::ZERO), |_, _| {
124+ Some(output.clone())
125+ });
126+ }
95127 }
Modified file: crates/monolith/src/nested/mod.rs+9 additions−1 deletions
@@ -257,7 +257,15 @@ pub fn render(state: &mut State) {
257257 dirty.redraw = wm.anim.active(now);
258258 lock.shown(&host.output);
259259 let elapsed = core.start.elapsed();
260- crate::frame::send_frame_callbacks(wm, lock, host.output_id, &host.output, now, elapsed);
260+ crate::frame::send_frame_callbacks(
261+ wm,
262+ lock,
263+ xwayland.as_ref().map_or(&[], |x| &x.unmanaged),
264+ host.output_id,
265+ &host.output,
266+ now,
267+ elapsed,
268+ );
261269 if damage.is_some() {
262270 captures.output_damaged(&host.output);
263271 }