atagen

monolith

public
Star0
Watch0

Commit e8128033f056

tarball

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

2 files changed+24-4
xdg: configure toplevels with a size whose buffer fills the rect

A client draws round(logical * scale) pixels, which at a fractional scale can miss the layout rect's physical width by one pixel and send the window through the crop and stretch fallback. configure now asks for a logical size that rounds to the rect's physical size when one exists.
Modified file: crates/monolith/src/state.rs+6 additions−2 deletions
@@ -394,10 +394,14 @@ impl State {
394394 }
395395
396396 let mut mapped = Vec::new();
397- for (id, rect, kind, _) in shown {
397+ for (id, rect, kind, o) in shown {
398398 let free = kind == Kind::Floating && self.wm.get(id).is_some_and(|s| s.natural_size);
399+ let scale = self
400+ .backend
401+ .output(o)
402+ .map_or(1.0, |o| o.current_scale().fractional_scale());
399403 if let Some(window) = self.wm.window(id).cloned()
400- && (free || configure(&window, rect, kind, self.wm.layout.maximized(id)))
404+ && (free || configure(&window, rect, kind, self.wm.layout.maximized(id), scale))
401405 {
402406 let loc = (rect.x.round() as i32, rect.y.round() as i32);
403407 self.wm.space.map_element(window, loc, false);
Modified file: crates/monolith/src/wm.rs+18 additions−2 deletions
@@ -222,7 +222,7 @@ pub enum Kind {
222222
223223 /// Sets a window's pending size and states. Returns false for windows
224224 /// squeezed to a line by an expanded sibling, which are not shown.
225-pub fn configure(window: &Window, rect: Rect, kind: Kind, maximized: bool) -> bool {
225+pub fn configure(window: &Window, rect: Rect, kind: Kind, maximized: bool, scale: f64) -> bool {
226226 let (x, y) = (rect.x.round(), rect.y.round());
227227 let w = (rect.right().round() - x) as i32;
228228 let h = (rect.bottom().round() - y) as i32;
@@ -240,8 +240,12 @@ pub fn configure(window: &Window, rect: Rect, kind: Kind, maximized: bool) -> bo
240240 );
241241 }
242242 if let Some(t) = window.toplevel() {
243+ let size = (
244+ logical_extent(rect.x, rect.right(), scale),
245+ logical_extent(rect.y, rect.bottom(), scale),
246+ );
243247 t.with_pending_state(|s| {
244- s.size = Some((w, h).into());
248+ s.size = Some(size.into());
245249 set_states(s, kind, maximized);
246250 });
247251 if t.is_initial_configure_sent() {
@@ -251,6 +255,18 @@ pub fn configure(window: &Window, rect: Rect, kind: Kind, maximized: bool) -> bo
251255 true
252256 }
253257
258+/// The logical length to ask a client for between `start` and `end`. Its
259+/// buffer is round(length * scale) pixels, so a length that rounds to the
260+/// span's physical pixels keeps the window from being cropped or stretched.
261+fn logical_extent(start: f64, end: f64, scale: f64) -> i32 {
262+ let physical = (end * scale).round() - (start * scale).round();
263+ let guess = (physical / scale).round() as i32;
264+ [guess, guess - 1, guess + 1]
265+ .into_iter()
266+ .find(|&c| c >= 1 && (f64::from(c) * scale).round() == physical)
267+ .unwrap_or((end.round() - start.round()) as i32)
268+}
269+
254270 /// Lets a floating toplevel pick its own size, returning the serial of the
255271 /// configure that says so. None if it was sent nothing.
256272 pub fn configure_free(t: &ToplevelSurface) -> Option<Serial> {