atagen

monolith

public
Star0
Watch0

Commit bb1e0d9edf31

tarball

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

4 files changed+54-1
cursor: preload the common shapes for each output's scale

Keeping only the chosen image per shape meant the first draw of a shape read the theme file inside a frame. Outputs appearing or changing scale now prepare the common shapes for every connected scale, and scales no output uses are dropped.
Modified file: crates/monolith/src/cursor.rs+38 additions−1 deletions
@@ -27,6 +27,31 @@ struct Prepared {
2727 hotspot: Point<i32, Physical>,
2828 }
2929
30+const PRELOADED: [CursorIcon; 18] = [
31+ CursorIcon::Default,
32+ CursorIcon::Text,
33+ CursorIcon::Pointer,
34+ CursorIcon::Grab,
35+ CursorIcon::Grabbing,
36+ CursorIcon::NotAllowed,
37+ CursorIcon::Wait,
38+ CursorIcon::Progress,
39+ CursorIcon::EwResize,
40+ CursorIcon::NsResize,
41+ CursorIcon::NResize,
42+ CursorIcon::SResize,
43+ CursorIcon::EResize,
44+ CursorIcon::WResize,
45+ CursorIcon::NwResize,
46+ CursorIcon::NeResize,
47+ CursorIcon::SwResize,
48+ CursorIcon::SeResize,
49+];
50+
51+fn scale_step(scale: f64) -> u32 {
52+ (scale * 120.0).round() as u32
53+}
54+
3055 pub struct Cursor {
3156 theme: xcursor::CursorTheme,
3257 size: u32,
@@ -63,6 +88,18 @@ impl Cursor {
6388 .find_map(|n| load_file(&self.theme, n))
6489 }
6590
91+ /// Reads the shapes clients ask for most at each of `scales`, so none is
92+ /// read from the theme inside a frame, and forgets other scales.
93+ pub fn preload(&mut self, renderer: &mut VulkanRenderer, scales: &[f64]) {
94+ let steps: Vec<u32> = scales.iter().map(|s| scale_step(*s)).collect();
95+ self.sized.retain(|(_, step), _| steps.contains(step));
96+ for &scale in scales {
97+ for icon in PRELOADED {
98+ self.sized(renderer, icon, scale);
99+ }
100+ }
101+ }
102+
66103 /// The theme's image nearest the cursor size times `scale`, stretched to
67104 /// exactly that size so it covers whole pixels. Only that image is kept,
68105 /// and an upload that fails is retried on the next call.
@@ -72,7 +109,7 @@ impl Cursor {
72109 icon: CursorIcon,
73110 scale: f64,
74111 ) -> Option<&Prepared> {
75- let key = (icon, (scale * 120.0).round() as u32);
112+ let key = (icon, scale_step(scale));
76113 if !self.sized.contains_key(&key) && !self.absent.contains(&icon) {
77114 let Some(images) = self.read(icon) else {
78115 self.absent.insert(icon);
Modified file: crates/monolith/src/drm/outputs.rs+2 additions
@@ -222,6 +222,7 @@ pub(super) fn connect(state: &mut State, connector: connector::Info, crtc: crtc:
222222 refresh_ns: 1_000_000_000_000 / u64::from(wl_mode.refresh.max(1) as u32),
223223 },
224224 );
225+ state.preload_cursors();
225226 }
226227
227228 pub(super) fn connector_name(connector: &connector::Info) -> String {
@@ -428,6 +429,7 @@ pub fn reconfigure_outputs(state: &mut State) {
428429 );
429430 }
430431 if changed {
432+ state.preload_cursors();
431433 state.reserve_layers();
432434 state.reconfigure_lock();
433435 state.apply_layout();
Modified file: crates/monolith/src/nested/mod.rs+2 additions
@@ -181,6 +181,7 @@ pub fn init(
181181 next_feedback: 0,
182182 awaiting_frame: Vec::new(),
183183 }));
184+ state.preload_cursors();
184185 queue.roundtrip(state)?;
185186 queue.roundtrip(state)?;
186187 WaylandSource::new(conn, queue).insert(event_loop.handle())?;
@@ -217,6 +218,7 @@ pub fn reconfigure(state: &mut State) {
217218 );
218219 state.wm.layout.set_output_area(o, area);
219220 tracing::info!(scale, "output reconfigured");
221+ state.preload_cursors();
220222 state.dirty.redraw = true;
221223 state.reserve_layers();
222224 state.reconfigure_lock();
Modified file: crates/monolith/src/scale.rs+12 additions
@@ -116,6 +116,18 @@ impl State {
116116 }
117117 }
118118
119+ /// Readies the cursor shapes for every output's scale, for when an output
120+ /// appears or changes scale.
121+ pub fn preload_cursors(&mut self) {
122+ let scales: Vec<f64> = self
123+ .wm
124+ .space
125+ .outputs()
126+ .map(|o| o.current_scale().fractional_scale())
127+ .collect();
128+ self.input.cursor.preload(&mut self.renderer, &scales);
129+ }
130+
119131 /// Retells the cursor and drag icon when the pointer crosses to an output.
120132 pub fn follow_pointer_scale(&mut self) {
121133 let under = self.pointer_output_ref();