This page covers two things. The first is the small Rhai script host that exists today. The second is the planned extension architecture: what public extensions would be allowed to do, and the constraints that design must respect. Public extensions are optional, later work outside the initial core release (roadmap). The planning record is spek 005, "Extension broker and public API".
Small Rhai scripts can extend the shell without rebuilding it. A script can add a bar cell, set a dock badge and post user-visible notes.
Only the legacy tuna-shell-host runs them. The host lives in
crates/shell-host/src/extensions.rs and is driven from the legacy panel
(crates/shell-host/src/panel.rs:1781-1787). The default GTK shell
(tuna-shell-gtk, ADR 0006) loads no
scripts. To try them, choose the legacy shell with
TUNA_SHELL_BIN=tuna-shell-host (packaging).
Copy a .rhai file into $XDG_DATA_HOME/tuna-shell/extensions/. The
default is ~/.local/share/tuna-shell/extensions/
(extension_dir() in extensions.rs, DATA_DIR_NAME in favorites.rs).
When neither XDG_DATA_HOME nor HOME is an absolute path, no scripts are
loaded. The shell rescans the directory on its status tick, every two
seconds, and compares file modification times. Adding, editing or
deleting a file takes effect without a restart.
bar_cell(text, icon): contribute the strip cell (text plus an icon name from the theme).on_press(id): declare the press action id for the cell.press(id): optional script function the shell calls on press.dock_badge(text): contribute the dock badge text.notice(text): record a user-visible note.print(text): captured into notes (never reaches stdout).
Scripts run on the shell host's status tick with an operation budget of
MAX_OPS (200,000 Rhai operations). They are kept off the paint and event
paths. Painters read only the cached plain-data ScriptOutput. A press on
the strip is queued and delivered to the script's press function on the
next tick.
The engine replaces the module resolver with a dummy, so import cannot
read files. It silences debug, captures print, and registers only the
functions above (extensions.rs:100-146). There is no filesystem, network
or process surface. A script that fails is disabled with a visible note
naming the reason. Failures include a parse error, a runtime error, an
exceeded budget and a failing press handler. Fix the file and the shell
reloads it.
crates/shell-host/sample-extensions/hello_cell.rhai is a greeting cell
that notes its own presses.
This is containment inside the shell process, not process isolation.
- Scripts share the shell host's address space and thread. A Rhai engine bug is a shell host bug.
- The engine configures no string, array or memory limits, so a script can exhaust the shell host's memory within its operation budget.
- Any process running as the user can write to the extension directory.
- The compositor is unaffected by all of this: the shell is a separate, supervised process, and apps stay connected across a shell restart (ADR 0003).
These limits are recorded in the security model.
The rest of this page is a planning document, not implementation guidance. Nothing below is shipped.
A public extension adds bounded, declarative contributions through explicitly granted, revocable capabilities. Examples are indicators, search results and layout requests. By default an extension cannot:
- observe raw input, keystrokes, the clipboard, pixels or window contents;
- read or write arbitrary files or use the network;
- inject input or drive window management directly;
- obtain capture, remote input or permission escalation through its own grants. Those keep their independent portal consent and compositor authorization (capture security).
Example capability names from spek 005 are read-workspace-summary,
add-indicator, add-search-provider and request-layout-change. Any
future network permission is explicit, scoped, revocable and tested.
Extensions are untrusted code. The authority model follows the security model:
- Authority. An extension holds only its granted capabilities. It never receives the compositor's privileged Wayland socket, the shell control socket or the shell's capture authority.
- Isolation. The operating-system process boundary and the broker's host-call policy are the security boundary. One process runs per extension, or per explicitly grouped package, supervised outside the compositor and the shell. WebAssembly guest isolation alone is not sufficient. Wasmtime may later run inside an isolated worker, but only after host imports, fuel, memory, filesystem and network permissions, and crash behaviour have security tests.
- Crash and resource containment. A crash, timeout, oversized message, memory or fuel exhaustion, or protocol violation disables that extension for the session with a user-visible diagnostic. The shell and compositor stay responsive. Each capability has a CPU/time budget, memory budget, message quota, restart policy and audit logging.
- Revocation. Revoking a grant while the extension runs stops broker and service access within a documented bound.
| Option | Fit |
|---|---|
| Native library loaded into the compositor or shell | Rejected. No native plugin ABI is allowed (spek 005 constraint, architecture). |
| In-process interpreter (the current Rhai host) | Acceptable only for the legacy shell's local scripts. It cannot provide crash, memory or revocation containment. |
| Separate process speaking a broker protocol over a private Unix socket or socketpair | Preferred. It matches the control socket's framing and limits (ADR 0002) and its peer authentication (ADR 0005). An inherited socketpair avoids a connectable path. |
| Separate process on the session bus | Possible for providers that already speak D-Bus, such as GNOME search providers. Bus names are not identity, so the broker still authenticates the process and enforces quotas. |
| Wasmtime guest inside a broker-supervised worker | Later and optional; the worker process, not the guest, is the boundary |
- The compositor stays authoritative and never waits on extension work. No extension call runs on the input or frame path (architecture).
- The shell is a supervised child that restarts from a finite budget. A broker must survive, or cleanly resynchronize across, shell restarts, as the shell resynchronizes with a full snapshot (ADR 0003).
- The shell renders extension UI from bounded declarative data. Search
providers are asynchronous, cancellable and rate-limited, as the
existing remote search providers are (
crates/shell-gtk/src/providers.rs).
GNOME Shell loads extensions as JavaScript in its own process. There they can monkey-patch any shell object, block the compositor's main loop, and take the session down with them. Tuna Desktop's roadmap excludes "in-process shell extensions" and "an unbounded plugin authority model" (roadmap). An in-process extension cannot be revoked, quota-limited or crash-contained in a way a reviewer can verify.
- Panel indicators: weather, build status, a VPN state cell. These are declarative text, icon and press actions, like the current Rhai cell.
- Dock badges and counters.
- Additional overview search providers.
- Layout and tiling policy requests, applied by the compositor after validation.
- Keyboard-shortcut actions that run a granted, declarative action.
| GNOME Shell | Tuna Desktop target | |
|---|---|---|
| Where code runs | Inside the shell/compositor process | Separate supervised process per extension |
| API surface | Any internal shell object | A small, versioned capability API |
| Failure | Can freeze or crash the session | Extension disabled for the session, shell and compositor unaffected |
| Permissions | None beyond the user's | Per-installation, revocable grants with a permission UI |
| Compatibility | Tied to GNOME Shell versions | Versioned API with a migration and deprecation policy. GNOME Shell extensions are not compatible. |
Spek 005 depends on the threat model, a stable shell control boundary, the shell's extension use-case inventory and the trusted-service and consent model (specs 000, 001, 002 and 004). Roadmap decision gate 6 requires four things before public extensions:
- initial use cases map to a small capability set;
- the API compatibility policy has been reviewed;
- the permission UI has been reviewed;
- all broker containment criteria pass
(roadmap; requirement R11 in the requirements). The API, once it exists, will need its own versioning policy alongside the crate API stability policy.