Toolkit

toolkit is the library every MixOS egui application is built on. It is the shell layer of the egui applications decision (see AGENTS.md §4.1). An app keeps its behaviour in a headless engine and its UI state in one serialisable struct. The toolkit draws the window around that state and routes every action through one command registry.

The UI-free registry and Fluent Strings now live in command. Toolkit keeps the egui adapter and its existing nullary command API, with the moved data types re-exported. Core and toolkit describe() emit stored chords in the keymaps grammar (Ctrl+=, ArrowLeft, or short forms), ordered Ctrl, Alt, Shift. Executed events use the command 0.2.0 v2 body with bounded params and result, and the legacy wrapper maps authorisation refusals to CommandError::Denied.

It gives each app:

  • the resolved design as an egui style, plus the embedded Inter and JetBrains Mono faces and the Lucide icons;
  • the client-side title bar, menu bars and menus, and the View › Theme submenu;
  • the chrome components (bars, buttons, panels, tabs, fields, toggles, combos, sliders, canvas scrollbars, tooltips, dialogs), drawn to the chrome specification in each chrome scheme;
  • the command registry, which generates menus, shortcuts, the palette and the Bus surface, and records every executed command;
  • the drive verbs, the UI-level Bus verbs that every app shares;
  • Fluent string catalogues, a key-chord grammar, and thumbnail decoding.

The source is libs/toolkit.

Who uses it

The apps term, ced, dopus, prefs, screenshot, backdrop and busviewer; the editor library; and four of compd's crates (decor, ui, scene-host, world). backdrop is the only one that turns on the layer feature.

Starting an app

Call toolkit::install(ctx, &theme) once on the egui context. It installs the image loaders the icons need and applies the theme's style and fonts. Theme::load() reads the shared theme.conf.mix from the MixOS etc directory ($MIXOS_ETC, else $MIXOS/etc, else /etc/mixos for root, else $XDG_CONFIG_HOME/mixos). It falls back to the embedded design, and says why on stderr, when the file is missing or unusable.

A minimal window, condensed from tests/snapshot.rs:

use toolkit::command::{Command, Registry, always};
use toolkit::{Icon, Strings, Theme, icons};

#[derive(Default)]
struct Demo { refreshed: u32 }

let mut commands = Registry::new();
commands.add(Command {
    id: "bus.refresh",           // stable, never reused once published
    label: "cmd-refresh",        // Fluent key
    menu: Some("menu-bus"),      // Fluent key of the top-level menu
    submenu: None,
    group: 0,
    shortcut: Some(egui::KeyboardShortcut::new(egui::Modifiers::COMMAND, egui::Key::R)),
    icon: Some(Icon::RefreshCw),
    enabled: always,
    run: |s: &mut Demo| s.refreshed += 1,
});

let theme = Theme::load();
toolkit::install(ctx, &theme);
let strings = Strings::new(include_str!("../i18n/en/demo.ftl"));
let stroke = icons::stroke_width(&theme);

// each frame:
let _ = toolkit::titlebar::show(ui, "Demo", Some(Icon::Server), stroke,
                                &commands, &state, &strings);

Main pieces

Theme and look

  • theme: Theme holds a resolved design together with the context it was compiled for (scheme, style, mode, contrast) and its framing (server- or client-side decorations, caption side). Build one with Theme::embedded(), for_context, from_source (a full design document or a selection-only file) or load(). Then adjust it with with_choice, framed and with_overlay.
  • session_look: turns the desktop profile's appearance, as settingsd holds it, into a Theme. It is pure, with no Bus and no I/O. An appearance that names a design this build does not know leaves the app's session theme in place.
  • theme_menu: the View › Theme submenu, added with Registry::theme_menu. It covers the hue schemes (Ocean, Crimson, Stone, Forest, Sunset, Mono), the chrome schemes (Pro, Studio, Classic), modes, styles and framing. Command ids are view.theme.<scheme>, view.mode.*, view.style.*, view.decorations.* and view.captions.*, and each axis can follow the session.
  • style: maps a hue scheme's design onto egui's Style. The module docs give the field-by-field mapping table.
  • chrome: the colours and geometry of the title bar and menus. For the chrome schemes (pro, studio, classic) it supplies the whole egui style, taken from the design's chrome family. Components read Chrome::style and Chrome::metrics and never ask which scheme they are drawing, so a new style needs only design data. See the chrome family decision.
  • fonts: the embedded faces. Weight is a design token: each typography role names a weight, and the static Inter file of that weight is the one installed. Monospace is JetBrains Mono.
  • icons: Lucide icons, from unmodified upstream SVGs. The Icon enum names them. Stroke width comes from the design metric icon.stroke_width, and colour comes from the widget's text colour at draw time. See the icons and type decision.

Window chrome and components

  • titlebar: the client-side title bar. From the left it holds the app mark, the registry's menus, the centred title, an optional right-hand group of controls, and the minimize, maximize and close buttons. Captions can be on the left instead. A window opened with titlebar::viewport is undecorated. Only the free gap between the menus and the buttons drags the window. Call titlebar::edges last in the frame to add the resize zones. Under server-side decorations the row becomes a plain menu bar.
  • menu: menu bars and menus drawn by the toolkit, not by egui's MenuBar. One highlight is shared by pointer and keyboard, a menu opens on press, and press-drag-release runs an item. Nav holds the open menu and its highlights as pure data and is tested without egui.
  • bars, button, panel, tabs, field, toggle, combo, slider, canvas, tooltip, dialog: the chrome components (options, status and tool bars, icon rail, push and icon buttons, dock panel groups, document tabs, value, text and search fields, checkboxes and switches, combo boxes, sliders, canvas scrollbars, tooltip wording, and modal dialogs).
  • markdown: Markdown drawn natively. Document::parse(&str) turns CommonMark with GFM tables, strikethrough and task lists into plain data (pulldown-cmark, parser only), so an app parses once and keeps the document in its state. markdown::show(ui, &document) draws it every frame from the theme: heading sizes stepped from the Heading style, code in the monospace face on the field surface (scrolled sideways, never wrapped), quotes after a rule, lists with read-only task boxes, striped tables, rules and links. Each text run is one selectable label. Links go to Context::open_url only for http, https and mailto (markdown::openable), with the address on hover, and show returns the link clicked. Images are never fetched (the alt text links to them) and raw HTML shows as text. markdown::looks_like_markdown(&str) is a conservative test for plain text written as Markdown: one fence, table or pair of headings, or two distinct signals among a heading, a list, bold or emphasis, inline code, a link and a non-reply quote, scanned outside quoted lines and the signature. The email reader uses both.

Commands

  • command: Command<S> is one action, with an id, a label key, a menu and submenu, a group, a shortcut, an icon, an enabled rule and a run handler over the app's state S. A handler only changes state. Work that leaves the process, such as a Bus call or a file write, is queued on the state for the app's effect loop. Registry<S> provides add, add_choice, add_toggle, execute(id, state, origin), describe, menus and model, shortcuts, search, shortcut_grid and rebind. The UI, the command line and Bus verbs all dispatch by id.
  • executed: every run is recorded with its Origin (menu, shortcut, palette, ui, bus, macro) and given a per-process sequence number. It goes into a bounded Journal that counts the runs it drops. Registry::take_executed hands the records to the app's Bus connection, which publishes them on <app>.command.executed (decision).
  • keymap: the chord grammar (Ctrl, Alt, Shift, then one key, joined by +). Registry::rebind applies the person's bindings from the desktop profile.

Drive verbs

drive lets an agent do in the window anything a person can. drive::install(ctx, app) turns on AccessKit, keeps each pass's widget tree, and injects queued pointer and key events through egui's input hook, the same path real input takes. The app registers these verbs under its own prefix (<app>.ui.tree and so on):

ui.tree, ui.click, ui.pointer, ui.scroll, ui.key, ui.type, ui.menu, ui.menu.list, ui.menu.open, ui.menu.close, ui.capture, window, window.state

The app forwards each request, with the prefix removed, to drive::request from inside a frame. It sends back whatever request, or drive::logic (called once per frame), returns. It holds later commands while drive::pending is true, and calls drive::finish before closing. Reads answer at once. Input verbs run one at a time, one step per pass, and answer one pass after their last step. At most 32 input requests can be queued. The driving guide shows them in use.

Strings, thumbnails, layer surfaces

  • strings: Strings::new(ftl) wraps an app's Fluent catalogue (i18n/en/<app>.ftl). It provides get, with (with arguments) and has. A missing key renders as the key itself, so a gap shows on screen instead of a blank. The toolkit's own labels, such as window buttons and the Theme menu, are in i18n/en/toolkit.ftl.
  • thumbnail: decodes cached thumbnail PNGs within explicit size limits on a Decoder thread, ready to upload as textures. Making the thumbnails is the job of thumbs.
  • layer (feature layer): egui programs on wlr-layer-shell surfaces, such as wallpapers, desktops and docks. layer::run(options, program) creates one surface per output (Outputs::Each), or a single surface the compositor places. Each surface has its own egui context and draws through wgpu's GL backend on its wl_surface. It follows fractional scale and takes pointer, scroll and optional keyboard input. It redraws only when something changes, so an idle surface costs nothing.

Features and dependencies

The default build has no features. layer adds smithay-client-toolkit 0.19.2 (the version eframe's winit already links), wayland-backend, wgpu, egui-wgpu and xkbcommon. Workspace dependencies: assets, config, design, settings (data types only, no Bus) and thumbs.

Assets and licences

assets/fonts/ holds static Inter (Light, Regular, Medium, SemiBold) and JetBrains Mono (Light, Regular, Medium) faces with their OFL texts. assets/icons/ holds the Lucide SVGs with LICENSE-lucide.txt, and mixos.dev uses the same icons. assets/licenses.conf.mix pins each notice by SHA-256. This includes the notices for egui's own fallback fonts, which are in assets/fonts/fallbacks/. scripts/install_licenses.mix installs them under <share>/licenses/toolkit (default share /opt/mixos/share). --check validates only the sources, and --verify checks an installed tree. See the bundled font licences decision.

Source layout

PathWhat
src/lib.rsmodule list and install
src/theme.rs, theme_menu.rs, session_look.rs, style.rs, chrome.rsthe look
src/fonts.rs, icons.rsembedded faces and Lucide icons
src/titlebar.rs, menu.rswindow chrome
src/bars.rs … src/dialog.rschrome components
src/command.rs, executed.rs, keymap.rscommands, executed records, chords
src/drive.rsthe drive verbs
src/strings.rs, thumbnail.rsFluent strings, thumbnail decoding
src/layer.rs, src/layer/layer-shell surfaces (feature layer)
i18n/en/toolkit.ftlthe toolkit's own strings
assets/, scripts/install_licenses.mixfonts, icons, licence notices

Testing

Unit tests sit in the modules; for example menu::Nav is tested as plain data. The integration tests in tests/ drive real frames through egui_kittest:

  • components.rs, menu.rs, titlebar.rs, chrome.rs: component, menu and title-bar behaviour under pointer and keyboard;
  • drive.rs: the drive verbs, requested inside frames the way an app's shell requests them;
  • snapshot.rs, components_snapshot.rs: offscreen wgpu snapshots across schemes and modes, compared with tests/snapshots/*.png. Regenerate them with UPDATE_SNAPSHOTS=1, and look at the images before committing;
  • spec_renders.rs: when CHROME_RENDER_DIR is set, it renders the chrome scenes for an out-of-tree comparison against the chrome specification's evidence. When it is unset, it does nothing;
  • asset_licenses_test.mix: install, drift and verify checks for the licence installer.

On an authorised build worker at a pushed commit:

cargo test --locked -p toolkit
cargo test --locked -p toolkit --features layer

This page as Markdown · This page describes the current main branch of MixOS.