oxwm

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

Added logic to move windows from monitors with a keybind, and toggle monitors with a keybind. removed all references to old hasmap window list

Commit
6c1e59a8a7eff1c0cdfb68232815a46b2047e59d
Parent
5767bfd
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-12-01 08:21:52

Diff

diff --git a/resources/test-config.lua b/resources/test-config.lua
index 3a931f6..0b5522b 100644
--- a/resources/test-config.lua
+++ b/resources/test-config.lua
@@ -77,13 +77,17 @@ oxwm.key.bind({ modkey }, "N", oxwm.layout.cycle())
 
 oxwm.key.bind({ modkey }, "A", oxwm.toggle_gaps())
 
--- Master area controls (dwm-style)
--- NOTE: Mod+H/L are currently used for directional focus
--- Using Comma/Period instead. To match dwm exactly, replace H/L bindings above
-oxwm.key.bind({ modkey }, "Comma", oxwm.set_master_factor(-5))   -- Decrease master area
-oxwm.key.bind({ modkey }, "Period", oxwm.set_master_factor(5))    -- Increase master area
-oxwm.key.bind({ modkey }, "I", oxwm.inc_num_master(1))             -- More master windows
-oxwm.key.bind({ modkey }, "P", oxwm.inc_num_master(-1))            -- Fewer master windows
+-- Master area controls
+oxwm.key.bind({ modkey }, "BracketLeft", oxwm.set_master_factor(-5))   -- Decrease master area
+oxwm.key.bind({ modkey }, "BracketRight", oxwm.set_master_factor(5))   -- Increase master area
+oxwm.key.bind({ modkey }, "I", oxwm.inc_num_master(1))                 -- More master windows
+oxwm.key.bind({ modkey }, "P", oxwm.inc_num_master(-1))                -- Fewer master windows
+
+-- Multi-monitor controls (dwm-style)
+oxwm.key.bind({ modkey }, "Comma", oxwm.monitor.focus(-1))              -- Focus previous monitor
+oxwm.key.bind({ modkey }, "Period", oxwm.monitor.focus(1))              -- Focus next monitor
+oxwm.key.bind({ modkey, "Shift" }, "Comma", oxwm.monitor.tag(-1))      -- Send window to previous monitor
+oxwm.key.bind({ modkey, "Shift" }, "Period", oxwm.monitor.tag(1))      -- Send window to next monitor
 
 oxwm.key.bind({ modkey, "Shift" }, "Q", oxwm.quit())
 oxwm.key.bind({ modkey, "Shift" }, "R", oxwm.restart())
diff --git a/src/config/lua_api.rs b/src/config/lua_api.rs
index b89d6e9..65132a8 100644
--- a/src/config/lua_api.rs
+++ b/src/config/lua_api.rs
@@ -88,6 +88,7 @@ pub fn register_api(lua: &Lua) -> Result<SharedBuilder, ConfigError> {
     register_client_module(&lua, &oxwm_table)?;
     register_layout_module(&lua, &oxwm_table)?;
     register_tag_module(&lua, &oxwm_table)?;
+    register_monitor_module(&lua, &oxwm_table)?;
     register_rule_module(&lua, &oxwm_table, builder.clone())?;
     register_bar_module(&lua, &oxwm_table, builder.clone())?;
     register_misc(&lua, &oxwm_table, builder.clone())?;
@@ -324,6 +325,23 @@ fn register_tag_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
     Ok(())
 }
 
+fn register_monitor_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
+    let monitor_table = lua.create_table()?;
+
+    let focus = lua.create_function(|lua, direction: i64| {
+        create_action_table(lua, "FocusMonitor", Value::Integer(direction))
+    })?;
+
+    let tag = lua.create_function(|lua, direction: i64| {
+        create_action_table(lua, "TagMonitor", Value::Integer(direction))
+    })?;
+
+    monitor_table.set("focus", focus)?;
+    monitor_table.set("tag", tag)?;
+    parent.set("monitor", monitor_table)?;
+    Ok(())
+}
+
 fn register_rule_module(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<(), ConfigError> {
     let rule_table = lua.create_table()?;
 
@@ -789,6 +807,7 @@ fn string_to_action(s: &str) -> mlua::Result<KeyAction> {
         "ChangeLayout" => Ok(KeyAction::ChangeLayout),
         "CycleLayout" => Ok(KeyAction::CycleLayout),
         "FocusMonitor" => Ok(KeyAction::FocusMonitor),
+        "TagMonitor" => Ok(KeyAction::TagMonitor),
         "ExchangeClient" => Ok(KeyAction::ExchangeClient),
         "ShowKeybindOverlay" => Ok(KeyAction::ShowKeybindOverlay),
         _ => Err(mlua::Error::RuntimeError(format!("unknown action '{}'. this is an internal error, please report it", s))),
diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
index 7d867ec..a21fb91 100644
--- a/src/keyboard/handlers.rs
+++ b/src/keyboard/handlers.rs
@@ -30,6 +30,7 @@ pub enum KeyAction {
     ChangeLayout,
     CycleLayout,
     FocusMonitor,
+    TagMonitor,
     ExchangeClient,
     ShowKeybindOverlay,
     SetMasterFactor,
diff --git a/src/overlay/keybind.rs b/src/overlay/keybind.rs
index 9789510..391b848 100644
--- a/src/overlay/keybind.rs
+++ b/src/overlay/keybind.rs
@@ -221,6 +221,7 @@ impl KeybindOverlay {
             KeyAction::ChangeLayout => "Change Layout".to_string(),
             KeyAction::CycleLayout => "Cycle Through Layouts".to_string(),
             KeyAction::FocusMonitor => "Focus Next Monitor".to_string(),
+            KeyAction::TagMonitor => "Send Window to Monitor".to_string(),
             KeyAction::ExchangeClient => "Exchange Client Windows".to_string(),
             KeyAction::SetMasterFactor => "Adjust Master Area Size".to_string(),
             KeyAction::IncNumMaster => "Adjust Number of Master Windows".to_string(),
diff --git a/src/window_manager.rs b/src/window_manager.rs
index e58eb9c..559325b 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -743,9 +743,9 @@ impl WindowManager {
         for (monitor_index, monitor) in self.monitors.iter().enumerate() {
             if let Some(bar) = self.bars.get_mut(monitor_index) {
                 let mut occupied_tags: TagMask = 0;
-                for (&window, &tags) in &self.window_tags {
-                    if self.window_monitor.get(&window).copied().unwrap_or(0) == monitor_index {
-                        occupied_tags |= tags;
+                for client in self.clients.values() {
+                    if client.monitor_index == monitor_index {
+                        occupied_tags |= client.tags;
                     }
                 }
 
@@ -773,15 +773,14 @@ impl WindowManager {
                     .windows
                     .iter()
                     .filter(|&&window| {
-                        let window_monitor_index = self.window_monitor.get(&window).copied().unwrap_or(0);
-                        if window_monitor_index != monitor_index
-                            || self.floating_windows.contains(&window)
-                            || self.fullscreen_windows.contains(&window)
-                        {
-                            return false;
-                        }
-                        if let Some(&tags) = self.window_tags.get(&window) {
-                            (tags & monitor.tagset[monitor.selected_tags_index]) != 0
+                        if let Some(client) = self.clients.get(&window) {
+                            if client.monitor_index != monitor_index
+                                || self.floating_windows.contains(&window)
+                                || self.fullscreen_windows.contains(&window)
+                            {
+                                return false;
+                            }
+                            (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0
                         } else {
                             false
                         }
@@ -925,6 +924,11 @@ impl WindowManager {
                     self.focus_monitor(*direction)?;
                 }
             }
+            KeyAction::TagMonitor => {
+                if let Arg::Int(direction) = arg {
+                    self.tag_monitor(*direction)?;
+                }
+            }
             KeyAction::ShowKeybindOverlay => {
                 let monitor = &self.monitors[self.selected_monitor];
                 self.keybind_overlay.toggle(
@@ -950,40 +954,12 @@ impl WindowManager {
         Ok(())
     }
 
-    fn focus_monitor(&mut self, direction: i32) -> WmResult<()> {
-        if self.monitors.is_empty() {
-            return Ok(());
-        }
-
-        let new_monitor = if direction > 0 {
-            (self.selected_monitor + 1) % self.monitors.len()
-        } else {
-            (self.selected_monitor + self.monitors.len() - 1) % self.monitors.len()
-        };
-
-        if new_monitor == self.selected_monitor {
-            return Ok(());
-        }
-
-        self.selected_monitor = new_monitor;
-
-        self.update_bar()?;
-
-        let visible = self.visible_windows_on_monitor(new_monitor);
-        if let Some(&win) = visible.first() {
-            self.focus(Some(win))?;
-        }
-
-        Ok(())
-    }
 
     fn is_window_visible(&self, window: Window) -> bool {
-        let window_mon = self.window_monitor.get(&window).copied().unwrap_or(0);
-
-        if let Some(&tags) = self.window_tags.get(&window) {
-            let monitor = self.monitors.get(window_mon);
+        if let Some(client) = self.clients.get(&window) {
+            let monitor = self.monitors.get(client.monitor_index);
             let selected_tags = monitor.map(|m| m.tagset[m.selected_tags_index]).unwrap_or(0);
-            (tags & selected_tags) != 0
+            (client.tags & selected_tags) != 0
         } else {
             false
         }
@@ -1033,6 +1009,44 @@ impl WindowManager {
             .position(|mon| mon.contains_point(x, y))
     }
 
+    fn rect_to_monitor(&self, x: i32, y: i32, w: i32, h: i32) -> usize {
+        let mut best_monitor = self.selected_monitor;
+        let mut max_area = 0;
+
+        for (idx, monitor) in self.monitors.iter().enumerate() {
+            let intersect_width = 0.max((x + w).min(monitor.window_area_x + monitor.window_area_width) - x.max(monitor.window_area_x));
+            let intersect_height = 0.max((y + h).min(monitor.window_area_y + monitor.window_area_height) - y.max(monitor.window_area_y));
+            let area = intersect_width * intersect_height;
+
+            if area > max_area {
+                max_area = area;
+                best_monitor = idx;
+            }
+        }
+
+        best_monitor
+    }
+
+    fn dir_to_monitor(&self, direction: i32) -> Option<usize> {
+        if self.monitors.len() <= 1 {
+            return None;
+        }
+
+        if direction > 0 {
+            if self.selected_monitor + 1 < self.monitors.len() {
+                Some(self.selected_monitor + 1)
+            } else {
+                Some(0)
+            }
+        } else {
+            if self.selected_monitor == 0 {
+                Some(self.monitors.len() - 1)
+            } else {
+                Some(self.selected_monitor - 1)
+            }
+        }
+    }
+
     // Dwm's g-loaded approach to handling the spam alternating crash.
     fn update_window_visibility(&self) -> WmResult<()> {
         for &window in &self.windows {
@@ -1579,7 +1593,9 @@ impl WindowManager {
                 .collect();
 
             for window in floating_windows {
-                let monitor_idx = *self.window_monitor.get(&window).unwrap_or(&self.selected_monitor);
+                let monitor_idx = self.clients.get(&window)
+                    .map(|c| c.monitor_index)
+                    .unwrap_or(self.selected_monitor);
                 let monitor = &self.monitors[monitor_idx];
 
                 let (outer_gap_h, outer_gap_v) = if self.gaps_enabled {
@@ -1679,7 +1695,9 @@ impl WindowManager {
                 client.is_fullscreen = true;
             }
 
-            let monitor_idx = *self.window_monitor.get(&window).unwrap_or(&self.selected_monitor);
+            let monitor_idx = self.clients.get(&window)
+                .map(|c| c.monitor_index)
+                .unwrap_or(self.selected_monitor);
             let monitor = &self.monitors[monitor_idx];
 
             if self.show_bar {
@@ -1723,7 +1741,9 @@ impl WindowManager {
                     .border_width(self.config.border_width),
             )?;
 
-            let monitor_idx = *self.window_monitor.get(&window).unwrap_or(&self.selected_monitor);
+            let monitor_idx = self.clients.get(&window)
+                .map(|c| c.monitor_index)
+                .unwrap_or(self.selected_monitor);
             if self.show_bar {
                 if let Some(bar) = self.bars.get(monitor_idx) {
                     self.connection.map_window(bar.window())?;
@@ -1906,14 +1926,15 @@ impl WindowManager {
 
         let transient_parent = self.get_transient_parent(window);
         let (window_tags, monitor_index) = if let Some(parent) = transient_parent {
-            let parent_tags = self.window_tags.get(&parent).copied().unwrap_or_else(|| {
-                self.monitors
+            if let Some(parent_client) = self.clients.get(&parent) {
+                (parent_client.tags, parent_client.monitor_index)
+            } else {
+                let tags = self.monitors
                     .get(self.selected_monitor)
                     .map(|m| m.tagset[m.selected_tags_index])
-                    .unwrap_or(tag_mask(0))
-            });
-            let parent_monitor = self.window_monitor.get(&parent).copied().unwrap_or(self.selected_monitor);
-            (parent_tags, parent_monitor)
+                    .unwrap_or(tag_mask(0));
+                (tags, self.selected_monitor)
+            }
         } else {
             let selected_tags = self.monitors
                 .get(self.selected_monitor)
@@ -2190,7 +2211,9 @@ impl WindowManager {
                 return Ok(());
             }
 
-            let monitor_idx = *self.window_monitor.get(&win).unwrap_or(&self.selected_monitor);
+            let monitor_idx = self.clients.get(&win)
+                .map(|c| c.monitor_index)
+                .unwrap_or(self.selected_monitor);
             if monitor_idx != self.selected_monitor {
                 self.selected_monitor = monitor_idx;
             }
@@ -2340,6 +2363,54 @@ impl WindowManager {
         Ok(())
     }
 
+    pub fn focus_monitor(&mut self, direction: i32) -> WmResult<()> {
+        if self.monitors.len() <= 1 {
+            return Ok(());
+        }
+
+        let target_monitor = match self.dir_to_monitor(direction) {
+            Some(idx) if idx != self.selected_monitor => idx,
+            _ => return Ok(()),
+        };
+
+        let old_selected = self.monitors
+            .get(self.selected_monitor)
+            .and_then(|m| m.selected_client);
+
+        if let Some(win) = old_selected {
+            self.unfocus(win)?;
+        }
+
+        self.selected_monitor = target_monitor;
+        self.focus(None)?;
+
+        Ok(())
+    }
+
+    pub fn tag_monitor(&mut self, direction: i32) -> WmResult<()> {
+        if self.monitors.len() <= 1 {
+            return Ok(());
+        }
+
+        let selected_window = self.monitors
+            .get(self.selected_monitor)
+            .and_then(|m| m.selected_client);
+
+        let window = match selected_window {
+            Some(win) => win,
+            None => return Ok(()),
+        };
+
+        let target_monitor = match self.dir_to_monitor(direction) {
+            Some(idx) => idx,
+            None => return Ok(()),
+        };
+
+        self.send_to_monitor(window, target_monitor)?;
+
+        Ok(())
+    }
+
     fn update_focus_visuals(
         &self,
         old_focused: Option<Window>,
@@ -2599,9 +2670,9 @@ impl WindowManager {
                     return Ok(None);
                 }
                 if self.windows.contains(&event.event) {
-                    if let Some(&window_monitor_index) = self.window_monitor.get(&event.event) {
-                        if window_monitor_index != self.selected_monitor {
-                            self.selected_monitor = window_monitor_index;
+                    if let Some(client) = self.clients.get(&event.event) {
+                        if client.monitor_index != self.selected_monitor {
+                            self.selected_monitor = client.monitor_index;
                             self.update_bar()?;
                         }
                     }
@@ -2727,16 +2798,15 @@ impl WindowManager {
                             .windows
                             .iter()
                             .filter(|&&window| {
-                                let window_monitor_index = self.window_monitor.get(&window).copied().unwrap_or(0);
-                                if window_monitor_index != monitor_index
-                                    || self.floating_windows.contains(&window)
-                                    || self.fullscreen_windows.contains(&window)
-                                {
-                                    return false;
-                                }
-                                let monitor_tags = self.monitors.get(monitor_index).map(|m| m.tagset[m.selected_tags_index]).unwrap_or(0);
-                                if let Some(&tags) = self.window_tags.get(&window) {
-                                    (tags & monitor_tags) != 0
+                                if let Some(client) = self.clients.get(&window) {
+                                    if client.monitor_index != monitor_index
+                                        || self.floating_windows.contains(&window)
+                                        || self.fullscreen_windows.contains(&window)
+                                    {
+                                        return false;
+                                    }
+                                    let monitor_tags = self.monitors.get(monitor_index).map(|m| m.tagset[m.selected_tags_index]).unwrap_or(0);
+                                    (client.tags & monitor_tags) != 0
                                 } else {
                                     false
                                 }
@@ -2780,7 +2850,9 @@ impl WindowManager {
             }
             Event::ConfigureRequest(event) => {
                 if self.windows.contains(&event.window) {
-                    let monitor_index = *self.window_monitor.get(&event.window).unwrap_or(&self.selected_monitor);
+                    let monitor_index = self.clients.get(&event.window)
+                        .map(|c| c.monitor_index)
+                        .unwrap_or(self.selected_monitor);
                     let monitor = &self.monitors[monitor_index];
                     let is_floating = self.floating_windows.contains(&event.window);
                     let is_tiling_layout = self.layout.name() != "normie";
@@ -2854,13 +2926,24 @@ impl WindowManager {
                             cached_geom.map(|g| g.y_position).unwrap_or(0)
                         };
 
+                        let final_width = if value_mask.contains(ConfigWindow::WIDTH) { event.width } else { cached_geom.map(|g| g.width).unwrap_or(1) };
+                        let final_height = if value_mask.contains(ConfigWindow::HEIGHT) { event.height } else { cached_geom.map(|g| g.height).unwrap_or(1) };
+
                         self.update_geometry_cache(event.window, CachedGeometry {
                             x_position: final_x,
                             y_position: final_y,
-                            width: if value_mask.contains(ConfigWindow::WIDTH) { event.width } else { cached_geom.map(|g| g.width).unwrap_or(1) },
-                            height: if value_mask.contains(ConfigWindow::HEIGHT) { event.height } else { cached_geom.map(|g| g.height).unwrap_or(1) },
+                            width: final_width,
+                            height: final_height,
                             border_width: if value_mask.contains(ConfigWindow::BORDER_WIDTH) { event.border_width } else { border_width },
                         });
+
+                        if is_floating {
+                            let new_monitor = self.rect_to_monitor(final_x as i32, final_y as i32, final_width as i32, final_height as i32);
+
+                            if new_monitor != monitor_index {
+                                self.send_to_monitor(event.window, new_monitor)?;
+                            }
+                        }
                     } else {
                         self.send_configure_notify(event.window)?;
                     }
@@ -3068,16 +3151,15 @@ impl WindowManager {
                 .windows
                 .iter()
                 .any(|&window| {
-                    let window_monitor_index = self.window_monitor.get(&window).copied().unwrap_or(0);
-                    if window_monitor_index != monitor_index
-                        || self.floating_windows.contains(&window)
-                        || self.fullscreen_windows.contains(&window)
-                    {
-                        return false;
-                    }
-                    if let Some(monitor) = self.monitors.get(monitor_index) {
-                        if let Some(&tags) = self.window_tags.get(&window) {
-                            return (tags & monitor.tagset[monitor.selected_tags_index]) != 0;
+                    if let Some(client) = self.clients.get(&window) {
+                        if client.monitor_index != monitor_index
+                            || self.floating_windows.contains(&window)
+                            || self.fullscreen_windows.contains(&window)
+                        {
+                            return false;
+                        }
+                        if let Some(monitor) = self.monitors.get(monitor_index) {
+                            return (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0;
                         }
                     }
                     false
@@ -3495,6 +3577,42 @@ impl WindowManager {
         }
     }
 
+    fn send_to_monitor(&mut self, window: Window, target_monitor: usize) -> WmResult<()> {
+        if target_monitor >= self.monitors.len() {
+            return Ok(());
+        }
+
+        let current_monitor = self.clients.get(&window).map(|c| c.monitor_index);
+        if current_monitor == Some(target_monitor) {
+            return Ok(());
+        }
+
+        self.unfocus(window)?;
+
+        self.detach(window);
+        self.detach_stack(window);
+
+        if let Some(client) = self.clients.get_mut(&window) {
+            client.monitor_index = target_monitor;
+            let new_tags = self.monitors
+                .get(target_monitor)
+                .map(|m| m.tagset[m.selected_tags_index])
+                .unwrap_or(1);
+            client.tags = new_tags;
+            self.window_tags.insert(window, new_tags);
+        }
+
+        self.window_monitor.insert(window, target_monitor);
+
+        self.attach_aside(window, target_monitor);
+        self.attach_stack(window, target_monitor);
+
+        self.focus(None)?;
+        self.apply_layout()?;
+
+        Ok(())
+    }
+
     fn remove_window(&mut self, window: Window) -> WmResult<()> {
         let initial_count = self.windows.len();