A Wayland compositor and desktop shell in Rust, built on Smithay.
Early. It runs, it draws, and a lot is missing — see Where it is.
The compositor owns the motion. Nothing animates itself. A window's arrival, a panel opening, a drag settling — all of it is sampled in the compositor, from one timeline. An application that wedges cannot stutter the desktop around it. macOS gets this property through Core Animation's render server; Wayland hands it to us for free, because the compositor already holds the buffers.
Physics, not durations. There are no easing curves. Every motion is a
damped harmonic oscillator, solved analytically — position and velocity
at any t in closed form, no integration. That is what makes a motion
interruptible: a panel caught halfway closed carries its velocity into
the reversal instead of restarting from standstill.
The vocabulary is three rhythms and three characters, and nothing else:
Preset::new(rhythm::HALF, character::SMOOTH)A literal number handed to an animation is a bug. The numbers live in
lib/motion/src/tokens.rs and nowhere else.
Battery is an architectural constraint, not a feature. The event loop sleeps. A still desktop costs zero wakeups. Every spring pins to its target when it settles and drops out of the frame loop — and "settles" means can no longer be seen, in pixels, not in a unit interval. Getting that wrong once drew 13,304 identical frames in 0.4 seconds.
The shell lives inside the compositor. The bar and its calendar are
not Wayland clients. No per-frame IPC, no buffer uploads, no protocol —
the shell asks the motion engine for physics by calling it. The cost is
isolation, paid deliberately: shell drawing runs inside catch_unwind,
so a panic drops a frame rather than the session.
Drawing is signed distance fields. A rounded rectangle is a formula, exact at any radius. A shadow is the closed form of a Gaussian convolved with that rectangle — four samples, and the cost does not depend on the blur radius. The same shadow on the CPU was 56% of a frame.
Working:
- Wayland compositor:
xdg-shell,wlr-layer-shell, both winit (nested) and udev/DRM (bare metal) backends - Window motion: placement, 1:1 drag, physics on release, arrival
- A frame clock that predicts presentation time from real vblanks
- GPU drawing layer: SDF shapes and shadows, a glyph atlas, offscreen backing stores
- A control library: button, switch, slider, popover, calendar
- A bar with a calendar popover
- Wallpaper as a client
Not there yet:
- Dock
- A live clock (the bar's is a placeholder)
- Calendar interaction (the hit testing exists; the pointer is not wired)
- Icons
- Window decorations, tiling, workspaces
- Multi-monitor (the architecture is per-screen; the container is not)
- XWayland
./dev-run.sh # nested in your current session, with a terminal
./dev-run.sh foot # or another client
./vm-run.sh # on DRM, in a VMDevelopment switches:
ZAYTUN_SLOWMO=10 |
every motion, ten times slower — the only way to judge a spring |
ZAYTUN_SAMPLE=now |
sample motion at now instead of the predicted vblank (A/B for micro-judder) |
ZAYTUN_PROBE=/tmp/bar.ppm |
read a shell surface back off the GPU and write it out |
ZAYTUN_DEV=1 |
freeze all motion at its end state — deterministic frames for pixel diffs |
RUST_LOG=comp=debug gives per-frame timing.
comp/ the compositor: scene, input, backends, frame clock
src/shell.rs the bar and its popover — inside this process
lib/motion/ springs, and the token vocabulary
lib/canvas/ GPU drawing: SDF shapes, shadows, glyph atlas
lib/ui/ controls: button, switch, slider, popover, calendar
lib/tokens/ spacing, radii, type ramp, shadow steps
lib/color/ the palette, by role
shell/wall/ wallpaper client
The v1 branch holds the first traversal — a working compositor with both
backends and a pixel-diff stand. This one is the deliberate second pass.
The reasoning — the Motion Law, the token codex, the compositor
architecture — lives in a sibling directory (zaytun-db) and is written
in Uzbek.
The English record is in the code. Comments here carry why, not what,
and the commit messages are long on purpose: git log is the first
document. If you want to know why the shadow is not a blur, why the shell
is not a client, or why sampling at now is a bug, it is written down at
the place it matters.
Issues and PRs welcome. Two things worth knowing before you write code:
- No bare numbers in design or motion. A spacing, a radius, a
response time — it belongs in
lib/tokensorlib/motion/tokens.rs, named. - Model and presentation are separate. Input, layout and hit testing read where a thing is. Animations only change how it is drawn. A click during an animation must land where the user aimed.
Both rules are load-bearing, and breaking either produces bugs that look like something else entirely.
GPL-3.0-or-later.