Files
komorebi/docs/linux/dev/platform-contract.md
T
LGUG2Z a7b78cc7e4 docs(linux): paths and links name the consolidated layout
The KDE docs were moved from komorebi-for-kde/ to docs/linux/ and the sources, nix files,
scripts and schemas to komorebi-linux/, nix/linux/, scripts/linux/ and schemas/linux/, so
every relative link that left docs/ (to sources, nix files, scripts, schemas, .envrc,
rust-toolchain.toml, the old CI file) was broken, and the text named the old paths.

Links were rewritten mechanically by resolving each broken target against the old
location and mapping it to the new one; link texts and code-span paths were updated to
the new paths (komorebi/src -> komorebi-linux/src, nix/ -> nix/linux/, scripts/ ->
scripts/linux/, docs/ -> docs/linux/, schema.json -> schemas/linux/schema.json). The
crate list of the README names komorebi-linux, komorebi-daemon and komorebi-version;
`cargo install --path komorebi` becomes komorebi-daemon; the comparison paragraph of
CONTRIBUTING.md says compare-gen.py is stale (it looks for the old sibling trees).

URLs of the old GitHub repositories are left as they are (a release decision).
COMPARE.md is left as it is (already marked stale).

Verified: a link checker over every markdown file (outside cli/, nix/, tools/, old-ci)
reports no broken link in docs/linux. Nothing was built.
2026-11-05 15:03:27 -08:00

9.8 KiB

The contract of a Platform

A shared scenario (nix/linux/tests/vm/lib/komorebi_vm/core/scenarios) runs on KDE here and on Windows in kfw's guests, through the Platform protocol (core/platform.py). A check that one port answers by how its implementation happens to work, and the other by what the system says, passes on one port for the wrong reason. This file lists what each member promises to both ports, and the quirks of each port that a scenario must not lean on. The docstrings of Platform are the contract; this is the audit behind them.

What each member promises

Member Both ports promise Quirk a scenario must not depend on
has(feature) "pointer", "lock"; a scenario that needs one is skipped, with the reason, where it is false KDE has lock only in a real Plasma session (session=True); Windows has both always
tolerance how far a frame may be short of the one asked for; the frame is never past it KDE 24 px (a terminal's cells), Windows 2 px (rounding). It bounds what a client rounds to; it is not a measure of a change, so a scenario asks the daemon's layout for the change (support.layout_rect) and then that the frame follows it within the tolerance
wait_for(p, t) polls p until it holds or t seconds, scaled for the host Windows turns a RuntimeError of a komorebic call in p into a false; KDE lets it raise. A predicate answers false for what it cannot tell yet, and relies on neither
komorebic(...), komorebic_status(...) komorebic raises if the command fails; _status returns the exit status and what it printed the exception type (AssertionError on KDE, RuntimeError on Windows): a scenario catches Exception or nothing
start_komorebi(), restart_with(c) the daemon starts from a clean state, with the windows that are open taken up Windows launches it up to three times; KDE once
rewrite_config(c) writes the file the daemon watches; returns before the daemon has read it the delay before a reload is the daemon's (a scenario waits for the state, or QUIET for what must not happen)
spawn(title, app_id, exe) opens a window and waits until the system has it; raises if it never does a title is taken as it is and no window has another's; exe is a name of letters, digits, _ and -, without an extension (exe_id(name) spells it as the daemon names it: name on KDE, name.exe on Windows)
os_window(c) the system's own frame of the window, or None; focused is the keyboard focus, for at most one window the frame of a minimized window is wherever the system keeps it; a scenario does not read it
hidden(c) true for a window the daemon put away: a stack's other windows, a workspace that is not shown, behind a monocle. False for a window the system does not have, one that is minimized, one that floats true on KDE for any window on a virtual desktop that is not shown (with more than one output, the desktop of the active output), on Windows for any cloaked window (another Windows desktop included). A scenario that asks for "put away" also asks state() where the window is, and counts "in view" through support.in_view, which needs the system to have the window
displays() each display's rectangle in the desktop's coordinates, left to right, as the system says displays are not one size (on Windows the first is the basic adapter, 1280x800, and the second, the only one whose mode can change, is 1024x768 by default and follows resize_display) and a scenario does not compute where the next begins
add_display(), remove_display() plugs a display in to the right, unplugs the last; return when the system agrees Windows removes only a display add_display plugged in (a hotplug port is used once); KDE enables a disabled output and disables the last enabled one, so a headless session with two outputs can unplug at once. A scenario unplugs only after plugging in, unless its suite started with more than one display
work_area() the area of the first display that windows may take, as the system says: the display less panels, docks and the taskbar KDE asks KWin (a script: its placement area), and answers with the whole output only if KWin does not answer. A headless session has no panel, so there it is the output
put_file(path, data), wallpaper(display), set_wallpaper(path, display) a file the daemon can read; the picture a display shows; the wallpaper as the user sets it KDE reads and sets it through the Plasma shell's D-Bus API. Windows: IDesktopWallpaper. restart_with in a real Plasma session restarts the module's unit with the user's komorebi.json
resize_grab(frame, grab) where the pointer is pressed to take a window by an edge or corner KDE: the gap outside the frame on every side (resize.outside_grab). Windows: left, right and bottom are the invisible border outside the visible frame; the top has none, so it is inside the frame's top row
close, minimize, activate, move_window act on the window as the user would; raise if there is no such window close returns before the window is gone (a scenario waits for os_window to be None); move_window puts the top left of the visible frame at (x, y)
lock(), unlock() lock and unlock the screen and return when it is so the lock screen is LogonUI on Windows and the Plasma greeter on KDE; a scenario asks only what the daemon and the windows do
pointer moves in the pixels of the first display; a drag is one press, a glide, one release a window manager that follows the pointer by a hover delay (Windows) or by its own focus policy (KDE) has to be given time: focus_follows_mouse.SETTLE
screenshot(name) the display as an image none

Findings of the review, and what was done

  1. work_area() on KDE was the whole output. Confirmed. The headless KWin has no panel so no suite could tell, but a scenario's "inside the work area" was weaker than on Windows wherever a panel exists. KdePlatform.work_area() asks KWin for the placement area of the leftmost output (Kde.screens, a KWin script like the daemon's bridge uses) and falls back to the output. Monocle and maximize now also check that their window stays inside it. To verify on KDE: a suite with a panel (drag-resize, run-kde) and core-e2e; the script (workspace.clientArea(KWin.PlacementArea, ...)) has not been run.
  2. tolerance is 24 on KDE. Partly confirmed. Most uses are right: a frame short of a tile by up to 24 px, in a tile hundreds of pixels wide. Not right: resize_commands checked that a decrease gave back the width within the tolerance, so a daemon that gave back less than the increase passed for a terminal. It now compares the daemon's layout and then the frame. inside(area) takes no slack for the tolerance, because a frame is never past what was asked.
  3. hidden() is true for any window on a desktop that is not shown. Confirmed as a difference, refuted as a source of vacuous passes. Every scenario that asks for a window to be put away also asks the daemon where it is (workspace_of, containers, monocle), and a window that was lost is not in view (support.in_view). The docstring now says what each port is true for. With two outputs on KDE only the first output's windows are certain.
  4. focus_follows_mouse passed --implementation windows and a KWin-sized settle. Confirmed. Both ports' komorebic default to windows, so the flag is gone, and the settle is a named wait, SETTLE, that is longer than either needs.
  5. Constants tuned for KDE. Confirmed for the 400,300 move of locking, the absolute room of padding (5000 and 2000 square pixels), the 60 px drag of resize, the first display's rectangle in hotplug (the work area is the right area) and the quiet period of 3 s written in nine places. They are now derived from the work area or named (support.QUIET). Checked and found neutral: the 1920, foot, KWin, wayland, app_id and .exe words in the shared scenarios are in comments, or go through exe_id and spawn. locking also did not check that the window moved, so a platform that could not move it while locked passed; it does now.
  6. Checks that pass for how an implementation answers. Confirmed: spawn did not raise on KDE if the window never came (a "stays unmanaged" check then passed for a window that was not there); close, minimize, activate and move_window on a window that is not there did not raise a message; displays() was the size the session was started with and not what KWin says; a program name that Windows rejects was shell-quoted into KDE; a title was a regular expression on KDE. All fixed. os_window().focused is each system's own answer and the same in meaning. The frame of a minimized window is not specified (KDE keeps it, Windows may park it off screen): no scenario reads it.

Open items

  • kfw's windows_platform.py builds Rect(left, top, right, bottom) for displays(), os_window and work_area(). Checked with the kfw session: its agent returns the width and height under right and bottom (the shape komorebi itself prints), so it is (x, y, w, h) in kfde's terms; the names are misleading, not wrong.
  • inside(area) has no slack. On Windows the DWM frame might round one pixel past; if the first run says so, the slack is a Platform member, not a number in the scenarios.
  • KDE hidden() with two outputs and per-output desktops answers for the desktop of the active output only.
  • Unverified on KDE, to run: core-e2e (work_area, spawn raising, in_view, resize_commands), hotplug (displays from KWin, work_area after an unplug), drag-resize (the drag distance, focus_follows_mouse with no flag, work area with a panel), core-lock (the move while locked). On Windows: every scenario above.