oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git

Added smartmovewin() and exchangeclient() functions

Commit
c92066c248ea74a0ffa96f62c1f16c91908f470f
Parent
d7af0b4
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-10-25 05:32:31

Diff

diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
index bd1a447..15df5de 100644
--- a/src/keyboard/handlers.rs
+++ b/src/keyboard/handlers.rs
@@ -23,6 +23,8 @@ pub enum KeyAction {
     CycleLayout,
     MoveToTag,
     FocusMonitor,
+    SmartMoveWin,
+    ExchangeClient,
     None,
 }
 
diff --git a/src/lib.rs b/src/lib.rs
index d5086de..293e3ce 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -132,6 +132,30 @@ impl Default for Config {
                     KeyAction::FocusStack,
                     Arg::Int(1),
                 ),
+                Key::new(
+                    vec![MODKEY, SHIFT],
+                    keycodes::K,
+                    KeyAction::ExchangeClient,
+                    Arg::Int(0), // UP
+                ),
+                Key::new(
+                    vec![MODKEY, SHIFT],
+                    keycodes::J,
+                    KeyAction::ExchangeClient,
+                    Arg::Int(1), // DOWN
+                ),
+                Key::new(
+                    vec![MODKEY, SHIFT],
+                    keycodes::H,
+                    KeyAction::ExchangeClient,
+                    Arg::Int(2), // LEFT
+                ),
+                Key::new(
+                    vec![MODKEY, SHIFT],
+                    keycodes::L,
+                    KeyAction::ExchangeClient,
+                    Arg::Int(3), // RIGHT
+                ),
                 Key::new(
                     vec![MODKEY],
                     keycodes::KEY_1,
diff --git a/src/window_manager.rs b/src/window_manager.rs
index 7f731c3..13ff6e2 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -411,6 +411,337 @@ impl WindowManager {
         Ok(())
     }
 
+    fn smart_move_window(&mut self, direction: i32) -> WmResult<()> {
+        let focused = match self
+            .monitors
+            .get(self.selected_monitor)
+            .and_then(|m| m.focused_window)
+        {
+            Some(win) => win,
+            None => return Ok(()),
+        };
+
+        if self.fullscreen_window == Some(focused) {
+            return Ok(());
+        }
+
+        if !self.floating_windows.contains(&focused) {
+            let float_width = (self.screen.width_in_pixels / 2) as u32;
+            let float_height = (self.screen.height_in_pixels / 2) as u32;
+            let border_width = self.config.border_width;
+            let center_width = ((self.screen.width_in_pixels - float_width as u16) / 2) as i32;
+            let center_height = ((self.screen.height_in_pixels - float_height as u16) / 2) as i32;
+
+            self.connection.configure_window(
+                focused,
+                &ConfigureWindowAux::new()
+                    .x(center_width)
+                    .y(center_height)
+                    .width(float_width)
+                    .height(float_height)
+                    .border_width(border_width)
+                    .stack_mode(StackMode::ABOVE),
+            )?;
+            self.floating_windows.insert(focused);
+        }
+
+        let current_geom = match self.connection.get_geometry(focused)?.reply() {
+            Ok(geom) => geom,
+            Err(_) => return Ok(()),
+        };
+
+        let c_x = current_geom.x as i32;
+        let c_y = current_geom.y as i32;
+        let c_width = current_geom.width as i32;
+        let c_height = current_geom.height as i32;
+
+        let monitor = match self.monitors.get(self.selected_monitor) {
+            Some(m) => m,
+            None => return Ok(()),
+        };
+
+        let (gap_ih, gap_iv, gap_oh, gap_ov) = if self.gaps_enabled {
+            (
+                self.config.gap_inner_horizontal as i32,
+                self.config.gap_inner_vertical as i32,
+                self.config.gap_outer_horizontal as i32,
+                self.config.gap_outer_vertical as i32,
+            )
+        } else {
+            (0, 0, 0, 0)
+        };
+
+        let (new_x, new_y) = match direction {
+            0 => {
+                // UP
+                let mut target = i32::MIN;
+                let top = c_y;
+                let mut ny = c_y - (monitor.height as i32 / 4);
+
+                for &other_window in &self.windows {
+                    if other_window == focused {
+                        continue;
+                    }
+                    if !self.floating_windows.contains(&other_window) {
+                        continue;
+                    }
+                    if !self.is_window_visible(other_window) {
+                        continue;
+                    }
+                    let other_mon = self.window_monitor.get(&other_window).copied().unwrap_or(0);
+                    if other_mon != self.selected_monitor {
+                        continue;
+                    }
+
+                    let other_geom = match self.connection.get_geometry(other_window)?.reply() {
+                        Ok(geom) => geom,
+                        Err(_) => continue,
+                    };
+
+                    let o_x = other_geom.x as i32;
+                    let o_y = other_geom.y as i32;
+                    let o_width = other_geom.width as i32;
+                    let o_height = other_geom.height as i32;
+
+                    if c_x + c_width < o_x || c_x > o_x + o_width {
+                        continue;
+                    }
+
+                    let bottom = o_y + o_height + gap_iv;
+                    if top > bottom && ny < bottom {
+                        target = target.max(bottom);
+                    }
+                }
+
+                if target != i32::MIN {
+                    ny = target;
+                }
+                ny = ny.max(monitor.y as i32 + gap_ov);
+                (c_x, ny)
+            }
+            1 => {
+                // DOWN
+                let mut target = i32::MAX;
+                let bottom = c_y + c_height;
+                let mut ny = c_y + (monitor.height as i32 / 4);
+
+                for &other_window in &self.windows {
+                    if other_window == focused {
+                        continue;
+                    }
+                    if !self.floating_windows.contains(&other_window) {
+                        continue;
+                    }
+                    if !self.is_window_visible(other_window) {
+                        continue;
+                    }
+                    let other_mon = self.window_monitor.get(&other_window).copied().unwrap_or(0);
+                    if other_mon != self.selected_monitor {
+                        continue;
+                    }
+
+                    let other_geom = match self.connection.get_geometry(other_window)?.reply() {
+                        Ok(geom) => geom,
+                        Err(_) => continue,
+                    };
+
+                    let o_x = other_geom.x as i32;
+                    let o_y = other_geom.y as i32;
+                    let o_width = other_geom.width as i32;
+
+                    if c_x + c_width < o_x || c_x > o_x + o_width {
+                        continue;
+                    }
+
+                    let top = o_y - gap_iv;
+                    if bottom < top && (ny + c_height) > top {
+                        target = target.min(top - c_height);
+                    }
+                }
+
+                if target != i32::MAX {
+                    ny = target;
+                }
+                ny = ny.min(monitor.y as i32 + monitor.height as i32 - c_height - gap_ov);
+                (c_x, ny)
+            }
+            2 => {
+                // LEFT
+                let mut target = i32::MIN;
+                let left = c_x;
+                let mut nx = c_x - (monitor.width as i32 / 6);
+
+                for &other_window in &self.windows {
+                    if other_window == focused {
+                        continue;
+                    }
+                    if !self.floating_windows.contains(&other_window) {
+                        continue;
+                    }
+                    if !self.is_window_visible(other_window) {
+                        continue;
+                    }
+                    let other_mon = self.window_monitor.get(&other_window).copied().unwrap_or(0);
+                    if other_mon != self.selected_monitor {
+                        continue;
+                    }
+
+                    let other_geom = match self.connection.get_geometry(other_window)?.reply() {
+                        Ok(geom) => geom,
+                        Err(_) => continue,
+                    };
+
+                    let o_x = other_geom.x as i32;
+                    let o_y = other_geom.y as i32;
+                    let o_width = other_geom.width as i32;
+                    let o_height = other_geom.height as i32;
+
+                    if c_y + c_height < o_y || c_y > o_y + o_height {
+                        continue;
+                    }
+
+                    let right = o_x + o_width + gap_ih;
+                    if left > right && nx < right {
+                        target = target.max(right);
+                    }
+                }
+
+                if target != i32::MIN {
+                    nx = target;
+                }
+                nx = nx.max(monitor.x as i32 + gap_oh);
+                (nx, c_y)
+            }
+            3 => {
+                // RIGHT
+                let mut target = i32::MAX;
+                let right = c_x + c_width;
+                let mut nx = c_x + (monitor.width as i32 / 6);
+
+                for &other_window in &self.windows {
+                    if other_window == focused {
+                        continue;
+                    }
+                    if !self.floating_windows.contains(&other_window) {
+                        continue;
+                    }
+                    if !self.is_window_visible(other_window) {
+                        continue;
+                    }
+                    let other_mon = self.window_monitor.get(&other_window).copied().unwrap_or(0);
+                    if other_mon != self.selected_monitor {
+                        continue;
+                    }
+
+                    let other_geom = match self.connection.get_geometry(other_window)?.reply() {
+                        Ok(geom) => geom,
+                        Err(_) => continue,
+                    };
+
+                    let o_x = other_geom.x as i32;
+                    let o_y = other_geom.y as i32;
+                    let o_height = other_geom.height as i32;
+
+                    if c_y + c_height < o_y || c_y > o_y + o_height {
+                        continue;
+                    }
+
+                    let left = o_x - gap_ih;
+                    if right < left && (nx + c_width) > left {
+                        target = target.min(left - c_width);
+                    }
+                }
+
+                if target != i32::MAX {
+                    nx = target;
+                }
+                nx = nx.min(monitor.x as i32 + monitor.width as i32 - c_width - gap_oh);
+                (nx, c_y)
+            }
+            _ => return Ok(()),
+        };
+
+        self.connection.configure_window(
+            focused,
+            &ConfigureWindowAux::new()
+                .x(new_x)
+                .y(new_y)
+                .stack_mode(StackMode::ABOVE),
+        )?;
+
+        self.connection.flush()?;
+        Ok(())
+    }
+
+    fn exchange_client(&mut self, direction: i32) -> WmResult<()> {
+        let focused = match self
+            .monitors
+            .get(self.selected_monitor)
+            .and_then(|m| m.focused_window)
+        {
+            Some(win) => win,
+            None => return Ok(()),
+        };
+
+        if self.fullscreen_window == Some(focused) || self.floating_windows.contains(&focused) {
+            return Ok(());
+        }
+
+        let visible = self.visible_windows();
+        if visible.len() < 2 {
+            return Ok(());
+        }
+
+        let current_idx = match visible.iter().position(|&w| w == focused) {
+            Some(idx) => idx,
+            None => return Ok(()),
+        };
+
+        let target_idx = match direction {
+            0 | 2 => {
+                // UP or LEFT - previous in stack
+                if current_idx == 0 {
+                    visible.len() - 1
+                } else {
+                    current_idx - 1
+                }
+            }
+            1 | 3 => {
+                // DOWN or RIGHT - next in stack
+                (current_idx + 1) % visible.len()
+            }
+            _ => return Ok(()),
+        };
+
+        let target = visible[target_idx];
+
+        let focused_pos = self.windows.iter().position(|&w| w == focused);
+        let target_pos = self.windows.iter().position(|&w| w == target);
+
+        if let (Some(f_pos), Some(t_pos)) = (focused_pos, target_pos) {
+            self.windows.swap(f_pos, t_pos);
+
+            self.apply_layout()?;
+
+            self.set_focus(focused)?;
+
+            if let Ok(geometry) = self.connection.get_geometry(focused)?.reply() {
+                self.connection.warp_pointer(
+                    x11rb::NONE,
+                    focused,
+                    0,
+                    0,
+                    0,
+                    0,
+                    geometry.width as i16 / 2,
+                    geometry.height as i16 / 2,
+                )?;
+            }
+        }
+
+        Ok(())
+    }
+
     fn toggle_fullscreen(&mut self) -> WmResult<()> {
         if let Some(focused) = self
             .monitors
@@ -526,6 +857,18 @@ impl WindowManager {
                 self.toggle_floating()?;
             }
 
+            KeyAction::SmartMoveWin => {
+                if let Arg::Int(direction) = arg {
+                    self.smart_move_window(*direction)?;
+                }
+            }
+
+            KeyAction::ExchangeClient => {
+                if let Arg::Int(direction) = arg {
+                    self.exchange_client(*direction)?;
+                }
+            }
+
             KeyAction::FocusStack => {
                 if let Arg::Int(direction) = arg {
                     self.cycle_focus(*direction)?;
diff --git a/test-config.ron b/test-config.ron
index e264036..b38fe05 100644
--- a/test-config.ron
+++ b/test-config.ron
@@ -40,6 +40,11 @@
         (modifiers: [Mod1, Shift], key: R, action: Restart),
         (modifiers: [Mod1], key: J, action: FocusStack, arg: -1),
         (modifiers: [Mod1], key: K, action: FocusStack, arg: 1),
+        // Exchange client (vim-style with Mod+Shift)
+        (modifiers: [Mod1, Shift], key: K, action: ExchangeClient, arg: 0), // UP
+        (modifiers: [Mod1, Shift], key: J, action: ExchangeClient, arg: 1), // DOWN
+        (modifiers: [Mod1, Shift], key: H, action: ExchangeClient, arg: 2), // LEFT
+        (modifiers: [Mod1, Shift], key: L, action: ExchangeClient, arg: 3), // RIGHT
         (modifiers: [Mod1], key: Key1, action: ViewTag, arg: 0),
         (modifiers: [Mod1], key: Key2, action: ViewTag, arg: 1),
         (modifiers: [Mod1], key: Key3, action: ViewTag, arg: 2),