Diff
diff --git a/Cargo.lock b/Cargo.lock
index 235a78c..2dde466 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -291,7 +291,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "oxwm"
-version = "0.7.4"
+version = "0.8.0"
dependencies = [
"chrono",
"dirs",
diff --git a/Cargo.toml b/Cargo.toml
index 63873eb..580fc5b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "oxwm"
-version = "0.7.4"
+version = "0.8.0"
edition = "2024"
[lib]
diff --git a/readme.org b/readme.org
index bc0e692..1aa8057 100644
--- a/readme.org
+++ b/readme.org
@@ -11,22 +11,26 @@
- [[#building-from-source][Building from Source]]
- [[#setting-up-oxwm][Setting up OXWM]]
- [[#configuration][Configuration]]
+ - [[#quick-example][Quick Example]]
+ - [[#features][Features]]
+ - [[#key-configuration-areas][Key Configuration Areas]]
+ - [[#creating-your-config][Creating Your Config]]
- [[#contributing][Contributing]]
- [[#key-bindings][Key Bindings]]
-- [[#features][Features]]
+- [[#features-1][Features]]
- [[#testing-with-xephyr][Testing with Xephyr]]
- [[#project-structure][Project Structure]]
- [[#architecture-notes][Architecture Notes]]
+ - [[#lua-configuration-system][Lua Configuration System]]
- [[#tag-system][Tag System]]
- [[#status-bar][Status Bar]]
- [[#layout-system][Layout System]]
- [[#current-todo-list][Current Todo List:]]
- - [[#priority-high-02][PRIORITY High]]
- - [[#priority-medium-14][PRIORITY Medium]]
- - [[#priority-low-11][PRIORITY Low]]
+ - [[#priority-high-04][PRIORITY High]]
- [[#development-roadmap][Development Roadmap]]
- - [[#current-focus][Current Focus]]
- - [[#future-enhancements][Future Enhancements]]
+ - [[#current-focus-v080][Current Focus (v0.8.0)]]
+ - [[#completed-features-88][Completed Features]]
+ - [[#future-enhancements-][Future Enhancements]]
- [[#license][License]]
* OXWM — DWM but Better (and oxidized)
@@ -181,8 +185,21 @@ oxwm.key.bind({ "Mod4" }, "Q", oxwm.client.kill())
oxwm.key.bind({ "Mod4", "Shift" }, "Q", oxwm.quit())
-- Add status bar blocks
-oxwm.bar.add_block("{}", "DateTime", "%H:%M", 60, "#0db9d7", true)
-oxwm.bar.add_block("RAM: {used}/{total} GB", "Ram", nil, 5, "#7aa2f7", true)
+oxwm.bar.set_blocks({
+ oxwm.bar.block.datetime({
+ format = "{}",
+ date_format = "%H:%M",
+ interval = 60,
+ color = "#0db9d7",
+ underline = true,
+ }),
+ oxwm.bar.block.ram({
+ format = "RAM: {used}/{total} GB",
+ interval = 5,
+ color = "#7aa2f7",
+ underline = true,
+ }),
+})
#+end_src
** Features
@@ -220,38 +237,75 @@ When contributing to OXWM:
4. Document any new features or keybindings
* Key Bindings
-Default keybindings (customizable in config.rs):
-
-| Binding | Action |
-|-------------------+-------------------------|
-| Super+Return | Spawn terminal |
-| Super+J/K | Cycle focus down/up |
-| Super+Q | Kill focused window |
-| Super+Shift+Q | Quit WM |
-| Super+Shift+R | Hot reload WM |
-| Super+1-9 | View tag 1-9 |
-| Super+Shift+1-9 | Move window to tag 1-9 |
-| Super+S | Screenshot (maim) |
-| Super+D | dmenu launcher |
-| Super+A | Toggle gaps |
-| Super+Shift+F | Toggle fullscreen |
-| Super+Shift+Space | Toggle floating |
+Default keybindings (fully customizable in =~/.config/oxwm/config.lua=):
+
+| Binding | Action |
+|------------------------+-----------------------------------|
+| Super+Return | Spawn terminal |
+| Super+J/K | Cycle focus through stack |
+| Super+Q | Kill focused window |
+| Super+Shift+Q | Quit WM |
+| Super+Shift+R | Hot reload WM |
+| Super+1-9 | View tag 1-9 |
+| Super+Shift+1-9 | Move window to tag 1-9 |
+| Super+Ctrl+1-9 | Toggle tag view (multi-tag) |
+| Super+Ctrl+Shift+1-9 | Toggle window tag (sticky) |
+| Super+S | Screenshot (maim) |
+| Super+D | dmenu launcher |
+| Super+A | Toggle gaps |
+| Super+Shift+F | Toggle fullscreen |
+| Super+Shift+Space | Toggle floating |
+| Super+F | Set normie (floating) layout |
+| Super+C | Set tiling layout |
+| Super+N | Cycle layouts |
+| Super+Comma/Period | Focus prev/next monitor |
+| Super+Shift+Comma/. | Send window to prev/next monitor |
+| Super+[/] | Decrease/increase master area |
+| Super+I/P | Inc/dec number of master windows |
+| Super+Shift+/ | Show keybinds overlay |
+| Super+Button1 (drag) | Move window (floating) |
+| Super+Button3 (drag) | Resize window (floating) |
* Features
-- Dynamic tiling layout with master/stack
-- Tag-based workspaces (9 tags by default)
-- Configurable gaps between windows
-- Status bar with modular block system
- - Battery, RAM, datetime, shell commands
- - Custom colors and update intervals
+- *Dynamic Tiling Layout* with adjustable master/stack split
+ - Master area resizing (mfact)
+ - Multiple master windows support (nmaster)
+- *Tag-Based Workspaces* (9 tags by default)
+ - Multi-tag viewing (see multiple tags at once)
+ - Sticky windows (window visible on multiple tags)
+- *Multiple Layouts*
+ - Tiling (master/stack)
+ - Normie (floating-by-default)
+ - Monocle (fullscreen stacking)
+ - Grid (equal-sized grid)
+ - Tabbed (tabbed windows)
+- *Lua Configuration System*
+ - Hot reload without restarting X (=Mod+Shift+R=)
+ - LSP support with type definitions and autocomplete
+ - No compilation needed - instant config changes
+- *Built-in Status Bar* with modular block system
+ - Battery, RAM, datetime, shell commands, static text
+ - Custom colors, update intervals, and underlines
- Click-to-switch tags
-- Window focus cycling
-- Hot reload without restarting X
-- Persistent window tags across restarts
-- Mouse hover to focus
-- Border indicators for focused windows
-- Fullscreen mode
-- Move Windows with Super HJKL
+ - Multi-monitor support (one bar per monitor)
+- *Advanced Window Management*
+ - Window focus cycling through stack
+ - Fullscreen mode
+ - Floating window support
+ - Mouse hover to focus (follow mouse)
+ - Border indicators for focused windows
+ - Configurable gaps (smartgaps support)
+ - Window rules (auto-tag, auto-float by class/title)
+- *Multi-Monitor Support*
+ - RandR multi-monitor detection
+ - Independent tags per monitor
+ - Move windows between monitors
+- *Keychord Support*
+ - Multi-key sequences (Emacs/Vim style)
+ - Example: =Mod+Space= then =T= to spawn terminal
+- *Persistent State*
+ - Window tags persist across WM restarts
+ - Uses X11 properties for state storage
* Testing with Xephyr
Test OXWM in a nested X server without affecting your current session:
@@ -340,7 +394,11 @@ src/
│
├── layout/
│ ├── mod.rs [Layout trait definition]
-│ └── tiling.rs [Tiling layout implementation]
+│ ├── tiling.rs [Tiling layout with master/stack]
+│ ├── monocle.rs [Fullscreen stacking layout]
+│ ├── grid.rs [Equal-sized grid layout]
+│ ├── tabbed.rs [Tabbed container layout]
+│ └── normie.rs [Floating-by-default layout]
│
└── errors.rs [Error types: WmError, ConfigError, etc.]
@@ -373,48 +431,43 @@ The bar uses a performance-optimized approach with a modular block system:
The tiling layout divides the screen into a master area (left half) and stack area (right half). The master window occupies the full height of the master area, while stack windows split the stack area vertically. Gaps are configurable and can be toggled at runtime.
* TODO Current Todo List:
-** PRIORITY High [0/2]
-- [ ] Convert keycodes to keysyms for cross-keyboard compatibility
- - Current hardcoded keycodes only work on specific keyboards
- - Need to use XKeysymToKeycode() for runtime conversion
- - Follow DWM's approach: keysym → keycode conversion
-- [ ] Fix fullscreen to persist across tags
- - Fullscreen state should be maintained when switching tags
- - Window should remain fullscreen when returning to its tag
-
-** PRIORITY Medium [2/4]
-- [ ] Add keybindings to increase/decrease window size
- - Master area resize (Mod+H / Mod+L)
- - Individual window resize for floating windows
-- [X] Keychords
- - Implemented with =oxwm.key.chord()= in the functional API
- - Example: =oxwm.key.chord({ { {"Mod4"}, "Space" }, { {}, "T" } }, oxwm.spawn("st"))=
-- [X] Fix cursor on hover for bar
- - Bar should show pointer cursor on hover
- - Indicate clickable tag areas
-- [ ] Add auto-detect terminal to default config
- - Auto-detect available terminal emulator
- - Priority order: st → alacritty → kitty → wezterm → xterm
- - Fallback to xterm if none found
-
-** PRIORITY Low [1/1]
-- [X] Create AUR package
- - Write PKGBUILD
- - Submit to AUR
- - Add installation instructions to README
+** PRIORITY High [0/4]
+- [ ] Add Window Titles to Bar
+ - Show focused window title in status bar
+ - Truncate if too long
+ - Update on window focus change
+- [ ] Add Horizontal Scroll Layout
+ - Master window on left, stack scrolls horizontally
+ - Alternative to vertical tiling
+- [ ] Add Hide Empty Tag Numbers Option
+ - Option to hide tags with no windows
+ - Configurable via =oxwm.bar.set_hide_empty_tags(bool)=
+- [ ] Add Swap Stack Bind
+ - Keybind to swap focused window with master
+ - Similar to dwm's zoom function (=Mod+Return=)
+ - Should work bidirectionally
* Development Roadmap
-** Current Focus
-- Multi-monitor support
-- Additional layouts (monocle, floating)
-- Master area resizing
-- Window swapping in layout
-
-** Future Enhancements
-- Per-window floating behavior
-- Per-program rules (auto-tag assignment, floating rules)
-- External bar support (polybar, etc)
-- Scratchpad functionality
-- Window minimize/restore
+** Current Focus (v0.8.0)
+- Refactoring to align with dwm's proven patterns
+- Improving core window management reliability
+- Maintaining Lua config and bar features while simplifying internals
+
+** Completed Features [8/8]
+- [X] Multi-monitor support with RandR
+- [X] Multiple layouts (tiling, monocle, grid, tabbed, normie)
+- [X] Master area resizing (mfact) and nmaster support
+- [X] Window rules (per-program auto-tag, floating)
+- [X] Lua configuration with hot-reload
+- [X] Built-in status bar with modular blocks
+- [X] Keychord support (multi-key sequences)
+- [X] Tag persistence across restarts
+
+** Future Enhancements [/]
+- [ ] Scratchpad functionality
+- [ ] Dynamic monitor hotplugging (currently only on startup)
+- [ ] External bar support (polybar, waybar compatibility)
+- [ ] Additional layouts (deck, spiral, dwindle)
+- [ ] Window minimize/restore
* License
[[https://www.gnu.org/licenses/gpl-3.0.en.html][GPL v3]]
diff --git a/src/window_manager.rs b/src/window_manager.rs
index b51ae5d..6ff405c 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -317,9 +317,14 @@ impl WindowManager {
}
pub fn show_migration_overlay(&mut self) {
- let message = "Your config.lua uses legacy syntax or has errors.\n\n\
- You are now running with default configuration.\n\n\
- Press Mod+Shift+/ to see default keybinds\n\
+ 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\
+ Please reach out to Tony, or check out the\n\
+ documentation for help with migration.\n\n\
+ We appreciate you testing oxwm!\n\n\
+ Press Mod+Shift+/ to see keybinds\n\
Press Mod+Shift+R to reload after fixing your config";
let screen_width = self.screen.width_in_pixels;
@@ -2380,6 +2385,10 @@ impl WindowManager {
let snap = 32;
let is_normie = self.layout.name() == "normie";
+ if !was_floating && !is_normie {
+ self.toggle_floating()?;
+ }
+
self.connection.grab_pointer(
false,
self.root,
@@ -2422,11 +2431,6 @@ impl WindowManager {
new_y = monitor.window_area_y + monitor.window_area_height - height as i32;
}
- if !was_floating && !is_normie &&
- ((new_x - orig_x as i32).abs() > snap || (new_y - orig_y as i32).abs() > snap) {
- self.toggle_floating()?;
- }
-
let should_resize = is_normie || self.clients
.get(&window)
.map(|c| c.is_floating)
diff --git a/templates/config.lua b/templates/config.lua
index abb67d0..42aa8fe 100644
--- a/templates/config.lua
+++ b/templates/config.lua
@@ -123,6 +123,26 @@ oxwm.gaps.set_enabled(true) -- Enable or disable gaps
oxwm.gaps.set_inner(5, 5) -- Inner gaps (horizontal, vertical) in pixels
oxwm.gaps.set_outer(5, 5) -- Outer gaps (horizontal, vertical) in pixels
+-------------------------------------------------------------------------------
+-- Window Rules
+-------------------------------------------------------------------------------
+-- Rules allow you to automatically configure windows based on their properties
+-- You can match windows by class, instance, title, or role
+-- Available properties: floating, tag, fullscreen, etc.
+--
+-- Common use cases:
+-- - Force floating for certain applications (dialogs, utilities)
+-- - Send specific applications to specific workspaces
+-- - Configure window behavior based on title or class
+
+-- Examples (uncomment to use):
+-- oxwm.rule.add({ class = "firefox", title = "Library", floating = true }) -- Make Firefox Library window floating
+-- oxwm.rule.add({ instance = "gimp", tag = 5 }) -- Send GIMP to workspace 6 (0-indexed)
+-- oxwm.rule.add({ instance = "mpv", floating = true }) -- Make mpv always float
+
+-- To find window properties, use xprop and click on the window
+-- WM_CLASS(STRING) shows both instance and class (instance, class)
+
-------------------------------------------------------------------------------
-- Status Bar Configuration
-------------------------------------------------------------------------------
@@ -167,6 +187,12 @@ oxwm.key.bind({ modkey }, "F", oxwm.layout.set("normie")) -- Set floating layout
oxwm.key.bind({ modkey }, "C", oxwm.layout.set("tiling")) -- Set tiling layout
oxwm.key.bind({ modkey }, "N", oxwm.layout.cycle()) -- Cycle through layouts
+-- Master area controls (tiling layout)
+oxwm.key.bind({ modkey }, "H", oxwm.set_master_factor(-5)) -- Decrease master area width
+oxwm.key.bind({ modkey }, "L", oxwm.set_master_factor(5)) -- Increase master area width
+oxwm.key.bind({ modkey }, "I", oxwm.inc_num_master(1)) -- Increment number of master windows
+oxwm.key.bind({ modkey }, "P", oxwm.inc_num_master(-1)) -- Decrement number of master windows
+
-- Gaps toggle
oxwm.key.bind({ modkey }, "A", oxwm.toggle_gaps()) -- Toggle gaps on/off
@@ -174,15 +200,15 @@ oxwm.key.bind({ modkey }, "A", oxwm.toggle_gaps()) -- Toggle gaps on/off
oxwm.key.bind({ modkey, "Shift" }, "Q", oxwm.quit()) -- Quit OXWM
oxwm.key.bind({ modkey, "Shift" }, "R", oxwm.restart()) -- Restart OXWM (reloads config)
--- Focus movement (vim keys)
-oxwm.key.bind({ modkey }, "H", oxwm.client.focus_direction("left")) -- Focus window to the left
-oxwm.key.bind({ modkey }, "J", oxwm.client.focus_direction("down")) -- Focus window below
-oxwm.key.bind({ modkey }, "K", oxwm.client.focus_direction("up")) -- Focus window above
-oxwm.key.bind({ modkey }, "L", oxwm.client.focus_direction("right")) -- Focus window to the right
+-- Focus movement (stack cycling)
+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
-- Multi-monitor support
-oxwm.key.bind({ modkey }, "Comma", oxwm.focus_monitor(-1)) -- Focus previous monitor
-oxwm.key.bind({ modkey }, "Period", oxwm.focus_monitor(1)) -- Focus 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
-- Workspace (tag) navigation
-- Switch to workspace N (tags are 0-indexed, so tag "1" is index 0)
@@ -207,12 +233,6 @@ oxwm.key.bind({ modkey, "Shift" }, "7", oxwm.tag.move_to(6))
oxwm.key.bind({ modkey, "Shift" }, "8", oxwm.tag.move_to(7))
oxwm.key.bind({ modkey, "Shift" }, "9", oxwm.tag.move_to(8))
--- Swap windows in direction (vim keys with Shift)
-oxwm.key.bind({ modkey, "Shift" }, "H", oxwm.client.swap_direction("left")) -- Swap with window to the left
-oxwm.key.bind({ modkey, "Shift" }, "J", oxwm.client.swap_direction("down")) -- Swap with window below
-oxwm.key.bind({ modkey, "Shift" }, "K", oxwm.client.swap_direction("up")) -- Swap with window above
-oxwm.key.bind({ modkey, "Shift" }, "L", oxwm.client.swap_direction("right")) -- Swap with window to the right
-
-------------------------------------------------------------------------------
-- Advanced: Keychords
-------------------------------------------------------------------------------
@@ -224,70 +244,6 @@ oxwm.key.chord({
{ {}, "T" }
}, oxwm.spawn_terminal())
--------------------------------------------------------------------------------
--- Status Bar Blocks
--------------------------------------------------------------------------------
--- Add informational blocks to the status bar using block constructors
--- Each block is created with oxwm.bar.block.<type>() and configured with a table:
--- format: Display format with {} placeholders
--- interval: Seconds between updates
--- color: Text color (from color palette)
--- underline: Whether to underline the block
---
--- Available block types:
--- ram(config) - Memory usage
--- datetime(config) - Date and time (requires date_format field)
--- shell(config) - Shell command output (requires command field)
--- static(config) - Static text (requires text field)
--- battery(config) - Battery status (requires charging, discharging, full fields)
-
-oxwm.bar.set_blocks({
- oxwm.bar.block.ram({
- format = "Ram: {used}/{total} GB",
- interval = 5,
- color = colors.light_blue,
- underline = true,
- }),
- oxwm.bar.block.static({
- format = "{}",
- text = " │ ",
- interval = 999999999,
- color = colors.lavender,
- underline = false,
- }),
- oxwm.bar.block.shell({
- format = "Kernel: {}",
- command = "uname -r",
- interval = 999999999,
- color = colors.red,
- underline = true,
- }),
- oxwm.bar.block.static({
- format = "{}",
- text = " │ ",
- interval = 999999999,
- color = colors.lavender,
- underline = false,
- }),
- oxwm.bar.block.datetime({
- format = "{}",
- date_format = "%a, %b %d - %-I:%M %P",
- interval = 1,
- color = colors.cyan,
- underline = true,
- }),
- -- Uncomment to add battery status (useful for laptops)
- -- oxwm.bar.block.battery({
- -- format = "Bat: {}%",
- -- charging = "⚡ Bat: {}%",
- -- discharging = "🔋 Bat: {}%",
- -- full = "✓ Bat: {}%",
- -- interval = 30,
- -- color = colors.green,
- -- underline = true,
- -- }),
-})
-
-------------------------------------------------------------------------------
-- Autostart
-------------------------------------------------------------------------------
diff --git a/templates/oxwm.lua b/templates/oxwm.lua
index 4b16123..2520d85 100644
--- a/templates/oxwm.lua
+++ b/templates/oxwm.lua
@@ -42,6 +42,14 @@ function oxwm.set_tags(tags) end
---@param symbol string Symbol to display (e.g., "[T]", "[F]", "[=]")
function oxwm.set_layout_symbol(name, symbol) end
+---Window rule module
+---@class oxwm.rule
+oxwm.rule = {}
+
+---Add a window rule
+---@param rule {class: string?, instance: string?, title: string?, role: string?, floating: boolean?, tag: integer?, fullscreen: boolean?} Rule configuration
+function oxwm.rule.add(rule) end
+
---Quit the window manager
---@return table Action table for keybinding
function oxwm.quit() end
@@ -62,10 +70,15 @@ function oxwm.toggle_gaps() end
---@return table Action table for keybinding
function oxwm.show_keybinds() end
----Focus monitor by index
----@param index integer Monitor index (-1 for previous, 1 for next)
+---Set master area factor (adjust master window width in tiling layout)
+---@param delta integer Delta to adjust by (negative to decrease, positive to increase)
---@return table Action table for keybinding
-function oxwm.focus_monitor(index) end
+function oxwm.set_master_factor(delta) end
+
+---Increment/decrement number of master windows
+---@param delta integer Delta to adjust by (negative to decrease, positive to increase)
+---@return table Action table for keybinding
+function oxwm.inc_num_master(delta) end
---Keybinding module
---@class oxwm.key
@@ -106,6 +119,10 @@ function oxwm.gaps.set_inner(horizontal, vertical) end
---@param vertical integer Vertical outer gap in pixels
function oxwm.gaps.set_outer(horizontal, vertical) end
+---Set smart gaps (disable outer gaps when only one window visible)
+---@param enabled boolean Enable or disable smart gaps
+function oxwm.gaps.set_smart(enabled) end
+
---Border configuration module
---@class oxwm.border
oxwm.border = {}
@@ -138,24 +155,24 @@ function oxwm.client.toggle_fullscreen() end
---@return table Action table for keybinding
function oxwm.client.toggle_floating() end
----Focus window in direction
----@param direction "up"|"down"|"left"|"right" Direction to focus
----@return table Action table for keybinding
-function oxwm.client.focus_direction(direction) end
-
----Swap window in direction
----@param direction "up"|"down"|"left"|"right" Direction to swap
----@return table Action table for keybinding
-function oxwm.client.swap_direction(direction) end
-
---Focus stack (next/previous window)
---@param dir integer Direction (1 for next, -1 for previous)
---@return table Action table for keybinding
function oxwm.client.focus_stack(dir) end
----Exchange client positions
+---Monitor management module
+---@class oxwm.monitor
+oxwm.monitor = {}
+
+---Focus monitor
+---@param dir integer Direction (-1 for previous, 1 for next)
+---@return table Action table for keybinding
+function oxwm.monitor.focus(dir) end
+
+---Send focused window to monitor
+---@param dir integer Direction (-1 for previous, 1 for next)
---@return table Action table for keybinding
-function oxwm.client.exchange() end
+function oxwm.monitor.tag(dir) end
---Layout management module
---@class oxwm.layout
@@ -184,6 +201,16 @@ function oxwm.tag.view(index) end
---@return table Action table for keybinding
function oxwm.tag.move_to(index) end
+---Toggle viewing a tag (allows viewing multiple tags at once)
+---@param index integer Tag index (0-based)
+---@return table Action table for keybinding
+function oxwm.tag.toggleview(index) end
+
+---Toggle tag on focused window (allows window to appear on multiple tags)
+---@param index integer Tag index (0-based)
+---@return table Action table for keybinding
+function oxwm.tag.toggletag(index) end
+
---Status bar configuration module
---@class oxwm.bar
oxwm.bar = {}