atagen

monolith

public
Star0
Watch0

Commit 621fc9d239f8

tarball

atagen <boss@atagen.co>2026-10-18 03:18 UTC

4 files changed+62-3
monolith: float dialogs and size-capped wayland windows at their own size

Only X11 windows were floated by type and size hints, so Wayland dialogs tiled. A floating Wayland window is now configured without a size and placed over its parent or output once its first buffer shows the size it chose, where it used to be forced to 60% of the output. A rule's floating now sets the state rather than toggling it, which tiled X11 dialogs that a rule asked to float.
Modified file: crates/monolith/src/state.rs+1 additions
@@ -359,6 +359,7 @@ impl State {
359359 let mut mapped = Vec::new();
360360 for (id, rect, kind, _) in shown {
361361 if let Some(window) = self.wm.window(id).cloned()
362+ && !self.wm.get(id).is_some_and(|s| s.natural_size)
362363 && configure(&window, rect, kind, self.wm.layout.maximized(id))
363364 {
364365 let loc = (rect.x.round() as i32, rect.y.round() as i32);
Modified file: crates/monolith/src/windows.rs+43 additions−2 deletions
@@ -50,8 +50,13 @@ impl State {
5050 f64::from(r.size.h),
5151 )
5252 });
53- let id = self.wm.layout.add_window(output, tag, float);
54- if rule.floating == Some(true) {
53+ let auto = float.is_some() || window.toplevel().is_some_and(crate::xdg::floats);
54+ let floating = rule.floating.unwrap_or(auto);
55+ let id = self
56+ .wm
57+ .layout
58+ .add_window(output, tag, float.filter(|_| floating));
59+ if floating != self.wm.layout.window(id).is_some_and(|w| w.floating) {
5560 self.wm.layout.toggle_floating(id);
5661 }
5762 if rule.sticky == Some(true) {
@@ -62,7 +67,11 @@ impl State {
6267 }
6368 self.view_if_first(id);
6469 let by_rule = rule.tag.is_some() || rule.output.is_some();
70+ let natural_size = floating && window.toplevel().is_some();
6571 self.wm.insert(id, window, by_rule, app_id, title);
72+ if let Some(s) = self.wm.get_mut(id) {
73+ s.natural_size = natural_size;
74+ }
6675 self.await_first_buffer(id);
6776 if self.wm.layout.visible(id) {
6877 self.wm.layout.focus(id);
@@ -98,12 +107,44 @@ impl State {
98107 return;
99108 };
100109 w.placed_by_rule = false;
110+ if std::mem::take(&mut w.natural_size) {
111+ let size = w.window.geometry().size;
112+ if size.w > 0 && size.h > 0 {
113+ self.float_at_natural_size(id, f64::from(size.w), f64::from(size.h));
114+ }
115+ }
116+ let Some(w) = self.wm.get_mut(id) else {
117+ return;
118+ };
101119 if std::mem::take(&mut w.awaiting_buffer) {
102120 self.wm.anim.spawned(id, now_ns());
103121 self.apply_layout();
104122 }
105123 }
106124
125+ /// Centres a window over its parent, or else its output.
126+ fn float_at_natural_size(&mut self, id: WindowId, w: f64, h: f64) {
127+ let parent = self
128+ .wm
129+ .window(id)
130+ .and_then(|win| win.toplevel()?.parent())
131+ .and_then(|p| self.window_id(&p))
132+ .and_then(|p| self.wm.get(p)?.placed)
133+ .map(|(_, r, _)| r);
134+ let output = self
135+ .wm
136+ .layout
137+ .window(id)
138+ .and_then(|win| win.output)
139+ .and_then(|o| self.wm.layout.output(o))
140+ .map(|o| o.area);
141+ if let Some(around) = parent.or(output) {
142+ let x = around.x + (around.w - w) / 2.0;
143+ let y = around.y + (around.h - h) / 2.0;
144+ self.wm.layout.set_float_rect(id, Rect::new(x, y, w, h));
145+ }
146+ }
147+
107148 pub fn unmap_window(&mut self, surface: &WlSurface) {
108149 self.wm
109150 .unmapped
Modified file: crates/monolith/src/wm.rs+4 additions
@@ -55,6 +55,9 @@ pub struct WindowState {
5555 pub placed: Option<(Kind, Rect, OutputId)>,
5656 /// Held on its seed until its first buffer lands.
5757 pub awaiting_buffer: bool,
58+ /// Configured without a size until its first buffer shows the one it
59+ /// picked.
60+ pub natural_size: bool,
5861 /// A rule sent it somewhere, so activation tokens leave it alone.
5962 pub placed_by_rule: bool,
6063 pub list_handle: Option<ForeignToplevelHandle>,
@@ -110,6 +113,7 @@ impl Wm {
110113 window,
111114 placed: None,
112115 awaiting_buffer: true,
116+ natural_size: false,
113117 placed_by_rule,
114118 list_handle: None,
115119 borders: None,
Modified file: crates/monolith/src/xdg.rs+14 additions−1 deletions
@@ -6,10 +6,23 @@ use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
66 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
77 use smithay::utils::Serial;
88 use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;
9+use smithay::wayland::compositor::with_states;
910 use smithay::wayland::shell::xdg::{
10- PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
11+ PopupSurface, PositionerState, SurfaceCachedState, ToplevelSurface, XdgShellHandler,
12+ XdgShellState,
1113 };
1214
15+/// Dialogs, and windows that cap their size and so could never fill a tile.
16+pub fn floats(toplevel: &ToplevelSurface) -> bool {
17+ let max = with_states(toplevel.wl_surface(), |s| {
18+ s.cached_state
19+ .get::<SurfaceCachedState>()
20+ .current()
21+ .max_size
22+ });
23+ toplevel.parent().is_some() || (max.w > 0 && max.h > 0)
24+}
25+
1326 impl XdgShellHandler for State {
1427 fn xdg_shell_state(&mut self) -> &mut XdgShellState {
1528 &mut self.protocols.xdg_shell_state