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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The pictures that stand for windows while they are moved by an animation are dimmer
and softer than the windows: text in the main terminal of a recording of resize commands
has its brightest pixels at 180 where the window has 215, and half as many pixels brighter
than 140 while the picture is up. Every resize command lays out the windows that it makes
bigger or smaller, and repeating them puts a picture over each of them every time, so it
is the commands that show it.
Windows are put where they go at once while the commands that resize them are handled, as
they are with animations off, and one that is on its way somewhere is stopped where it is
first. Dragging the edge of a window to resize it is not one of them, and has the pictures
that cover the windows that follow it, as it did. Animations of other commands are as they
were.