Under the hood
Architecture
Six crates, one dependency direction and one drawing contract. libLuna is not trying to recreate GTK — it is trying to stay small enough to hold in your head and fix.
Layer names
| Layer | GNOME equivalent | KDE equivalent | Luna |
|---|---|---|---|
| Toolkit | GTK | Qt | libLuna |
| Environment / shell | GNOME | KDE Plasma | Luna |
| Wayland compositor | Mutter | KWin | Selene |
These docs cover libLuna, but the architecture only makes sense together: applications paint with libLuna, speak standard Wayland, and Selene accepts windows and manages the screen.
Crate map
liblunare-exports the layers + preludelibluna-coregeometry · colour · events · theme · tokenslibluna-renderscene · text · SVG · CPU backendlibluna-platformWayland · windows · input · clipboardlibluna-widgets · libluna-i18n77 widgets · layout · translationsDependencies only point downwards. Widgets do not know how DRM works. The platform does not know how to paint a button. The theme does not know who uses it.
Drawing and text
Scene graph
libluna-render::scene is a backend-independent list of DrawCommands. Widgets build the scene without touching pixels.
CPU backend
tiny-skia rasterises shapes with anti-aliasing; the RGBA8888 buffer goes through wl_shm.
Text
fontdue + a system font. No kerning or complex scripts yet; cosmic-text is planned without a contract change.
Icons
SVG rasterised with resvg (no text or bitmaps) plus a set of pastel vector icons.
Separating the scene from the backend makes it possible to add a GPU (EGL/dmabuf) backend without changing widgets. It is also why tests can run without a window: a scene can be compared or rendered to PNG.
The widget model
The target model is retained, but the drawing contract stays immediate:
Widget:
measure(constraints) -> Size // how much it wants
allocate(rect) // where it goes
paint(scene, theme) // drawing
hit(event) -> Handled | Propagate // input
focus / grab / hover
Today layout is a separate Node tree with measure/allocate passes, while widgets remain leaves with paint and *_at methods. That lets you use them by hand (as in the demo) or through layout.
Talking to the compositor
| Case | Protocol | In libLuna |
|---|---|---|
| Regular application window | xdg-shell + CSD | AppConfig::new(...), your own HeaderBar. |
| Panel / dock / background | wlr-layer-shell | AppConfig::layer(LayerConfig::top_panel(...)). |
| OSD / notifications | wlr-layer-shell overlay | LayerConfig::overlay(...), transparency. |
| Lock screen | overlay + exclusive keyboard | LayerConfig::lock_screen(...). |
| Clipboard | wl_data_device | Frame::copy_to_clipboard / paste_from_clipboard. |
There is no client-side X11 backend. XWayland serves X11 applications inside Selene, but a new libLuna app speaks native Wayland.
Theming
Tokens live in libluna-core::style: spacing, radii, typography, control metrics and the palette. Widgets read them in paint and layout, so a style changes geometry, not just colours.
// 1. variant
frame.set_preference(ThemePreference::Dark);
// 2. accent
frame.set_accent(Accent::Teal);
// 3. style from a file (colours + geometry)
frame.set_style(Some("nord".into()));
Details: Themes & tokens.
Deliberately deferred
AT-SPI accessibility
Important for a real desktop, but it needs a D-Bus bridge and an accessibility tree. Planned later.
IME / text-input-v3
Comfortable CJK input and a virtual keyboard. Basic text events work today.
GPU / dmabuf
Zero-copy for Vulkan/EGL clients. Rendering is CPU today, but the scene contract stays.
Fractional scaling
Blur-free HiDPI. Scale is already exposed through Frame::scale and Scale.
Drag & drop
Clipboard works; cross-application DnD comes later.
xdg_activation
A new window sometimes needs to be found behind another. The protocol is planned in Selene.
Repository and building
# the whole project
make build
make test
# libLuna only
cd libluna
cargo test --workspace
cargo doc --no-deps
# demo and gallery
cargo run --manifest-path apps/luna-demo/Cargo.toml --bin gallery
cargo run --manifest-path apps/luna-demo/Cargo.toml --bin render -- docs/images/out.png
Each directory is a separate Cargo workspace with its own Cargo.lock. The meta-repository only orchestrates the build. More about releases: docs/repositories.md.