atagen

monolith

public
Star0
Watch0

Commit 20a4ebbb7f15

tarball

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

2 files changed+59-9
windows: size and place floating windows by rule

float_at_natural_size always centred a float over its parent or output at the size its client picked, so config could neither fix a size nor a spot. WindowState now keeps the matching Placement, and float_rect_for applies its size, position, centring and bounds to rule sized windows at map and to natural sizes at first buffer.
Modified file: crates/monolith/src/windows.rs+50 additions−9 deletions
@@ -1,6 +1,7 @@
11 use crate::anim::Ghost;
22 use crate::state::{State, now_ns};
33 use crate::wm::{Kind, LastFrame, identity, root};
4+use monolith_config::At;
45 use monolith_layout::{OutputId, Rect, WindowId};
56 use monolith_render_vk::VulkanTexture;
67 use smithay::backend::renderer::Renderer;
@@ -70,13 +71,19 @@ impl State {
7071 }
7172 self.view_if_first(id);
7273 let by_rule = rule.tag.is_some() || rule.output.is_some();
73- let natural_size = floating && !fullscreen && window.toplevel().is_some();
74+ let natural_size =
75+ floating && !fullscreen && window.toplevel().is_some() && rule.size.is_none();
7476 let reclassify = !floating && rule.floating.is_none() && window.toplevel().is_some();
75- self.wm.insert(id, window, by_rule, app_id, title);
77+ let shaped = rule.at.is_some() || rule.min_size.is_some() || rule.max_size.is_some();
78+ let positioned = rule.size.is_some() || (shaped && !natural_size && !fullscreen);
79+ self.wm.insert(id, window, by_rule, app_id, title, rule);
7680 if let Some(s) = self.wm.get_mut(id) {
7781 s.natural_size = natural_size;
7882 s.reclassify = reclassify;
7983 }
84+ if positioned {
85+ self.float_by_rule(id);
86+ }
8087 self.await_first_buffer(id);
8188 if self.wm.layout.visible(id) && !self.wm.layout.covered(id) {
8289 self.wm.layout.focus(id);
@@ -320,9 +327,27 @@ impl State {
320327 self.apply_layout();
321328 }
322329
323- /// Centres a window over its parent, or else its output. One already
324- /// floating glides there from where it is drawn.
325- fn float_at_natural_size(&mut self, id: WindowId, w: f64, h: f64) {
330+ /// Gives a floating window the size and spot its rules ask for, keeping
331+ /// whichever of the two they leave out.
332+ fn float_by_rule(&mut self, id: WindowId) {
333+ let Some(win) = self.wm.layout.window(id).filter(|win| win.floating) else {
334+ return;
335+ };
336+ let held = win.float_rect.map(|r| (r.w, r.h));
337+ let size = self.wm.get(id).and_then(|s| s.rule.size);
338+ let Some((w, h)) = size.map(|(w, h)| (f64::from(w), f64::from(h))).or(held) else {
339+ return;
340+ };
341+ if let Some(rect) = self.float_rect_for(id, w, h) {
342+ self.wm.layout.set_float_rect(id, rect);
343+ }
344+ }
345+
346+ /// Where a floating window of this size goes. A rule can pick the spot,
347+ /// otherwise it is centred over its parent, or else its output.
348+ fn float_rect_for(&self, id: WindowId, w: f64, h: f64) -> Option<Rect> {
349+ let rule = self.wm.get(id).map(|s| &s.rule);
350+ let (w, h) = rule.map_or((w, h), |r| r.clamp_size(w, h));
326351 let parent = self
327352 .wm
328353 .layout
@@ -337,10 +362,26 @@ impl State {
337362 .and_then(|win| win.output)
338363 .and_then(|o| self.wm.layout.output(o))
339364 .map(|o| o.area);
340- if let Some(around) = parent.or(output) {
341- let x = around.x + (around.w - w) / 2.0;
342- let y = around.y + (around.h - h) / 2.0;
343- let rect = Rect::new(x, y, w, h);
365+ let at = rule.and_then(|r| r.at);
366+ let around = if at.is_some() {
367+ output
368+ } else {
369+ parent.or(output)
370+ }?;
371+ let (x, y) = match at {
372+ Some(At::Position(x, y)) => (around.x + f64::from(x), around.y + f64::from(y)),
373+ _ => (
374+ around.x + (around.w - w) / 2.0,
375+ around.y + (around.h - h) / 2.0,
376+ ),
377+ };
378+ Some(Rect::new(x, y, w, h))
379+ }
380+
381+ /// Sizes a window to what its client picked. One already floating glides
382+ /// there from where it is drawn.
383+ fn float_at_natural_size(&mut self, id: WindowId, w: f64, h: f64) {
384+ if let Some(rect) = self.float_rect_for(id, w, h) {
344385 self.wm.layout.set_float_rect(id, rect);
345386 if let Some((Kind::Floating, from, o)) = self.wm.get(id).and_then(|s| s.placed) {
346387 let now = now_ns();
Modified file: crates/monolith/src/wm.rs+9 additions
@@ -3,6 +3,7 @@
33
44 use crate::anim::Animator;
55
6+use monolith_config::Placement;
67 use monolith_layout::{Layout, OutputId, Rect, WindowId};
78 use monolith_render_vk::VulkanTexture;
89 use slotmap::SecondaryMap;
@@ -76,6 +77,11 @@ pub struct WindowState {
7677 pub borders: Option<[SolidColorBuffer; 4]>,
7778 pub app_id: String,
7879 pub title: String,
80+ /// The title it mapped with, which `initial_title` rules match.
81+ pub initial_title: String,
82+ /// What the rules matching it say. Tearing presentation will read
83+ /// `tearing` from here.
84+ pub rule: Placement,
7985 }
8086
8187 impl WindowState {
@@ -124,6 +130,7 @@ impl Wm {
124130 placed_by_rule: bool,
125131 app_id: String,
126132 title: String,
133+ rule: Placement,
127134 ) {
128135 self.windows.insert(
129136 id,
@@ -139,7 +146,9 @@ impl Wm {
139146 list_handle: None,
140147 borders: None,
141148 app_id,
149+ initial_title: title.clone(),
142150 title,
151+ rule,
143152 },
144153 );
145154 self.index(id);