srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core/src/manager/tests.rs
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 /crates/core/src/manager/tests.rs
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 'crates/core/src/manager/tests.rs')
-rw-r--r--crates/core/src/manager/tests.rs115
1 files changed, 115 insertions, 0 deletions
diff --git a/crates/core/src/manager/tests.rs b/crates/core/src/manager/tests.rs
index fade3aa..152c71d 100644
--- a/crates/core/src/manager/tests.rs
+++ b/crates/core/src/manager/tests.rs
@@ -799,6 +799,121 @@
assert!(wm.drag_top_edge_monitor().is_none());
}
+ /// A monitor with a 30px top bar reserved, like the real session.
+ fn wm_with_top_bar() -> WindowManager {
+ let mut wm = WindowManager::new();
+ wm.set_monitors(vec![{
+ let mut m = Monitor::new(0, "primary", Rect::new(0, 30, 1920, 1050));
+ m.full_geometry = Rect::new(0, 0, 1920, 1080);
+ m.primary = true;
+ m
+ }]);
+ wm
+ }
+
+ #[test]
+ fn a_remembered_position_under_the_top_bar_is_pushed_clear_of_it() {
+ // The reported bug: an app whose remembered y predates the bar (or
+ // was saved on a monitor without one) reopened with its titlebar
+ // tucked underneath, unreachable.
+ let mut wm = wm_with_top_bar();
+ wm.set_remembered_geometry("someapp".to_string(), (100, 5, 800, 600));
+ let a = wm.alloc_window_id();
+ let mut w = Window::new(a, "w");
+ w.app_id = "someapp".into();
+ wm.add_window(w);
+ let g = wm.window(a).unwrap().geometry;
+ assert_eq!(g.x, 100, "x was fine and must be left alone");
+ assert_eq!(g.y, 30, "pushed down to the usable area's own top edge");
+ }
+
+ #[test]
+ fn a_remembered_position_that_already_clears_the_bar_is_untouched() {
+ let mut wm = wm_with_top_bar();
+ wm.set_remembered_geometry("someapp".to_string(), (100, 200, 800, 600));
+ let a = wm.alloc_window_id();
+ let mut w = Window::new(a, "w");
+ w.app_id = "someapp".into();
+ wm.add_window(w);
+ assert_eq!((wm.window(a).unwrap().geometry.x, wm.window(a).unwrap().geometry.y), (100, 200));
+ }
+
+ #[test]
+ fn a_position_remembered_on_a_now_disconnected_monitor_is_pulled_back_on_screen() {
+ // Five of the eleven entries in the real store were saved with a
+ // second monitor attached. They used to be discarded outright, so
+ // those apps stopped remembering anything at all.
+ let mut wm = wm_with_top_bar();
+ wm.set_remembered_geometry("someapp".to_string(), (2400, 300, 800, 600));
+ let a = wm.alloc_window_id();
+ let mut w = Window::new(a, "w");
+ w.app_id = "someapp".into();
+ wm.add_window(w);
+ let g = wm.window(a).unwrap().geometry;
+ assert_eq!(g.x, 1120, "clamped so its right edge sits on the monitor's own right edge");
+ assert_eq!(g.y, 300, "y was already valid and is kept");
+ assert!(g.x >= 0 && g.x + g.width as i32 <= 1920, "fully on screen: {g:?}");
+ }
+
+ #[test]
+ fn a_remembered_window_larger_than_the_screen_still_starts_at_the_usable_origin() {
+ let mut wm = wm_with_top_bar();
+ wm.set_remembered_geometry("someapp".to_string(), (500, 500, 4000, 3000));
+ let a = wm.alloc_window_id();
+ let mut w = Window::new(a, "w");
+ w.app_id = "someapp".into();
+ wm.add_window(w);
+ let g = wm.window(a).unwrap().geometry;
+ assert_eq!((g.x, g.y), (0, 30), "clamped to the usable origin, not left off-screen");
+ }
+
+ #[test]
+ fn a_min_size_rule_raises_the_floor_an_interactive_resize_stops_at() {
+ let mut wm = wm_with_monitor();
+ wm.add_rule(WindowRule {
+ matcher: WindowMatch { class: Some("bigapp".into()), ..Default::default() },
+ actions: WindowRuleActions { min_size: Some((600, 400)), ..Default::default() },
+ });
+ let a = wm.alloc_window_id();
+ let mut w = Window::new(a, "w");
+ w.app_id = "bigapp".into();
+ w.geometry = Rect::new(100, 100, 900, 700);
+ wm.add_window(w);
+ assert_eq!(wm.window(a).unwrap().min_size, (600, 400));
+
+ // Drag the bottom-right corner far past the minimum.
+ wm.start_resize(a, ResizeEdge::BottomRight, 1000, 800);
+ wm.update_resize(-5000, -5000);
+ let g = wm.window(a).unwrap().geometry;
+ assert_eq!((g.width, g.height), (600, 400), "must stop at the rule's own minimum");
+ }
+
+ #[test]
+ fn without_a_rule_a_window_keeps_the_global_minimum() {
+ let mut wm = wm_with_monitor();
+ let a = wm.alloc_window_id();
+ wm.add_window(Window::new(a, "w"));
+ assert_eq!(wm.window(a).unwrap().min_size, (MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT));
+ }
+
+ #[test]
+ fn a_min_size_rule_also_floors_a_remembered_size() {
+ // A size remembered from before the rule existed must not reopen
+ // the window below its new minimum.
+ let mut wm = wm_with_monitor();
+ wm.add_rule(WindowRule {
+ matcher: WindowMatch { class: Some("bigapp".into()), ..Default::default() },
+ actions: WindowRuleActions { min_size: Some((700, 500)), ..Default::default() },
+ });
+ wm.set_remembered_geometry("bigapp".to_string(), (100, 100, 300, 200));
+ let a = wm.alloc_window_id();
+ let mut w = Window::new(a, "w");
+ w.app_id = "bigapp".into();
+ wm.add_window(w);
+ let g = wm.window(a).unwrap().geometry;
+ assert_eq!((g.width, g.height), (700, 500));
+ }
+
#[test]
fn a_dialog_opens_centered_not_cascaded_into_the_corner() {
let mut wm = wm_with_monitor();