wlroots compositor in Zig
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
stacksmash 2605d9b446 suictl: write output through std.Io instead of a raw libc write loop
Command output and status-stream events were written with a hand-rolled
std.c.write loop (writeStdoutAll) — the leftover of routing output to stdout
rather than stderr, where std.debug.print goes. main() already receives the
app Io via std.process.Init, so use it: a buffered std.Io.File.stdout() writer,
threaded to the Wayland callbacks through the listener data pointer, with an
explicit flush. The status line collapses to a single print(); writeStdoutAll
and the raw libc write are gone.
2026-07-12 16:57:08 -04:00
contrib export XDG_CURRENT_DESKTOP/XDG_SESSION_TYPE, pass DISPLAY to the init environment, ship a portal backend config and document the color-scheme setup 2026-07-10 13:25:37 -04:00
protocol status stream: suisei-status-unstable-v1 delivers state changes as 2026-07-11 17:55:08 -04:00
src input: merge physical keyboards into a wlr.KeyboardGroup 2026-07-12 16:28:38 -04:00
suictl suictl: write output through std.Io instead of a raw libc write loop 2026-07-12 16:57:08 -04:00
.gitignore build on current zig master: pin wayland/pixman to the forge forks (path deps were stale machine-local clones), replace @cImport with a translate-c module, migrate removed std APIs (posix.fork/getuid/clock_gettime -> os.linux, dupeZ -> dupeSentinel, ArrayListUnmanaged{} -> .empty), fix a returned error set that should be an error, and raise minimum_zig_version accordingly 2026-07-10 03:02:44 -04:00
build.zig drop translate-c: the only symbols suisei needed beyond std.c were 2026-07-12 00:51:14 -04:00
build.zig.zon bump version to 0.1.1-dev 2026-07-10 22:13:18 -04:00
COMMANDS.md suictl map/unmap: add Hyper and Meh modifier aliases, share one parser 2026-07-12 16:28:22 -04:00
LICENSE Initial commit 2026-07-10 02:34:02 -04:00
README.md docs: guard each audio daemon separately, one healthy daemon must not skip the others 2026-07-10 14:41:54 -04:00

suisei

A dynamic tiling Wayland compositor written in Zig.

Windows are tiled into a main area and a stack, and organized with tags. Configuration is a mix of compile-time constants and runtime commands sent with suictl over a small custom protocol.

Building

Needs a recent zig master (see minimum_zig_version in build.zig.zon), wlroots 0.19, wayland, xkbcommon, pixman, libinput and libevdev.

zig build

Binaries land in zig-out/bin. Put suisei and suictl in your PATH and start suisei from a TTY, optionally with a command to spawn:

suisei foot

To launch it from a display manager, add a wayland-sessions desktop entry that execs the binary.

Configuration

On startup suisei runs ~/.config/suisei/init if it's executable. It's a plain script, typically a series of suictl calls — keybindings, layout, colors and keyboard settings all live here:

#!/bin/sh
suictl keyboard-layout -options caps:swapescape us
suictl set-gaps 5
suictl map Mod4 b spawn firefox

The init also starts session services. It runs again on every compositor restart, spawned daemons outlive the session, and dead daemons leave their socket files behind — so guard each service by probing the connection, and clear stale sockets before starting:

export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus"
suictl setenv DBUS_SESSION_BUS_ADDRESS "$DBUS_SESSION_BUS_ADDRESS"
if ! dbus-send --session --dest=org.freedesktop.DBus --type=method_call \
     --print-reply / org.freedesktop.DBus.Peer.Ping >/dev/null 2>&1; then
    rm -f "$XDG_RUNTIME_DIR/bus"
    dbus-daemon --session --address="$DBUS_SESSION_BUS_ADDRESS" &
    sleep 0.2
fi
dbus-update-activation-environment --all

# audio: pipewire, then its session manager and pulse bridge — guarded
# separately, since any one of them can be down while the others run
if ! pw-cli info 0 >/dev/null 2>&1; then
    rm -f "$XDG_RUNTIME_DIR/pipewire-0"
    pipewire &
    sleep 0.3
fi
pgrep -x wireplumber >/dev/null || wireplumber &
if ! pgrep -x pipewire-pulse >/dev/null; then
    rm -f "$XDG_RUNTIME_DIR/pulse/native"
    pipewire-pulse &
fi

The same commands work from any shell while the compositor is running. See COMMANDS.md for the full list.

Dark mode and portals

suisei exports XDG_CURRENT_DESKTOP=suisei. Install xdg-desktop-portal, xdg-desktop-portal-gtk (settings: color scheme, fonts) and xdg-desktop-portal-wlr (screen sharing), and copy contrib/suisei-portals.conf to ~/.config/xdg-desktop-portal/.

Run the session bus at a fixed path, owned by the session. At the top of your init:

export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus"
suictl setenv DBUS_SESSION_BUS_ADDRESS "$DBUS_SESSION_BUS_ADDRESS"
[ -S "$XDG_RUNTIME_DIR/bus" ] || dbus-daemon --session --address="$DBUS_SESSION_BUS_ADDRESS" &
sleep 0.2
dbus-update-activation-environment --all
gsettings set org.gnome.desktop.interface color-scheme prefer-dark

Guard on the socket, not the process name — a system dbus-daemon is usually running and would fool a pgrep check.

The export covers the init's own children, suictl setenv covers everything the compositor spawns afterwards, and the activation update lets D-Bus-activated services (like the portals) see the session. (xdg-desktop-portal older than 1.17 only reads portals.conf, not the per-desktop file — copy it under that name too if needed.)

Portal-aware apps (GTK, Qt, Firefox, Electron) then follow the system color scheme. Set the default:

gsettings set org.gnome.desktop.interface color-scheme prefer-dark

or prefer-light. Put it in your init for a persistent default, or bind toggles:

suictl map Mod4 F9 spawn gsettings set org.gnome.desktop.interface color-scheme prefer-dark
suictl map Mod4 F10 spawn gsettings set org.gnome.desktop.interface color-scheme prefer-light

Typical keys

There are no built-in bindings; map them in your init. A typical set, with Alt as Mod:

Mod+Shift+Return        spawn terminal
Mod+d                   launcher
Mod+q                   close window
Mod+j / Mod+k           focus next / previous
Mod+Return              zoom into main area
Mod+f                   toggle fullscreen
Mod+Shift+f             toggle floating
Alt+left drag           move floating window
Alt+right drag          resize floating window
Mod+h / Mod+l           shrink / grow main area
Mod+[1-9,0]             view tag
Mod+Shift+[1-9,0]       move window to tag
Mod+Ctrl+[1-9,0]        toggle tag visibility
Mod+Ctrl+Shift+[1-9,0]  toggle window tag membership
Mod+Tab                 previous tag set
Mod+Escape              quit

Tags are bitmasks: a window can carry several tags and any combination can be viewed at once. suictl takes the raw mask, so suictl set-focused-tags 3 views tags 1 and 2.

License

GPL-3.0, see LICENSE.