oxwm

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

Added autotile functionality.

Commit
5b51cfe9d552bea20637823fb140989f23671e10
Parent
0367894
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-12-13 08:19:15

Diff

diff --git a/resources/test-config.lua b/resources/test-config.lua
index d094520..0ff7764 100644
--- a/resources/test-config.lua
+++ b/resources/test-config.lua
@@ -33,6 +33,7 @@ local modkey = "Mod1"
 oxwm.set_terminal("st")
 oxwm.set_modkey(modkey)
 oxwm.set_tags({ "1", "2", "3", "4", "5", "6", "7", "8", "9" })
+oxwm.auto_tile(true);
 
 oxwm.set_layout_symbol("tiling", "[T]")
 oxwm.set_layout_symbol("normie", "[F]")
@@ -42,7 +43,7 @@ oxwm.border.set_focused_color(colors.blue)
 oxwm.border.set_unfocused_color(colors.grey)
 
 oxwm.gaps.set_enabled(true)
-oxwm.gaps.set_smart(true)  -- Disable outer gaps when only 1 window (dwm smartgaps)
+oxwm.gaps.set_smart(true) -- Disable outer gaps when only 1 window (dwm smartgaps)
 oxwm.gaps.set_inner(5, 5)
 oxwm.gaps.set_outer(5, 5)
 
@@ -78,16 +79,16 @@ oxwm.key.bind({ modkey }, "N", oxwm.layout.cycle())
 oxwm.key.bind({ modkey }, "A", oxwm.toggle_gaps())
 
 -- 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
+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 }, "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.rs b/src/config/lua.rs
index b819702..e651a83 100644
--- a/src/config/lua.rs
+++ b/src/config/lua.rs
@@ -48,5 +48,6 @@ pub fn parse_lua_config(
         scheme_occupied: builder_data.scheme_occupied,
         scheme_selected: builder_data.scheme_selected,
         autostart: builder_data.autostart,
+        auto_tile: builder_data.auto_tile,
     });
 }
diff --git a/src/config/lua_api.rs b/src/config/lua_api.rs
index e6190fa..cae6189 100644
--- a/src/config/lua_api.rs
+++ b/src/config/lua_api.rs
@@ -32,6 +32,7 @@ pub struct ConfigBuilder {
     pub scheme_occupied: ColorScheme,
     pub scheme_selected: ColorScheme,
     pub autostart: Vec<String>,
+    pub auto_tile: bool,
 }
 
 impl Default for ConfigBuilder {
@@ -70,6 +71,7 @@ impl Default for ConfigBuilder {
                 underline: 0x444444,
             },
             autostart: Vec::new(),
+            auto_tile: false,
         }
     }
 }
@@ -738,6 +740,12 @@ fn register_misc(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<()
         Ok(())
     })?;
 
+    let builder_clone = builder.clone();
+    let auto_tile = lua.create_function(move |_, enabled: bool| {
+        builder_clone.borrow_mut().auto_tile = enabled;
+        Ok(())
+    })?;
+
     parent.set("set_terminal", set_terminal)?;
     parent.set("set_modkey", set_modkey)?;
     parent.set("set_tags", set_tags)?;
@@ -750,6 +758,7 @@ fn register_misc(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<()
     parent.set("inc_num_master", inc_num_master)?;
     parent.set("show_keybinds", show_keybinds)?;
     parent.set("focus_monitor", focus_monitor)?;
+    parent.set("auto_tile", auto_tile)?;
     Ok(())
 }
 
diff --git a/src/lib.rs b/src/lib.rs
index 4621da7..e2cd37d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -94,8 +94,8 @@ pub struct Config {
     pub scheme_occupied: ColorScheme,
     pub scheme_selected: ColorScheme,
 
-    // Autostart commands
     pub autostart: Vec<String>,
+    pub auto_tile: bool,
 }
 
 #[derive(Debug, Clone, Copy)]
@@ -338,6 +338,7 @@ impl Default for Config {
                 underline: 0xad8ee6,
             },
             autostart: vec![],
+            auto_tile: false,
         }
     }
 }
diff --git a/src/window_manager.rs b/src/window_manager.rs
index 765810b..294deda 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -2535,9 +2535,92 @@ impl WindowManager {
             }
         }
 
+        if self.config.auto_tile && !was_floating && !is_normie {
+            let drop_monitor_idx = self
+                .clients
+                .get(&window)
+                .map(|c| c.monitor_index)
+                .unwrap_or(monitor_idx);
+
+            if let Some((x, y, w, h)) = final_client {
+                let center = (x as i32 + w as i32 / 2, y as i32 + h as i32 / 2);
+                if let Some(target) = self.tiled_window_at(window, drop_monitor_idx, center) {
+                    self.detach(window);
+                    self.insert_before(window, target, drop_monitor_idx);
+                }
+            }
+
+            self.floating_windows.remove(&window);
+            if let Some(client) = self.clients.get_mut(&window) {
+                client.is_floating = false;
+            }
+            self.apply_layout()?;
+        }
+
         Ok(())
     }
 
+    fn tiled_window_at(
+        &self,
+        exclude: Window,
+        monitor_idx: usize,
+        (px, py): (i32, i32),
+    ) -> Option<Window> {
+        let monitor = self.monitors.get(monitor_idx)?;
+        let tags = monitor.tagset[monitor.selected_tags_index];
+        let mut current = monitor.clients_head;
+
+        while let Some(win) = current {
+            let c = self.clients.get(&win)?;
+            current = c.next;
+
+            if win == exclude || c.is_floating || (c.tags & tags) == 0 {
+                continue;
+            }
+
+            let (x, y) = (c.x_position as i32, c.y_position as i32);
+            let (w, h) = (
+                c.width as i32 + c.border_width as i32 * 2,
+                c.height as i32 + c.border_width as i32 * 2,
+            );
+
+            if px >= x && px < x + w && py >= y && py < y + h {
+                return Some(win);
+            }
+        }
+        None
+    }
+
+    fn insert_before(&mut self, window: Window, target: Window, monitor_idx: usize) {
+        let Some(monitor) = self.monitors.get_mut(monitor_idx) else {
+            return;
+        };
+
+        if monitor.clients_head == Some(target) {
+            if let Some(c) = self.clients.get_mut(&window) {
+                c.next = Some(target);
+            }
+            monitor.clients_head = Some(window);
+            return;
+        }
+
+        let mut current = monitor.clients_head;
+        while let Some(w) = current {
+            let Some(c) = self.clients.get(&w) else { break };
+            if c.next != Some(target) {
+                current = c.next;
+                continue;
+            }
+            if let Some(prev) = self.clients.get_mut(&w) {
+                prev.next = Some(window);
+            }
+            if let Some(inserted) = self.clients.get_mut(&window) {
+                inserted.next = Some(target);
+            }
+            break;
+        }
+    }
+
     fn resize_window_with_mouse(&mut self, window: Window) -> WmResult<()> {
         let is_fullscreen = self
             .clients
@@ -2574,12 +2657,32 @@ impl WindowManager {
             return Ok(());
         };
 
-        if self.monitors.get(monitor_idx).is_none() {
-            return Ok(());
-        }
+        let monitor = match self.monitors.get(monitor_idx) {
+            Some(m) => m,
+            None => return Ok(()),
+        };
 
         let is_normie = self.layout.name() == "normie";
 
+        if self.config.auto_tile && !was_floating && !is_normie {
+            let mut tiled_count = 0;
+            let mut current = monitor.clients_head;
+            while let Some(w) = current {
+                if let Some(c) = self.clients.get(&w) {
+                    let visible = (c.tags & monitor.tagset[monitor.selected_tags_index]) != 0;
+                    if visible && !c.is_floating {
+                        tiled_count += 1;
+                    }
+                    current = c.next;
+                } else {
+                    break;
+                }
+            }
+            if tiled_count <= 1 {
+                return Ok(());
+            }
+        }
+
         if !was_floating && !is_normie {
             self.toggle_floating()?;
         }
@@ -2698,6 +2801,14 @@ impl WindowManager {
             }
         }
 
+        if self.config.auto_tile && !was_floating && !is_normie {
+            self.floating_windows.remove(&window);
+            if let Some(client) = self.clients.get_mut(&window) {
+                client.is_floating = false;
+            }
+            self.apply_layout()?;
+        }
+
         Ok(())
     }
 
diff --git a/templates/oxwm.lua b/templates/oxwm.lua
index 45239a8..e2e724a 100644
--- a/templates/oxwm.lua
+++ b/templates/oxwm.lua
@@ -37,6 +37,10 @@ function oxwm.set_modkey(modkey) end
 ---@param tags string[] Array of tag names
 function oxwm.set_tags(tags) end
 
+---Enable or disable automatic tiling of new windows
+---@param enabled boolean Enable or disable auto-tiling
+function oxwm.auto_tile(enabled) end
+
 ---Set layout symbol override
 ---@param name string Layout name (e.g., "tiling", "normie", "tabbed", "grid", "monocle")
 ---@param symbol string Symbol to display (e.g., "[T]", "[F]", "[=]")