Adding a window-manager backend
Home · Development · Platform support
A backend is the adapter between Tessera’s tiling core and one window manager or compositor. This guide lists the files you touch, the rules the adapter must obey, and the evidence needed before anyone may call the result “supported”. Questions and proposals go to the issue tracker.
Writing an adapter does not make a profile supported. Support is announced per complete profile (OS, version, session, integration, architecture) and only after the evidence listed in Announcing support.
Architecture in five rules
| Rule | Where it lives |
|---|---|
| Tessera never becomes a window manager. It observes state and requests operations from the session’s own WM. | CLAUDE.md, Architecture |
BackendId is a closed enum with one variant per adapter. Backends are compiled in; there is no plugin loader and no runtime registration. |
crates/tessera-backend/src/registry.rs |
| One active adapter per session. A requested backend that is unknown or unavailable fails; the registry never silently falls back. | BackendRegistry::select |
Capabilities are atomic and optional: one BackendOperation per thing that can fail on a platform. An operation you do not implement is reported unsupported, never as success. |
crates/tessera-backend/src/capabilities.rs |
| A mutation counts as applied only when the resulting state is observed. A delivered request is not an applied change. | OperationResult::Applied { observed, at } in operation.rs |
Crate roles you will meet:
| Crate | Role |
|---|---|
tessera-core |
Protocol-neutral tiling policy; no X11, GTK or config dependencies. |
tessera-backend |
BackendFactory, BackendRegistry, BackendId, SessionDriver, capability facets, the shared contract suite. |
tessera-backend-x11 |
The xfwm4 adapter; the only crate that uses x11rb. |
tessera-backend-kwin, tessera-backend-gnome |
Experimental KWin and GNOME Shell Wayland adapters (IPC clients of a KWin script / GNOME extension). |
tessera-backend-macos, tessera-backend-windows |
macOS Accessibility and Windows User32/DWM adapters. |
tessera-app |
Composition root: registers adapters per OS in src/platform/. |
The adapter contract
BackendFactory
Implement the trait in tessera_backend::registry:
| Method | Obligation |
|---|---|
id() |
Return your BackendId variant. |
serves(&SessionFacts) |
Say which sessions you target, using desktop (XDG_CURRENT_DESKTOP), session_type (XDG_SESSION_TYPE) and display (DISPLAY). The default returns true for every session, so always override it. |
probe() |
Inspect the session without taking a window-manager role. Return Probe::available(OperationSet), Probe::available_degraded(..) or Probe::unavailable(UnavailableReason). Declare only operations you really implement here. |
open(&Probe) |
Return a Box<dyn SessionDriver>. Refuse when the probe was unavailable. |
open_inspection() |
Optional read-only session for the debug dumps. The default refuses with NotImplemented, so an adapter that cannot inspect says so instead of reporting an empty session. |
SessionDriver and facets
The driver (in session.rs) always provides snapshot() (window and output inventory) and events() (drain pending changes, never block). Everything else is an optional facet accessor that returns None by default, for example geometry_request(), geometry_submit(), window_maximize(), window_focus_request(), workspace_activation(), output_enumeration(), hotkey_register(), inspection(), decorations() and picker(). The capability report is derived from your probe’s OperationSet, never from these accessors alone, so keep the two consistent.
Tiling needs the nine operations in BackendOperation::TILING_REQUIRED, starting with WindowEnumerate, WindowObserve, WindowGeometryRead, WindowMove and WindowResize (see capabilities.rs for the full list). Below that floor a provider may open only in diagnostic mode with tiling off (BackendRegistry::select_diagnostic).
Semantics that tests enforce
- Unsupported is explicit. A missing operation fails with an
UnsupportedReason(for exampleNotImplemented); it never returns success. - Degraded is declared. If an operation works with reduced fidelity, say so through
Probe::available_degradedand aDegradedReason. - Applied means observed. Report
Appliedonly after reading the state back. Use the otherOperationResultoutcomes (for example pending confirmation) for everything else. - Events are delivered, never dropped. Document any coalescing or suppression you do.
Step-by-step checklist
- Create the crate
crates/tessera-backend-<name>and add it to the workspace. ImplementBackendFactoryandSessionDriver.Xfwm4BackendFactoryincrates/tessera-backend-x11/src/xfwm4/factory.rsis the reference. - Add identity in
crates/tessera-backend/src/registry.rs: a variant onBackendIdand one row inBACKENDS(variant, canonical name, accepted aliases, in declaration order). A misplaced row fails a compile-time assertion. - Handle the exhaustive matches. These stop compiling until the new variant is covered:
BackendId::is_experimental(new adapters start experimental unless the manifest says otherwise);BackendId::identity_field(whatWindowSnapshot::classcarries:WmClass,BundleIdorAppId);profile_forincrates/tessera-app/tests/support_manifest_and_release_gate_contract.rs.
- Register the factory in
compiled_backends()of the matching module undercrates/tessera-app/src/platform/(linux.rs,macos.rs,windows.rs,other.rs). On Linux this is a.register(&*ADAPTER)call chained aftertessera_backend_x11::xfwm4::registry(), with the adapter held in a lazily builtstatic(seeKWINandGNOME). It must stay inert untilprobeoropen. Add the dependency tocrates/tessera-app/Cargo.toml. - Add an integration endpoint only if selection needs one. KWin serves a D-Bus endpoint for its script (
serve_kwin); GNOME dials the extension (connect_gnome). Both are listed inINTEGRATIONSinplatform/linux.rsand run byserve_integrations_for; each returns at once for a session its adapter does not serve. - Add unavailable reasons. A new refusal is a variant of
UnavailableReasonincrates/tessera-backend/src/error.rs, mapped inunavailable_reason_text(crates/tessera-app/src/error.rs) to a Fluent message. The message must exist in every catalog undercrates/tessera-i18n/i18n/<locale>/tessera.ftl(de,en,es,fr,it,ja,ko,pt-BR,ru,zh-CN) and should tell the user what to do. - Declare the profile in
docs/compatibility/support-manifest.yaml(see below). - Write tests (see below) and record what could not run.
Tests
| Layer | Where | What it proves |
|---|---|---|
| Registry selection | crates/tessera-app/tests/platform_providers.rs |
Served, unserved, ambiguous and served-but-unavailable sessions select or refuse as intended. |
| Registry and facets | crates/tessera-backend/tests/ (backend_registry.rs, atomic_capabilities_contract.rs, capability_matrix_and_unsupported_contract.rs, session_probe_and_refusal_contracts.rs) |
Probe and refusal behavior, unsupported never success, an unreachable integration never reports applied. |
| Semantic contract | tessera_backend::contract (ContractFixture, ContractSubject, violations) |
The shared suite every adapter must pass. It exercises only operations your probe declares, and reads effects back through the inventory or event stream. |
| Adapter-specific | crates/tessera-backend-<name>/tests/ |
Protocol behavior; see crates/tessera-backend-x11/tests/ for the shape. |
Supply a ContractFixture from a synthetic model or a real session, then assert the result is empty:
let subject = ContractSubject::from_driver(session.driver());
let found = violations(&subject, session.capabilities(), &fixture);
assert!(found.is_empty(), "adapter violates the semantic contract: {found:#?}");
Isolated environments
Display-dependent tests never run against your own desktop or session bus. Use scripts/test-env.sh <profile> <cmd...>, which builds a private environment and refuses your :0.
| Profile | Environment |
|---|---|
x11-bare |
Private Xvfb, no window manager. |
x11 |
Private Xvfb plus a live xfwm4. |
x11-dummy |
Xorg dummy server with two RandR outputs (hotplug, resolution and rotation tests). |
wayland |
Headless Weston (host Weston, else scripts/run-weston-session-container.sh -- <cmd>). |
kwin-wayland |
Headless kwin_wayland --virtual with the shipped script, in a container. |
gnome-wayland |
Headless GNOME Shell with the shipped extension, in a container. |
gate |
Everything plus the workspace gate (fmt, clippy, tests). |
Session images are also reachable directly with scripts/run-session-container.sh <xfce|kwin-wayland|gnome> -- <cmd>. Their containers have no network, no host display and no cargo: compile on the host, then pass the built test binary.
Exit codes of test-env.sh: the command’s own code, 2 usage, 3 UNAVAILABLE (a prerequisite is missing), 4 it refused the user’s display, 5 xfwm4 died during a gate run.
A test that could not run is UNRUN, never passed. Skipped tests print an UNRUN: <reason> line, and the gate ends with a gate: UNRUN summary. Do not count them, and say which ones were UNRUN when you report results.
Support manifest
Every registered backend needs one of three things in docs/compatibility/support-manifest.yaml and its contract test: a promoted profile, an experimental profile, or an entry in REGISTERED_UNSUPPORTED (in support_manifest_and_release_gate_contract.rs) with the reason. A stale entry also fails.
Profiles carry a profile_id, an estado_de_evidencia (values in the manifest today: implemented-unverified, experimental, blocked), and the declared operacoes. The list is an inventory of what the profile declares, not a claim of availability.
Announcing support
A new adapter stays out of the supported set until all of these hold for the complete profile:
- a verified control/event contract for the compositor or window manager;
- the shared semantic contract tests pass;
- CI evidence from the same commit;
- a smoke run in a real session of that profile (virtual servers and containers support development but are not a real session).
Claims about window-manager behavior are pinned to the latest stable source release; the xfwm4 reference is 4.20.0 (tag xfwm4-4.20.0). If you cannot establish a criterion, the profile remains experimental or blocked.
Consent for compositor integrations
Some adapters need code inside the compositor (a GNOME Shell extension, a KWin script). Tessera does not install or remove these on its own:
- The user installs and removes the extension with
tessera gnome-extension install|remove. Without--yesthe command asks on the terminal and changes nothing unless the answer is yes.tessera gnome-extension statusonly reports. - The adapter checks the integration before use: handshake, health, and version compatibility. A missing, incompatible or unverified peer yields a typed
UnavailableReasonsuch asGnomeExtensionAbsent,GnomeExtensionIncompatibleorKWinScriptIncompatible, never a silent success. - Persistent permissions (for example macOS Accessibility) need explicit consent at the point of risk.
Worked example: GNOME Wayland
GnomeWayland fit the contract without a window-manager special case: one BackendId row, one .register(&*GNOME) in platform/linux.rs, one connect_gnome entry in INTEGRATIONS, and a profile that stays blocked in the manifest until a real GNOME smoke exists. The adapter is experimental and not counted as supported.