Experimental Wayland WM client for river's river-window-management-v1 protocol.
Chinese docs: README.zh-CN.md
Language:
- English (this document)
- Simplified Chinese:
README.zh-CN.md
- This codebase is primarily implemented by Codex, not handwritten by a human developer.
- This project is an experiment/prototype.
- Not recommended for daily-use desktop environments.
- i3-like default tiling (equal-width columns)
- Other layouts:
master_stack,vertical_stack,monocle - Multi-output assignment and layout
- Fullscreen and floating/transient support
- Interactive move/resize via seat operations
- Key bindings (
river_xkb_bindings_v1) and pointer bindings - Lua config (
lua5.1by default) - Runtime control tool:
devilctl
Install these on your machine:
zig(0.15.x required)wayland-scanner- Wayland client development files (
libwayland-client) - Lua 5.1 development files (
lua5.1) river(for nested test script / target protocol environment)swaybg(for wallpaper in nested test script)- A compositor/session where river protocols are available (for real run), or nested test prerequisites for
scripts/test-in-hyprland.sh
Package names can vary slightly by distro release. The following sets are the usual baseline.
Debian (apt):
sudo apt update
sudo apt install -y zig libwayland-bin libwayland-dev wayland-protocols liblua5.1-0-dev pkg-config swaybgFedora (dnf):
sudo dnf install -y zig wayland-devel lua-devel pkgconf-pkg-config wayland-protocols-devel swaybg riverArch Linux (pacman):
sudo pacman -S --needed zig wayland lua51 pkgconf wayland-protocols swaybg riverGentoo (emerge):
sudo emerge --ask dev-lang/zig dev-libs/wayland dev-lang/lua:5.1 dev-util/pkgconf gui-apps/swaybgopenSUSE (zypper):
sudo zypper install -y zig wayland-devel lua51-devel pkgconf-pkg-config wayland-protocols-devel swaybg riverFor Ubuntu/Debian package naming, wayland-scanner is provided by libwayland-bin.
This project expects river 0.4.0.
As of February 17, 2026, distro packages are generally not 0.4.0 (or unavailable in official repos), so check first:
river --versionIf output is not 0.4.0, build river 0.4.0 from source:
mkdir -p ~/src
cd ~/src
git clone https://codeberg.org/river/river.git river-0.4.0
cd river-0.4.0
git fetch --tags
git checkout v0.4.0
zig build -Dman-pages=falseThen point the nested script to that build:
cd /path/to/devilwm
RIVER_DIR=~/src/river-0.4.0 ./scripts/test-in-hyprland.shcd devilwm
zig buildOptional flags:
-Dverbose-logs=true-Dlua-lib=<name>(default:lua5.1)
Binaries are produced at:
zig-out/bin/devilwmzig-out/bin/devilctl
Installed default config template:
zig-out/share/devilwm/default-config.lua(or<prefix>/share/devilwm/default-config.luaafter install)
cd devilwm
./scripts/test-in-hyprland.shUseful env vars:
APP_COUNT(default4)APP_STAGGER_SEC(default0.25)SKIP_BUILD=1(skip rebuild)WALLPAPER_FILE=/path/to/wallpaper(default:assets/default-wallpaper.svg)WALLPAPER_CMD='...'(override wallpaper startup command)WALLPAPER_DELAY_SEC=0.5(delay before starting swaybg to avoid startup race)SWAYBG_LOG=/tmp/swaybg.log(capture swaybg stderr/stdout for troubleshooting)WALLPAPER_FALLBACK_COLOR=#9b111e(used when image wallpaper fails to load)
By default the nested script uses the built-in devil emoji wallpaper (assets/default-wallpaper.svg).
If SVG fails to load, set WALLPAPER_FILE=assets/default-wallpaper.png or use fallback color.
Note: many swaybg builds support only PNG files unless compiled with gdk-pixbuf.
If swaybg is available, it is launched automatically; otherwise wallpaper setup is skipped.
- Build first:
zig build - Start
devilwmin a session whereriver-window-management-v1is exposed. - Use
devilctlto send runtime commands.
Examples:
./zig-out/bin/devilctl focus next
./zig-out/bin/devilctl layout monocle
./zig-out/bin/devilctl spawn footSupported commands:
focus next|prevswap next|prevlayout next|i3|monocle|master|verticalclosespawn <shell command>
Note: runtime commands are consumed during manage cycles.
Config search order:
$DEVILWM_CONFIG$XDG_CONFIG_HOME/devilwm/config.lua$HOME/.config/devilwm/config.lua./devilwm.lua
Start from config/default.lua.
On first run, if $DEVILWM_CONFIG is not set and no user config exists, devilwm auto-creates:
$XDG_CONFIG_HOME/devilwm/config.lua(preferred), or$HOME/.config/devilwm/config.lua
- Create a user config:
mkdir -p ~/.config/devilwm
cp config/default.lua ~/.config/devilwm/config.lua- Edit
~/.config/devilwm/config.lua. - Restart
devilwmto apply changes.
Use a custom config path:
DEVILWM_CONFIG=/path/to/config.lua ./zig-out/bin/devilwmMinimal example:
return {
layout = "master_stack",
focus_on_interaction = true,
default_app = "foot",
bindings = {
{ mods = "Mod4", key = "Return", action = "spawn", cmd = "foot" },
{ mods = "Mod4", key = "q", action = "close" },
{ mods = "Mod4", key = "space", action = "layout_next" },
},
}Supported top-level fields:
layout = "i3" | "master_stack" | "vertical_stack" | "monocle"focus_on_interaction = true|falsedefault_app = "foot"control_path = "/tmp/devilwm-1000.commands"focused_border = { width=2, r=0x..., g=0x..., b=0x..., a=0x... }unfocused_border = { ... }rules = { { app_id="foo", title="bar", floating=true, fullscreen=false, output=1 } }bindings = { ... }pointer_bindings = { ... }
Binding action names:
spawn,closefocus_next,focus_prevswap_next,swap_prevlayout_next,layout_set
Complete field details:
rules[*]:app_idsubstring match (optional)titlesubstring match (optional)floatingboolean (optional)fullscreenboolean (optional)output1-based output index (optional)bindings[*]:modsmodifier string, e.g.Mod4,Mod4+Shift,Ctrl+Altkeysingle character, known names (Return,space,tab,escape) or numeric keysym stringactionone of action names abovecmdrequired only forspawn(falls back todefault_appwhen omitted)layoutrequired only forlayout_set(i3|master_stack|vertical_stack|monocle)pointer_bindings[*]:modssame format as keyboard bindingsbuttonLinux input button code (left button is typically272)actionone of action names abovecmdforspawn(optional)layoutforlayout_set(optional)