srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core/src/manager/focus.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2026-08-25 14:49:00 +0200
committersrdusr <[email protected]>2026-08-25 14:49:00 +0200
commit8b3ef3c7b28570122126a1c8b3d6695fc3647557 (patch)
tree5e37511996bbaa891476a37906d621ab1912853f /crates/core/src/manager/focus.rs
parent807d0ba1680cdc1322bfe9584781416339f6f76d (diff)
downloadsrdwm-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/focus.rs')
-rw-r--r--crates/core/src/manager/focus.rs87
1 files changed, 87 insertions, 0 deletions
diff --git a/crates/core/src/manager/focus.rs b/crates/core/src/manager/focus.rs
index f1473cf..6defab6 100644
--- a/crates/core/src/manager/focus.rs
+++ b/crates/core/src/manager/focus.rs
@@ -149,8 +149,29 @@ impl WindowManager {
/// predictable either way. With no neighbour in that direction the
/// window is pushed to the corresponding edge of its monitor instead, so
/// the key still does something sensible.
+ /// How far one press moves a window, as a fraction of the monitor.
+ ///
+ /// An eighth crosses the screen in eight presses, which is fine
+ /// control without being tedious, and passes through the middle at the
+ /// fourth - the owner asked for "increments, or one side, middle,
+ /// other side", and stepping gives both.
+ const MOVE_STEP_FRACTION: i32 = 8;
+
pub fn move_window_direction(&mut self, dir: Direction) -> Option<WindowId> {
let focused = self.focused_id()?;
+ // Only a tiling layout swaps with the neighbour. Everywhere else a
+ // window has its own geometry and "move it left" means move it, not
+ // trade places with whatever happens to be over there.
+ let tiling = self
+ .windows
+ .get(&focused)
+ .and_then(|w| self.workspace(w.workspace))
+ .map(|ws| ws.layout == "tiling")
+ .unwrap_or(false);
+ if !tiling {
+ self.nudge_window(focused, dir);
+ return None;
+ }
match self.neighbour_in(dir) {
Some(other) => {
let a = self.windows.get(&focused)?.geometry;
@@ -184,4 +205,70 @@ impl WindowManager {
}
}
+ /// Grows or shrinks the focused window one step along `dir`.
+ ///
+ /// The keyboard counterpart to dragging an edge. srdwm has had
+ /// Super+right-drag since before this, which covers the mouse, but
+ /// nothing resized a window without one - reported as a missing
+ /// keybinding alongside screen lock.
+ ///
+ /// `grow` decides which way: growing extends the window's far edge in
+ /// `dir`, shrinking pulls it back, so Right always affects the right
+ /// edge whichever way it moves. The window's top-left stays put, which
+ /// is what every keyboard resize does and what keeps repeated presses
+ /// predictable.
+ ///
+ /// Bounded by the same minimums a drag is, and by the monitor's usable
+ /// area, so a window can neither be resized to nothing nor grown off
+ /// the screen.
+ pub fn resize_window_direction(&mut self, dir: Direction, grow: bool) -> Option<WindowId> {
+ let focused = self.focused_id()?;
+ let area = self.windows.get(&focused).and_then(|w| self.monitor_for(w.monitor)).map(|m| m.geometry)?;
+ let step_x = (area.width as i32 / Self::MOVE_STEP_FRACTION).max(1);
+ let step_y = (area.height as i32 / Self::MOVE_STEP_FRACTION).max(1);
+ let w = self.windows.get_mut(&focused)?;
+ let (min_w, min_h) = (w.min_size.0.max(crate::placement::MIN_WINDOW_WIDTH) as i32, w.min_size.1.max(crate::placement::MIN_WINDOW_HEIGHT) as i32);
+ let delta_x = if grow { step_x } else { -step_x };
+ let delta_y = if grow { step_y } else { -step_y };
+ match dir {
+ Direction::Left | Direction::Right => {
+ let room = area.right() - w.geometry.x;
+ w.geometry.width = (w.geometry.width as i32 + delta_x).clamp(min_w, room.max(min_w)) as u32;
+ }
+ Direction::Up | Direction::Down => {
+ let room = area.bottom() - w.geometry.y;
+ w.geometry.height = (w.geometry.height as i32 + delta_y).clamp(min_h, room.max(min_h)) as u32;
+ }
+ }
+ Some(focused)
+ }
+
+ /// Moves a window one step in `dir`, stopping flush against the edge of
+ /// its monitor's usable area.
+ ///
+ /// This is what a directional move does outside a tiling layout. It used
+ /// to slam the window all the way to the edge in one press, which the
+ /// owner reported as "it should move in increments, one side, middle,
+ /// other side - not just extreme left/up/right/down".
+ ///
+ /// The step is a fraction of the monitor rather than a pixel count, so
+ /// the same keypress feels the same on a laptop panel and on a 4K
+ /// display. Landing within one step of an edge snaps flush to it, so the
+ /// window can always be put exactly against the side rather than a few
+ /// pixels short of it.
+ fn nudge_window(&mut self, id: WindowId, dir: Direction) {
+ let Some(area) = self.windows.get(&id).and_then(|w| self.monitor_for(w.monitor)).map(|m| m.geometry) else { return };
+ let step_x = (area.width as i32 / Self::MOVE_STEP_FRACTION).max(1);
+ let step_y = (area.height as i32 / Self::MOVE_STEP_FRACTION).max(1);
+ let Some(w) = self.windows.get_mut(&id) else { return };
+ let (width, height) = (w.geometry.width as i32, w.geometry.height as i32);
+ let (min_x, max_x) = (area.x, (area.right() - width).max(area.x));
+ let (min_y, max_y) = (area.y, (area.bottom() - height).max(area.y));
+ match dir {
+ Direction::Left => w.geometry.x = (w.geometry.x - step_x).max(min_x),
+ Direction::Right => w.geometry.x = (w.geometry.x + step_x).min(max_x),
+ Direction::Up => w.geometry.y = (w.geometry.y - step_y).max(min_y),
+ Direction::Down => w.geometry.y = (w.geometry.y + step_y).min(max_y),
+ }
+ }
}