- OXWM — DWM but Better (and oxidized)
- Installation
- Configuration
- Contributing
- Key Bindings
- Features
- Testing with Xephyr
- Project Structure
- Architecture Notes
- Current Todo List:
- Development Roadmap
- License
A dynamic window manager written in Zig, inspired by dwm but designed to evolve beyond it. OXWM features a clean, functional Lua API for configuration with hot-reloading support, ditching the suckless philosophy of “edit + recompile”. Instead, we focus on lowering friction for users with sane defaults, LSP-powered autocomplete, and instant configuration changes without restarting your X session.
Documentation: ox-docs.vercel.app
Add oxwm to your flake.nix inputs:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
oxwm.url = "github:tonybanters/oxwm";
oxwm.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, oxwm, ... }: {
nixosConfigurations.yourhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
oxwm.nixosModules.default
];
};
};
}Add this to your configuration.nix:
{ config, pkgs, ... }:
{
services.xserver = {
enable = true;
windowManager.oxwm.enable = true;
};
# Recommended: Install a display manager
services.xserver.displayManager.lightdm.enable = true;
# Or use another display manager like sddm, gdm, etc.
}After rebuilding your system with sudo nixos-rebuild switch, log in via your display manager.
On first launch, your initial config file will be automatically created and placed in ~/.config/oxwm/config.lua. Edit it and reload with Mod+Shift+R.
If you want to pin or customize the oxwm package:
{
services.xserver.windowManager.oxwm = {
enable = true;
# Use a specific version or build with custom options
package = oxwm.packages.${pkgs.system}.default;
};
}For development, use the provided dev shell:
# Clone the repository
git clone https://github.com/tonybanters/oxwm
cd oxwm
# Enter the development environment
nix develop
# Build and test
cargo build
just testAUR: AUR URL
yay -S oxwm-git
This will automatically put a desktop session file into your xsessions directory.
Manually: Install dependencies:
sudo pacman -S zig libx11 libxft freetype2 fontconfig pkg-configSee Build from source
git clone https://github.com/tonybanters/oxwm
cd oxwm
zig build -Doptimize=ReleaseSafe
sudo cp zig-out/bin/oxwm /usr/local/bin/Or use the justfile:
just installAdd the following to your ~/.xinitrc:
exec oxwmThen start X with:
startxIf using a display manager (LightDM, GDM, SDDM), OXWM should appear in the session list after installation.
OXWM uses a clean, functional Lua API for configuration. On first run, a default config is automatically created at ~/.config/oxwm/config.lua.
Here’s what the new functional API looks like:
-- Set basic options
oxwm.set_terminal("st")
oxwm.set_modkey("Mod4")
oxwm.set_tags({ "1", "2", "3", "4", "5", "6", "7", "8", "9" })
-- Configure borders
oxwm.border.set_width(2)
oxwm.border.set_focused_color("#6dade3")
oxwm.border.set_unfocused_color("#bbbbbb")
-- Configure gaps
oxwm.gaps.set_enabled(true)
oxwm.gaps.set_inner(5, 5) -- horizontal, vertical
oxwm.gaps.set_outer(5, 5)
-- Set up keybindings
oxwm.key.bind({ "Mod4" }, "Return", oxwm.spawn("st"))
oxwm.key.bind({ "Mod4" }, "Q", oxwm.client.kill())
oxwm.key.bind({ "Mod4", "Shift" }, "Q", oxwm.quit())
-- Add status bar blocks
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,
}),
})- Hot-reload: Changes take effect immediately with
Mod+Shift+R(no X restart needed) - LSP Support: Full autocomplete and type hints for the API (
oxwm.luadefinitions included) - Functional API: Clean, discoverable functions instead of nested tables
- No compilation: Edit and reload instantly
Edit ~/.config/oxwm/config.lua to customize:
- Basic settings (terminal, modkey, tags)
- Borders and colors
- Window gaps
- Status bar (font, blocks, color schemes)
- Keybindings and keychords
- Layout symbols
- Autostart commands
After making changes, reload OXWM with Mod+Shift+R
Generate the default config:
oxwm --initOr just start OXWM - it will create one automatically on first run.
When contributing to OXWM:
- Never commit your personal
~/.config/oxwm/config.lua - Only modify
templates/config.luaif adding new configuration options - Test your changes with
just testusing Xephyr/Xwayland - Document any new features or keybindings
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) |
- 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
- Hot reload without restarting X (
- 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
- 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+SpacethenTto spawn terminal
- Persistent State
- Window tags persist across WM restarts
- Uses X11 properties for state storage
Test OXWM in a nested X server without affecting your current session:
just testThis starts Xephyr on display :1 and launches OXWM inside it.
Or manually:
Xephyr -screen 1280x800 :1 &
DISPLAY=:1 cargo runsrc/
├── main.zig [Entry point - handles init, config loading, WM startup]
│ ├── pub fn main() [Initialize display, load config, start event loop]
│
├── overlay.zig [Overlay rendering / auxiliary window logic]
│
├── x11/ [CORE - X11 handling layer]
│ ├── display.zig [X11 connection setup and root management]
│ │ ├── init() [Open display, acquire root window]
│ │ └── setup() [Prepare atoms, WM hints, etc.]
│ │
│ ├── events.zig [Main event loop + event dispatching]
│ │ ├── run() [Event loop]
│ │ ├── handleEvent() [Route X11 events]
│ │ └── manage/unmanage() [Window lifecycle handling]
│ │
│ └── xlib.zig [Low-level Xlib bindings and helpers]
│
├── config/
│ ├── config.zig [Config structure definitions]
│ └── lua.zig [Lua config loader and execution]
│ ├── loadConfig() [Load and execute config.lua]
│ ├── registerAPI() [Expose oxwm.* functions to Lua]
│ └── initDefaultConfig() [Create default config if missing]
│
├── bar/
│ ├── bar.zig [Status bar window implementation]
│ │ ├── init() [Create X11 bar window]
│ │ ├── draw() [Render tags and blocks]
│ │ ├── updateBlocks() [Refresh dynamic blocks]
│ │ └── handleClick() [Mouse click detection]
│ │
│ └── blocks/
│ ├── blocks.zig [Block interface definitions]
│ ├── battery.zig [Battery status block]
│ ├── cpu_temp.zig [CPU temperature block]
│ ├── datetime.zig [Date/time block]
│ ├── format.zig [Formatting utilities]
│ ├── ram.zig [RAM usage block]
│ ├── shell.zig [Shell command block]
│ └── static.zig [Static text block]
│
├── layouts/
│ ├── tiling.zig [Master/stack tiling layout]
│ ├── monocle.zig [Fullscreen stacking layout]
│ ├── scrolling.zig [Scrolling stack layout]
│ └── floating.zig [Floating layout]
│
tests/
├── config_tests.zig [Config validation tests]
├── keybind_tests.zig [Keybinding tests]
├── layout_tests.zig [Layout behavior tests]
├── lua_config_tests.zig [Lua config parsing tests]
└── main_tests.zig [Core WM tests]
templates/
├── config.lua [Default Lua configuration]
├── oxwm.lua [Lua LSP type definitions]
└── tonarchy-config.lua [Alternative preset configuration]OXWM uses mlua to embed a Lua interpreter. The functional API is implemented in src/config/lua_api.rs:
- Each API function (e.g.,
oxwm.border.set_width()) is registered as a Lua function - Functions modify a shared ConfigBuilder that accumulates settings
- When config execution completes, the builder produces the final Config struct
- Type definitions in
templates/oxwm.luaprovide LSP autocomplete and documentation
Tags are implemented as bitmasks (TagMask = u32), allowing windows to belong to multiple tags simultaneously. Each window has an associated TagMask stored in a HashMap. Tags persist across WM restarts using X11 properties (_NET_CURRENT_DESKTOP for selected tags, _NET_CLIENT_INFO for per-window tags).
The bar uses a performance-optimized approach with a modular block system:
- Only redraws when invalidated
- Pre-calculates tag widths on creation
- Blocks update independently based on their configured intervals
- Supports custom colors and underline indicators
- Color schemes (normal/occupied/selected) control tag appearance
- Easily extensible - add new block types in src/bar/blocks/
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.
- [ ] 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
- Refactoring to align with dwm’s proven patterns
- Improving core window management reliability
- Maintaining Lua config and bar features while simplifying internals
- [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
- [ ] Scratchpad functionality
- [ ] Dynamic monitor hotplugging (currently only on startup)
- [ ] External bar support (polybar, waybar compatibility)
- [ ] Additional layouts (deck, spiral, dwindle)
- [ ] Window minimize/restore
