atagen

monolith

public
Star0
Watch0

Commit 243161cd0662

tarball

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

4 files changed+57-18
windows: place floats clear of bars and at their fixed or ruled size

float_rect_for and Layout::place centred on the full output area and never clamped, so a dialog on a parent near an edge ended up off screen or under the bar while scratchpads and drags used the usable area. A fixed-size dialog took its current geometry, which is the 60% default before it has drawn, and a window reclassified as a float at its first buffer never got its rule's size or position.
Modified file: crates/layout/src/lib.rs+8 additions−4 deletions
@@ -403,10 +403,14 @@ impl Layout {
403403 return;
404404 }
405405 if win.floating {
406- if win.float_rect.is_none() {
407- let rect = centred(self.outputs[o].area, 0.6);
408- self.windows[w].float_rect = Some(rect);
409- self.windows[w].float_default = true;
406+ let usable = self.outputs[o].usable();
407+ let win = &mut self.windows[w];
408+ match win.float_rect {
409+ Some(rect) => win.float_rect = Some(clamp_float(rect, usable)),
410+ None => {
411+ win.float_rect = Some(centred(usable, 0.6));
412+ win.float_default = true;
413+ }
410414 }
411415 self.outputs[o].floating.push(w);
412416 return;
Modified file: crates/layout/src/scratchpad.rs+2 additions−3 deletions
@@ -24,7 +24,7 @@ impl Layout {
2424 }
2525 };
2626 self.scratchpads[s].windows.push(w);
27- let area = self.windows[w].output.map(|o| self.outputs[o].area);
27+ let area = self.windows[w].output.map(|o| self.outputs[o].usable());
2828 let win = &mut self.windows[w];
2929 win.scratchpad = Some(s);
3030 win.floating = true;
@@ -56,13 +56,12 @@ impl Layout {
5656 sp.output = Some(o);
5757 let w = sp.windows[next];
5858 self.last_scratchpad = Some(s);
59- let area = self.outputs[o].area;
6059 let usable = self.outputs[o].usable();
6160 let win = &mut self.windows[w];
6261 if win.output != Some(o) {
6362 win.float_rect = match (from, win.float_rect) {
6463 (Some(from), Some(r)) => Some(translate(r, from, usable)),
65- _ => Some(centred(area, 0.6)),
64+ _ => Some(centred(usable, 0.6)),
6665 };
6766 win.output = Some(o);
6867 }
Modified file: crates/layout/src/tests.rs+23 additions
@@ -884,3 +884,26 @@ fn a_modal_brings_its_parent_up_beneath_it() {
884884 l.remove_window(dialog);
885885 assert_eq!(l.focused(), Some(parent));
886886 }
887+
888+/// Floats start inside what the bars leave of their output, whether they came
889+/// with a rect hanging off its edge or take the default one.
890+#[test]
891+fn new_floats_land_inside_the_bars() {
892+ let mut l = Layout::default();
893+ let o = l.add_output("a", AREA);
894+ let bar = Insets {
895+ top: 40.0,
896+ ..Insets::default()
897+ };
898+ l.set_reserved(o, bar);
899+ let usable = l.output(o).unwrap().usable();
900+
901+ let hanging = l.add_window(None, None, Some(Rect::new(900.0, -30.0, 300.0, 200.0)));
902+ let r = l.window(hanging).and_then(|w| w.float_rect).unwrap();
903+ assert!(r.x >= usable.x && r.right() <= usable.right() && r.y >= usable.y);
904+
905+ let plain = l.add_window(None, None, None);
906+ l.toggle_floating(plain);
907+ let r = l.window(plain).and_then(|w| w.float_rect).unwrap();
908+ assert_eq!(r.y + r.h / 2.0, usable.y + usable.h / 2.0);
909+}
Modified file: crates/monolith/src/windows.rs+24 additions−11 deletions
@@ -2,7 +2,7 @@ use crate::anim::Ghost;
22 use crate::state::{State, now_ns};
33 use crate::wm::{Kind, LastFrame, identity, root};
44 use monolith_config::At;
5-use monolith_layout::{OutputId, Rect, TagId, WindowId};
5+use monolith_layout::{OutputId, Rect, TagId, WindowId, clamp_float};
66 use monolith_render_vk::VulkanTexture;
77 use smithay::backend::renderer::Renderer;
88 use smithay::backend::renderer::utils::{import_surface_tree, with_renderer_surface_state};
@@ -232,10 +232,14 @@ impl State {
232232 }
233233 let reclassify = std::mem::take(&mut w.reclassify);
234234 let size = w.window.geometry().size;
235- let sized = natural || (reclassify && self.float_if_hinted(id));
235+ let reclassified = reclassify && self.float_if_hinted(id);
236+ let sized = natural || reclassified;
236237 if sized && size.w > 0 && size.h > 0 {
237238 self.float_at_natural_size(id, f64::from(size.w), f64::from(size.h));
238239 }
240+ if reclassified {
241+ self.float_by_rule(id);
242+ }
239243 if !self.release_hold(id) && sized {
240244 self.apply_layout();
241245 }
@@ -321,9 +325,10 @@ impl State {
321325 };
322326 let geometry = window.geometry().size;
323327 let current = (geometry.w > 0 && geometry.h > 0).then_some(geometry);
328+ let fixed = window.toplevel().and_then(crate::xdg::fixed_size);
324329 let size = match (window.x11_surface(), window.toplevel()) {
325330 (Some(x), _) => crate::xwayland::fixed_size(x),
326- (_, Some(t)) if crate::xdg::fixed_size(t).is_some() => current,
331+ (_, Some(_)) if fixed.is_some() => fixed,
327332 (_, Some(t)) => {
328333 let held = self.visual_rect(id);
329334 let serial = crate::wm::configure_free(t);
@@ -426,7 +431,8 @@ impl State {
426431 }
427432
428433 /// Where a floating window of this size goes. A rule can pick the spot,
429- /// otherwise it is centred over its parent, or else its output.
434+ /// otherwise it is centred over its parent, or else its output, and kept
435+ /// clear of the bars.
430436 fn float_rect_for(&self, id: WindowId, w: f64, h: f64) -> Option<Rect> {
431437 let rule = self.wm.get(id).map(|s| &s.rule);
432438 let (w, h) = rule.map_or((w, h), |r| r.clamp_size(w, h));
@@ -442,22 +448,29 @@ impl State {
442448 .layout
443449 .window(id)
444450 .and_then(|win| win.output)
445- .and_then(|o| self.wm.layout.output(o))
446- .map(|o| o.area);
451+ .and_then(|o| self.wm.layout.output(o))?;
452+ let usable = output.usable();
447453 let at = rule.and_then(|r| r.at);
448454 let around = if at.is_some() {
449- output
455+ usable
450456 } else {
451- parent.or(output)
452- }?;
457+ parent.unwrap_or(usable)
458+ };
453459 let (x, y) = match at {
454- Some(At::Position(x, y)) => (around.x + f64::from(x), around.y + f64::from(y)),
460+ Some(At::Position(x, y)) => {
461+ return Some(Rect::new(
462+ output.area.x + f64::from(x),
463+ output.area.y + f64::from(y),
464+ w,
465+ h,
466+ ));
467+ }
455468 _ => (
456469 around.x + (around.w - w) / 2.0,
457470 around.y + (around.h - h) / 2.0,
458471 ),
459472 };
460- Some(Rect::new(x, y, w, h))
473+ Some(clamp_float(Rect::new(x, y, w, h), usable))
461474 }
462475
463476 /// Sizes a window to what its client picked. One already floating glides