oxwm

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

Implemented movestack patch, so windows can be moved with a keybind, added keybind to lua file and lua lsp.

Commit
d974d56ca26eae5d43340aab0865bed45aec1d82
Parent
ced2a2a
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-12-02 08:25:53

Diff

diff --git a/resources/test-config.lua b/resources/test-config.lua
index 3db44ae..d094520 100644
--- a/resources/test-config.lua
+++ b/resources/test-config.lua
@@ -95,6 +95,9 @@ oxwm.key.bind({ modkey, "Shift" }, "R", oxwm.restart())
 oxwm.key.bind({ modkey }, "J", oxwm.client.focus_stack(1))
 oxwm.key.bind({ modkey }, "K", oxwm.client.focus_stack(-1))
 
+oxwm.key.bind({ modkey, "Shift" }, "J", oxwm.client.move_stack(1))
+oxwm.key.bind({ modkey, "Shift" }, "K", oxwm.client.move_stack(-1))
+
 -- View tag (switch workspace)
 oxwm.key.bind({ modkey }, "1", oxwm.tag.view(0))
 oxwm.key.bind({ modkey }, "2", oxwm.tag.view(1))
diff --git a/src/config/lua_api.rs b/src/config/lua_api.rs
index 99fd763..089baec 100644
--- a/src/config/lua_api.rs
+++ b/src/config/lua_api.rs
@@ -255,10 +255,15 @@ fn register_client_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError>
         create_action_table(lua, "FocusStack", Value::Integer(dir as i64))
     })?;
 
+    let move_stack = lua.create_function(|lua, dir: i32| {
+        create_action_table(lua, "MoveStack", Value::Integer(dir as i64))
+    })?;
+
     client_table.set("kill", kill)?;
     client_table.set("toggle_fullscreen", toggle_fullscreen)?;
     client_table.set("toggle_floating", toggle_floating)?;
     client_table.set("focus_stack", focus_stack)?;
+    client_table.set("move_stack", move_stack)?;
 
     parent.set("client", client_table)?;
     Ok(())
@@ -773,6 +778,7 @@ fn string_to_action(s: &str) -> mlua::Result<KeyAction> {
         "SpawnTerminal" => Ok(KeyAction::SpawnTerminal),
         "KillClient" => Ok(KeyAction::KillClient),
         "FocusStack" => Ok(KeyAction::FocusStack),
+        "MoveStack" => Ok(KeyAction::MoveStack),
         "Quit" => Ok(KeyAction::Quit),
         "Restart" => Ok(KeyAction::Restart),
         "Recompile" => Ok(KeyAction::Recompile),
diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
index 8a99eaa..bef80e9 100644
--- a/src/keyboard/handlers.rs
+++ b/src/keyboard/handlers.rs
@@ -15,6 +15,7 @@ pub enum KeyAction {
     SpawnTerminal,
     KillClient,
     FocusStack,
+    MoveStack,
     Quit,
     Restart,
     Recompile,
diff --git a/src/overlay/keybind.rs b/src/overlay/keybind.rs
index 2665832..f030c14 100644
--- a/src/overlay/keybind.rs
+++ b/src/overlay/keybind.rs
@@ -203,6 +203,7 @@ impl KeybindOverlay {
             },
             KeyAction::SpawnTerminal => "Launch Terminal".to_string(),
             KeyAction::FocusStack => "Focus Next/Previous Window".to_string(),
+            KeyAction::MoveStack => "Move Window Up/Down Stack".to_string(),
             KeyAction::ViewTag => match &binding.arg {
                 Arg::Int(n) => format!("View Workspace {}", n),
                 _ => "View Workspace".to_string(),
diff --git a/src/window_manager.rs b/src/window_manager.rs
index 6792422..6ebf110 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -322,8 +322,7 @@ impl WindowManager {
     pub fn show_migration_overlay(&mut self) {
         let message = "We are on version 0.8.0 now.\n\n\
                        Your config file has been deprecated once again.\n\
-                       We apologize for this, but we have provided you\n\
-                       with a new default config.\n\n\
+                       Backup your current config, and run oxwm --init to generate a new one with correct values.\n\n\
                        Please reach out to Tony, or check out the\n\
                        documentation for help with migration.\n\n\
                        We appreciate you testing oxwm!\n\n\
@@ -790,6 +789,11 @@ impl WindowManager {
                     self.focusstack(*direction)?;
                 }
             }
+            KeyAction::MoveStack => {
+                if let Arg::Int(direction) = arg {
+                    self.move_stack(*direction)?;
+                }
+            }
             KeyAction::Quit | KeyAction::Restart => {
                 // Handled in handle_event
             }
@@ -2282,6 +2286,139 @@ impl WindowManager {
         Ok(())
     }
 
+    pub fn move_stack(&mut self, direction: i32) -> WmResult<()> {
+        let monitor_index = self.selected_monitor;
+        let monitor = match self.monitors.get(monitor_index) {
+            Some(m) => m.clone(),
+            None => return Ok(()),
+        };
+
+        let selected = match monitor.selected_client {
+            Some(win) => win,
+            None => return Ok(()),
+        };
+
+        let selected_client = match self.clients.get(&selected) {
+            Some(c) => c,
+            None => return Ok(()),
+        };
+
+        let target = if direction > 0 {
+            let next = self.next_tiled(selected_client.next, &monitor);
+            if next.is_some() {
+                next
+            } else {
+                self.next_tiled(monitor.clients_head, &monitor)
+            }
+        } else {
+            let mut previous = None;
+            let mut current = monitor.clients_head;
+            while let Some(window) = current {
+                if window == selected {
+                    break;
+                }
+                if let Some(client) = self.clients.get(&window) {
+                    let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index];
+                    if visible_tags != 0 && !client.is_floating {
+                        previous = Some(window);
+                    }
+                    current = client.next;
+                } else {
+                    break;
+                }
+            }
+            if previous.is_none() {
+                let mut last = None;
+                let mut current = monitor.clients_head;
+                while let Some(window) = current {
+                    if let Some(client) = self.clients.get(&window) {
+                        let visible_tags = client.tags & monitor.tagset[monitor.selected_tags_index];
+                        if visible_tags != 0 && !client.is_floating {
+                            last = Some(window);
+                        }
+                        current = client.next;
+                    } else {
+                        break;
+                    }
+                }
+                last
+            } else {
+                previous
+            }
+        };
+
+        let target = match target {
+            Some(t) if t != selected => t,
+            _ => return Ok(()),
+        };
+
+        let mut prev_selected = None;
+        let mut prev_target = None;
+        let mut current = monitor.clients_head;
+
+        while let Some(window) = current {
+            if let Some(client) = self.clients.get(&window) {
+                if client.next == Some(selected) {
+                    prev_selected = Some(window);
+                }
+                if client.next == Some(target) {
+                    prev_target = Some(window);
+                }
+                current = client.next;
+            } else {
+                break;
+            }
+        }
+
+        let selected_next = self.clients.get(&selected).and_then(|c| c.next);
+        let target_next = self.clients.get(&target).and_then(|c| c.next);
+
+        let temp = if selected_next == Some(target) {
+            Some(selected)
+        } else {
+            selected_next
+        };
+
+        if let Some(client) = self.clients.get_mut(&selected) {
+            client.next = if target_next == Some(selected) {
+                Some(target)
+            } else {
+                target_next
+            };
+        }
+
+        if let Some(client) = self.clients.get_mut(&target) {
+            client.next = temp;
+        }
+
+        if let Some(prev) = prev_selected {
+            if prev != target {
+                if let Some(client) = self.clients.get_mut(&prev) {
+                    client.next = Some(target);
+                }
+            }
+        }
+
+        if let Some(prev) = prev_target {
+            if prev != selected {
+                if let Some(client) = self.clients.get_mut(&prev) {
+                    client.next = Some(selected);
+                }
+            }
+        }
+
+        if let Some(monitor) = self.monitors.get_mut(monitor_index) {
+            if monitor.clients_head == Some(selected) {
+                monitor.clients_head = Some(target);
+            } else if monitor.clients_head == Some(target) {
+                monitor.clients_head = Some(selected);
+            }
+        }
+
+        self.apply_layout()?;
+        Ok(())
+    }
+
     pub fn focus_monitor(&mut self, direction: i32) -> WmResult<()> {
         if self.monitors.len() <= 1 {
             return Ok(());
@@ -3669,6 +3806,22 @@ impl WindowManager {
         None
     }
 
+    fn next_tagged(&self, start: Option<Window>, tags: u32) -> Option<Window> {
+        let mut current = start;
+        while let Some(window) = current {
+            if let Some(client) = self.clients.get(&window) {
+                let visible_on_tags = (client.tags & tags) != 0;
+                if !client.is_floating && visible_on_tags {
+                    return Some(window);
+                }
+                current = client.next;
+            } else {
+                break;
+            }
+        }
+        None
+    }
+
     fn attach(&mut self, window: Window, monitor_index: usize) {
         if let Some(monitor) = self.monitors.get_mut(monitor_index) {
             if let Some(client) = self.clients.get_mut(&window) {
@@ -3684,40 +3837,24 @@ impl WindowManager {
             None => return,
         };
 
-        if monitor.clients_head.is_none() {
+        let new_window_tags = self.clients.get(&window).map(|c| c.tags).unwrap_or(0);
+        let first_tagged = self.next_tagged(monitor.clients_head, new_window_tags);
+
+        if first_tagged.is_none() {
             self.attach(window, monitor_index);
             return;
         }
 
-        let num_master = monitor.num_master.max(1) as usize;
-        let mut current = monitor.clients_head;
-        let mut position = 0;
-
-        while position < num_master - 1 {
-            if let Some(current_window) = current {
-                if let Some(current_client) = self.clients.get(&current_window) {
-                    current = current_client.next;
-                    position += 1;
-                } else {
-                    break;
-                }
-            } else {
-                break;
-            }
-        }
-
-        if let Some(insert_after) = current {
-            if let Some(after_client) = self.clients.get(&insert_after) {
+        if let Some(insert_after_window) = first_tagged {
+            if let Some(after_client) = self.clients.get(&insert_after_window) {
                 let old_next = after_client.next;
                 if let Some(new_client) = self.clients.get_mut(&window) {
                     new_client.next = old_next;
                 }
-                if let Some(after_client_mut) = self.clients.get_mut(&insert_after) {
+                if let Some(after_client_mut) = self.clients.get_mut(&insert_after_window) {
                     after_client_mut.next = Some(window);
                 }
             }
-        } else {
-            self.attach(window, monitor_index);
         }
     }
 
diff --git a/templates/config.lua b/templates/config.lua
index 42aa8fe..50c4c21 100644
--- a/templates/config.lua
+++ b/templates/config.lua
@@ -204,6 +204,10 @@ oxwm.key.bind({ modkey, "Shift" }, "R", oxwm.restart()) -- Restart OXWM (reloads
 oxwm.key.bind({ modkey }, "J", oxwm.client.focus_stack(1))  -- Focus next window in stack
 oxwm.key.bind({ modkey }, "K", oxwm.client.focus_stack(-1)) -- Focus previous window in stack
 
+-- Window movement (swap position in stack)
+oxwm.key.bind({ modkey, "Shift" }, "J", oxwm.client.move_stack(1))  -- Move window down in stack
+oxwm.key.bind({ modkey, "Shift" }, "K", oxwm.client.move_stack(-1)) -- Move window up in stack
+
 -- Multi-monitor support
 oxwm.key.bind({ modkey }, "Comma", oxwm.monitor.focus(-1))       -- Focus previous monitor
 oxwm.key.bind({ modkey }, "Period", oxwm.monitor.focus(1))       -- Focus next monitor