Swindle
A dwl fork with dwindle tiling and a Lua config that hot-reloads. The poor man's Hyprland.
About
Swindle began at kantiankant/swindle, which is now archived. It lives on at hakuxyz/swindle, where it is actively maintained.
Under the hood it is dwl with a different personality: one tiling layout (dwindle, a binary-split tree), configuration in Lua instead of a header file, live reload on save, split touchpad and mouse settings, a configurable keyboard layout, and a dwl-IPC client called smsg for bars and scripts.
Install
On CachyOS or Arch, install the dependencies:
sudo pacman -S base-devel wlroots0.20 wayland wayland-protocols libinput libxkbcommon lua54 libxcb xcb-util-wm pixman libdrm seatd
Then build and install:
make
sudo make install
This installs swindle and smsg to /usr/local/bin and an example config to /etc/swindle. There is also a Nix flake in the repo (nix build, nix run).
Your first config
Swindle refuses to start without a config file. It takes the first readable file it finds:
swindle -c /path/to/config.lua$XDG_CONFIG_HOME/swindle/config.lua, else~/.config/swindle/config.lua/etc/swindle/config.lua
Copy the example config and trim it down:
mkdir -p ~/.config/swindle
cp /etc/swindle/config.lua ~/.config/swindle/config.lua
A minimal config that opens a terminal and a launcher looks like this:
sloppy_focus = true
log_level = "error"
appearance = {
border_px = 2,
gaps = 10,
root_color = 0x222222ff,
border_color = 0x00000000,
focus_color = 0xe8e8e8ff,
urgent_color = 0xff0000ff,
fullscreen_bg = 0x000000ff,
}
keyboard = { layout = "us" }
monitors = {
{ name = nil, scale = 1.0, layout = "dwindle", x = -1, y = -1 },
}
autostart = {}
keybinds = {
{ mods = {"logo"}, key = "q", action = "spawn", args = {"foot"} },
{ mods = {"logo"}, key = "space", action = "spawn", args = {"wofi", "--show", "drun"} },
{ mods = {"logo"}, key = "w", action = "killclient" },
{ mods = {"logo"}, key = "f", action = "togglefullscreen" },
{ mods = {"logo", "shift"}, key = "e", action = "quit" },
}
for i = 1, 9 do
local key = tostring(i)
local mask = 1 << (i - 1)
table.insert(keybinds, { mods = {"logo"}, key = key, action = "view", args = {tostring(mask)} })
table.insert(keybinds, { mods = {"logo", "shift"}, key = key, action = "tag", args = {tostring(mask)} })
end
buttons = {
{ mods = {"logo"}, button = "left", action = "moveresize", args = {"move"} },
{ mods = {"logo"}, button = "right", action = "moveresize", args = {"resize"} },
}
Save the file while swindle runs and most changes apply instantly: appearance, keybinds and keyboard layout. Pointer settings take effect on newly plugged devices. Only autostart never re-runs. Check syntax without starting with luac5.4 -p config.lua.
Reference
Input: touchpad vs mouse
Generic keys are the fallback. Per-class keys win when set, anything else falls back:
input = {
scroll_method = "2fg",
scroll_method_touchpad = "2fg",
scroll_method_mouse = "edge",
natural_scrolling = false,
natural_scrolling_touchpad = true,
natural_scrolling_mouse = false,
tap_to_click = true,
click_method = "button_areas",
accel_profile = "adaptive",
accel_speed = 0.0,
}
Keyboard
keyboard = {
layout = "de",
variant = "",
options = "caps:escape",
model = "",
}
Rules and monitors
rules = {
{ app_id = "Gimp", floating = true, monitor = -1 },
{ app_id = "firefox", tags = 1 << 8, monitor = -1 },
}
monitors = {
{ name = nil, mfact = 0.55, nmaster = 1, scale = 1.0,
layout = "dwindle", x = -1, y = -1 },
}
Autostart
autostart = {
"dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_SESSION_TYPE",
"waybar",
}
Actions
Every keybind maps to an action. The dwindle helpers are setmfact, zoom and focusstack:
{ mods = {"logo"}, key = "i", action = "setmfact", args = {"+0.05"} },
{ mods = {"logo"}, key = "d", action = "setmfact", args = {"-0.05"} },
{ mods = {"logo", "shift"}, key = "m", action = "zoom" },
{ mods = {"logo"}, key = "n", action = "focusstack", args = {"next"} },
{ mods = {"logo"}, key = "p", action = "focusstack", args = {"prev"} },
| Action | Args | Does |
|---|---|---|
spawn | argv | run a program |
killclient | close the focused window | |
togglefloating | float or unfloat | |
togglefullscreen | fullscreen toggle | |
togglegaps | gaps on or off, this monitor | |
togglesmartgaps | smart gaps on or off | |
setgaps | "+5" / "10" | relative or absolute gaps |
setmfact | "+0.05" / "55" | split ratio, delta or percent |
zoom | swap focused with first tiled | |
focusstack | "next" / "prev" | cycle focus |
focusdir / swapdir | direction | directional focus or swap |
view / toggleview | mask or "all" | show tags |
tag / toggletag | mask | assign window to tags |
focusmon / tagmon | "left" / "right" | across monitors |
setlayout | 0 / 1 | floating or dwindle |
moveresize | "move" / "resize" | mouse drag action |
chvt | number | switch virtual terminal |
quit | exit swindle |
Help
- No config found. Copy the example to
~/.config/swindle/config.luaor pass-c. - Wrong keyboard layout. Set
keyboard.layout. It hot-reloads. - XWayland socket busy. Another session owns it. Harmless, or start from a TTY.
- No bar or launcher. Swindle ships none. Add
waybarandwofiorrofiyourself. - Broken syntax. Run
luac5.4 -p config.luabefore restarting.
Still stuck? Open an issue at hakuxyz/swindle.