← Otto User GuideOtto Developer Guide
Otto is a Wayland compositor built on Smithay, rendered with Skia, and driven by a retained scene graph (lay-rs).
If you have written a compositor before, the one structural surprise is that
Otto does not draw windows imperatively each frame. It maintains a scene
graph — a tree of layers with positions, opacity, blur and animations —
and hands the whole tree to the renderer as a single render element. Most of
src/workspaces/ is code that mutates that tree; almost none of it draws.
Read these first
| Page | What it covers |
|---|---|
| Project Structure | Where everything lives, feature flags, how to build |
| Rendering | Scene graph → render elements → Skia → screen |
| Render Loop | When Otto wakes up, when it renders, damage tracking |
| Wayland Protocols | The one-big-state pattern and how to find any protocol handler |
| The Scene Graph | The layer tree: how it is shaped, how surfaces enter it, how subtrees become KMS planes |
| Layers | The unit the tree is made of: properties, content closures, transactions, caching, damage |
Subsystems
| Page | What it covers |
|---|---|
| otto-kit | The toolkit the apps and the compositor’s own chrome are built on |
| Dock | The compositor-drawn dock: data flow, layers, magnification |
| Exposé | The all-windows overview: layout, mirrors, drag-and-drop, multi-output |
| Window Move | How interactive window drags are implemented |
| DRM Planes | Handing parts of the scene to display hardware instead of the GPU |
| Foreign Toplevel | Exposing the window list to taskbars and launchers |
| Screen Sharing | Portal, PipeWire, wlr-screencopy, window capture |
| Accessibility | Key grabs for screen readers, and the shell and kit apps on AT-SPI |
| Color Scheme | How apps learn whether Otto is in light or dark mode |
| Settings D-Bus API | The org.otto.Settings wire contract |
| RDP Bridge | Serving a virtual output over RDP (otto-rdp) |
| Debug Action Hook | Driving builtin shortcut actions from a script ($OTTO_ACTION_FILE) |
| Versioning & Releases | One workspace version for the compositor and every component, and how to bump it |
| Remote-Desktop Indicator | The sharing indicator otto-rdp publishes while a client is watching |
Design docs and plans
These describe work that is exploratory, partial, or superseded. Each says so at the top — check that before trusting the details.
| Page | Status |
|---|---|
| otto-kit Roadmap | Partially built — gap analysis for the UI toolkit |
| Surface Style Protocol | Superseded — the original design behind otto-surface-style-v1 |
| Screenshot Portal Plan | Partly built — the portal exists and shells out to grim; Otto-drawn selection is still the plan |
| AirPlay Screenshare | Exploration only |
Specs
docs/developer/ explains how things work today. specs/
holds the behavioural contracts — what a feature must do, written to be
verified against. Where a subsystem has both, the spec is authoritative for
behaviour and the doc is authoritative for structure. See
specs/README.md.
Conventions
Two coordinate spaces, and mixing them causes scale-dependent bugs.
- Physical pixels — raw hardware pixels. Used for layer positions
(
set_position,change_position) andoutput.current_mode().size. - Logical pixels (points) — physical ÷ scale.
output_geometry(output).sizereturns these, so it must not be used for layer positions.
Always take the scale from the output itself —
output.current_scale().fractional_scale() as f32. WorkspacesModel.scale is
a global fallback and does not belong in geometry code.
Suffix physical-pixel variables with _px (width_px, offset_px) so the
space is visible at the call site.