- Zig 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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. |
||
| contrib | ||
| protocol | ||
| src | ||
| suictl | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| COMMANDS.md | ||
| LICENSE | ||
| README.md | ||
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.