feat(nix): every layout is tried with three, two and one windows

commands.layouts and layouts.run close windows from the end and open them
again with their own titles. The KDE extra layouts_by_count keeps only what
reads the daemon's log: KWin has each window where the daemon last asked.
This commit is contained in:
LGUG2Z committed 2026-10-30 12:52:20 -07:00
1 parent 7d32b02ec8
commit dc710ce419
5 files changed
+131 -48

No files matched your search

+4 -4
View File
@@ -42,7 +42,7 @@ The suite had its own layout loop (section 2) and then called the shared `layout
|---|---|
| 1, the daemon answers with both outputs; five windows | extra `setup` |
| 2, every layout with five windows: inside the outputs, apart, healthy | shared `layouts`; the extra repeats it at five windows, see below |
| 2, every layout with three, two and one windows | extra `layouts_by_count` |
| 2, every layout with three, two and one windows | shared `commands.layouts` and `layouts.run` (they close windows from the end and open them again); the extra still goes through the counts for the log check |
| 2, each window where the daemon last asked KWin for it | extra `layouts_by_count` (KDE only: reads the daemon's log) |
| 3, the layouts and the sweep of commands | shared `layouts` and `sweep` |
| 3, `replace-configuration` made the window manager from the file | extra `replace_configuration` |
@@ -52,9 +52,9 @@ The suite had its own layout loop (section 2) and then called the shared `layout
| 4, `komorebic query ...`, `quick-save-resize`, `save-resize` | extra `queries` (candidates for the shared list) |
| 5, every window is where the daemon last asked; a clean stop | extra `at_the_end` |
The extra's layout loop still goes through five windows, for the daemon's log check at that count;
it states its overlap verdict for five windows too, so that one is made twice. Dropping it from the
extra would lose the five-window `where asked` check, which is KDE's.
The extra's layout loop still goes through five, three, two and one windows, for the daemon's log
check at each count. It no longer states an overlap verdict: the shared `layouts` makes that one at
every count, with the extra's own check left to say only where KWin has the windows.
## drag-resize
@@ -10,6 +10,7 @@ command does is checked elsewhere; this says none of them breaks a running daemo
from collections.abc import Sequence
from komorebi_vm.core.checks import Checks
from komorebi_vm.core.geometry import Rect
from komorebi_vm.core.platform import Client, Platform
LAYOUTS: tuple[str, ...] = (
@@ -285,15 +286,18 @@ def unhealthy(platform: Platform, clients: list[Client]) -> str | None:
return None
def layouts(platform: Platform, checks: Checks, clients: list[Client]) -> None:
"""Changes to every layout, and checks that the windows stay inside the work area and apart
from each other, and that the daemon stays healthy. The scrolling layout is a strip whose
columns can be past the edge, so its frames are not looked at."""
area = platform.work_area()
FEWER = (3, 2, 1)
"""The counts of windows below the one `layouts` starts with that it tries every layout with."""
def _each_layout(
platform: Platform, checks: Checks, clients: list[Client], label: str, area: Rect
) -> None:
"""Changes to every layout, with `clients` the windows there are, and checks them."""
for layout in LAYOUTS:
status, out = platform.komorebic_status("change-layout", layout)
if status != 0 and NOT_A_COMMAND in out:
checks.skip(f"change-layout {layout}", "the port has no such layout")
checks.skip(f"change-layout {layout}{label}", "the port has no such layout")
continue
problems = []
if status != 0:
@@ -313,7 +317,45 @@ def layouts(platform: Platform, checks: Checks, clients: list[Client]) -> None:
for b in titles[i + 1 :]
if frames[a].overlaps(frames[b])
]
checks.check(f"change-layout {layout}: " + ("; ".join(problems) or "ok"), not problems)
checks.check(
f"change-layout {layout}{label}: " + ("; ".join(problems) or "ok"), not problems
)
def layouts(platform: Platform, checks: Checks, clients: list[Client]) -> None:
"""Changes to every layout, and checks that the windows stay inside the work area and apart
from each other, and that the daemon stays healthy. The scrolling layout is a strip whose
columns can be past the edge, so its frames are not looked at.
It does the same with three, two and one windows (those of `FEWER` that `clients` has more
than): the others are closed from the end, and opened again with their own titles at the end,
with the layout `bsp`.
"""
area = platform.work_area()
_each_layout(platform, checks, clients, "", area)
closed: list[Client] = []
for keep in (n for n in FEWER if n < len(clients)):
for client in clients[keep:]:
if client not in closed:
platform.close(client)
closed.append(client)
left = clients[:keep]
platform.wait_for(lambda n=keep: platform.state().managed() == n, timeout=20)
_each_layout(platform, checks, left, f" with {keep} windows", area)
if closed:
platform.komorebic("change-layout", "bsp")
for client in closed:
platform.spawn(client.title)
checks.check(
"the windows that were closed are opened again and managed",
platform.wait_for(
lambda: (
unhealthy(platform, clients) is None
and platform.state().managed() == len(clients)
),
timeout=30,
),
)
def sweep(
@@ -49,9 +49,42 @@ def _check_layout(platform: Platform, checks: Checks, clients: list[Client], lay
)
FEWER = (3, 2, 1)
"""The counts of windows below the one the scenario starts with that every layout is tried with."""
def _fewer(platform: Platform, checks: Checks, clients: list[Client]) -> None:
"""Every layout with fewer windows: those of `FEWER` that `clients` has more than, the others
closed from the end, and opened again with their own titles at the end."""
closed: list[Client] = []
for keep in (n for n in FEWER if n < len(clients)):
for client in clients[keep:]:
if client not in closed:
platform.close(client)
closed.append(client)
left = clients[:keep]
checks.check(
f"{keep} windows are left managed",
platform.wait_for(lambda n=keep: platform.state().managed() == n, timeout=20),
)
for layout in LAYOUTS:
_check_layout(platform, checks, left, layout)
platform.komorebic("change-layout", "bsp")
for client in closed:
platform.spawn(client.title)
checks.check(
"the windows that were closed are opened again and tiled",
platform.wait_for(
lambda: platform.state().managed() == len(clients) and tiled(platform, clients),
timeout=30,
),
)
def run(platform: Platform, checks: Checks, clients: list[Client]) -> None:
"""Changes the layout of the first workspace to each of `LAYOUTS` with `clients`, which are
tiled windows in a container each, and then with two windows more, which it closes.
tiled windows in a container each, and then with two windows more, which it closes, and with
three, two and one windows of `clients`, which it closes, and opens again.
The layout is `bsp` again at the end.
"""
@@ -77,3 +110,4 @@ def run(platform: Platform, checks: Checks, clients: list[Client]) -> None:
timeout=20,
),
)
_fewer(platform, checks, clients)
@@ -1,9 +1,9 @@
"""What only KDE can tell about the commands, round the shared `commands` scenarios.
Every window the daemon has is a window KWin has, and the frame the daemon last asked KWin for is
the one KWin carries out. The shared scenarios check the layouts with five windows and that every
command is understood and leaves the daemon healthy, as the operating system sees it; what is here
reads KWin (`kdotool`) and the daemon's log.
the one KWin carries out. The shared scenarios check the layouts with five, three, two and one
windows and that every command is understood and leaves the daemon healthy, as the operating system
sees it; what is here reads KWin (`kdotool`) and the daemon's log.
The suite runs `setup`, `layouts_by_count`, the shared `layouts` and `sweep`,
`replace_configuration`, the shared `handover`, `title_rule` and `prints`, `queries` and
@@ -60,29 +60,6 @@ def healthy(platform: KdePlatform) -> int:
return 0
def _outside_or_overlapping(platform: KdePlatform) -> list[str]:
"""The windows that are outside the outputs, or on another window (2 pixels are allowed)."""
kde = platform.kde
width, height = platform.size
rects = {wid: kde.rect(wid) for wid in _live_windows(platform)}
bad = []
for wid, r in rects.items():
if r.x < -1 or r.y < -1 or r.x + r.w > width + 1 or r.y + r.h > height + 1:
bad.append(f"{kde.title(wid)} outside at {r}")
ids = list(rects)
for i, a in enumerate(ids):
for b in ids[i + 1 :]:
ra, rb = rects[a], rects[b]
if (
ra.x < rb.x + rb.w - 2
and rb.x < ra.x + ra.w - 2
and ra.y < rb.y + rb.h - 2
and rb.y < ra.y + ra.h - 2
):
bad.append(f"{kde.title(a)} on {kde.title(b)}")
return bad
def _open_window(platform: KdePlatform, n: int) -> None:
platform.spawn(f"win{n}")
@@ -128,11 +105,12 @@ def setup(platform: KdePlatform, checks: Checks, clients: list[Client]) -> None:
def layouts_by_count(platform: KdePlatform, checks: Checks, clients: list[Client]) -> None:
"""Every layout with five, three, two and one windows: the windows are in their outputs and
apart, the daemon is healthy, and KWin has each window where the daemon last asked for it.
"""Every layout with five, three, two and one windows: KWin has each window where the daemon
last asked for it, and the daemon is healthy.
The shared `layouts` checks five windows. The windows that were closed are opened again, and
the layout is `bsp` at the end.
The shared `layouts` checks that the windows are inside the outputs and apart, at every one of
those counts. The windows that were closed are opened again, and the layout is `bsp` at the
end.
"""
kde = platform.kde
for layout in LAYOUTS:
@@ -147,14 +125,16 @@ def layouts_by_count(platform: KdePlatform, checks: Checks, clients: list[Client
lambda: all(_where_asked(platform, w) is None for w in _live_windows(platform)),
timeout=10,
)
# the scrolling layout is a strip whose columns can be past the edge of the output
if layout != "scrolling":
verdict += [f"{keep}w: {bad}" for bad in _outside_or_overlapping(platform)]
verdict += [
f"{keep}w: {bad}"
for w in _live_windows(platform)
if (bad := _where_asked(platform, w))
]
if reason := healthy(platform):
verdict.append(f"{keep}w: not healthy ({reason})")
checks.check(
f"{layout}: the windows are in their outputs and do not overlap, and the daemon is"
" healthy" + "".join(f"\n {v}" for v in verdict),
f"{layout}: KWin has every window where the daemon last asked, with 5, 3, 2 and 1"
" windows" + "".join(f"\n {v}" for v in verdict),
verdict == [],
)
for n in range(kde.managed() + 1, len(clients) + 1):
+28 -1
View File
@@ -2,7 +2,8 @@
are wrong in one way."""
from komorebi_vm.core.checks import Checks
from komorebi_vm.core.scenarios import handover, title_rule
from komorebi_vm.core.geometry import Rect
from komorebi_vm.core.scenarios import commands, handover, title_rule
from tests.fake_platform import FakePlatform
@@ -62,3 +63,29 @@ def test_a_daemon_that_does_not_manage_again_fails_handover() -> None:
clients = opened(platform, checks)
handover.run(platform, checks, clients)
assert any("manage: " in f for f in checks.failures)
def test_every_layout_is_tried_with_fewer_windows_and_the_windows_are_opened_again() -> None:
platform = FakePlatform()
checks = Checks()
clients = opened(platform, checks)
base = checks.passed
commands.layouts(platform, checks, clients)
assert checks.failed == 0, checks.failures
assert checks.passed - base == len(commands.LAYOUTS) * 3 + 1
assert platform.state().managed() == len(clients)
def test_a_layout_that_overlaps_two_windows_fails_commands_layouts() -> None:
class Overlapping(FakePlatform):
def komorebic(self, *args: str) -> str:
out = super().komorebic(*args)
if args == ("change-layout", "grid"):
self.tiles = [Rect(0, 0, 1000, 1080)] * len(self.containers)
return out
platform = Overlapping()
checks = Checks()
clients = opened(platform, checks)
commands.layouts(platform, checks, clients)
assert any("grid with 2 windows" in f for f in checks.failures), checks.failures