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(¤t_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