srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2026-05-11 16:57:00 +0200
committersrdusr <[email protected]>2026-05-11 16:57:00 +0200
commit646b37e7e3aa5931079c6b9e804f420bb43c74d5 (patch)
tree159825fae0086c162b36edc6e78da07d975ab6b0 /docs
parentcaec1e7c355bbe6437afe87cd3dab6b64fb91e0a (diff)
downloadsrdwm-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 'docs')
-rw-r--r--docs/DEFAULTS.md54
-rw-r--r--docs/TODO.md68
2 files changed, 122 insertions, 0 deletions
diff --git a/docs/DEFAULTS.md b/docs/DEFAULTS.md
index 3497410..2cc1614 100644
--- a/docs/DEFAULTS.md
+++ b/docs/DEFAULTS.md
@@ -20,6 +20,7 @@ srd.set("general.gpu", false) -- Default: false - udev
srd.set("general.desktop_icons", true) -- Default: true - see "Desktop icons" below
srd.set("general.desktop_icons_all_monitors", true) -- Default: true - mirror icons onto every monitor, not just primary
srd.set("general.config_reload_on_write", true) -- Default: true - re-read init.lua when it changes on disk
+srd.set("general.maximize_covers_dock", true) -- Default: true - maximize runs under a bottom dock, not above it
srd.set("general.reserve_top", 0) -- Default: 0 - static space reserved before any bar/dock connects
srd.set("general.reserve_bottom", 0) -- Default: 0 - see "Startup space reservation" below
srd.set("general.reserve_left", 0) -- Default: 0
@@ -881,3 +882,56 @@ One limit to know: a reload does not re-register key *grabs* with the
backend, so a brand new key combination needs a restart before the
compositor sees that key at all. An existing combination picks up its new
action immediately.
+
+## Window minimum sizes
+
+Every window has a minimum size it cannot be resized below. Three sources,
+in increasing precedence:
+
+1. A global floor, used when nothing else applies.
+2. The client's own declared minimum - `xdg_toplevel.set_min_size` for a
+ native Wayland window, the ICCCM size hints for an XWayland one. Read
+ from the client and refreshed as it changes.
+3. A `min_width`/`min_height` window rule, which overrides both. Either may
+ be given alone; the other axis keeps the global floor.
+
+```lua
+srd.rule({ class = "Alacritty" }, { min_width = 480, min_height = 320 })
+```
+
+A rule wins permanently: the client's own declared minimum is never allowed
+to overwrite it, so an application that reports a minimum too large for a
+small screen can be overridden. The minimum applies to interactive resizing,
+to a size restored from window memory, and to any programmatic resize.
+
+## Maximize
+
+Maximize targets the monitor's full rectangle minus the space reserved by
+top, left and right layer-shell surfaces. A bottom-anchored dock is
+deliberately not subtracted, so a maximized window runs to the bottom of the
+screen and the dock floats over it - the usual behaviour for an auto-hiding
+dock. Set `general.maximize_covers_dock = false` to have maximize stop above
+the dock instead.
+
+A maximized window draws no border. Its 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) a border would otherwise draw as a hard line
+directly against the bar.
+
+## Where a window opens
+
+In order: a `geometry` window rule wins outright; otherwise a position and
+size remembered for that `app_id` are restored; otherwise smart placement
+picks a free grid cell, falling back to a cascade.
+
+A remembered position is clamped into the target monitor's *usable* area --
+the region left after bars and docks reserve their space. Without that
+clamp, an app whose remembered `y` was smaller than the top bar's height
+reopened with its titlebar underneath the bar, where it could not be
+grabbed. A position remembered on a monitor that is no longer connected is
+clamped onto a current monitor rather than discarded, so undocking does not
+make an app forget where it lives.
+
+Dialogs are centred instead, and take neither a remembered position nor a
+remembered size - window memory is keyed by `app_id`, which a dialog shares
+with the window that spawned it.
diff --git a/docs/TODO.md b/docs/TODO.md
index 04ccbdb..d93f0d2 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -1,5 +1,73 @@
# TODO / planned features - master checklist
+## Spawn placement, per-window minimum sizes, and how maximize looks (2026-08-28)
+
+Four reports after the owner restarted into the day's build, with a
+screenshot. The screenshot was measured rather than eyeballed: at 1920x1080,
+the top bar occupies y=0..29, a 4px accent border sits at y=30..33, the
+window's titlebar starts at y=34, the bottom border is at y=1027..1030, and
+49px of bare desktop sits below it.
+
+**Windows spawning too close to the top bar - a real bug, and the reason it
+was "sometimes".** 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 underneath the bar, unreachable. It depended entirely on the stored
+value, which is why it happened for some apps and not others: the live store
+holds `org.wezfurlong.wezterm` at y=44 and `firefox` at y=69 against a 30px
+bar. Remembered positions are now clamped into the monitor's *usable* area.
+
+**"Doesn't remember placement after logout" - half true, and the half that
+failed is now fixed.** Window memory does persist (`~/.local/state/srd/
+window-memory.json`, written on close, drag-end and resize-end, loaded at
+startup by both backends). But five of the eleven entries in the live store
+were saved while a second monitor was connected, at x >= 2000. Those points
+are on no current monitor, and the old code discarded them outright and fell
+back to a fresh cascade - so those apps appeared to remember nothing at all.
+A remembered position with no matching monitor is now clamped onto the
+monitor the window would have been placed on anyway, keeping as much of the
+remembered placement as still fits.
+
+**Per-window minimum sizes.** One global floor for every window 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 size hints for XWayland), and a `min_width`/`min_height` window rule
+that overrides both. A rule wins permanently - the backend refreshes the
+client's declared minimum on every decoration redraw and must not quietly
+undo a deliberate override, which is what `Window::min_size_from_rule`
+guards. The minimum applies to interactive resize, to a size restored from
+window memory, and to programmatic resizes.
+
+**Maximize.** Three separate faults in one report:
+
+- *Borders.* A maximized window now draws none. Its edges are the screen's
+ edges, so a border has nothing to separate it from - and the only place
+ maximize stops short is the bar strip, which is exactly where the measured
+ 4px line was. The left/right/bottom strips were falling off-screen, which
+ is why the screenshot showed a line under the bar and nowhere else.
+- *Stopping above the dock.* `maximize_geometry_for` subtracted every
+ exclusive zone including a bottom-anchored dock's, which is the 49px band
+ in the screenshot. Bottom zones are now skipped, so maximize runs to the
+ bottom of the screen and the dock floats over it. Top/left/right are still
+ honoured, so a maximized window never hides the bar.
+ `general.maximize_covers_dock = false` restores the old behaviour.
+- *Sitting too close to the bar.* With the border gone the window sits flush
+ under the bar rather than with an accent line crowding it.
+
+**Verification, stated plainly.** The placement and minimum-size work is
+covered by seven new tests (269 core tests total) built on the real numbers
+from the live store and a 30px bar. Maximize geometry was measured live in a
+nested instance: a window maximized on a split half reports exactly that
+half's rect. The border removal and the dock behaviour are **not** confirmed
+on screen - the nested backend has no bar or dock to reserve a zone, so
+there is nothing there to test against, and an attempt to check the border
+produced a control that failed (`srd set border_width` only affects windows
+created after the call, so neither the maximized nor the unmaximized window
+had one). Confirming those two needs either the owner's own session or a
+purpose-built layer-shell client for the nested one.
+
+515 tests pass, clippy clean.
+
## A wrong "blocked" of my own, corrected: monitor split works in a nested compositor (2026-08-28)
Written earlier the same day, as the reason the monitor-seam shadow fix could