A scrollable-column tiling window manager for X11, in the suckless style.
Niri's layout model, dwm's construction: ~900 lines of C11, Xlib as the only
dependency, configured by editing config.h and recompiling. No decorations
except borders, no menus, no bars.
Windows live in columns on an infinite horizontal strip. The screen is a viewport that scrolls over the strip, so opening a window never resizes the ones you already have. Each new window opens in its own column to the right of the focused one. A column can hold several windows stacked vertically (move a window into position with Mod+Shift+direction). There are 9 independent workspaces, each with its own strip.
Gaps are decoration, not layout: columns and cells tile edge to edge, and
every window is simply inset by gappx inside its cell (set in config.h).
Not everything tiles: dialogs, notifications, splash screens, utility and menu windows, transient windows, and fixed-size windows float above the strip at the position and size they ask for (centered if they don't ask for one). Notifications never steal focus. Mod+Shift+Space toggles the focused window between floating and tiled; a float can be moved with Mod+left-drag and resized with Mod+right-drag.
make # needs libX11 headers (libX11-devel)
sudo make install
Run it from ~/.xinitrc:
exec hwm
To try it without leaving your current session, install Xephyr and run a nested server:
Xephyr -screen 1280x720 :1 &
DISPLAY=:1 ./hwm &
DISPLAY=:1 xterm &
Everything a key can do, hwm send can do too, over a unix socket at
$XDG_RUNTIME_DIR/hwm$DISPLAY.sock:
hwm send view 2 # same as Mod+2
hwm send setwidth 0.5
hwm send spawn hterm -e htop
hwm send # lists the commands
hwm dump # monitors, workspaces, columns, windows
hwm keys # the key bindings with what they do
dump prints one object per line (mon, ws, col, win, float) with
key=value fields and sel/focused/visible/current flags, for awk.
A command acts on the monitor under the pointer, exactly like a keypress.
The wire format is the argv NUL-separated; the reply is ok followed by any
payload, or err <why>.
| Binding | Action |
|---|---|
| Mod+Return | spawn terminal |
| Mod+Space | app launcher (rofi) |
| Mod+Tab | workspace/window overview (hws) |
| Mod+q | close window |
| Mod+Shift+e | quit hwm |
| Mod+Shift+r | restart hwm in place |
| Mod+h/l (or arrows) | focus column left/right, auto-scrolls |
| Mod+j/k | focus window down/up within the column |
| Mod+Shift+h/l | move window: alone → swap columns; stacked → split into its own column |
| Mod+Shift+j/k | move window down/up within the column |
| Mod+Ctrl+h/l | stack window into the adjacent column |
| Mod+r | cycle column width (1/3, 1/2, 2/3, 1) |
| Mod+Ctrl+1..4 | set column width to the Nth preset |
| Mod+minus / Mod+equal | shrink / grow column width |
| Mod+f | toggle fullscreen |
| Mod+Shift+Space | toggle floating for the focused window |
| Mod+[ / Mod+] | scroll the strip left/right |
| Mod+1..9 | switch workspace |
| Mod+Shift+1..9 | send window to workspace |
| Mod+left-drag | scroll the strip; on a float: move it |
| Mod+right-drag | resize the focused column or float |
| Mod+scroll wheel | scroll the strip |
| click | focus window (focus also follows mouse) |
Everything lives in config.h: colors, border width, gap size,
width presets, workspace count, keys, mouse buttons, and autostart
commands. Edit and make. The key, button, width, and autostart tables
are stb_ds arrays assembled in initconfig(), so bindings can also be
generated with plain loops — the per-workspace keys are.
hwm watches its own binary and restarts in place when it changes, so a
plain make (or sudo make install, if you run the installed copy)
applies the new configuration within a couple of seconds. Windows survive
a restart — hwm re-adopts them on startup — but the arrangement resets:
every window comes back in its own column, on workspace 1 unless its app
has a remembered placement (below). Mod+Shift+r forces a restart without
a rebuild.
With preservelayout set, hwm remembers where each app was last placed:
its workspace and column width, keyed by the WM_CLASS class. A new window
of that app opens on that workspace, at that width, to the right of the
workspace's selected window (last, if nothing is selected), and hwm
switches to the workspace. Moving, resizing or sending a window to
another workspace updates the record at once. The records live in
layoutfile (~/.config/hackable/hwm.layout), one
app:workspace:column:percent line per app, and the file is read every
time a window opens, so it can be edited by hand. hwm leaves the column
field empty; a column index written by hand places the window at that
column (clamped to the number of columns there) when it lands on another
workspace, and is dropped once the app moves. A column of -1
(app::-1: or app:workspace:-1:) opens the app floating — floatsize
of the monitor, centered, like togglefloat — and hwm never rewrites that
line either. A line with empty
workspace and column fields, app::: or app:::percent, opts an app
out: it opens wherever you are (at that width, if given) and hwm never
rewrites its line. Apps that share a
WM_CLASS (every hterm, say) share one placement; SDL programs such as
hterm take a distinct class from SDL_VIDEO_X11_WMCLASS=name in their
environment.
autostart in config.h is a list of shell commands run when hwm starts,
including after every restart — keep them idempotent or guard them
(e.g. pgrep -x app || app).