The window manager decides where every window goes, and asks a compositor to put it there. If it asked KWin directly, every test of its logic would have to boot a virtual machine, so everything it needs from the desktop goes through one trait, `Backend`: queries for windows, monitors, the focus and the pointer, and commands to move, focus, raise, minimize and close a window and to put the pointer somewhere. A real backend answers from a table that compositor events keep up to date, so a query never waits for the compositor; a command returns once it has been accepted, not once the client has acted. `FakeBackend` is the other implementation: an in-memory desktop that does what it is told, at once and exactly, and records every command so that a test can assert on what the window manager asked for. A test can also do what a user does without the window manager asking: move a window, close it, change the focus, turn a window into a dialog. It is strict where KWin is (a command about a window that is gone is an error) and clamps where a GTK4 client does (a minimum size). `with_backend` overrides the process-wide backend for the current thread only and puts the previous one back on drop, also on a panic, which is what keeps one test's desktop out of the next. The module is the first to have `#![deny(missing_docs)]`. The contract (what must be implemented, that a backend is `Send + Sync`) is proved by two `compile_fail` examples with their error codes, and the rules nothing checks are listed as such. docs/dev/ARCHITECTURE.md describes the trait, its roles and the layer that enforces each rule, and docs/dev/DESIGN.md has the first decisions with their reasons. Both grow with the code.
5.3 KiB
Architecture
komorebi is a tiling window manager. It decides where every window goes and asks a
compositor to put it there. Everything it needs from the compositor is behind one trait,
Backend, so the window manager is written, and
tested, without one.
This page describes that trait, who implements and calls it, and where each of its rules is enforced. It grows with the code: each part of the daemon that is added has its place here.
Vocabulary
- Window: a top-level surface a client has asked the compositor for, named by the
compositor's
u32id. - Monitor: a connected output, with its bounds and its work area (its bounds minus the panels).
- Compositor: the program that owns the screen and the windows. On KDE Plasma it is KWin.
- Backend: the implementation of the
Backendtrait that talks to a compositor. - Fake:
FakeBackend, the in-memory backend the tests use.
Design
The window manager reads the desktop and changes it through [Backend] and through
nothing else. A real backend answers from a table that compositor events keep up to date,
and sends commands to the compositor. The fake answers from a table the test fills in, and
records the commands. The window manager cannot tell them apart, which is the point:
the same logic runs against both. The reasons are in
DESIGN.md.
The model
The desktop, as the window manager sees it, is two lists and a pointer:
- Windows, each a [
WindowInfo]: its id, process, title,app_id, executable, frame, whether it is minimized, whether it is a normal application window (as opposed to a dialog, a splash screen or a dock), and whether it is alive. A window that has been destroyed stays in the table for a short time withalivefalse, so that the event that mentions it can still be logged. - Monitors, each a [
MonitorInfo]: its id, name, an identity that survives a hot-plug, its bounds and its work area. - The pointer, a position if the compositor says where it is, and the focus, the id of the window that has it.
A command is one of: move and resize a window, focus it, raise it, minimize it or bring it back, close it, and put the pointer in the middle of a rectangle.
The parts
backend: the trait, the two data types, and the process-wide handle ([set_backend], [backend], [with_backend]).backend::fake: the fake.
The handle is a process-wide singleton so that a window can be a small Copy value
that carries only its id, and ask the backend for what it needs. A test replaces it for
its own thread with [with_backend], which puts the previous one back when the test is
done, also if it panics.
Traits and roles
[Backend] is the compositor. It is implemented once for each compositor and once for
the fake. The window manager is its only caller, through [backend]. The contract:
- Queries (
windows,window,foreground_window,cursor_pos) answer from the backend's own table and never wait for the compositor.monitorsmay fail. - A command returns once the compositor has accepted it, not once the client has acted on
it. The frame a window ends up with is what
window_rectsays afterwards, because a client may refuse a size. - A command about a window that does not exist, or is no longer alive, is an error. It never panics.
shutdown,is_window,window_rectandmonitor_from_pointhave defaults, written in terms of the queries. An implementor should not need to override them, exceptshutdownwhen it created something in the compositor that has to be given up.
What is guaranteed, and by what
| Rule | Enforced by | Proof | Escape hatch |
|---|---|---|---|
| A backend implements every query and command | type system: the methods have no defaults | compile_fail,E0046 on [Backend] |
none |
| A backend can be shared between threads | type system: Backend: Send + Sync + 'static |
compile_fail,E0277 on [Backend] |
none |
| The window manager asks for a backend that was never installed | runtime error: [backend] panics, naming [set_backend] and [with_backend] |
# Panics on [backend] |
none |
| A test's backend does not outlive the test | framework path: [with_backend] restores the previous one on drop |
with_backend_puts_the_previous_backend_back_when_the_closure_panics |
none |
| A command about a missing window is an error, not a panic | test | a_command_about_a_window_nobody_opened_is_an_error and a_command_about_a_closed_window_is_an_error, against the fake only |
none |
| Queries never block on the compositor | convention only | none | none |
| Only the backend module reaches the compositor | convention only | none | none |
The last two rows are rules nothing checks. A real backend that waits inside a query breaks the first, and a module that talks to D-Bus directly breaks the second.
Where to look in the code
komorebi/src/backend/mod.rs: the trait.komorebi/src/backend/fake.rs: the fake and its tests.komorebi/src/core: the vocabulary the rest is written in.