diff options
| author | srdusr <[email protected]> | 2026-05-11 16:57:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-05-11 16:57:00 +0200 |
| commit | 646b37e7e3aa5931079c6b9e804f420bb43c74d5 (patch) | |
| tree | 159825fae0086c162b36edc6e78da07d975ab6b0 /crates/core/src/manager/windows.rs | |
| parent | caec1e7c355bbe6437afe87cd3dab6b64fb91e0a (diff) | |
| download | srdwm-646b37e7e3aa5931079c6b9e804f420bb43c74d5.tar.gz srdwm-646b37e7e3aa5931079c6b9e804f420bb43c74d5.zip | |
Fix spawn placement under the top bar, add per-window minimum sizes, and clean up maximize
Four reports after restarting into today's build, with a screenshot. The
screenshot was measured, not eyeballed: top bar y=0..29, a 4px accent border
at y=30..33, titlebar from y=34, bottom border at y=1027..1030, and 49px of
bare desktop below it.
Windows spawning too close to the top bar. A remembered position was
validated only by asking whether it landed on some monitor's full_geometry,
which includes the strip a top bar reserves, so an app whose remembered y was
small reopened with its titlebar under the bar. That is why it was
"sometimes": it depended on the stored value, and the live store holds
wezterm at y=44 and firefox at y=69 against a 30px bar. Remembered positions
are now clamped into the monitor's usable area.
Placement not surviving a logout. Window memory does persist, but five of the
eleven entries in the live store were saved with a second monitor attached,
at x >= 2000. Those points match no current monitor and were discarded
outright, falling back to a fresh cascade, so those apps appeared to remember
nothing. Such a position is now clamped onto a monitor that exists instead.
Per-window minimum sizes. One global floor is wrong in both directions.
Three sources now, in increasing precedence: the global floor, the client's
own declared minimum (xdg_toplevel.set_min_size or ICCCM hints), and a
min_width/min_height window rule overriding both. A rule wins permanently --
the backend refreshes the client's declared minimum on every decoration
redraw and must not undo a deliberate override.
Maximize, three faults in one report. A maximized window now draws no
border: its edges are the screen's edges, and the only place maximize stops
short is the bar strip, which is exactly where the measured line was.
maximize_geometry_for no longer subtracts a bottom-anchored dock's exclusive
zone, so maximize runs to the bottom of the screen and the dock floats over
it; top, left and right are still honoured.
general.maximize_covers_dock = false restores the old behaviour. With the
border gone the window sits flush under the bar instead of with an accent
line crowding it.
Verified: seven new tests on the real numbers from the live store, and
maximize geometry measured live in a nested instance (a window maximized on a
split half reports exactly that half's rect). NOT confirmed on screen: the
border removal and the dock behaviour - the nested backend has no bar or
dock to reserve a zone, and an attempt to check the border produced a failing
control, since srd set border_width only affects windows created after it.
515 tests pass, clippy clean.
Diffstat (limited to 'crates/core/src/manager/windows.rs')
| -rw-r--r-- | crates/core/src/manager/windows.rs | 44 |
1 files changed, 39 insertions, 5 deletions
diff --git a/crates/core/src/manager/windows.rs b/crates/core/src/manager/windows.rs index bd13b0c..0186679 100644 --- a/crates/core/src/manager/windows.rs +++ b/crates/core/src/manager/windows.rs @@ -60,6 +60,10 @@ impl WindowManager { if let Some(margin) = a.resize_margin { window.resize_margin = Some(margin); } + if let Some(min) = a.min_size { + window.min_size = min; + window.min_size_from_rule = true; + } if let Some(ratio) = a.aspect_ratio { window.aspect_ratio = Some(ratio); } @@ -87,8 +91,8 @@ impl WindowManager { let mut remembered_position: Option<(i32, i32)> = None; if !window.app_id.is_empty() && !window.is_dialog { if let Some((x, y, w, h)) = self.remembered_geometry.get(&window.app_id).copied() { - window.geometry.width = w.max(MIN_WINDOW_WIDTH); - window.geometry.height = h.max(MIN_WINDOW_HEIGHT); + window.geometry.width = w.max(window.min_size.0); + window.geometry.height = h.max(window.min_size.1); remembered_position = Some((x, y)); } } @@ -137,7 +141,16 @@ impl WindowManager { // current monitor's own *full* geometry (not the exclusive-zone- // shrunk usable one): a remembered position under where a bar now // sits is still "a real monitor, just partly covered", not invalid. - let remembered_monitor = remembered_position.and_then(|(x, y)| self.monitors.iter().find(|m| m.full_geometry.contains_point(x, y))); + // Whichever monitor a remembered position lands on, else the one + // the window would have been placed on anyway - a position saved + // while a second monitor was connected must not be thrown away + // just because that monitor is gone. Clamped onto the surviving + // monitor below instead, which keeps "reopen where I left it" + // meaningful across a docking change rather than silently falling + // back to a fresh cascade. + let remembered_monitor = remembered_position + .and_then(|(x, y)| self.monitors.iter().find(|m| m.full_geometry.contains_point(x, y))) + .or(target_monitor); // A dialog is centred, and neither cascaded nor restored to a // remembered spot. Reported as "even dialog/starter windows spawn // that side, most times should be centered". @@ -160,8 +173,25 @@ impl WindowManager { } } else if let (Some((x, y)), Some(monitor)) = (remembered_position, remembered_monitor) { window.monitor = monitor.id; - window.geometry.x = x; - window.geometry.y = y; + // Clamped into the monitor's *usable* area, not its full one. + // + // The check above only asked whether the remembered point was + // on some monitor at all, and `full_geometry` includes the + // strip a top bar reserves - so an app whose remembered `y` + // was small reopened with its titlebar tucked under the bar, + // unreachable. Reported as windows spawning "so close to top + // bar", and "sometimes" precisely because it depended on the + // remembered value: this store currently holds a window at + // y=44 with a 30px bar above it. + // + // The same clamp is what makes a position from a + // now-disconnected monitor usable rather than discarded: it is + // pulled back onto a monitor that exists, keeping as much of + // the remembered placement as still fits. + let area = monitor.geometry; + let (w, h) = (window.geometry.width as i32, window.geometry.height as i32); + window.geometry.x = x.clamp(area.x, (area.right() - w).max(area.x)); + window.geometry.y = y.clamp(area.y, (area.bottom() - h).max(area.y)); } else if let Some(monitor) = target_monitor { window.monitor = monitor.id; let layout_name = self.workspace(workspace).map(|w| w.layout.clone()).unwrap_or_default(); @@ -280,6 +310,10 @@ impl WindowManager { if let Some(margin) = actions.resize_margin { window.resize_margin = Some(margin); } + if let Some(min) = actions.min_size { + window.min_size = min; + window.min_size_from_rule = true; + } if let Some(ratio) = actions.aspect_ratio { window.aspect_ratio = Some(ratio); } |