Decision 0053
53. One compositor, and it is Hyprland
Architecture decision record 0053: 53. One compositor, and it is Hyprland
Supersedes the compositor half of ADR-0019 · Refines ADR-0021
Context
ADR-0019 chose Niri and rejected Hyprland, and closed with a line that came due: “Hyprland users will ask; the answer is in this record.” The question arrived — Omarchy has put Hyprland in front of a large audience — and the first answer given was the wrong one twice over.
The first wrong answer was to make it an option. tacid.desktop.compositor was written, both
sessions were generated from the same tokens and the same bindings, and a second reference machine
was added so CI would evaluate the branch. It was defensible in the small and wrong in the large.
A machine whose window manager is a setting is a machine with two faces to design, two to test and
two to answer questions about, and the second one is always the one nobody has looked at lately.
Everything else here works the other way round: the genome declares what the machine is. An
option was a way of not deciding, dressed up as flexibility.
The second wrong answer was the reason given for the default. The record said Hyprland renames configuration keys between releases, and that on a system whose whole claim is that a machine is a value you can boot back to, this turns a rollback into a session that will not start.
That is false, and it was the load-bearing argument. A NixOS generation pins the compositor
binary and the configuration generated for it in the same closure. Generation N−1 boots N−1’s
Hyprland with N−1’s configuration; they were built together and they always match. There is no
state in which a new compositor meets an old configuration by rolling back. What the churn
actually costs is maintenance at upgrade time — a nixpkgs bump may need an edit here — and
checks.*.session reads this machine’s generated configuration with Hyprland’s own parser on
every push, so a rename lands on a pull request rather than on somebody’s greeter. That check
earned itself on its first run, failing on three layerrule lines that an older Hyprland accepted
and 0.56 does not, and again on the window rules for dialogs and picture-in-picture. Both were the
same rename: 0.56 takes a window or layer rule as a named block with a match block inside it, so
the one-line windowrule = float, class:… that every guide still shows is not a rule quietly
ignored — it is a parse error, seven of them, on a file the compositor would have started with.
It has one blind spot, and the fix for it is beside it rather than in it. Hyprland resolves a key
name through xkbcommon when the key is pressed, not when the file is read, so a binding on a name
no keysym has parses perfectly and can never fire. Nine did — the media and brightness keys, bound
to what is written on them rather than to what X11 called the symbol. The check now looks every
bound name up in xkbcommon’s own header, which is the only thing that will say so, and the module
keeps two columns instead of one: what a person reads and what the compositor is given. A mouse
button is not a keysym and no header has one, so bindm’s are set aside by name and added back
into the count the check holds itself to — a binding cannot leave it through the gap.
With that argument gone, what remained for Niri was that it is written in Rust — and against it, the thing it is actually for. Niri is a scrollable-tiling compositor: one endless horizontal strip of columns that a person scrolls sideways through. It is a striking idea and it is a poor place to keep a day’s work, and it is not a setting that can be turned off. It is the whole concept.
Prior decisions
ADR-0019 named Niri and rejected Hyprland. ADR-0021 says one token file generates every surface.
ADR-0047 made tacid-shell a layer-shell client that knows nothing about the compositor under it.
ADR-0001 is the claim the wrong argument was made in the name of: a machine is a value you can
build, diff and boot back to.
Decision
Hyprland, and no option. programs.hyprland.enable is unconditional under
tacid.desktop.enable, /etc/hypr/hyprland.conf is composed from the genome, and greetd runs
/run/wrappers/bin/Hyprland --config on it — the wrapper rather than the store path, because that
is where the cap_sys_nice lives that lets a compositor raise its own scheduling priority.
Workspaces are discrete and numbered one to ten; windows behave the way windows behave.
The colours come from design/tokens.toml through tacid-design, which emits
hyprland/dark.conf and hyprland/light.conf. What that fragment mostly does is subtract:
animations off, rounding zero, blur off, shadow off. Those are not omissions, they are the tokens
— motion only when it explains a state change, and nothing decorative. Hyprland’s own logo and
splash are off too, because what is behind the windows on this machine is this machine’s answer
and not its compositor’s.
Where the compositor lacks something the module supplies it rather than letting a key go quiet:
Print runs grim into the directory a screenshot belongs in, and tacid.desktop.wallpaper —
an option the classifier has always treated as a real reversible change, and which until recently
rendered nowhere — starts swaybg.
Niri is gone, along with the generator that produced its theme. A generator with no consumer is a file that stops being true without anybody noticing.
sway stays, as apparatus. checks.*.face draws the shell under sway with the Pixman renderer
and a headless backend, in seconds, on every push. That is not a second face: it is the compositor
that can certainly render with no card at all, which is what makes it the right place to prove
that the shell draws. The shell has now been photographed under Niri, under sway, and under a
headless wlroots, unmodified each time, which is the finding that made the compositor replaceable
in the first place.
Consequences
- One face to design, test and answer questions about. The cost of the option was not the code that implemented it; it was that from then on every question about the desktop would have had two answers.
- A wrong argument is out of the record. It appeared in ADR-0053 as first written and in the pull request that carried it, and it would have gone on being cited. The correction is here rather than quietly dropped, because the next person to weigh a compositor will meet the same claim somewhere else.
- Neither compositor ever had a software renderer, so nothing is lost there and nothing is
gained: a machine with no working GPU driver gets a tty and the TUI, which ADR-0020 requires to
be complete, and the greeter is
tuigreeton the console for the same reason. Hyprland’s aquamarine wants GLES like Smithay’s renderer does; whether llvmpipe over an emulator’s card node is enough for it is whatchecks.*.desktopnow asks. - Hyprland’s churn is a maintenance cost, not a correctness one, and it is paid by
checks.*.sessionon every push rather than by a person at a greeter. - C++ in the session. It is not Tacid’s C++ and it never links into anything of Tacid’s — the shell is a separate process speaking Wayland — but it is on the machine, and ADR-0003’s “Rust for every Tacid component” was always about Tacid’s components rather than about everything that runs.
Alternatives rejected
- Keeping the option. Rejected above, and rejected by the maintainer in the same words the manifesto uses: no easy choices.
- Niri. The scrollable strip is its concept and cannot be configured away, and the argument that had been holding it in place did not survive being checked.
- sway as the shipped compositor. The most conservative engineering answer: an i3-compatible configuration format that has barely moved in a decade, a software renderer, and already in CI. It loses on what people arriving actually know and on what the desktop is expected to look like in 2026, and the correctness argument that would have justified the conservatism turned out not to exist.
- A floating or stacking compositor. Closer still to “ordinary windows”, at the cost of the keyboard-first tiling this system is otherwise built around, and of a much smaller ecosystem.
- A compositor of Tacid’s own on Smithay. Still the honest long-term answer if Hyprland ever stops, and still not worth the years while it has not.