Commit Graph
1714 Commits
Author SHA1 Message Date
LGUG2Z 7054d02110 feat(e2e): add what only Windows checks as extras
padding (the border's 7 px), cloaking (and what stop does to it), the
config file watch, the system's window tracking behind focus follows
mouse, and a second display with a window sent to it. Each is run after
the tiling windows as windows_<name>, its checks labelled [windows].
2026-10-30 18:22:44 -07:00
LGUG2Z 2f1df7decd fix(e2e): rewrite_config only writes the file
The daemon watches the configuration file it was started with, so the
extra replace-configuration after each write hid the reload that the
scenario is meant to check.
2026-10-30 18:22:44 -07:00
LGUG2Z 30b73be36e feat(e2e): sync the neutral core from kfde 22a81d0b 2026-10-30 18:22:44 -07:00
LGUG2Z ca4744e77e feat(e2e): sync the neutral core from kfde ff599907 2026-10-30 18:22:44 -07:00
LGUG2Z d990729e14 feat(e2e): record the scope of each check in the result file
A check made inside Checks.scoped("windows") has a scope field in
result.json, next to what it already had.
2026-10-30 18:22:44 -07:00
LGUG2Z 32af5164ef feat(e2e): sync the neutral core from kfde d4f67164 2026-10-30 18:22:44 -07:00
LGUG2Z 97712f7510 feat(e2e): name the exe of a rule with its extension on Windows
kfw matches a rule's id against the exe name of the window, which has the
extension, so exe_id returns program.exe.
2026-10-30 18:22:44 -07:00
LGUG2Z 27ce93fe17 feat(e2e): sync the neutral core from kfde 13edc355 2026-10-30 18:22:44 -07:00
LGUG2Z 206fa1748a feat(e2e): add probes of padding, exe rules and locking
w1_padding.py compares kfw's padding with its frames, w1_rules_exe.py runs
the exe rules with the extension in the id, w1_lock.py moves a window
while the screen is locked.
2026-10-30 18:22:44 -07:00
LGUG2Z 275e6b24ae feat(e2e): run the scenarios kfde added on the Windows guest
focus_follows_mouse, layouts, floating, minimizing, padding, maximizing,
workspace_moves, resize_commands, promoting, flipping, pausing, tiling_off,
lifecycle_hidden, scrolling and locking take the tiling windows; the others
are run as they are.
2026-10-30 18:22:44 -07:00
LGUG2Z d9c12d9fc0 feat(e2e): the Platform methods the new scenarios need on Windows
minimize, spawn with a class or an exe name (a copy of the test window
under that name), rewrite_config (writes the file and has the daemon read
it again), lock and unlock (the lock screen and the account's password
through QMP) and move_window (SetWindowPos, corrected for the invisible
border).
2026-10-30 18:22:44 -07:00
LGUG2Z 62ba64a573 feat(e2e): let the test window take its class from --class
The default stays KomorebiTestWin. A rule that matches by class needs
windows of different classes in the same run.
2026-10-30 18:22:44 -07:00
LGUG2Z ca2bb69587 feat(e2e): sync the neutral core from kfde f07c3eb8
Takes in kfde's revs d2745b80 (layouts, floating), 22e0ed01 (minimizing),
06e16878 (padding), 59777d7d (maximizing), 789e2a51 (workspace_moves),
7420cb58 (resize_commands), b66fe560 (promoting), 6f310858 (flipping),
33697a67 (pausing), d423f99b (tiling_off), aebdf2b0 (lifecycle_hidden),
cdac999a (scrolling), a9e5bd80 (rules_class) and f07c3eb8 (rules_exe,
config_reload, locking).
2026-10-30 18:22:44 -07:00
LGUG2Z a8d0a5096d feat(e2e): add the probes behind the Windows guest's findings
Each w1_*.py prints what it saw: the pointer, a second display, hotplug,
the drag, the placement rule, focus follows mouse and what stop leaves
hidden. W1.md has the results.
2026-10-30 18:22:44 -07:00
LGUG2Z 07ef8e93d6 feat(nix): put the e2e guest tools in the devshell
qemu, swtpm, xorriso, socat, git, python with pillow and pytest, OVMF_FD
and PYTHONPATH, so the build scripts and the runner work from nix develop.
2026-10-30 18:22:44 -07:00
LGUG2Z 5840f36e1c feat(e2e): build the golden image in two layers
build-base.sh does the unattended install (about 18 minutes) and
build-golden.sh puts policies, Defender and the display driver on top of
it in about 2.5 minutes through the guest agent (provision.ps1). A qcow2
chain with a relative backing path keeps the two together.

build-ref.sh builds komorebi and komorebic of any commit, and
KOMOREBI_E2E_BINS makes nix run .#e2e-windows test those binaries.
2026-10-30 18:22:44 -07:00
LGUG2Z 9337042a0f feat(e2e): drive a Windows guest under QEMU with the neutral Platform
guest.py starts QEMU and swtpm on an overlay of the golden image, with
saved-state restore (state.py), qmp.py sends keys and the pointer, agent.py
talks to the guest agent, windows_platform.py implements the Platform
protocol and run.py runs the scenarios, one guest each.
2026-10-30 18:22:44 -07:00
LGUG2Z d2a6d34a6d fix(e2e): log what ends the guest agent's connections
A windowless program has nowhere to print a panic, so the agent
writes panics and ended connections to C:\e2e\agent.log.
2026-10-30 18:22:44 -07:00
LGUG2Z db7d8480ad feat(e2e): sync the neutral core from kfde a563bf9c 2026-10-30 18:22:44 -07:00
LGUG2Z 1874b1fc10 feat(e2e): sync the neutral core from kfde 13270678 2026-10-30 18:22:44 -07:00
LGUG2Z 52f4c96817 feat(e2e): sync the neutral core from kfde 2c7e13dd 2026-10-30 18:22:44 -07:00
LGUG2Z 632b6cc366 feat(e2e): sync the neutral core from kfde 7de53180 2026-10-30 18:22:44 -07:00
LGUG2Z 8a8674ac9b feat(e2e): sync the neutral core from kfde bf5da0e6 2026-10-30 18:22:44 -07:00
LGUG2Z 7c59f3e2c6 feat(nix): run the Windows e2e scenarios with nix run .#e2e-windows
E2E_GOLDEN names the golden image built by build-golden.sh; the komorebi,
komorebic and test window binaries, QEMU, swtpm and an OVMF with TPM support
come from the flake. Also exposes the guest agent and the test window as
packages.
2026-10-30 18:22:44 -07:00
LGUG2Z fc38fbf739 feat(e2e): sync the neutral core from kfde a1024d16 2026-10-30 18:22:44 -07:00
LGUG2Z 8c990e4e1d feat(e2e): sync the neutral core from kfde 06c2b6e2 2026-10-30 18:22:44 -07:00
LGUG2Z e20e02bd12 feat(e2e): sync the neutral core from kfde 7db3f9c5 2026-10-30 18:22:44 -07:00
LGUG2Z 3ae77df287 fix(nix): build the Windows SDK the same on every host
xwin makes case-alias symlinks on a case-sensitive filesystem and none on a
case-insensitive one, so the SDK hash depended on the host. Build with
--disable-symlinks, pin that tree's hash, add the CA bundle xwin needs, and
add the lowercase aliases the linker asks for in a separate derivation.
2026-10-30 18:22:44 -07:00
LGUG2Z 048cf3b951 feat(e2e): sync the neutral core from kfde 79f05a86 2026-10-30 18:22:44 -07:00
LGUG2Z 76918d5a88 feat(e2e): sync the neutral core from kfde 5e7e4ff1
Adds commands.unknown_monitor: a window sent to a monitor that does not
exist must stay managed. From kfde change lkprqpuv.
2026-10-30 18:22:44 -07:00
LGUG2Z d2adcc9c28 feat(e2e): sync the neutral core from kfde 8b86c8ee
The commands sweep plugs in a second display first when the platform has
only one. From kfde change xzwqolrv.
2026-10-30 18:22:44 -07:00
LGUG2Z 72681c9913 feat(e2e): sync the neutral core from kfde 934fa65a
Platform gains displays, add_display, remove_display and komorebic_status,
Checks gains skip, Snapshot gains workspace_of, and the core gains the
commands, rules and hotplug scenarios. From kfde change swulsqkl.
2026-10-30 18:22:44 -07:00
LGUG2Z 40a9aa3739 feat(e2e): sync the neutral core from kfde 57e510da
Platform gains close(client), and the core gains the monocle and closing
scenarios. From kfde change nykrktvm.
2026-10-30 18:22:44 -07:00
LGUG2Z 94333870d8 feat(e2e): sync the neutral core from kfde 0be4c6ee
The Platform protocol gains has() and a Pointer, and the core gains the
resize scenario that drags the edge two tiles share. From kfde change
wumvluop.
2026-10-30 18:22:44 -07:00
LGUG2Z b34b02814f feat(e2e): sync the neutral core from kfde 6b6b7bb5
The recorded kfw and kfde states are now fixtures read by test_recorded.py,
and the sync script copies them. The only change inside komorebi_vm.core is a
diagnostic print in the workspaces scenario. From kfde change vvplllnz.
2026-10-30 18:22:44 -07:00
LGUG2Z 5ea4057ec5 fix(e2e): give the guest agent no console window
The agent opened a console, which the daemon under test tiled as a window of
its own, so every scenario saw one window too many. Build it as a windows
subsystem program and start its children with CREATE_NO_WINDOW.
2026-10-30 18:22:44 -07:00
LGUG2Z d00c4436d0 feat(e2e): vendor the neutral core from kfde
komorebi_vm.core (Rect, Checks, pixels, Snapshot, the Platform protocol and
the first scenarios) and its unit tests, copied by kfde's scripts/sync-e2e-core
from kfde change mxoqrxnx (accdd653). kfde is the source of truth until the
core moves here; the copy is not to be edited in this tree.
2026-10-30 18:22:44 -07:00
LGUG2Z eee24dad36 feat(e2e): add the guest agent and the test window for the Windows guest
komorebi-e2e-agent runs in the interactive session of the guest and answers
the host over a forwarded TCP port: exec, spawn, os_windows (DWM frame, cloak
and focus), displays, window verbs and file transfer. komorebi-testwin is a
plain Win32 window with a stable exe name and class and a control channel on
stdin. Both cross-build from Linux and pass clippy; neither has run in a
guest yet.
2026-10-30 18:22:44 -07:00
LGUG2Z 0c6864b4bc feat(e2e): add the W0 probe for a Windows 11 guest under QEMU
An unattended install of the Windows 11 evaluation ISO under QEMU with nested
KVM on WSL2, and a boot script that times the golden image to a desktop.
Measured: install 21.6 min, desktop in 36 to 51 s, with other VMs running.
W0.md records the numbers and what was found on the way: no SMM under nested
SVM, so no secure-boot OVMF; the firmware needs TPM support for Setup to see
the TPM; and the evaluation reads as expired unless the guest clock is set
back.
2026-10-30 18:22:44 -07:00
LGUG2Z 72aa4c97ad test(border): leave the paint tests to a real desktop
They create a window and a Direct2D border and check that it is painted, which
needs a desktop to paint on. Run them with --ignored.
nightly
2026-10-30 18:21:25 -07:00
LGUG2Z aa353a7158 fix(border): do not look for a border while holding the ghosts
The border manager holds the locks of the borders while it handles a
notification and asks whether a window is ghosted as it does, which takes the
lock of the ghosts. An animation of a ghost for a window that had no border
when it was made, a new window, took the lock of the ghosts first and then
looked for the border, which takes the locks of the borders. Each waited for
the other, for good: the border manager answered no more notifications, which
piled up until they were dropped, and every animation that was landed after
that waited for its full two seconds with its ghost on screen.

Look for the border with nothing held, and remember it afterwards.
2026-10-30 16:18:33 -07:00
LGUG2Z 9922a34bd7 chore(border): say what the border manager is stuck on
A recording had the thread that handles border notifications stop answering
after it put a border in place, while the channel it reads from filled up and
every animation that landed after that waited for its full two seconds.
Nothing said where it was.

Say what step the thread is in, warn once when it has been in one for two
seconds, say when a border has been put in place, and name the animations that
did not land, with what each of them was doing.
2026-10-30 16:13:07 -07:00
LGUG2Z 961c62383d fix(border): paint a border that is shown asynchronously
With window_handling_behaviour set to Async, a window is shown by the thread
that owns it, after the call to show it has returned. A new border was
invalidated before that, and the show does not redraw it, so it stayed blank
until something else made the border manager invalidate it again, which a
window that is restored by the user does not do.

Invalidate the border from the thread of its window, which handles what it is
sent in order, after the show.
2026-10-30 16:07:49 -07:00
LGUG2Z 103e2cf4bf chore(border): trace why a border is not painted
A border that is created and shown but never painted looks like a border
that was never made. Say when one is created, set and painted, and why a
paint is skipped, along with what the border manager saw of the foreground
window when it was notified.
2026-10-30 16:02:07 -07:00
LGUG2Z 3fa3d429fa feat(wm): track a window the user maximizes or restores
A window maximized with its button or title bar fills the work area, but the
window manager only had a window as maximized when it was done by the
command, so the layout was not put back when it was restored and the rest of
the workspace was laid out around a window that took all of it.

The location change of a window of the window manager is looked at for a
change of whether it is maximized, and the window manager is told of one as
Maximize and Unmaximize, which do what the command does. What the command
itself does is already known by the time these are read.
2026-10-30 15:51:24 -07:00
LGUG2Z 365d705c75 fix(animation): land every animation of a batch on its own
The last frame of the animations of a batch was drawn one after the other
before any of them was landed, so one window that did not come back from it
kept all of them from landing: their ghosts stayed up, their borders stayed
where the animation had put them, and nothing was logged as done.

Each animation now draws its last frame and lands on a thread of its own, and
the engine waits for the batch for two seconds at most.
2026-10-30 15:45:37 -07:00
LGUG2Z 2a1716bbff docs(schema): regenerate the bar schema for the pan commands
schema.bar.json embeds the socket messages, so it gets Pan, with the PanDirection that it
takes, and PanGesture, with what the fingers of a gesture do. The other two schemas are
the same as they were.
2026-10-30 15:45:37 -07:00
LGUG2Z a65b5c231e feat(komorebic): pan the Scrolling layout with the fingers on a touchpad
komorebic pan --stream reads the gestures of fingers on a touchpad from standard input, as
whkd writes them for a swipe of three or four fingers (docs/gesture-stream.md in its
repository): a line for each begin, move, end and cancel, with the distances in centimetres,
and any field or message that it does not know ignored. The row follows the fingers while they
are down, and when they lift it comes to rest with a column against an edge of the screen,
going at the speed that the times of the moves say that the fingers were. Focus goes to the
nearest column in view if the focused one is not. A gesture that is given up, or that is
not finished when the stream stops, puts the row back to what it was.

komorebic works out nothing but what it is asked to: --sensitivity multiplies the distances,
--reversed flips the way that the row goes, and --paging takes a step for every two
centimetres that the fingers go, as komorebic pan does, and not move the row with them. Moves
that wait while one is told are added up, as whkd says that they may be.

A gesture is told to komorebi on one connection, which it handles in order, as messages
that are sent on a connection each are not. With ghost movement the pictures that cover the
windows near the screen are taken when the fingers go down, a move of the fingers sets them
where the row puts the windows and does nothing else, and when the fingers lift they glide to
where it comes to rest, over the time that an animation takes, and the windows are shown. Without
it the workspace is laid out a step at a time, as it is for a drag. Ten centimetres of the
fingers move the row the width of the screen. The golden transcript of whkd and the swipes
of its demo are fixtures of the tests that read the stream.
2026-10-30 15:45:37 -07:00
LGUG2Z a63057c232 feat(komorebic): pan the Scrolling layout to the left or the right
komorebic pan <left|right> moves the view along the row of the Scrolling layout by a column
without moving focus first, and lets it come to rest with a column against an edge of the
screen. Focus goes to the nearest column on the screen if the focused one has gone off it. It
does nothing in the other layouts.

The way that the view goes is the way that focus goes for the commands that have directions:
panning to the left brings what is to the left of the screen into view, which is to move the
row to the right.

This is a command that a wheel can be bound to, as it is with whkd:
alt + wheelup : komorebic pan left.
2026-10-30 15:45:37 -07:00
LGUG2Z dd961f50f6 fix(border): draw a border again for a window that is not where it was drawn
A border follows its window from its own thread, which draws it again when the window has
changed its size or its place, and compares the window with window_rect to see whether
it has. The border manager sets window_rect too, from its thread, to where it finds the
window when it is told that the layout has changed, which is before windows that are moved
without waiting for them (window_handling_behaviour Async) have moved, or after, and when it
is after, the border sees a window that is where it already is and is not drawn again: in
a recording of three commands that resize windows the border of the focused terminal stays
the size that it was while the window grows by 200 pixels, and those of the others follow.

A border remembers the rect that it was last drawn around, and is drawn again for a window that
is not there, whatever window_rect says.
2026-10-30 15:45:37 -07:00