zbus 6 splits the proxy and service parts of the crate into features, which
komorebi now asks for, makes the builders of connections, proxies and match
rules infallible until build(), and renames the zvariant aliases.
cargo update moves two crates, regex from 1.12.3 to 1.13.1 and libz-sys from
1.1.30 to 1.1.31. winapi-util 0.1.11 keeps its version and now resolves to
windows-sys 0.61.2, which the lock already had, and not to 0.48.0. Cargo.toml
does not change, and neither do the git dependencies, which are pinned by
revision. A second cargo update finds nothing left to move.
Six crates stay where they are because their newer versions are semver-major and
need a Cargo.toml edit and code changes, which a lock update does not carry:
dirs 6.0.0 (7.0.0 is out), nanoid 0.4.0 (0.5.0), parking_lot 0.12.5 (0.13.0),
shadow-rs 1.7.1 (2.0.0), sysinfo 0.38.4 (0.39.6) and zbus 5.19.0 (6.1.0).
cargo deny check passes. It reports the seven advisories deny.toml already
ignores, all of them unmaintained crates and none a vulnerability, and no new
one. The gate is green on an aarch64 box and on x86-1: clippy with -D warnings,
nextest, doctests, rustdoc and the nix checks that need no VM, and udeps on the
aarch64 box.
All 32 VM suites pass on KVM on x86-1 at this commit, which is the tip of the
two bumps, rdp included. latency, which measures time, passed when it ran alone;
it failed once while another suite ran on the same host.
kglobalaccel keeps the actions of a client that died, and their keys: a daemon
that is killed leaves its keys swallowed and running nothing until they are
unregistered. And kglobalaccel runs inside KWin, so a KWin that restarts forgets
every key komorebi registered. Neither should leave a user without keys.
kwin::hotkeys::release unregisters every action of the component, whatever
registered it, and komorebic hotkeys release runs it, with or without a daemon.
The NixOS module's unit runs it after the daemon stops (ExecStopPost), however
it stops, and a start already takes over what is left. The KWin backend's
NameOwnerChanged watch now also follows org.kde.kglobalaccel: when it has an
owner again, kwin::hotkeys::rebind loads the key bindings into the new one,
trying for 15 seconds.
Loading moves from the daemon's main into kwin::hotkeys::load, which keeps its
settings (Keybindings: file, format, plan mode, engine options), so that reload
can read the file again: the message ReloadHotkeys, komorebic hotkeys reload. A
new engine replaces the running one without unregistering its keys, and takes
them over as a start does, so the keys both files have stay grabbed. The message
HotkeysState answers with the engine's mode, the combos it holds and whether a
chain is pending (null without an engine); komorebic hotkeys state prints it
with the actions kglobalaccel has for the component. komorebic now depends on
komorebi for the two calls to kglobalaccel, and on serde_json for the state.
Tests: the socket messages without key bindings, state and release without a
session bus, reload without a daemon, and a nixos-module-eval check of
ExecStopPost. The VM test vm-restart-survival runs the module's unit in a Plasma
session: a key works, hotkeys state shows it; with the daemon stopped by SIGSTOP
the key runs nothing until hotkeys release gives it back; after SIGKILL the
unit's journal shows the release and the restarted daemon has the key; after the
KWin process is killed, the daemon registers the key with the new KWin; and a
reload of a changed file swaps the key.
vm-keys-engine asks the engine and does not sleep. A key that is not grabbed yet
reaches the focused window, which showed as a binding that does not run, so the
suite presses one once komorebic hotkeys state answers, which it does when the
engine has made the calls of its last event: for a mode, for a chord chain
beginning and ending, and for the keys of the application that takes the focus.
It waits for the old daemon to exit before it starts the next (a daemon started
meanwhile failed to load its KWin script), and activates a window until KWin has
it (KWin lists a window before it can have the focus). The last check reloads
the key bindings with komorebic hotkeys reload while wev has the focus and every
binding is for the terminal, a start with no key to grab.
A static shortcut can only be one combo, pressed, the same in every application.
Modes, chord chains, release bindings and per-application bindings need komorebi
to hold the keys and decide what each press does: the engine of
komorebi-hotkeys, a state machine with no I/O. This commit runs it against
KWin's global shortcut daemon, which KWin 6.8 runs inside itself on the session
bus.
kwin::hotkeys owns the component komorebi in kglobalaccel, with one action per
combo, named after the combo (Alt+H). An activation of the engine is
setShortcutKeys(id, [[combo]], 6), with a doRegister the first time; a
deactivation is setInactive, after which the key reaches the focused window
again. The calls of one event are sent without waiting for each other's answers,
which Phase 0 measured at 60 calls in 2.4 ms. globalShortcutPressed and
globalShortcutReleased of /component/komorebi feed the engine;
globalShortcutRepeated is ignored, so a held key runs its binding once. A
komorebic command runs the komorebic next to the daemon with its words when they
need no shell, anything else runs through sh -c. The calls are computed by
Registry, which is pure and tested; the D-Bus part is proved by the VM test.
The engine hears the presses at the path /component/komorebi and does not ask
kglobalaccel for the object with getComponent. kglobalaccel makes the object
when the component gets its first action, so a start with no key to grab (every
binding is for an application other than the one with the focus) has none to ask
for: getComponent fails with NoSuchComponent, and an engine that asked would
never start. A signal is matched by the path it comes from, so the presses are
heard as soon as there is an object. PLATFORM.md says so.
kglobalaccel keeps the actions and the grabs of a client that was killed, so a
start is an idempotent reconcile: the actions of the component that the keymap
names are taken over and released unless the default mode wants them, the others
are unregistered. A stop releases and unregisters everything, from
KWinBackend::shutdown, which SIGINT, SIGTERM and the Stop message all go
through.
The daemon takes --keybindings FILE, --keybindings-format skhd|whkd|sxhkd (by
default from the file's name) and --keybindings-mode (the mode komorebic hotkeys
compile wrote the static shortcuts in, so that the daemon leaves those to KDE);
without --keybindings it reads the first of skhdrc, whkdrc and sxhkdrc in the
config directory (HOME_DIR, KOMOREBI_CONFIG_HOME). It logs one line about the
file and the diagnostics at debug level. An engine that cannot start is logged
and the window manager runs without keys; the fake backend runs none.
The VM test vm-keys-engine starts the daemon in a Plasma session with an skhdrc
in the config directory, presses its keys (a command, one through a shell,
komorebic retile reaching the daemon), kills the daemon and sees its keys stay
registered, starts it with another file and sees the old keys unregistered and
the new ones work, and stops it with komorebic stop, after which no key is
registered and none runs. The library gains spawn_komorebi,
kill_spawned_komorebi and shortcut_actions, and Checks.finish names the checks
that failed, since nix build shows only the last lines of a failed run. The
suite's node turns the module's unit off: the test runs daemons of its own.
komorebi users keep their bindings to komorebic in a file of skhd, whkd or
sxhkd. On Plasma under Wayland no client sees the keys of another, and the
program that runs a command on a key is KDE's global shortcut daemon, which runs
inside KWin. A binding that is one combo, pressed, the same in every
application, in a file without modes, can be a static shortcut: a .desktop file
that KWin reads by itself, that shows in System Settings and that works with no
komorebi process at all.
komorebic hotkeys compile reads a file of any of the three formats, plans it
with komorebi-hotkeys in the mode it is given (auto, engine or static; the
default is the crate's), prints the diagnostics of reading and planning, and
writes the static shortcuts to $XDG_DATA_HOME/kglobalaccel or --output,
replacing the files it wrote before and no other. --strict refuses a file with a
warning or an error before writing anything, --dry-run says what a write would
change, and --reload hands each new file to the running KWin. komorebic
export-kde-shortcuts keeps the spike's interface for an skhdrc, in the static
mode. Both are thin: the reading, the plan and the files are the crate's. The
bindings that need the engine are counted; the daemon does not run them yet.
docgen gives a subcommand of a subcommand a page of its own
(hotkeys-compile.md). KOMOREBI_CONFIG_HOME, or ~/.config/komorebi, is where
komorebic looks for a file it is not given.
komorebic check gains a section for the key bindings, between the configuration
and the desktop: the first key binding file of the config home, how many
bindings it has and where they go, the lines that cannot be read (a warning, and
the first five) and the bindings not realized as their own daemon would (a
note), and whether the generated files in the shortcut directory are the ones
hotkeys compile would write now (a warning with how many to add, to change and
to remove). --configuration-only keeps it, as it looks at files.
The VM test vm-keys-static boots a Plasma session with shortcut files that
hotkeys compile wrote into the system profile at build time, presses their keys
through QEMU's keyboard, and sees each command run, a % in one of them included;
then it writes a binding in the middle of the session with export-kde-shortcuts
--reload and presses it. The library gains Keys, which sends a combo as KDE
writes it through QEMU's sendkey.
komorebi for Windows' users bind komorebic in a whkdrc. `whkd::parse` reads it
into the same model as an skhdrc: `keys : command` bindings with the Windows
virtual key names whkd takes, per-application lists with `Ignore` and `Default`,
`.pause` and `.pause_hook`. A `#` starts a comment wherever it is, as in whkd.
whkd requires its sections in a fixed order; the reader takes them in any.
`.shell` is noted, since commands run through `sh -c`, side-specific modifiers
are approximated, and keys KDE has no key for (the keypad's) are left out with a
warning.
whkd's own parser, `whkd-parser`, is not used to read: it requires `.shell` to
be pwsh, powershell or cmd and panics on anything else, and it matches Windows
process names. It is under the same license as komorebi, so it is a
dev-dependency at v0.2.10, and a test reads every whkdrc of the corpus and the
example of whkd's README with both parsers and requires the same bindings.
deny.toml allows its git source.
The corpus test now also reads the whkdrc fixtures, and checks that the files of
a case read into the same keymap, apart from where each binding was written.
komorebi users keep their bindings to komorebic in the file of a hotkey daemon:
an skhdrc on macOS, a whkdrc on Windows, an sxhkdrc on X11. On KDE Plasma the
program that runs a command on a key is KDE's global shortcut daemon, so the
bindings have to be read and turned into shortcuts. This adds
`komorebi-hotkeys`, a crate of its own that does not depend on the daemon, with
the model all three formats are read into: a `Keymap` of modes of bindings, each
a chord, an edge and a body (a command, a mode switch, or one action per
application), and `Diagnostic`s with spans.
A key is a Qt key code with the modifier flags or'ed in, which is what
kglobalacceld stores, so the same key is the same value whatever file named it.
`qt::key_to_qt` knows the three vocabularies: skhd's literals and macOS
keycodes, whkd's virtual key names and sxhkd's X keysyms, including the XF86
media keys. `Combo` applies the shifted symbol rule measured on KWin 6.8.1
(Alt+Shift+1 arrives as Alt+!), and its fields are private so that no combo
skips it; a `compile_fail` example proves it. Positions are read as on a US
layout.
The fixture corpus writes each case in every format that can express it; the
parsers are written against it. The flake's source filter keeps the fixtures, so
that the nix checks can read them.
A theme gives the borders their colors, in place of choosing the color of each
kind of window: a Catppuccin theme, a Base16 theme or a palette of one's own,
with the color it gives each of the six kinds of border and its default for the
ones it does not name. The Theme message puts the six colors where the border
manager keeps them and asks for the look again. The palette of a picture is made
by the same generator as komorebi for Windows, which takes seconds for a large
picture, so each one is kept in the cache directory, in a file named for the
picture, a hash of its path, size and time of last change, and the variant: the
palette of a picture painted over, or of one of the same name in another folder,
is made again and not mistaken.
The themes come from komorebi-themes at the revision komorebi-layouts is at. It
pulls egui for its types, which the license list, the sources and the advisories
of deny.toml now have to allow, and the colors the border manager keeps are the
themes' own again.
A subscriber that asks for the changes of the state only is sent a Theme message
all the same: the colors of the borders are not part of the state, so a theme
never changes it, and the subscriber would not hear of the colors at all.
komorebi.json takes border_colours, a color for each kind of window as red,
green and blue or as a hex color, and a theme, which wins over them.
komorebi.json is described as a JSON Schema, made from the Rust types that read
it: komorebic prints the schema of komorebi.json, of an applications.json, of a
notification and of a message, and the daemon answers the same four questions on
its socket. schema.json and schema.asc.json are committed (just jsonschema).
The module's config option is typed from the schema: schemars-nixgen makes
nix/komorebi-options.nix from schema.json (just nixgen), so a layout komorebi
does not have, or a value of the wrong kind, does not evaluate. The module
writes config as a komorebi.json and gives it to the daemon with --config.
configFile and the file the module writes are checked against the schema when
the system is built. applicationSpecificConfiguration takes files from GitHub,
and there are none by default, since no community file for Plasma is known.
Two checks keep the generated files honest without the tools that make them.
schema-fresh compares the schemas with what komorebic prints, and the hash on
the first line of the options with the schema. nix-options-validation evaluates
the options against the example configuration, written in Nix and in JSON.
nixos-module-eval also evaluates the module with the sources turned off: with
applicationSpecificConfiguration.enable = false the sources are left out of the
config that is written, and with no config either there is nothing to write and
the unit passes no --config. ARCHITECTURE has the guarantee row that names the
two checks.
The daemon takes its settings from a komorebi.json: the file --config names, or
the one in the config home ($KOMOREBI_CONFIG_HOME, else ~/.config/komorebi).
Without a file it runs on its defaults, as before.
StaticConfig reads the file in three steps, because the monitors are known only
after the backend has been asked. preload makes the window manager and applies
the settings that are process-wide, postload gives each monitor and workspace
the entry of its place (by the index display_index_preferences gives a monitor's
serial number id, else in order), and main asks KWin for a desktop for each
workspace the file lists. A watcher on the file sends the daemon a
ReloadStaticConfiguration about two seconds after it changes, whether an editor
wrote the file in place or renamed a new one over it, and a file that does not
parse changes nothing.
A workspace can change its layout, its work area offset and its layout options
with the number of its containers, from the file or with komorebic
(workspace-layout-rule, its named form and the two that clear), and
layout_defaults gives every workspace of a layout the options it sets none of.
One file is meant to serve every platform, so a key that does nothing on Plasma
is accepted and ignored. config_parity checks the example and the files of other
platforms; the keys of features that are not in yet are listed there, and the
test fails when a list names a key that is read.
The file also sets what the stacks, the float override, the floating layer and
monocle set while the daemon runs: window_container_behaviour, with its rules by
container count in a workspace, float_override, float_override_placement,
floating_layer_behaviour (for a monitor and a workspace too),
floating_layer_placement and monocle_focus_behaviour. Their features come before
this commit, but a file to put the keys in does not, so they are part of this
one.
komorebic gains configuration, generate-static-config and start --config. The
NixOS module gets configFile, which the unit gives the daemon as --config.
komorebi managed every normal window that is alive, and there was no way to keep
an application out of the layout. The rule engine decides whether a window is
managed: an ignore rule keeps the windows it matches out, unless a manage rule
matches them too, and a float rule puts a window in the floating windows of its
workspace when it opens. A rule matches a window by its title, its executable,
its class or the path of its executable. Plasma's shell and KRunner are ignored
from the start.
`komorebic ignore-rule` and `manage-rule` add a rule, `session-float-rule`
floats the focused window and the windows like it until the daemon stops,
`clear-session-float-rules` removes those rules, and `global-state` shows the
rules. The `DebugWindow` message says why a window is managed or not.
Applications often open a window before they give it its title, so the bridge
now reports a title change, and a window komorebi does not have is asked about
again when its title changes, for a rule on the title to decide.
The vm-commands sweep adds and shows rules, and a section of its own checks that
a window an ignore rule matches by its first title is tiled once it has another.
vm-kwin-events checks that a window giving itself another title reaches the
daemon as a title update.
The daemon answers on its socket, but a person has no way to write a message on
it. komorebic is that way: each subcommand that asks something of the daemon is
one message, sent with komorebi-client, and a question's answer is printed.
komorebic is a separate program because the daemon takes no input but its
socket, so a key binding, a script and a terminal all drive it the same way.
It has what the daemon can answer so far: state, monitor-information, query,
retile and stop. Around them, start runs the komorebi on the PATH in the
background and passes it what follows a --, such as --fake-backend to try it
without a compositor; log follows the daemon's log, which the daemon now writes
to a file a day in LOG_DIR as well as to stdout; data-directory shows where the
socket is; completions prints a shell completion script. The hidden docgen
writes each subcommand's help to docs/cli, in fences that name their language
and wrapped at 100 columns whatever the width of the terminal, so that the pages
are the same wherever they are generated; just docgen runs it.
The user guide starts here: docs/index.md for KDE Plasma, and a page on running
komorebi. tests/cli.rs runs each subcommand against a socket the test serves in
place of the daemon. In the VM tests the guests get komorebic next to the
daemon, and vm-core-e2e reads the daemon's state with it (the windows it
manages, the one it focuses, after KWin restarts too) and stops it with
komorebic stop, which unloads the bridge and removes the socket.
In the VM test library, Kde.state asks again, a second (times the scale) after
an empty answer or a failed command, up to 10 times. The daemon drops a query
when it cannot take hold of the window manager within a second, and komorebic
then prints an empty line and succeeds; komorebic itself gives the daemon a
second to answer and fails when that is not enough. A daemon busy with the first
windows does either on a machine that is loaded or slow, and a suite that polled
the state died on JSONDecodeError. An answer that is not JSON still fails at
once.
Until now the daemon tiled what KWin reported and nothing else could reach it.
komorebi takes its orders on a socket, as it does on Windows and on macOS: it
handles no key itself, and whatever binds keys runs a command that writes a
message.
The daemon binds komorebi.sock in $XDG_RUNTIME_DIR/komorebi (DATA_DIR), reads
one JSON SocketMessage per line on a thread per connection, and handles each
message with the window manager locked, as the thread that handles events does.
The first messages are the ones the first komorebic needs: State (the window
manager as JSON, from state.rs), MonitorInformation, Query with a StateQuery,
Retile and Stop. A question is answered on the same connection, and the answer
ends when the daemon closes it. Stop unloads the bridge from KWin, removes the
socket and exits; so does a stop signal.
The socket is bound once the daemon is connected to KWin, so a komorebi that
cannot connect, such as a second one in the same session, which the bus refuses,
leaves the socket of the one that runs alone. With --fake-backend the daemon
answers on its socket with no compositor at all, which is how the tests run it.
komorebi-client is the other end: send_message, send_batch and send_query, and
the types the messages are made of. Its doctests serve the socket themselves,
tests/daemon.rs runs it against a window manager over the fake desktop, and
komorebi/tests/socket.rs runs the daemon binary from its start to a Stop.
KWin can go away under a running daemon while the session bus stays: it crashed,
or kwin_wayland --replace took its place. Its windows go with it, and so does
the bridge, which would have reported them closing; in the spikes the old
windows vanished without a single removed event. What the daemon does see is the
bus, so the thread that serves the connection watches NameOwnerChanged for
org.kde.KWin. When the name loses its owner, Table::compositor_lost marks every
window closed and returns a Destroy for each, so the window manager takes them
out of its layout, and their ids are not given out again. When the name has an
owner again, the daemon loads the bridge into the new KWin, trying for up to 15
seconds while KWin is not ready for it, and the bridge reports the new desktop
in a snapshot.
zbus does not end a held call when its caller leaves the bus, so the Poll of the
old bridge is still waiting when the new bridge calls, and it was woken first:
it took the commands for the new KWin and answered them to nobody.
vm-kwin-restart lost the tile of a window opened in the third KWin that way in
one run of three, and a recording of the bus showed the batch going to the old
bridge. A Poll held since before KWin last left the bus is answered with nothing
and passes its wake-up on to the next call, which a unit test holds.
The VM suite vm-kwin-restart (from the spike's spike7-resync) restarts the
headless KWin twice under a running daemon, through Kde.restart_kwin in the VM
library, and checks that the daemon sees KWin go, takes the old windows out of
its layout, loads the bridge into the new KWin, and tiles the windows that open
there.
KWin has no protocol that lets another process read or set the geometry of a
window, but its scripting API has both, and a signal for every change the window
manager follows. So the KWin backend is in two parts: bridge.js, a script the
daemon loads into KWin, which reports what KWin sees and holds no window manager
logic, and a D-Bus service in the daemon, org.komorebi.Bridge, which the script
calls with every event as a JSON string.
Every decision about what an event means is made by the table in kwin::state,
which is pure and is unit-tested with the JSON the script sends: which windows
may be managed, the ids komorebi gives KWin's windows, and the Show, Destroy,
FocusChange, Minimize and Restore events of the window manager. Events reach the
window manager in the order KWin raised them: the Event interface handles its
calls inline (zbus reordered them otherwise), and one channel and one thread,
window_manager_event_listener and listen_for_events, carry them on.
Commands go to the script as the answer to a Poll call that the script always
has open and the daemon holds until it has something to send: in the spikes this
was as fast as any other channel, and it makes one call every 10 seconds when
nothing happens. Here the only command is the cursor query. Every command of the
Backend trait is refused with "the KWin backend only reads the desktop", so the
daemon follows KWin and moves nothing.
The bridge is JavaScript that KWin runs as it is, and it gets types and tests
without a JavaScript build chain. tsc checks it as strict TypeScript through
JSDoc types of every message, against kwin-api.d.ts, a declaration of the part
of KWin's scripting API it uses, in which every property is read-only and
callDBus takes one string. bridge.test.js runs it under node --test against a
fake KWin in a node:vm context. The messages of a session are JSON fixtures that
both sides' tests read: the bridge's tests check that it sends those events, and
the table's and the backend's tests read the same events and commands, so a
message cannot change on one side alone. just check-js and the flake's
komorebi-bridge-js check run both, with nodejs and typescript from nixpkgs;
there is no package.json.
The daemon now connects to KWin by default, and --fake-backend is hidden. The VM
suite vm-kwin-events starts it against a headless KWin and checks, from its
debug log, that the script loads and reports what was open, that KWin's signals
arrive as events and in order, and that stopping the daemon unloads the script.
docs/dev/PLATFORM.md starts with what komorebi needs from Plasma.
zbus 5's zvariant, zbus_names and zvariant_derive are flagged unmaintained,
since their code moved into zbus 6; deny.toml ignores the three advisories.
With the model in place, the window manager can do its job: load the desktop
from the backend, tile it, and keep it tiled as the compositor reports what
happens to windows. The events are the Windows implementation's, each about one
window and carrying it: a window is shown, destroyed, given the focus, minimized
or restored. A new window gets a container of its own after the focused one, a
closed one leaves whichever workspace has it, a window the user minimizes is out
of the layout until it is restored, and every event ends with the workspace in
front tiled again.
At startup every monitor is loaded, and every open window that is normal, alive
and not minimized goes on the monitor its middle is on. An index from each
managed window to its monitor and workspace is rebuilt around every event, so
that the first event komorebi sees can be the close of a window it picked up at
startup.
Whether a window is managed is, for now, only whether the compositor calls it a
normal window and it is alive; the rules that ignore, manage or float
applications come later. When a window closes, komorebi leaves the choice of the
next focus to KWin unless KWin focuses nothing, so the two do not fight.
The daemon runs this against a backend chosen on the command line.
--fake-backend, an in-memory desktop with one monitor, is the only one, and
without it komorebi exits with a usage error instead of pretending to manage a
desktop it cannot reach.
The window manager tiles a tree of four levels: monitors, each with a ring of
workspaces, each with a ring of containers, each with a ring of windows. It is
the model komorebi has on Windows and macOS, and the first thing the daemon
keeps that the backend does not give it.
A window is a Copy handle that carries the compositor's id and asks the backend
for everything else, so moving one is a call on the handle and needs no context
passed to it. A workspace tiles itself: the layout gives each container a tile
of the work area its monitor hands it, less the offset and the padding, and
every window of the container is asked to move there. A locked container keeps
its index because the workspace inserts, removes and swaps its containers
through LockableSequence.
This is tiling only. Stacks, monocle, maximize, the floating layer, resize
adjustments, placement rules and the link between workspaces and KWin's desktops
come with the commits that use them. impl_ring_elements!, which gives every
holder of a ring the same accessors, lands here with its first users.
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.
Configuration, commands and the layout code all speak in the same small types:
how a new window is added to a workspace, what happens to a window that crosses
a monitor boundary, which kind of window a border colour is for, how an
application is matched by what it calls itself. They are defined once, in
`core`, with the layout types re-exported next to them so that the rest of the
crate imports from one place.
The layouts themselves are `komorebi-layouts` from komorebi, a git dependency as
in komorebi for Mac, at f08aa55 (the tip of master when this was written). That
revision has the fix for resizing a tile by dragging its edge
(LGUG2Z/komorebi#1765) and the placement helpers this port uses, so nothing is
vendored. The crate pulls in serde_yaml, which has an advisory
(RUSTSEC-2024-0449, unmaintained unsafe-libyaml) for reading the Windows-only
custom layouts; nothing here calls it, and deny.toml says so where it ignores
the advisory.
The module also has what the rest is built from. `Ring` is a list with one
focused element, which monitors, workspaces and containers each keep their
children in; it has the unit tests and the example upstream's version never had.
`lockable_sequence` keeps locked containers at their positions when others are
inserted or removed around them. `core::pathext` resolves environment variables
in configured paths, and `core::config_generation` has the rules that match an
application, with tests for the two shapes a rule takes in JSON (an object, and
a list that must all match).
Types that belong to a feature come with the feature: the socket messages, the
placement rules and the animation styles are not here. The `impl_ring_elements`
macro waits for the first type that uses it.
Every module has a `//!` summary, and the README says what the library has.
komorebi for KDE is built up as a Cargo workspace with one crate per concern,
the way komorebi and komorebi for Mac are. The first two crates are the
`komorebi` daemon, which does nothing yet but report its version, and `version`,
which owns the string every binary of the workspace reports.
`version` reads the commit from git when HEAD is on a branch, and from
COMMIT_HASH otherwise. A jj workspace leaves HEAD detached, so
`.cargo/config.toml` defaults the variable to `unknown` (nix will set the real
commit), and a plain `cargo build` in a jj checkout compiles.
The tooling files come from komorebi for Mac: the licence, deny.toml (trimmed to
the licences in use), rustfmt.toml and the toolchain file. `just check` runs the
formatting check, clippy and rustdoc with warnings denied, and the tests.
rustfmt.toml uses an unstable option, so formatting needs a nightly rustfmt. The
`rg` calls in `just lint` name a path because rg reads stdin when it is given
none and stdin is not a terminal.
The README and CONTRIBUTING.md say how to run, check and commit.