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/wayland | |
| 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/wayland')
| -rw-r--r-- | crates/wayland/src/input/layers.rs | 12 | ||||
| -rw-r--r-- | crates/wayland/src/state/lifecycle.rs | 20 | ||||
| -rw-r--r-- | crates/wayland/src/udev/platform.rs | 2 | ||||
| -rw-r--r-- | crates/wayland/src/udev/render.rs | 11 | ||||
| -rw-r--r-- | crates/wayland/src/winit/nested_platform.rs | 4 | ||||
| -rw-r--r-- | crates/wayland/src/winit/render.rs | 11 |
6 files changed, 53 insertions, 7 deletions
diff --git a/crates/wayland/src/input/layers.rs b/crates/wayland/src/input/layers.rs index b429fbb..4450013 100644 --- a/crates/wayland/src/input/layers.rs +++ b/crates/wayland/src/input/layers.rs @@ -142,7 +142,7 @@ pub(super) fn background_layer_surface_under(state: &CompState, pos: Point<f64, /// respecting every edge here is what every mainstream desktop's own /// maximize convention already does. Fullscreen is unaffected - it never /// called this function, and still doesn't. -pub(crate) fn maximize_geometry_for(output: &Output, full: srdwm_core::Rect) -> srdwm_core::Rect { +pub(crate) fn maximize_geometry_for(output: &Output, full: srdwm_core::Rect, covers_dock: bool) -> srdwm_core::Rect { let mut rect = full; // `exclusive_zone`/`margin` are logical (a layer-shell client reports // its own reservation the same way every other layer-shell geometry @@ -162,7 +162,15 @@ pub(crate) fn maximize_geometry_for(output: &Output, full: srdwm_core::Rect) -> rect.y += shrink; rect.height = rect.height.saturating_sub(shrink as u32); } - if data.anchor.contains(Anchor::BOTTOM) && !data.anchor.contains(Anchor::TOP) { + // A bottom-anchored zone (a dock) is deliberately NOT subtracted + // when `covers_dock` is set: maximize runs to the bottom of the + // screen and the dock floats over it. Asked for directly - "not + // sure why it doesn't go all the way down past where dock would + // be" - and it matches the common dock convention (macOS, and any + // auto-hiding dock), where the dock overlays a maximized window + // rather than permanently shortening it. Top/left/right zones are + // still honoured, so a maximized window never hides the bar. + if data.anchor.contains(Anchor::BOTTOM) && !data.anchor.contains(Anchor::TOP) && !covers_dock { let shrink = scaled(data.margin.bottom); rect.height = rect.height.saturating_sub(shrink as u32); } diff --git a/crates/wayland/src/state/lifecycle.rs b/crates/wayland/src/state/lifecycle.rs index 92f7d9d..163064e 100644 --- a/crates/wayland/src/state/lifecycle.rs +++ b/crates/wayland/src/state/lifecycle.rs @@ -131,9 +131,29 @@ impl CompState { } true }); + // The client's own declared minimum, read from the same place + // `resizable` above comes from. A rule's `min_size` still wins -- + // it is applied at map time and this must not undo it - so this + // only ever raises the floor above the global default, never + // overwrites a deliberate override. + let declared_min = self.id_to_window.get(&id).and_then(|dw| { + if let Some(toplevel) = dw.toplevel() { + let min = smithay::wayland::compositor::with_states(toplevel.wl_surface(), |states| { + let mut cached = states.cached_state.get::<smithay::wayland::shell::xdg::SurfaceCachedState>(); + cached.current().min_size + }); + return (min.w > 0 && min.h > 0).then_some((min.w as u32, min.h as u32)); + } + dw.x11_surface().and_then(|x| x.min_size()).and_then(|m| (m.w > 0 && m.h > 0).then_some((m.w as u32, m.h as u32))) + }); if let Some(win) = self.wm.borrow_mut().window_mut(id) { win.is_dialog = is_dialog; win.resizable = resizable; + if let Some(min) = declared_min { + if !win.min_size_from_rule { + win.min_size = min; + } + } } let show_maximize = { let wm = self.wm.borrow(); diff --git a/crates/wayland/src/udev/platform.rs b/crates/wayland/src/udev/platform.rs index 0ae65f7..ea2260e 100644 --- a/crates/wayland/src/udev/platform.rs +++ b/crates/wayland/src/udev/platform.rs @@ -830,7 +830,7 @@ impl Platform for UdevPlatform { // deliberately *not* defaulted from `usable` the way `Monitor:: // new` alone would (see the fullscreen note below). let full = srdwm_core::Rect::new(head.location.x, head.location.y, head.size.0 as u32, head.size.1 as u32); - let maximize = crate::input::maximize_geometry_for(&head.output, full); + let maximize = crate::input::maximize_geometry_for(&head.output, full, wm.maximize_covers_dock); let name = head.output.name(); let split = wm.monitor_split(&name); let parts = split.map(|s| s.parts).unwrap_or(1).max(1); diff --git a/crates/wayland/src/udev/render.rs b/crates/wayland/src/udev/render.rs index aa05529..3bd9a52 100644 --- a/crates/wayland/src/udev/render.rs +++ b/crates/wayland/src/udev/render.rs @@ -334,7 +334,16 @@ impl CompState { // be_masked` degenerates to just `w.decorated` when // masking can never succeed). let border_curve_is_safe = w.decorated; - if w.border_width > 0 { + // No border on a maximized window. A maximized window's own + // edges are the screen's edges, so a border has nothing to + // separate it from - and where maximize *does* stop short (the + // strip a top bar reserves) the border lands in that gap, drawn + // as a hard line right against the bar. Reported live with a + // screenshot: a 4px accent line between the bar and the window, + // and none anywhere else, because the left/right/bottom strips + // fall off-screen. Fullscreen is already borderless for the + // same reason, via `decorated` being cleared. + if w.border_width > 0 && !w.maximized { let strips = decoration::border_strips(geom, w.border_width); if let Some(buffer) = self.border_top_decorations.get(&id) { let (row0, rows, shift) = decoration::border_top_visible_rows(border_curve_is_safe, w.border_width, w.corner_radius); diff --git a/crates/wayland/src/winit/nested_platform.rs b/crates/wayland/src/winit/nested_platform.rs index 3b762ab..b0fa529 100644 --- a/crates/wayland/src/winit/nested_platform.rs +++ b/crates/wayland/src/winit/nested_platform.rs @@ -117,7 +117,7 @@ impl Platform for WaylandPlatform { let usable = srdwm_core::Rect::new(zone.loc.x, zone.loc.y, zone.size.w as u32, zone.size.h as u32); let full_size = self.backend.window_size(); let full = srdwm_core::Rect::new(0, 0, full_size.w as u32, full_size.h as u32); - let maximize = crate::input::maximize_geometry_for(&self.output, full); + let maximize = crate::input::maximize_geometry_for(&self.output, full, self.wm.borrow().maximize_covers_dock); // Expanded into one `Monitor` per split part, exactly as // `udev/platform.rs`'s own `monitors()` does - see the split drain // in `poll` above for why this backend supports it at all. @@ -149,7 +149,7 @@ impl Platform for WaylandPlatform { // "usable, shrunk rect" `toggle_maximize` targets. let full = self.backend.window_size(); m.full_geometry = srdwm_core::Rect::new(0, 0, full.w as u32, full.h as u32); - m.maximize_geometry = crate::input::maximize_geometry_for(&self.output, m.full_geometry); + m.maximize_geometry = crate::input::maximize_geometry_for(&self.output, m.full_geometry, self.wm.borrow().maximize_covers_dock); m.primary = true; m }]) diff --git a/crates/wayland/src/winit/render.rs b/crates/wayland/src/winit/render.rs index ccc64a2..432ebba 100644 --- a/crates/wayland/src/winit/render.rs +++ b/crates/wayland/src/winit/render.rs @@ -313,7 +313,16 @@ impl WaylandPlatform { // doc comment further down; that's an overlap between two // pieces of this window's own decoration, not with its content, // so it doesn't contradict this paragraph.) - if w.border_width > 0 { + // No border on a maximized window. A maximized window's own + // edges are the screen's edges, so a border has nothing to + // separate it from - and where maximize *does* stop short (the + // strip a top bar reserves) the border lands in that gap, drawn + // as a hard line right against the bar. Reported live with a + // screenshot: a 4px accent line between the bar and the window, + // and none anywhere else, because the left/right/bottom strips + // fall off-screen. Fullscreen is already borderless for the + // same reason, via `decorated` being cleared. + if w.border_width > 0 && !w.maximized { let color = crate::state::effective_border_color(w.border_color, focused == Some(id), self.wm.borrow().theme.border_inactive_dim); let strips = decoration::border_strips(frame, w.border_width); // Strips 0/1 (top/bottom) are rounded on their own two |