atagen

monolith

public
Star0
Watch0

Commit 5920d2cc09bc

tarball

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

2 files changed+37-7
windows: reclassify a tiled toplevel at its first buffer

xdg::floats ran on the first bufferless commit, before GTK has sent its min and max size, which it only does after the first configure. Inkscape's splash and start screen therefore tiled. A toplevel with no rule is now looked at again when its first buffer lands, while it is still held on its seed, and floats at its own size if it qualifies by then.
Modified file: crates/monolith/src/windows.rs+32 additions−6 deletions
@@ -68,9 +68,11 @@ impl State {
6868 self.view_if_first(id);
6969 let by_rule = rule.tag.is_some() || rule.output.is_some();
7070 let natural_size = floating && window.toplevel().is_some();
71+ let reclassify = !floating && rule.floating.is_none() && window.toplevel().is_some();
7172 self.wm.insert(id, window, by_rule, app_id, title);
7273 if let Some(s) = self.wm.get_mut(id) {
7374 s.natural_size = natural_size;
75+ s.reclassify = reclassify;
7476 }
7577 self.await_first_buffer(id);
7678 if self.wm.layout.visible(id) {
@@ -107,17 +109,41 @@ impl State {
107109 return;
108110 };
109111 let natural = std::mem::take(&mut w.natural_size);
110- if natural {
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- }
112+ let reclassify = std::mem::take(&mut w.reclassify);
113+ let size = w.window.geometry().size;
114+ let sized = natural || (reclassify && self.float_if_hinted(id));
115+ if sized && size.w > 0 && size.h > 0 {
116+ self.float_at_natural_size(id, f64::from(size.w), f64::from(size.h));
115117 }
116- if !self.release_hold(id) && natural {
118+ if !self.release_hold(id) && sized {
117119 self.apply_layout();
118120 }
119121 }
120122
123+ /// Floats a tiled window whose size hints, which GTK only sets after its
124+ /// first configure, show it to be a dialog or fixed-size. It is still held
125+ /// on its seed, so it never shows tiled.
126+ fn float_if_hinted(&mut self, id: WindowId) -> bool {
127+ let hinted = self
128+ .wm
129+ .window(id)
130+ .and_then(|w| w.toplevel())
131+ .is_some_and(crate::xdg::floats);
132+ let tiled = self
133+ .wm
134+ .layout
135+ .window(id)
136+ .is_some_and(|w| !w.floating && !w.fullscreen && w.scratchpad.is_none());
137+ if !hinted || !tiled {
138+ return false;
139+ }
140+ self.wm.layout.toggle_floating(id);
141+ if let Some(w) = self.wm.get_mut(id) {
142+ w.placed = None;
143+ }
144+ true
145+ }
146+
121147 /// Ends a window's hold on its seed, returning whether that ran the
122148 /// layout.
123149 fn release_hold(&mut self, id: WindowId) -> bool {
Modified file: crates/monolith/src/wm.rs+5 additions−1 deletions
@@ -58,6 +58,9 @@ pub struct WindowState {
5858 /// Configured without a size until its first buffer shows the one it
5959 /// picked.
6060 pub natural_size: bool,
61+ /// Tiled by default, but its size hints are only reliable at its first
62+ /// buffer and may turn it into a float.
63+ pub reclassify: bool,
6164 /// A rule sent it somewhere, so activation tokens leave it alone.
6265 pub placed_by_rule: bool,
6366 pub list_handle: Option<ForeignToplevelHandle>,
@@ -114,6 +117,7 @@ impl Wm {
114117 placed: None,
115118 awaiting_buffer: true,
116119 natural_size: false,
120+ reclassify: false,
117121 placed_by_rule,
118122 list_handle: None,
119123 borders: None,
@@ -194,7 +198,7 @@ impl Wm {
194198 pub fn any_settling(&self) -> bool {
195199 self.windows
196200 .values()
197- .any(|w| w.awaiting_buffer || w.natural_size)
201+ .any(|w| w.awaiting_buffer || w.natural_size || w.reclassify)
198202 }
199203 }