Fork of kantiankant/swindle with respect, itself a fork of dwl
Tip
Want a status bar? vindle draws none, but any layer shell bar works and the tiling area avoids it. See External Bars
- Quick Start
- What vindle is
- What changed on top of swindle
- Features
- Screenshots
- Installation
- Usage
- Key Bindings
- Configuration
- Window Rules
- Monitors
- External Bars
- How It Works
- Contributing
- Related
git clone https://github.com/tinyopsec/vindle
cd vindle
make && sudo make install
cp /etc/vindle/config.lua ~/.config/vindle/config.lua
vindleNote
The default terminal is foot and the default launcher is fuzzel. Change them in your config before starting if you use something else (see Configuration)
vindle needs XDG_RUNTIME_DIR for the Wayland socket. On a seat it is set already; with a bare flow, export it first
vindle manages windows and input on Wayland and nothing else. One tiling layout (dwindle) plus floating, 9 tags as a bitmask, predictable focus, a double border inherited from the swindle lineage and kept. No bar, no widgets, no wallpaper, no animations, no themes. External programs cover the rest: foot, fuzzel, waybar, swaybg.
It is written to be read. About 7700 lines of C, headers and Lua in total, the compositor itself about 3900. If you spare an evening you will know where everything lives.
Everything below is user visible, no refactors for beauty:
- Comfortable standard keymap (hjkl focus, t tile, f float, g gaps)
usfirst andrusecond out of the box with Alt+Shift toggle (I am Kazakh, Russian is near native, Latin must work on first boot); one line ininput.layoutchanges the order, binds work in every layout- Dwindle honors the monitor
mfactinstead of always splitting evenly - Mouse drag floats a tiled window in place and carries it across tags
- Unknown config keys print a warning instead of failing silently
- Monitor fields reapply on config save, no restart dance for them
smsgclient tool on the frozen dwl-ipc protocol- Wayland native defaults: foot, fuzzel, waybar, swaybg
- A real man page (
man vindle)
| Category | Details |
|---|---|
| Layouts | Dwindle (binary split, honors mfact) and floating |
| Workspaces | 9 tags via bitmasks; windows may carry multiple tags |
| Mouse support | Move, resize, toggle floating via modifier plus button; a tiled window floats in place at its current size; carry a dragged window to another tag with the tag keys |
| Gaps | Configurable gaps with smart_gaps; toggle at run time with Mod+g |
| Borders | Double ring: inner plus outer widths and colors for normal, focus and urgent states. The two rings plus the gap read as one calm frame, my favorite part of the look |
| Fullscreen | Toggle via keybind or client request |
| Urgent hints | Activation requests mark windows urgent instead of stealing focus |
| Dialogs | Dialog windows and fixed size windows float automatically |
| Layer shell | Bars and panels reserve space; tiling avoids it |
| XWayland | Optional at build time (XWAYLAND in config.mk) |
| Keyboard | us first, ru second with Alt+Shift toggle (my native pair, changeable in one line); layout independent binds with key repeat |
| Hot reload | Config watcher reapplies values on save; input options still need a restart |
| IPC | Frozen dwl-ipc surface plus smsg client tool |
| Compilation | Zero warnings under -std=c11 -Wall -Wextra |
This is my screenshot, vindle on Void Linux
| Dependency | Purpose | Note |
|---|---|---|
| wlroots 0.20 | Compositor backend | Pinned major, compiled with libinput |
| wayland | Protocols | Server side |
| libinput | Pointer and touchpad config | |
| xkbcommon | Keyboard layouts | |
| lua5.4 | Config file | Frozen, no upgrades |
| wayland-protocols, wayland-scanner, pkg-config | Build time only | |
| xcb, xcb-icccm | XWayland build flag only |
Install these with their development packages, then build. Tested on Void Linux.
git clone --depth 1 https://github.com/tinyopsec/vindle
cd vindle
make
sudo make install # installs to /usr/local/bin/vindle, config to /etc/vindle, man page to share/man/man1Change PREFIX in config.mk to install elsewhere. To stage the install (packaging), use make DESTDIR=/tmp/stage install. Downstream packaging is out of scope for this repo on purpose: the portable Makefile covers all targets.
Linux is the first class target. BSD builds on a best effort basis with no promises.
gmake LUA_PKG=lua-5.4
sudo gmake ETCDIR=/usr/local/etc/vindle installStart from a TTY, not from inside X11 (unset DISPLAY or set WLR_BACKENDS to pick the DRM backend). Seat access is required: enable seatd, join the video group. If no user config exists, the installed default is used directly.
sudo make uninstallFrom a TTY:
vindleWith an explicit config or a startup command:
vindle -c ~/.config/vindle/config.lua
vindle -s "waybar"vindle -v prints the version. vindle -d enables debug logging.
For display managers:
# /usr/share/wayland-sessions/vindle.desktop
[Desktop Entry]
Name=vindle
Comment=Minimal tiling Wayland compositor
Exec=vindle
Type=ApplicationDefaults in example/config.lua:
{ mods = {"logo"}, key = "Return", action = "spawn", args = {"foot"} },
{ mods = {"logo"}, key = "d", action = "spawn", args = {"fuzzel"} },Use any Wayland native pair instead, for example foot plus bemenu or alacritty plus fuzzel.
Important
Copy /etc/vindle/config.lua to ~/.config/vindle/config.lua and edit the copy. Reinstalls overwrite /etc/vindle
The default modifier is Super (Win).
| Key | Action |
|---|---|
| Mod + Return | Spawn terminal (foot) |
| Mod + d | Spawn launcher (fuzzel) |
| Mod + q | Kill focused window |
| Mod + Shift + e | Quit vindle |
| Mod + h | Focus left |
| Mod + j | Focus down |
| Mod + k | Focus up |
| Mod + l | Focus right |
| Mod + Tab | Toggle to the previously viewed tag set |
| Mod + t | Dwindle layout |
| Mod + f | Floating layout |
| Mod + F11 | Toggle fullscreen |
| Mod + Shift + Space | Toggle floating for focused window |
| Mod + 0 | View all tags |
| Mod + Shift + 0 | Assign focused window to all tags |
| Mod + g | Toggle gaps |
| Mod + Shift + H/J/K/L | Swap with the neighbor in that direction |
| Mod + comma/period | Focus monitor left/right |
| Mod + Shift + less/greater | Send focused window to monitor left/right |
| Key | Action |
|---|---|
| Mod + 1-9 | Switch to tag |
| Mod + Ctrl + 1-9 | Toggle tag view (show alongside current) |
| Mod + Shift + 1-9 | Move focused window to tag |
| Mod + Ctrl + Shift + 1-9 | Toggle tag assignment on focused window |
| Button | Action |
|---|---|
| Mod + Left | Move window (tiled windows float in place) |
| Mod + Middle | Toggle floating |
| Mod + Right | Resize window |
While dragging, tag and view keys apply to the dragged window, so it travels to the new tag with you.
All bindings live in keybinds and buttons in the Lua config.
The chain is example/config.lua (repo) to /etc/vindle/config.lua (installed default) to ~/.config/vindle/config.lua (yours). The compositor watches your file and reloads on save.
| Section | Holds |
|---|---|
| Top level | sloppy_focus, bypass_surface_visibility, log_level |
appearance |
Inner and outer border widths and colors (normal, focus, urgent), gaps, smart_gaps, root and fullscreen colors |
input |
Repeat rate and delay, libinput options, layout/variant/options for XKB |
rules |
app_id, title, tags, floating, monitor per window |
monitors |
name, mfact, nmaster, scale, layout, transform, x, y per output |
keybinds, buttons |
Mods plus key or button plus action plus args |
autostart |
Plain shell commands, run in order once the socket is live |
Reload notes:
- Appearance, rules, binds, autostart list and monitor
mfactapply on save without a restart - Input options (repeat, libinput, layout) apply at startup only
- Misspelled keys print
unknown keywarnings to standard error. The session keeps running
rules = {
{ app_id = "Gimp", floating = true, monitor = -1 },
{ app_id = "MPlayer", floating = true, monitor = -1 },
}Empty app_id or title matches anything. monitor -1 means current. Dialogs and fixed size windows float even without a rule.
monitors = {
{ name = nil, mfact = 0.5, nmaster = 1, scale = 1.0,
layout = "dwindle", x = -1, y = -1 },
}First match wins; empty name matches any output. mfact is the first child share of every dwindle split (0.5 splits evenly, 0.7 was tried live and looked ugly so 0.5 stays). nmaster is accepted but reserved by the dwindle layout. x and y of -1 mean automatic placement
vindle has no bar and never will: drawing widgets is outside its scope. It cooperates with layer shell bars, so the bar reserves its edge and tiling uses what is left.
Minimal waybar autostart (~/.config/vindle/config.lua):
autostart = {
"dbus-update-activation-environment WAYLAND_DISPLAY XDG_SESSION_TYPE XDG_CURRENT_DESKTOP",
"waybar",
}Start wallpaper the same way (swaybg -i ~/wall.png -m fill), since the compositor manages no wallpaper itself.
vindle renders through the wlroots scene graph. Each window owns a scene tree with its surface plus eight border rectangles (four inner, four outer). Tiling writes geometry into a per tag binary tree and the tree is walked top down with integer arithmetic, so there is no pixel drift. Every split divides the parent in the mfact share; wider parents split left and right, taller ones top and bottom.
Tags are bitmasks. Each client carries a tag bitmask and the active view is a bitmask. A client is visible when the bitwise AND of its tags and the current view is nonzero.
Focus is explicit and predictable: the focused client leads the focus stack, tag switches refocus the top visible client, urgency never steals focus, fullscreen is tracked per monitor. The focused window is the dwindle insertion anchor, so new windows split what you look at.
State leaves the compositor two ways: human readable stdout lines (tags, titles, layouts per output, same shape as dwl) and the frozen dwl-ipc protocol consumed by smsg. The ext-workspace protocol publishes one workspace object per tag per monitor for panels that speak it.
It is hard to explain in words, but you can grasp the entire code structure if you spend an evening reading the code. I use this compositor daily, so rough edges get fixed fast.
Found a bug or a rough edge. Open an issue, I read everything myself.
What a good patch looks like
- Small change in current style, no rewrites for beauty
- No new dependencies, no new config mechanisms, no new IPC or protocols
- It builds with zero warnings (
cc -std=c11 -Wall -Wextra), warnings itch - Prove it:
luac5.4 -p example/config.luaplus a headless run with socket up and zero config errors - New behavior needs a live check or an honest
NOT VERIFIEDline
| Project | Link |
|---|---|
| swindle | github.com/kantiankant/swindle |
| dwl | codeberg.org/dwl/dwl |
| Spec | Link |
|---|---|
| wlroots | gitlab.freedesktop.org/wlroots/wlroots |
| Wayland | wayland.freedesktop.org |
| xkbcommon | xkbcommon.org |
| Lua 5.4 | lua.org/manual/5.4 |
GPL-3.0, see LICENSE plus attributions in licenses/

