diff options
| author | srdusr <[email protected]> | 2026-08-25 14:49:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-08-25 14:49:00 +0200 |
| commit | 8b3ef3c7b28570122126a1c8b3d6695fc3647557 (patch) | |
| tree | 5e37511996bbaa891476a37906d621ab1912853f /crates/core/src/manager/tests.rs | |
| parent | 807d0ba1680cdc1322bfe9584781416339f6f76d (diff) | |
| download | srdwm-8b3ef3c7b28570122126a1c8b3d6695fc3647557.tar.gz srdwm-8b3ef3c7b28570122126a1c8b3d6695fc3647557.zip | |
Move a window in steps, and let the keyboard resize one at all
Two of the owner's reported gaps, both about moving a window without a
mouse.
Super+Shift+HJKL slammed the window to the far edge of the monitor in one
press: "it should move in increments - one side, middle, other side - not
just extreme left/up/right/down". It now steps an eighth of the monitor per
press, which crosses the screen in eight, passes through the middle at the
fourth, and stops flush against the edge rather than short of it. A
fraction rather than a pixel count, so the same key feels the same on a
laptop panel and a 4K display.
Only outside a tiling layout. There a window does not own its geometry --
stepping it would be undone by the next arrange - so the neighbour swap
stays, and three tests that assumed swapping on a dynamic workspace now say
which layout they mean.
Keyboard resize did not exist. Super+right-drag has resized with the mouse
for a while, which is why this went unnoticed, but nothing resized a window
without one. `srd.window.resize("right", "grow"|"shrink")` grows or shrinks
from the far edge in that direction, leaving the top-left where it is, and
is bounded by the same minimums a drag is and by the monitor's usable area
- a window can be resized neither to nothing nor off the screen, and a
test holds each key down forty times to prove it.
Wired into the owner's config: Super+Ctrl+arrows resize (the arrow says
which way the far edge moves, so Right always widens), and Super+Ctrl+L
locks the screen. Hyprland only ever bound the lock to XF86ScreenSaver,
which most keyboards do not have; Super+L is the convention everywhere else
but is already focus-right here. Ctrl+arrows rather than Ctrl+HJKL because
Super+Ctrl+K is already kill-process.
Verified through the real path in a nested compositor running that config:
89 bindings, all 89 described, the five new ones registered.
Diffstat (limited to 'crates/core/src/manager/tests.rs')
| -rw-r--r-- | crates/core/src/manager/tests.rs | 112 |
1 files changed, 110 insertions, 2 deletions
diff --git a/crates/core/src/manager/tests.rs b/crates/core/src/manager/tests.rs index b5a54c0..0cb7782 100644 --- a/crates/core/src/manager/tests.rs +++ b/crates/core/src/manager/tests.rs @@ -116,6 +116,98 @@ assert!(!win.size_is_provisional, "a deliberately remembered size must never be second-guessed by the client's own default"); } + /// "Super+Shift+H should move the window in increments, one side, + /// middle, other side - not just to the extreme left." + #[test] + fn a_directional_move_steps_rather_than_slamming_to_the_edge() { + let mut wm = wm_with_monitor(); + let id = wm.alloc_window_id(); + let mut w = Window::new(id, "w"); + w.geometry = Rect::new(800, 400, 400, 300); + wm.add_window(w); + wm.focus_window(id); + wm.window_mut(id).unwrap().geometry = Rect::new(800, 400, 400, 300); + + wm.move_window_direction(Direction::Left); + let after_one = wm.window(id).unwrap().geometry.x; + assert!(after_one < 800, "did not move"); + assert!(after_one > 0, "slammed to the edge in one press: {after_one}"); + + // Repeated presses keep walking it across, and it stops flush. + for _ in 0..20 { + wm.move_window_direction(Direction::Left); + } + assert_eq!(wm.window(id).unwrap().geometry.x, 0, "should end flush against the left edge"); + } + + /// Keyboard resize: the missing half of "we are missing keybinds like + /// screen lock, resize windows". + #[test] + fn keyboard_resize_grows_and_shrinks_from_the_far_edge() { + let mut wm = wm_with_monitor(); + let id = wm.alloc_window_id(); + wm.add_window(Window::new(id, "w")); + wm.focus_window(id); + wm.window_mut(id).unwrap().geometry = Rect::new(100, 100, 600, 400); + + wm.resize_window_direction(Direction::Right, true); + let grown = wm.window(id).unwrap().geometry; + assert!(grown.width > 600, "did not grow: {grown:?}"); + assert_eq!((grown.x, grown.y), (100, 100), "the top-left must not move"); + + wm.resize_window_direction(Direction::Right, false); + assert_eq!(wm.window(id).unwrap().geometry.width, 600, "shrink should undo the grow"); + } + + /// A window can be resized neither to nothing nor off the screen. + #[test] + fn keyboard_resize_stays_within_its_limits() { + let mut wm = wm_with_monitor(); + let id = wm.alloc_window_id(); + wm.add_window(Window::new(id, "w")); + wm.focus_window(id); + wm.window_mut(id).unwrap().geometry = Rect::new(100, 100, 600, 400); + let area = wm.monitors()[0].geometry; + + for _ in 0..40 { + wm.resize_window_direction(Direction::Right, true); + wm.resize_window_direction(Direction::Down, true); + } + let big = wm.window(id).unwrap().geometry; + assert!(big.right() <= area.right(), "grew off the right: {big:?}"); + assert!(big.bottom() <= area.bottom(), "grew off the bottom: {big:?}"); + + for _ in 0..40 { + wm.resize_window_direction(Direction::Right, false); + wm.resize_window_direction(Direction::Down, false); + } + let small = wm.window(id).unwrap().geometry; + assert!(small.width >= crate::placement::MIN_WINDOW_WIDTH, "shrank below the minimum width"); + assert!(small.height >= crate::placement::MIN_WINDOW_HEIGHT, "shrank below the minimum height"); + } + + /// A tiling workspace still swaps with the neighbour: there a window + /// does not own its own geometry, and stepping it would be undone by + /// the next arrange. + #[test] + fn a_directional_move_still_swaps_on_a_tiling_workspace() { + let mut wm = wm_with_monitor(); + let ws = wm.current_workspace(); + wm.set_layout(ws, "tiling"); + let left = wm.alloc_window_id(); + wm.add_window(Window::new(left, "left")); + let right = wm.alloc_window_id(); + wm.add_window(Window::new(right, "right")); + // Side by side, so "the window to the left" is unambiguous. + wm.window_mut(left).unwrap().geometry = Rect::new(0, 0, 900, 1000); + wm.window_mut(right).unwrap().geometry = Rect::new(960, 0, 900, 1000); + wm.focus_window(right); + + assert_eq!(wm.move_window_direction(Direction::Left), Some(left), "should have swapped with its neighbour"); + assert_eq!(wm.window(right).unwrap().geometry.x, 0, "the two should have traded places"); + assert_eq!(wm.window(left).unwrap().geometry.x, 960); + } + /// A client that accepts the size placement assumed must not be moved /// again: re-placing would consume another cascade step for nothing, /// and the cascade wraps - measured, two windows landing on exactly @@ -2343,6 +2435,11 @@ #[test] fn moving_a_window_swaps_it_with_its_neighbour() { let mut wm = wm_with_monitor(); + // Swapping is tiling behaviour. On a dynamic workspace a window owns + // its own geometry and "move it right" moves it - see + // `a_directional_move_steps_rather_than_slamming_to_the_edge`. + let ws = wm.current_workspace(); + wm.set_layout(ws, "tiling"); let left = wm.alloc_window_id(); let mut a = Window::new(left, "left"); a.geometry = Rect::new(0, 0, 400, 400); @@ -2364,7 +2461,7 @@ } #[test] - fn moving_with_no_neighbour_pushes_to_the_monitor_edge() { + fn moving_with_no_neighbour_walks_to_the_monitor_edge() { let mut wm = wm_with_monitor(); let id = wm.alloc_window_id(); let mut w = Window::new(id, "only"); @@ -2373,10 +2470,21 @@ wm.window_mut(id).unwrap().geometry = Rect::new(500, 300, 200, 150); wm.focus_window(id); + // One press moves one step, not all the way: the owner asked for + // increments rather than "just extreme left/up/right/down". assert_eq!(wm.move_window_direction(Direction::Left), None); + let stepped = wm.window(id).unwrap().geometry.x; + assert!(stepped < 500 && stepped > 0, "should have stepped, not slammed: {stepped}"); + + // Held down, it still ends flush against the edge. + for _ in 0..20 { + wm.move_window_direction(Direction::Left); + } assert_eq!(wm.window(id).unwrap().geometry.x, 0, "should hug the left edge"); - wm.move_window_direction(Direction::Down); + for _ in 0..20 { + wm.move_window_direction(Direction::Down); + } let g = wm.window(id).unwrap().geometry; let mon = wm.primary_monitor().unwrap().geometry; assert_eq!(g.bottom(), mon.bottom(), "should hug the bottom edge"); |