mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-13 20:33:41 +01:00
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.
9.8 KiB
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
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) andcore-e2e; the script (workspace.clientArea(KWin.PlacementArea, ...)) has not been run.toleranceis 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_commandschecked 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.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.focus_follows_mousepassed--implementation windowsand a KWin-sized settle. Confirmed. Both ports'komorebicdefault towindows, so the flag is gone, and the settle is a named wait,SETTLE, that is longer than either needs.- Constants tuned for KDE. Confirmed for the 400,300 move of
locking, the absolute room ofpadding(5000 and 2000 square pixels), the 60 px drag ofresize, the first display's rectangle inhotplug(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: the1920,foot,KWin,wayland,app_idand.exewords in the shared scenarios are in comments, or go throughexe_idandspawn.lockingalso did not check that the window moved, so a platform that could not move it while locked passed; it does now. - Checks that pass for how an implementation answers. Confirmed:
spawndid not raise on KDE if the window never came (a "stays unmanaged" check then passed for a window that was not there);close,minimize,activateandmove_windowon 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().focusedis 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.pybuildsRect(left, top, right, bottom)fordisplays(),os_windowandwork_area(). Checked with the kfw session: its agent returns the width and height underrightandbottom(the shapekomorebiitself 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 aPlatformmember, 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_areaafter an unplug),drag-resize(the drag distance,focus_follows_mousewith no flag, work area with a panel),core-lock(the move while locked). On Windows: every scenario above.