Solium
Solium is the Wayland compositor of Lilium DE, written in Rust on Smithay and configured in Lua. Its window frames, its wallpaper and the desktop shell it hosts are QML running inside the compositor, in one engine, so the desktop can be one design that you change without rebuilding anything.
Captured by the compositor reading back its own framebuffer, in a nested session with the shipped configuration and nothing else, under the light theme Solium shipped until 2026-10-04 and not retaken yet: the shipped theme is now dark grey. The window on the left wears the default frame, a QML scene rendered in-process; the one on the right asked to draw its own and was let. The wallpaper is a QML file too. docs/modes.md has every mode, frame by frame.
Status
Alpha, and not yet anyone’s daily desktop. The first release, v0.1.0,
will be a preview release, and it is not cut until the desktop is
daily-drivable (the daily-drive label), the native preview shell is written
(a bar at the bottom, a dock at the top, quick search, desktop icons, widgets
with real data and a native lock screen), and there are packages: the point
where it is ready for an open beta. docs/beta.md is that road.
It is being readied for daily use, and the daily-driving trial that decides
whether it is ready has not happened yet. What stands in the way is the
daily-drive label, and the honest reasons are specific:
- A hosted shell is not yet a whole desktop (#169): it has no icons yet, and the preview shell itself is not written.
- Suspend and resume have never been tested (#64), and a session on the hardware sometimes starts with no input devices and stops itself (#48).
- Nothing has run unattended for hours on the hardware (#65).
- There is no explicit sync, so Vulkan and NVIDIA clients can stutter (#59).
- There are no touchpad settings, so no tap-to-click (#157); no volume, brightness, media or screenshot keys by default (#151); and logind’s lock and sleep requests are ignored (#153).
- There are no packages in a repository: a Fedora 44 package, or an install, is built from a checkout (#66).
- Two tiling bugs found in use are fixed, and stay open until real use bears that out: a window drawing past its tile over its neighbours (#133), and a closing window keeping its tile until its client had gone (#128).
Short of that trial, it has been checked on the hardware at commit dfc95ce,
on a desktop with an NVIDIA RTX 3070 and on a Microsoft Surface Pro 7 (Intel
Ice Lake integrated graphics), both on Fedora 44. On both, QML renders on the
GPU and its animations run, the screens go off when idle, swaylock locks,
and the Caps Lock and layout pill works with the shipped configuration. On the
Surface Pro 7, closing the lid turned the screen off, though Solium has no lid
handling of its own. Touch reaches applications’ windows, but nothing Solium
draws itself reacts to touch yet (#181).
And a bug in a compositor takes the session with it. The ones that get found are the ones real use finds, which is why the trial matters more than the test suite.
What works today
- Layouts. Dwindle tiling, scrolling, floating and overview, each running per monitor, with a workspace per monitor or one for the whole desk. A minimum tile, with a new window overflowing to the next empty workspace (#134); applications’ own minimum and maximum sizes (#115); focus and move by direction, and floating and fullscreen toggles (#150); and a reload that keeps every layout as it was (#116, #118).
- Windows. A window exists from the keypress that launches it, and shows a loading window until its application has drawn; an X11 application’s window does not yet replace its loading window (#180). Relaunching a single-instance application that is already running brings its existing window forward where it is, instead of moving it into a new slot (#177). Closing and quitting fade out (#126, #127); a resize follows the drag (#113); a modal dialog floats over its parent (#72); a drag shows its icon (#57); and one stacking order serves drawing and clicks, with a fullscreen window covering the bars (#141, #142) and getting its place back after (#92). X11 clients work through XWayland, and menus and tooltips are placed, grabbed and kept on screen.
- Chrome. Eleven pane styles and per-corner rounding, QML on the GPU by default with a software fallback (#147), and cursor themes and the shapes clients ask for (#81, #24).
- Shells.
shell = { scene = … }hosts one in the compositor’s own QML engine, one instance on every monitor (or on the oneshell = { on = … }names), each reading its own monitor live asSolium.monitor(#161). It takes the pointer only where its items take input, so the windows under it can still be clicked (#173). It gets every mouse button, the wheel and the modifiers, a popup holds the pointer withGrab, and an item that asks withSolium.keyboardgets the keyboard (#163). A bar reserves its edge, whatever the scene’s size, withreserveonsol.surfaceorSolium.surface.reservefrom QML (#162). Any layer-shell client works too; bars and lockers both get the frame callbacks they rely on (#149), and a hosted scene’s timers fire on an idle desktop (#164). - Monitors. Several at once, each at its own refresh rate, arranged from
the configuration or guessed; plugged in and unplugged while the session runs
(#43); scaled, worked out from the panel or set. Screens go dark after ten
minutes with nobody at the machine, and
wlopmandswayidlecan turn them off too (#54); the ten-minute screen-off has been seen working on both machines named above. - Input. Keyboard layouts, with bindings that keep working under a
non-Latin one (#132) and on shifted keys (#121). Escape reaches
applications: the overview binds it only while it is open (#174). A small
pill near the text field’s caret says Caps Lock is on, or which layout you
just switched to; it is written in Lua and QML as configuration rather than
in the compositor, and set with
keyboard.indicator(#178). On touch screens, touch reaches applications’ windows and a tap focuses the window under it, but nothing Solium draws itself answers touch yet (#181). - The lock and idle. The lock fails safe: the session is locked before the locker has drawn, and stays locked if the locker crashes. An idle inhibitor counts only while its window is on screen, and a browser’s D-Bus inhibitor holds the screens on too (#152).
- The session. Started from the login screen, Solium tells systemd and
D-Bus where its display is and starts
graphical-session.targetand XDG autostart, and stops them when it ends (#146). Programs Solium starts get the environment Solium itself was started with, not the settings Qt has written into it since, and inherit no descriptor beyond stdio (#175). - Protocols.
xdg-shell,xdg-decoration,xdg-output,xdg-activation,xdg-dialog,wlr-layer-shell,wlr-screencopy,wlr-output-power-management,ext-session-lock,ext-idle-notify,idle-inhibit,cursor-shape,wp-viewporter,wp-fractional-scale,wp-presentation,wp-single-pixel-buffer,linux-dmabuf,relative-pointer,pointer-constraints,primary-selection,text-input-v3andxwayland-shell. Both selections, copy and paste, cross the X11 boundary.text-input-v3tells the compositor where the focused text field’s caret is; with no input method yet, no text is sent back (#26).
Not yet
No input method, so no IME and no on-screen keyboard (#26); no clipboard
manager, which wants data-control (#52); no window rules (#56); no night
light (#69); a virtual machine or a remote desktop cannot have Super
(#58); and portal dialogs cannot be parented to the window that opened them
(#67). Screenshots and recording come from wlr-screencopy, so grim and
wf-recorder should work, but the portals, screen sharing included, have never
been tested end to end (#83). Nothing the compositor draws reacts to touch
yet (frame buttons, a hosted shell, the overview, window edges), though touch
reaches applications’ windows (#181); touch gestures are the touch epic
(#7) and come after v0.1.0.
docs/gaps.md is everything not built yet, at length.
A first configuration
Everything is a file you write, and none of it needs the compositor rebuilt.
~/.config/solium/user.lua holds only what you want changed, and is merged
over the defaults:
return {
pane = "left",
}
Press super+shift+r and every open window is framed again, with its
titlebar down the left side. solium --check says, without starting anything,
whether the configuration would load, which bindings it made, which
settings it sets that nothing reads, with what you probably meant, and whether
every scene it declares at load and every pane style of your own builds
without an error or a QML warning. A reload whose
configuration fails to load keeps the session as it was, and says why in the
log:
~/.local/state/solium/session.log for a session from the login screen or
--tty, and the terminal for a nested one.
docs/ricing.md goes on from there, and
Every setting
lists what there is to set. Your own QML in ~/.config/solium/qml shadows
what ships, file by file. A single Solium/Theme.qml there is meant to
restyle every frame without copying the rest; until
#88 is fixed, the shipped
one still wins.
How it is built
Five ideas carry most of the design. Each is a choice with a cost, and the cost is named.
Chrome is QML, inside the compositor
Titlebars, the wallpaper, every pane style and the pointer Solium draws when
no cursor theme is set are QML, rendered in-process through
QQuickRenderControl and drawn as ordinary render elements. Not a shell
talking to a compositor over a protocol — the same process, the same frame. A
shell is hosted in that same engine, as QML written against Solium’s own API,
through shell = { scene = ... } in the configuration: see
docs/shell-boundary.md. Quickshell support was
removed (#172).
That is why a titlebar can be reloaded while the session runs, why a hosted
shell can read the same theme as the window frames, and why a decoration can be
a gradient, a shader or a clock without the compositor learning what any of
those are. It is also why Qt is a hard dependency and why the compositor drives
Qt by hand. There is no Qt event loop: each frame advances Qt’s animations on
the compositor’s clock, and between frames the compositor’s own event loop
serves Qt’s timers and the descriptors Qt waits on, on that same clock, so a
Timer fires on an idle desktop and costs a frame only when it changes
something.
On the hardware, QML renders on the GPU: Qt draws into a buffer the compositor
allocated, once a trial render in a short-lived child process has shown that
works on this machine. Where it does not, QML renders in software and the log
says why. solium --tty --qml gpu or --qml software (or SOLIUM_QML=) forces
one, and the last log line beginning QML renderer: says which a session got —
see dev/README.md.
A pane owns a window and its chrome
A pane is a window plus the layers drawn around it. Layers have a depth —
behind, frame, above — and a bleed, which is how far past the window’s
own rectangle a layer may paint. The client is drawn between them, which is the
thing a single decoration file cannot express: a glow behind, a titlebar in
front, and the window in the middle of its own frame.
A style is a folder under qml/panes/, so a pane style is a bundle you can
copy, not a setting you can only choose from.
Everything a window does is one transform
Position, size, opacity, depth and rotation pivot are one Frame per window per
moment, and animation is interpolation between two of them. A layout does not
move windows; it says where they should be, and the transform layer gets them
there. Which is why every mode animates identically and why a thumbnail in
overview carries its own titlebar — the frame is part of the window’s transform,
not a thing drawn beside it.
The policy is Lua, the arithmetic is a crate
Tiling, scrolling, floating and overview are Lua scripts: when to split, where
a new window goes, what a key does. The arithmetic they ask for — the dwindle
tree and the scrolling strip — is crates/layout, which knows nothing of
windows and also arranges the boxes in dev/preview’s animation page.
sol.present hands a script the same transform the compositor uses, so a
layout somebody writes is not a second-class one. Bindings and the modes
themselves live in Lua too, and --check will tell you what actually loaded —
because a configuration that fails to parse at startup leaves a compositor with
no layouts and no bindings at all, and that failure should cost a log line
rather than a session.
Effects are fragment programs with declared inputs
An effect says what it needs — nothing, or its own pixels — and the compositor arranges the passes. What is behind it is the third input, the one a blur needs, and it is declared and not built yet. Rounded corners are a shader over the window’s own texture, which is why they apply to the pane rather than to the client, and why a corner radius can differ per corner so a titlebar and a window can meet more than one way.
Drawing is GLES2, through Smithay’s GlesRenderer, and the code says so
rather than hiding behind Smithay’s generic renderer traits: a window drawn
through four corners, the rounded-corner shader and QML on the GPU each need
GLES or EGL, which those traits do not offer.
And the parts that are deliberately not ours
Solium does not draw a bar or own a notification of its own: it hosts the
shell’s QML as configuration, and layer-shell clients beside it. It tells the
session where its display is and leaves starting the rest to systemd. It blanks
its screens after ten minutes alone and no other idle policy is its own: it
reports idleness and lets a policy daemon act on the rest; it exposes
wlr-screencopy and lets a portal record.
docs/shell-boundary.md is where that line is drawn
and defended.
Why this stack
Smithay is a Wayland compositor library, not a compositor. It hands over protocol plumbing, input and backends while leaving layout, rendering and policy to us — which is where a desktop environment’s character actually lives. niri is Rust-on-Smithay with working touch and gesture support, so the multi-form-factor path has a reference implementation rather than being a bet. Rust because a crash in a compositor takes the session with it, and memory safety removes an entire class of them.
GLES2, because Smithay has no Vulkan renderer — its backend::vulkan is
device enumeration only — and niri, the reference implementation this stack
was chosen for, uses GLES. The price is that a Vulkan backend would be a port,
not a swap: the render elements are typed on GlesRenderer, the four-corner
warp calls GL directly, rounded corners are a GLSL ES program, and QML on the
GPU shares its buffers through EGL.
The spike lists what each would
need.
Documentation
All of it is also a website, https://lilium-linux.github.io/solium/, with reference pages made from the code.
| For | Read |
|---|---|
| using it | docs/ricing.md, configuring it — start here; Every setting and the configuration reference, the shipped Key bindings, and Flags and environment |
| scripting it in Lua | docs/modes.md, desktop modes and how to write one; docs/animation.md, the animation engine; the Lua API |
| styling it in QML | docs/decorations.md, window frames and everything else drawn in QML; the pane styles’ README, the contract a style is written against |
| writing a shell | docs/shell-boundary.md, what a hosted shell is given and what belongs to the compositor |
| working on it | CONTRIBUTING.md, how to send a change and the rules it is judged by; docs/architecture.md, how it is built and why; dev/README.md, the build, the gate and the instruments it is tested with; SECURITY.md; THIRD_PARTY.md, what came from other projects |
| where it stands | docs/status.md, with the roadmap, everything not built yet and the road to a public preview |
| why it is the way it is | the decision records in docs/spikes/ and docs/design/, each with the evidence that settled it |
Thanks to
- Smithay — Solium is a Smithay compositor, and most of what is hard about being one is Smithay’s work rather than this project’s: DRM, GBM, libinput, the seat, the protocol implementations. Its example compositor is also the first place to look when something here does not make sense.
- niri — the reference for how a serious
Smithay compositor is actually put together, and the answer to more than one
“surely this cannot be the way” while reading DRM code. Its scrolling layout
is why
lua/scrolling.luaexists to be compared against. - Hyprland — where this started. Solium
began as a Hyprland fork, and that work is archived: it proved the decoration
and window-control ideas, and its post-mortem — including why an
out-of-process QML shell was the wrong architecture — is in the
lilium-derepository. The ideas carried over and none of the code did, but the case that a compositor is allowed to be beautiful and configurable at the same time was made there first. - Quickshell — the demonstration that a desktop shell can be QML all the way down. Solium hosts QML inside the compositor rather than beside it, which is a different answer to the same question, and it is a different answer because Quickshell had already shown what the question was.
License
Solium is free software under the GNU General Public License, version 3
only (GPL-3.0-only).
The logo, logotype and default wallpaper are © 2026 Illia Kotomin, licensed CC BY-SA 4.0; see docs/brand.
Contributions are welcome; see CONTRIBUTING.md. Each contributor agrees once to the Contributor License Agreement; the pull request template carries the one sentence that does it. What came from other projects is listed, with its licence, in THIRD_PARTY.md.