← Desktop LabDOCUMENTATION / BENEATH THE SURFACE

A desktop engine,
you can follow.

Start with the programming model. Follow one frame from state to pixels. Then build your own client on the same framework.

CHOOSE YOUR GUIDE

Configure the desktop.
Or build with the library.

Choose a guided path

Boot to pixels explains Linux, installed programs and session startup. Follow the wiring traces desktop_adapters(), state, declarations, their runtime callers and a complete launcher interaction, with an interactive coordinate illustration and an architecture assessment.

01 / STRUCTS + TRAITS + FUNCTIONS

Declarative views.
Explicit state changes.

Lucent uses a hybrid Rust style: structs own state, traits define contracts, and functions compose data. There is no class inheritance. A component’s view(&self, cx) reads state and returns elements; update(&mut self, message, effects) handles a typed message and changes state.

Views stay free of OS operations. Use cases make decisions; service adapters do the external work. Effects deliver results back to the UI thread as messages. Rust’s ownership rules make those boundaries explicit.

The core component contract
use lucent_api::*;

#[derive(Clone)]
enum Message { Increment }
struct Counter(u32);

impl Component for Counter {
    type Message = Message;

    fn view(&self, _: &ViewContext) -> Element<Message> {
        Element::row(vec![
            Element::text(format!("Count: {}", self.0)),
            Element::button("Add", Message::Increment)
                .id("increment"),
        ]).gap(12.).padding(16.)
    }

    fn update(&mut self, _: Message, fx: &mut Effects<Message>) {
        self.0 += 1;
        fx.redraw("example");
    }
}

This minimal example illustrates the framework contract. For Lucent’s actual appearance, compose with the design-system recipes. The launcher now owns private interaction and motion state in a child controller. Its borrowed inputs and typed intents are composed through the framework’s message/effect mapping API.

If you know the browser engine

Think of Element as a declarative view tree, Layout as layout and hit testing, Paint as a display list, and our renderer as the code that turns it into GPU work. This implementation rebuilds the affected surface’s tree and scene; it has no DOM diff or general CSS engine.

02 / FOLLOW ONE FRAME

When does each part run?

Click a stage to follow opening the launcher. The first dirty frame can draw immediately when no frame is pending. Subsequent animated frames are paced by compositor callbacks.

Desktop client · Rust

Update state

An input event becomes a typed Message. Component::update changes the authoritative state. Opening the launcher retargets its width and height motions and marks the dock surface dirty. Async effects return their results as new messages.

Message → update(&mut self, message, effects)
launcher = true · target width = 448 logical px
Read the implementation ↗

Who interpolates? Our CPU-side Motion implementation evaluates elapsed time and easing. Vulkan renders the values it receives. Wayland supplies surface protocols and frame pacing. An ordinary layer surface is not a secure lock screen.

03 / A SMALL VOCABULARY

Compose behavior.
Keep the boundaries.

Component
Own state, implement view and update; compose children with Element::map and Effects::delegate.
Element<Message>
A declarative tree of rows, columns, stacks, grids, text, images, inputs and click/drag handlers.
Effects<Message>
Request targeted redraws and run work on the effect worker. Results return as typed messages.
Subscription<Message>
An identified stream of events with cancellation, used for clocks and external service updates.
SurfaceSpec
Declare a native surface’s layer, anchors, dimensions, exclusive zone and keyboard behavior.
Motion
A time-based scalar transition with retargeting, a duration and a cubic Bézier easing curve.
WidgetRegistry
Register named widget views and descriptors, then compose them without changing the framework.
Theme
The optional lucent-design crate supplies semantic roles, generated tokens and component recipes.

The API contains no raw Wayland objects or GPU handles. Surface lifecycle belongs to lucent-wayland; GPU resource ownership belongs to lucent-render. Browse the complete API source ↗

04 / MEANING BEFORE MECHANICS

The domain describes
what the desktop does.

LayerResponsibilityConcrete example
DomainModels and service portsApplication, Workspace, Placement, Settings
Use casesDecisions independent of renderingRank a search; launch or focus; clamp a drag
AdaptersExternal capabilitiesXDG discovery, Hyprland IPC, system commands
ClientMap events to behavior and viewsOpen a result, display a failure, redraw the dock

For example, clicking a dock icon prefers focusing a running application; selecting a launcher result starts a new instance. The use case chooses the behavior through ports. The XDG adapter launches an argument array and handles desktop-entry field codes without sending arbitrary commands through a shell.

Current system adapters include command-backed integrations. Native notification history is implemented in memory. A complete typed D-Bus control center, tray, clipboard and IME remain future work. See the ownership and scope guide for the current split with Omarchy.

05 / FROM PAINT TO PIXELS

Our renderer.
Established GPU machinery.

We own the display-list translation, primitive pipeline, rounded-rectangle shader, texture cache and frame scheduling. wgpu supplies GPU access through Vulkan. fontdue supplies glyph coverage. SCTK supplies Wayland client building blocks.

  1. Layout and hit testing stay in logical pixels.
  2. The buffer size is the logical surface size multiplied by its integer output scale.
  3. Text uses shared kerning-aware advances. Glyph coverage is rasterized at 2× physical density and area-resolved to the output texture. Placement is snapped to physical pixels.
  4. Image assets prefer SVG or larger originals. Previews use Lanczos filtering, then cached premultiplied mip levels smooth reductions as cards animate.
  5. Primitive records go through queue.write_buffer, render-pass draws, queue.submit and queue.present.

There is one shared GPU device for the application. Animated shapes retain subpixel coordinates, while settled text aligns to the pixel grid. The full framebuffer is not supersampled. Textures are cached, and the runtime stops requesting redraws when no work remains.

Current limits

The prepared VM uses Mesa software Vulkan. Better texture quality does not turn that into hardware acceleration. Integer HiDPI is supported; fractional scale handling and complex-script shaping are still open. Historical performance numbers on the landing page describe the earlier 2-vCPU, 3-GiB baseline.

COMPONENT QUALITY

Test the pixels.
Test the interaction.

The framework tracks hover, keyboard focus and controlled selection separately. selected(true) marks a selected row; focus_within() outlines a composite input. Focus color, width, caret and switch geometry come from the design system.

Selection fill and outline use the same row state and geometry on the first frame. There is no trailing 350 ms selection-background animation; the dock and panel morphs remain animated. In the launcher, Tab and Shift+Tab cycle sections. Arrows select and Enter activates. Outside that composite, framework buttons use Tab traversal and Enter or Space activation.

Native reference images cover light/dark, 1×/2×, scrolling, narrow widths, empty results, long input, a fixed animation frame, notifications/history, lock/login UI, the native keybinding picker, submenus, drag grid, dock, dense/narrow bars and wallpaper controls. These run through the same Vulkan Canvas as the desktop. Geometry checks independently require complete rows, an aligned caret, grid-aligned shell bounds, symmetric insets, equal theme gutters and bar capsules that do not overlap. The view and animation targets share a pure geometry module; settled layout follows the 16-pixel shell grid and 8-pixel control rhythm.

Host · native pixels and real keyboard input
python3 vm/test-visual.py
python3 vm/test-launcher.py
python3 vm/test-menu.py
python3 vm/test-desktop-polish.py

Open reports/local/visual-tests/index.html to compare actual renders and highlighted differences. CI retains this report as an artifact. Intentional baseline changes require image review. See native component captures.

PORTS / ADAPTERS / CLIENTS

One renderer.
Explicit system boundaries.

Desktop::new(DesktopAdapters) receives application, compositor, settings, clock, audio, media, weather, wallpaper, session and notification interfaces. Only platform.rs chooses their production implementations. Tests supply in-memory adapters to the same components. Framework crates know nothing about those OS services or Lucent’s visual theme.

Dependencies and event flow
DesktopAdapters → Desktop::update → Effects → port
OS adapter → domain snapshot → Message → view

NotificationInbox → NotificationPort → D-Bus
SessionScreen → AuthenticationPort → PAM / greetd
SessionScreen → Component API → Vulkan renderer

The notification center is a child component, composed with Element::map and Effects::delegate. It handles native toasts, paged history, DND, dismissal and standard application actions. Timeout and replacement policy are pure use cases; D-Bus lives in its adapter.

Native keybinding menu. Super+K keeps Omarchy’s installed binding discovery and action dispatcher. A per-user select/input adapter passes typed entries to the independent lucent-menu client. Its pure Selection use case handles search and navigation; the component returns the original selected value only after releasing its Wayland keyboard surface. No commands run inside the component. A separate Omarchy adapter reads installed menu definitions and user extensions, evaluates availability and dispatches chosen actions. System, Style and other nested menus use the same native picker, with Back/Escape navigation. Explicit route mappings select native theme and wallpaper pickers; user actions retain precedence. Read the integration contract.

Dragging a widget reveals a grid made from ordinary decorative Elements. Its spacing, line width and contrast use design tokens; the grid has no input handlers and disappears on release. The renderer needs no grid-specific code. Ordinary app corners are a Hyprland decoration default generated from component.window.radius, so browser and terminal windows share the same rule.

The lock and login clients share a native SessionScreen. A secure lock creates ext-session-lock surfaces on every output. PAM verifies the current account; greetd handles login authentication and session launch. Secret text stays outside the scene and texture cache, and neither client exposes the desktop’s debugging socket.

Measured boundaries, with remaining work

Widget coordination still shares the desktop state struct. Several OS adapters still use commands and polling. History is in memory; rich notification markup, images and vendor command hints remain unsupported. These are explicit limits, not completed features.

07 / START SMALL

Your first native surface.

Use the independent hello-layer example to learn the component contract before extending the desktop. It runs inside a Wayland session with layer-shell support and a working Vulkan driver.

Run, test and generate API documentation
cargo run --manifest-path lucent/Cargo.toml -p hello-layer

cargo test --manifest-path lucent/Cargo.toml --workspace
cargo doc --manifest-path lucent/Cargo.toml --workspace --no-deps

To change the visual language, edit design/tokens.json and regenerate the consumers. To add a widget, implement a view and register its descriptor in the desktop widget registry. To add a system capability, start with its domain model, port and use case.