mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-15 05:12:28 +01:00
554d77aed9795f2b2f013ac2925f3979791bb3ff
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
41c32aad8e |
feat(wm): layouts
Every workspace started in BSP and stayed there. `komorebic change-layout` gives the focused workspace one of the layouts of komorebi-layouts, `cycle-layout next|previous` steps through them, and `flip-layout` mirrors the tiles along an axis, on top of the flip there is. A flip mirrors the tiles, not the order of the containers, and the directions of focus and move are flipped with it, so `focus left` still goes to the tile on the left. `toggle-tiling` turns the tiling of the focused workspace off and on: while it is off, no window of the workspace is moved, and the windows that open are taken in and tiled once it is on again. `preselect-direction` reserves the place on one side of the focused window for the next window: an empty container holds it, the layout gives it a tile, and the next window takes it. `cancel-preselect` gives it back. A new VM suite, vm-commands, after the spike's m8 script, runs every layout with five, three, two and one windows against a real KWin with two outputs, and then every komorebic command there is, checking after each that the daemon is alive, answers, and has every window KWin has, and that KWin has the windows where the daemon asked. Every command that comes later adds its line to that sweep. |
||
|
|
a86e2bdc63 |
build(nix): a NixOS module for komorebi
On NixOS, running komorebi should take one flake input and one module line. The flake exports `services.komorebi` as nixosModules.default (and .komorebi). It adds the flake's overlay, which gains komorebi-full, installs komorebi and komorebic, and runs the daemon in every Plasma session as a systemd user unit, wanted by plasma-workspace.target and ordered after plasma-core.target, which Plasma orders after KWin. systemd starts a daemon that fails again; the exit status of a SIGTERM (130) counts as success, so a stopped unit is not failed. The unit's PATH is the one a terminal in the session has, instead of the few store paths NixOS gives a unit. The options are enable, package, logLevel and a read-only execStart, the command line the unit runs. There is no configFile: the daemon reads no configuration file yet, and an option that linked one would do nothing. nixos-module-eval evaluates seven scenarios as real NixOS systems and checks their assertions, the unit and execStart, with builtins.tryEval for the two that must not evaluate. Nothing in it builds a Linux package, so it runs on a Mac as well. The VM nodes import the module, so every suite runs komorebi as the module installs it. The headless node's komorebi@NAME runs execStart, which has --log-level, so the nodes set logLevel to debug and the suites stop passing it; latency, which times the daemon, keeps the default level. The session node starts the module's unit at login; render-smoke turns it off, since a tiled terminal would cover the desktop it looks at. A new fast suite, vm-module, logs in to Plasma and checks that the unit runs the daemon with the module's log level and PATH, that a window opened in the session is tiled, that a killed daemon is started again, and that a stop is not a failure. docs/installation.md covers NixOS first, and building from source on other distributions. There is no binary cache: the Komorebi license does not allow third parties to redistribute binaries. |
||
|
|
6f657e53e3 |
feat(cli): komorebic
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. |
||
|
|
4386003e30 |
feat(kwin): survive KWin restarting
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. |
||
|
|
b463191d00 |
feat(kwin): move and focus windows through a held call
With the commands of the Backend trait carried out, the daemon tiles a real KWin. A command looks up the UUID KWin knows the window by, is queued as JSON for the bridge, and returns; the held Poll call is answered with the queue, one millisecond after the first command, so that a retile reaches KWin as one batch. The bridge sets the frame, activates, raises, minimizes, restores or closes the window, and reports what it could not do. set_rect records the frame it asked for in the table, so the window manager sees it at once, and the geometry event that follows says what the client made of it. A KWin script cannot move the pointer, so the pointer follows the focus through KWin's own "Move Mouse to Focus" action, invoked over kglobalaccel once KWin reports the window active. The commands are typed in bridge.js, and the declarations of KWin's API let the bridge set a window's frame, its minimized state and the active window, raise and close it, and nothing else. The session the tests of both sides share grows a second batch: the backend's tests check that set_rect, focus, raise, minimize, unminimize and close send it, and the bridge's tests answer the bridge with it and check what KWin is asked to do, down to the command for a window that has closed, which comes back as failed. The bridge's tests also cover findWindow's fallback to KWin's list, a frame set as a plain object, and a command KWin throws on. Three VM suites check it against a real KWin. vm-core-e2e (from the spike's m2-e2e, the sections that need no komorebic) compares what the daemon asked for, read from its debug log, with what KWin has, read with kdotool: three windows inside the screen, not overlapping, sharing it, the same focused window on both sides, and the same again after a minimize, a restore and a close. vm-client-geometry (slow) has eight real clients answer the frames komorebi asks for: Qt programs and xterm take them exactly, foot sizes itself to whole cells, GTK 4 programs and Firefox keep to their minimum sizes. vm-latency (slow) times retiles from a recording of the session bus: on HVF, from the daemon's answer to the last frame KWin reports, p50 17.3 ms, p95 27.7 ms. vm-kwin-events no longer checks that nothing moves. The VM library gives session commands the test user's home and data directories rather than root's, which a GTK client was refused. The bridge's second request for a position after a resize, which matters for windows off the screen, comes with the scrolling layout. |
||
|
|
15ec3c1274 |
feat(kwin): read the desktop through a script in KWin
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. |
||
|
|
0725705298 |
ci(github): run the VM tests on KVM
`discover` asks the flake for the suites of each tier (`legacyPackages.x86_64-linux.vmTiers`) and hands them to two matrices, so a new directory under `nix/tests/vm/suites` is run without an edit to the workflow. `vm-fast` runs the fast tier on every pull request, one machine per suite, as a sandboxed `nix build` of the check. `vm-slow` runs on master, every week, and on a pull request labeled `ci:vm-slow`; it builds the driver and runs it by hand, outside the sandbox, so that the screenshots and journals of a failed run are uploaded with `if: always()`. A composite action installs nix with the store cached and, for the VM jobs, frees disk space (a Plasma guest is a closure of about 10 GB), writes the udev rule that makes /dev/kvm writable for the runner's user, checks it with `test -w`, and adds `kvm` to nix's system features. KVM on hosted runners is documented for public repositories only. Whether this private repository has it is not known: the first run of these jobs says, and if it does not, they need a different runner. `lint` no longer runs `nix flake check`: on Linux that now boots every VM suite, which a runner with no KVM cannot do. It builds the checks that need no virtual machine, and `vm-lib`, the library's own tests. CONTRIBUTING.md and docs/dev/vm-tests.md say what runs where. |
||
|
|
fec9c2773a |
build(nix): a Plasma session node for tests
A virtual KWin has no output to draw on, so every test about pixels, and later about the pointer, needs a real session. The `session` node is the headless node plus a login: plasma-login-manager logs the test user in by itself, the way nixpkgs' own `plasma6` test does, and Plasma draws on the machine's display. The choice of login manager is made in `nodes/session.nix` and nowhere else, and the file says how to switch to sddm. The node adds no display device. The test driver gives every aarch64 guest one virtio-gpu and takes screenshots of it; a second device is a second KWin output that screenshots never show, which is what a first attempt did (a blank 1280x800 console). `Kde.session` attaches to the session once the desktop is up: the shell is running, the user manager knows the Wayland display, and the splash screen has gone. The display is read from the user manager because plasma-login-manager's greeter has its own KWin and takes `wayland-0`, and the user's session gets `wayland-1`. The splash condition is there because KSplash has over a thousand colors on it, so a "not blank" check passed on the splash screen. NixOS wraps programs, which is why processes are found by their command lines: `pgrep -x plasmashell` never matches `.plasmashell-wrapped`, and an exact match for the splash would have passed because nothing matched. `Kde.shot` returns a screenshot as a Pillow image. `vm-render-smoke` is the first slow suite: it starts a session, opens a terminal, and checks the middle of the window is dark and that the desktop behind it has thousands of colors. On aarch64 under HVF five runs gave the same screenshot, with the desktop up after 34 seconds. docs/dev/vm-tests.md describes the node and the two traps. |
||
|
|
3c51b9b4bf |
build(nix): a NixOS test framework for headless KWin
The unit tests run against a fake desktop and cannot say whether KWin does what the daemon asks. This adds the other half: NixOS machines with Plasma in them, a real KWin, and a Python library to drive it. Every directory under `nix/tests/vm/suites` is a test. The generator lists the directory, so a new suite appears as `checks.<system>.vm-NAME` on Linux and as `legacyPackages.<system>.vm.NAME` everywhere, with no edit to the flake. A suite names its tier and its node and has a `test.py`; `legacyPackages.<system>.vmTiers` lists the names of each tier as JSON for the CI matrix, `vmGuests` the guests to build on a Linux builder, and `vmTcg` the same suites for a machine with no KVM. `scale` multiplies every wait: 1 for KVM, 1.5 for HVF, 8 for TCG. The headless node runs `kwin_wayland --virtual` as user units of a lingering user, one pair of units per session (`session-bus@NAME`, `kwin-virtual@NAME`), so a test can run several sessions with their own bus and socket. A virtual KWin has no output to draw on, so anything that needs pixels or a pointer needs the session node. The library, `komorebi_vm`, has `Checks` (counted checks that fail once, at the end) and `Kde` (start a session, run clients and tools inside it, read windows through kdotool, poll with `wait_for`). It depends on a small `Machine` protocol rather than on the test driver. The `vm-lib` check runs its unit tests against a fake machine, with ruff and ty. The driver type checks each `test.py` with ty, which is how a protocol that did not match `QemuMachine.execute` was caught. `vm-kwin` starts a KWin with two outputs, spawns a foot window, reads it back, starts a second session and checks that it cannot see the first. On a Mac the guest is built on a Linux builder and the driver run directly, because the macOS nix sandbox cannot give QEMU the hypervisor: `scripts/vm-mac`, `just vm NAME`. docs/dev/vm-tests.md covers this, adding a test, and the builder. |