mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-15 05:12:28 +01:00
feat(nix): commands as shared steps and KDE extras
This commit is contained in:
1 parent
b09011dce7
commit
bc5f4babc4
2 files changed
+319
-256
No files matched your search
@@ -0,0 +1,291 @@
|
||||
"""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 suite runs `setup`, `layouts_by_count`, the shared `layouts` and `sweep`,
|
||||
`replace_configuration`, `handover`, `title_rule`, the shared `prints`, `queries` and `at_the_end`.
|
||||
The daemon runs on a session with two outputs, and `clients` are five windows on the first.
|
||||
"""
|
||||
|
||||
import shlex
|
||||
|
||||
from komorebi_vm import daemon
|
||||
from komorebi_vm.core.checks import Checks
|
||||
from komorebi_vm.core.platform import Client
|
||||
from komorebi_vm.kde_platform import CELL, KdePlatform
|
||||
|
||||
OUTPUTS = 2
|
||||
LAYOUTS = (
|
||||
"bsp",
|
||||
"columns",
|
||||
"rows",
|
||||
"vertical-stack",
|
||||
"horizontal-stack",
|
||||
"ultrawide-vertical-stack",
|
||||
"grid",
|
||||
"right-main-vertical-stack",
|
||||
"scrolling",
|
||||
)
|
||||
QUERIES = (
|
||||
"focused-monitor-index",
|
||||
"focused-workspace-index",
|
||||
"focused-container-index",
|
||||
"focused-window-index",
|
||||
"focused-workspace-name",
|
||||
"focused-workspace-layout",
|
||||
"focused-container-kind",
|
||||
"version",
|
||||
)
|
||||
|
||||
|
||||
def _live_windows(platform: KdePlatform) -> list[str]:
|
||||
"""The ids of the windows KWin has: every client of the suite is a foot."""
|
||||
return platform.kde.run("kdotool search --class foot")[1].split()
|
||||
|
||||
|
||||
def healthy(platform: KdePlatform) -> int:
|
||||
"""0 when the daemon is running, answers `komorebic state` and has every window KWin has.
|
||||
|
||||
Otherwise the reason: 1 not running, 2 no state, 3 not the windows KWin has.
|
||||
"""
|
||||
kde = platform.kde
|
||||
if not kde.komorebi_running():
|
||||
return 1
|
||||
if kde.run("komorebic state")[0] != 0:
|
||||
return 2
|
||||
if kde.managed_all() != len(_live_windows(platform)):
|
||||
return 3
|
||||
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}")
|
||||
|
||||
|
||||
def _close_window(platform: KdePlatform, n: int) -> None:
|
||||
kde = platform.kde
|
||||
title = f"win{n}"
|
||||
kde.close(kde.wid(title))
|
||||
kde.wait_for(lambda: kde.wid(title) == "", timeout=10)
|
||||
|
||||
|
||||
def _where_asked(platform: KdePlatform, wid: str) -> str | None:
|
||||
"""None when KWin has the window where the daemon last asked, else what is wrong."""
|
||||
kde = platform.kde
|
||||
title = kde.title(wid)
|
||||
want, have = daemon.asked(kde.komorebi_log(), title), kde.rect(wid)
|
||||
if want is None:
|
||||
return f"{title}: the daemon never asked for a frame"
|
||||
if (have.x, have.y) != (want.x, want.y) or not (
|
||||
0 <= want.w - have.w < CELL and 0 <= want.h - have.h < CELL
|
||||
):
|
||||
return f"{title}: asked {want}, has {have}"
|
||||
return None
|
||||
|
||||
|
||||
def setup(platform: KdePlatform, checks: Checks, clients: list[Client]) -> None:
|
||||
"""The daemon answers with both outputs and has the five windows, as KWin has them."""
|
||||
kde = platform.kde
|
||||
checks.check(
|
||||
"the daemon answers komorebic, with both outputs",
|
||||
kde.wait_for(
|
||||
lambda: (
|
||||
kde.run("komorebic monitor-information")[0] == 0
|
||||
and len(kde.state()["monitors"]["elements"]) == OUTPUTS
|
||||
),
|
||||
timeout=30,
|
||||
),
|
||||
)
|
||||
checks.check(
|
||||
"five windows, and the daemon has all five",
|
||||
kde.wait_for(lambda: kde.managed() == len(clients) and healthy(platform) == 0, timeout=20),
|
||||
)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
The shared `layouts` checks five windows. The windows that were closed are opened again, and
|
||||
the layout is `bsp` at the end.
|
||||
"""
|
||||
kde = platform.kde
|
||||
for layout in LAYOUTS:
|
||||
kde.komorebic("change-layout", layout)
|
||||
verdict = []
|
||||
for keep in (5, 3, 2, 1):
|
||||
while kde.managed() > keep:
|
||||
_close_window(platform, kde.managed())
|
||||
kde.wait_for(lambda: healthy(platform) == 0, timeout=10)
|
||||
# the daemon retiles on the close; give KWin the time to carry it out
|
||||
kde.wait_for(
|
||||
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)]
|
||||
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),
|
||||
verdict == [],
|
||||
)
|
||||
for n in range(kde.managed() + 1, len(clients) + 1):
|
||||
_open_window(platform, n)
|
||||
kde.wait_for(lambda: kde.managed() == len(clients), timeout=20)
|
||||
kde.komorebic("change-layout", "bsp")
|
||||
|
||||
|
||||
def replace_configuration(platform: KdePlatform, checks: Checks, clients: list[Client]) -> None:
|
||||
"""After the shared sweep ran `replace-configuration`, the window manager is made again from
|
||||
the file, whose first workspace is named `again`."""
|
||||
first = platform.kde.state()["monitors"]["elements"][0]["workspaces"]["elements"][0]
|
||||
checks.check(
|
||||
"replace-configuration made the window manager again from the file",
|
||||
first["name"] == "again",
|
||||
)
|
||||
|
||||
|
||||
def handover(platform: KdePlatform, checks: Checks, clients: list[Client]) -> None:
|
||||
"""`minimize`, `unmanage` and `manage` on the focused window: the daemon lets go of it, and
|
||||
takes it up again.
|
||||
|
||||
They take a window out of the daemon's hands, so the shared sweep does not run them.
|
||||
"""
|
||||
kde = platform.kde
|
||||
focused = kde.active()
|
||||
title = kde.title(focused)
|
||||
before = kde.managed()
|
||||
kde.wait_for(lambda: kde.focused_title() == title, timeout=10)
|
||||
kde.komorebic("minimize")
|
||||
checks.check(
|
||||
f"minimize: the daemon lets go of {title} when KWin minimizes it",
|
||||
kde.wait_for(lambda: kde.managed() == before - 1, timeout=10) and kde.wid(title) != "",
|
||||
)
|
||||
kde.activate(focused)
|
||||
checks.check(
|
||||
f"{title}, restored, is tiled again",
|
||||
kde.wait_for(lambda: kde.managed() == before and healthy(platform) == 0, timeout=10),
|
||||
)
|
||||
kde.wait_for(lambda: kde.focused_title() == title, timeout=10)
|
||||
kde.komorebic("unmanage")
|
||||
checks.check(
|
||||
f"unmanage: the daemon lets go of {title}, which stays open",
|
||||
kde.wait_for(lambda: kde.managed() == before - 1, timeout=10) and kde.wid(title) != "",
|
||||
)
|
||||
width, height = platform.size[0] // OUTPUTS, platform.size[1]
|
||||
|
||||
def centered() -> bool:
|
||||
r = kde.rect(focused)
|
||||
return (
|
||||
abs(r.x + r.w // 2 - width // 2) <= CELL and abs(r.y + r.h // 2 - height // 2) <= CELL
|
||||
)
|
||||
|
||||
checks.check(f"{title} is in the middle of the work area", kde.wait_for(centered, timeout=10))
|
||||
kde.komorebic("manage")
|
||||
checks.check(
|
||||
f"manage: {title} is tiled again",
|
||||
kde.wait_for(lambda: kde.managed() == before and healthy(platform) == 0, timeout=10),
|
||||
)
|
||||
|
||||
|
||||
def title_rule(platform: KdePlatform, checks: Checks, clients: list[Client]) -> None:
|
||||
"""A rule that matches a title decides when the window has its title: a window that opens
|
||||
with a title the ignore rule matches is left alone, and tiled once it has another."""
|
||||
kde = platform.kde
|
||||
kde.komorebic("ignore-rule", "title", "pending-rule")
|
||||
before_all = kde.managed_all()
|
||||
|
||||
def ignored(title: str) -> bool:
|
||||
"""True once the daemon has logged that it leaves the window titled `title` alone."""
|
||||
return any(
|
||||
"should not be managed" in line and f"title: {title}," in line
|
||||
for line in kde.komorebi_log().splitlines()
|
||||
)
|
||||
|
||||
# foot is titled pending-rule when it opens, and gives itself another title after 4 seconds
|
||||
rename = 'sleep 4; printf "\\033]0;renamed-rule\\007"; sleep 3000'
|
||||
kde.spawn(f"foot -T pending-rule sh -c {shlex.quote(rename)}")
|
||||
checks.check(
|
||||
"a window the ignore rule matches by its title is left alone",
|
||||
kde.wait_for(lambda: ignored("pending-rule"), timeout=20)
|
||||
and kde.managed_all() == before_all,
|
||||
)
|
||||
checks.check(
|
||||
"once it has a title the rule does not match, it is tiled",
|
||||
kde.wait_for(
|
||||
lambda: kde.wid("renamed-rule") != "" and kde.managed_all() == before_all + 1,
|
||||
timeout=20,
|
||||
)
|
||||
and healthy(platform) == 0,
|
||||
)
|
||||
# the daemon's log names a window by the title it had when it was focused, which for this one
|
||||
# is the first: close it, so that the last check finds every window it looks for in the log
|
||||
kde.close(kde.wid("renamed-rule"))
|
||||
checks.check(
|
||||
"closed, it leaves komorebi",
|
||||
kde.wait_for(lambda: kde.wid("renamed-rule") == "" and healthy(platform) == 0, timeout=10),
|
||||
)
|
||||
|
||||
|
||||
def queries(platform: KdePlatform, checks: Checks, clients: list[Client]) -> None:
|
||||
"""`komorebic query` answers, and the resize commands that save and load are understood."""
|
||||
kde = platform.kde
|
||||
for query in QUERIES:
|
||||
status, out = kde.run(f"komorebic query {query}")
|
||||
checks.check(f"query {query} answers", status == 0 and out.strip() != "")
|
||||
checks.check(
|
||||
"quick-save-resize and quick-load-resize",
|
||||
kde.run("komorebic quick-save-resize && komorebic quick-load-resize")[0] == 0,
|
||||
)
|
||||
checks.check(
|
||||
"save-resize and load-resize",
|
||||
kde.run(
|
||||
"komorebic save-resize /tmp/e2e-resize.json"
|
||||
" && komorebic load-resize /tmp/e2e-resize.json"
|
||||
)[0]
|
||||
== 0,
|
||||
)
|
||||
|
||||
|
||||
def at_the_end(platform: KdePlatform, checks: Checks, clients: list[Client]) -> None:
|
||||
"""The daemon has every window KWin has, each is where the daemon last asked for it, and a
|
||||
clean stop leaves the daemon not running."""
|
||||
kde = platform.kde
|
||||
checks.check("the daemon has every window KWin has, and answers", healthy(platform) == 0)
|
||||
for wid in _live_windows(platform):
|
||||
print(f" {kde.title(wid)}: {kde.rect(wid)}")
|
||||
mismatch = [m for m in (_where_asked(platform, w) for w in _live_windows(platform)) if m]
|
||||
for m in mismatch:
|
||||
print(f" {m}")
|
||||
checks.check("every window is where the daemon last asked for it", mismatch == [])
|
||||
kde.komorebic("stop")
|
||||
checks.check("a clean stop", kde.wait_for(lambda: not kde.komorebi_running()))
|
||||
@@ -1,35 +1,23 @@
|
||||
# Every layout, and a sweep of komorebic's commands, against a real headless KWin with two outputs.
|
||||
#
|
||||
# After each command the same things have to be true: komorebi is alive, it can still say what it
|
||||
# knows (`komorebic state`), and every window it has is a window KWin has. After each layout, the
|
||||
# windows are inside the outputs and do not lie on each other. A command is also expected to be
|
||||
# understood (exit status 0). The Rust tests check what each command does against the fake; this
|
||||
# suite checks that none of them breaks the daemon in a real session, and that what the daemon
|
||||
# asks KWin for is what KWin carries out. Every command `komorebic` has is in the sweep, but
|
||||
# `minimize` and `unmanage`, which take a window out of komorebi's hands and have a section of
|
||||
# their own.
|
||||
# After each command the same things have to be true: the daemon is alive, it can still say what it
|
||||
# knows (`komorebic state`), and every window it has is a window the system has. The shared scenarios
|
||||
# (`komorebi_vm.core.scenarios.commands`, which run on Windows too) check that, for every layout with
|
||||
# five windows and for every command, and that a command is understood (exit status 0). What only
|
||||
# a real KWin can tell is in `komorebi_vm.platforms.kde.extras.commands`: every layout with fewer
|
||||
# windows, that what the daemon asks KWin for is what KWin carries out, and the commands that take
|
||||
# a window out of the daemon's hands. The Rust tests check what each command does against the fake.
|
||||
import shlex
|
||||
|
||||
from komorebi_vm import Checks, Kde, daemon
|
||||
from komorebi_vm.core.platform import Client
|
||||
from komorebi_vm import Checks, Kde
|
||||
from komorebi_vm.core.scenarios import commands
|
||||
from komorebi_vm.core.suite import platform_only, run_suite, shared
|
||||
from komorebi_vm.kde_platform import KdePlatform
|
||||
from komorebi_vm.platforms.kde.extras import commands as kde_commands
|
||||
|
||||
W, H = 1920, 1080
|
||||
OUTPUTS = 2
|
||||
# foot sizes itself to whole cells of its font, so its frame can be short of the one asked for
|
||||
CELL = 24
|
||||
LAYOUTS = [
|
||||
"bsp",
|
||||
"columns",
|
||||
"rows",
|
||||
"vertical-stack",
|
||||
"horizontal-stack",
|
||||
"ultrawide-vertical-stack",
|
||||
"grid",
|
||||
"right-main-vertical-stack",
|
||||
"scrolling",
|
||||
]
|
||||
CONFIG = "/tmp/e2e-config.json"
|
||||
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
@@ -37,247 +25,31 @@ machine.wait_for_unit("user@1000.service")
|
||||
|
||||
checks = Checks()
|
||||
kde = Kde.headless(machine, "main", size=(W, H), outputs=OUTPUTS, scale=SCALE)
|
||||
|
||||
|
||||
def live_windows() -> list[str]:
|
||||
"""The ids of the windows KWin has: every client of this suite is a foot."""
|
||||
return kde.run("kdotool search --class foot")[1].split()
|
||||
|
||||
|
||||
def healthy() -> int:
|
||||
"""0 when komorebi is running, answers `komorebic state` and has every window KWin has.
|
||||
|
||||
Otherwise the reason: 1 not running, 2 no state, 3 not the windows KWin has.
|
||||
"""
|
||||
if not kde.komorebi_running():
|
||||
return 1
|
||||
status, _ = kde.run("komorebic state")
|
||||
if status != 0:
|
||||
return 2
|
||||
if kde.managed_all() != len(live_windows()):
|
||||
return 3
|
||||
return 0
|
||||
|
||||
|
||||
def outside_or_overlapping() -> list[str]:
|
||||
"""The windows that are outside the outputs, or on another window (2 pixels are allowed)."""
|
||||
rects = {wid: kde.rect(wid) for wid in live_windows()}
|
||||
bad = []
|
||||
for wid, r in rects.items():
|
||||
if r.x < -1 or r.y < -1 or r.x + r.w > W * OUTPUTS + 1 or r.y + r.h > H + 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(n: int) -> None:
|
||||
title = f"win{n}"
|
||||
kde.spawn(f"foot -T {title} sleep 3000")
|
||||
kde.wait_for(lambda: kde.wid(title) != "", timeout=30)
|
||||
|
||||
|
||||
def close_window(n: int) -> None:
|
||||
title = f"win{n}"
|
||||
kde.close(kde.wid(title))
|
||||
kde.wait_for(lambda: kde.wid(title) == "", timeout=10)
|
||||
|
||||
|
||||
def where_asked(wid: str) -> str | None:
|
||||
"""None when KWin has the window where the daemon last asked, else what is wrong."""
|
||||
title = kde.title(wid)
|
||||
want, have = daemon.asked(kde.komorebi_log(), title), kde.rect(wid)
|
||||
if want is None:
|
||||
return f"{title}: the daemon never asked for a frame"
|
||||
if (have.x, have.y) != (want.x, want.y) or not (
|
||||
0 <= want.w - have.w < CELL and 0 <= want.h - have.h < CELL
|
||||
):
|
||||
return f"{title}: asked {want}, has {have}"
|
||||
return None
|
||||
|
||||
|
||||
print("== 1. start, and five windows on the first output")
|
||||
kde.start_komorebi()
|
||||
checks.check(
|
||||
"the daemon answers komorebic, with both outputs",
|
||||
kde.wait_for(
|
||||
lambda: kde.run("komorebic monitor-information")[0] == 0
|
||||
and len(kde.state()["monitors"]["elements"]) == OUTPUTS,
|
||||
timeout=30,
|
||||
),
|
||||
)
|
||||
for n in range(1, 6):
|
||||
open_window(n)
|
||||
checks.check(
|
||||
"five windows, and komorebi has all five",
|
||||
kde.wait_for(lambda: kde.managed() == 5 and healthy() == 0, timeout=20),
|
||||
)
|
||||
|
||||
print("== 2. every layout, with five, three, two and one windows")
|
||||
for layout in LAYOUTS:
|
||||
kde.komorebic("change-layout", layout)
|
||||
verdict = []
|
||||
for keep in (5, 3, 2, 1):
|
||||
while kde.managed() > keep:
|
||||
close_window(kde.managed())
|
||||
kde.wait_for(lambda: healthy() == 0, timeout=10)
|
||||
# the daemon retiles on the close; give KWin the time to carry it out
|
||||
kde.wait_for(lambda: all(where_asked(w) is None for w in live_windows()), 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()]
|
||||
reason = healthy()
|
||||
if reason:
|
||||
verdict.append(f"{keep}w: not healthy ({reason})")
|
||||
checks.check(
|
||||
f"{layout}: the windows are in their outputs and do not overlap, and komorebi is healthy"
|
||||
+ "".join(f"\n {v}" for v in verdict),
|
||||
verdict == [],
|
||||
)
|
||||
for n in range(kde.managed() + 1, 6):
|
||||
open_window(n)
|
||||
kde.wait_for(lambda: kde.managed() == 5, timeout=20)
|
||||
kde.komorebic("change-layout", "bsp")
|
||||
|
||||
print("== 3. a sweep of commands (each must be understood, and leave komorebi healthy)")
|
||||
platform = KdePlatform(kde, (W * OUTPUTS, H))
|
||||
clients = [Client(f"win{n}") for n in range(1, 6)]
|
||||
# the same layouts and the same commands run against kfw: komorebi_vm.core.scenarios.commands has the
|
||||
# list, and a command a port lacks is skipped there with its name
|
||||
print(" the layouts, through the neutral platform")
|
||||
commands.layouts(platform, checks, clients)
|
||||
kde.komorebic("change-layout", "bsp")
|
||||
CONFIG = "/tmp/e2e-config.json"
|
||||
|
||||
platform.start_komorebi()
|
||||
clients = [platform.spawn(f"win{n}") for n in range(1, 6)]
|
||||
kde.sh(
|
||||
"printf %s "
|
||||
+ shlex.quote('{ "monitors": [ { "workspaces": [ { "name": "again", "layout": "BSP" } ] } ] }')
|
||||
+ f" > {CONFIG}"
|
||||
)
|
||||
commands.sweep(platform, checks, clients, CONFIG)
|
||||
|
||||
first_workspace = kde.state()["monitors"]["elements"][0]["workspaces"]["elements"][0]
|
||||
checks.check(
|
||||
"replace-configuration made the window manager again from the file",
|
||||
first_workspace["name"] == "again",
|
||||
args = (platform, checks, clients)
|
||||
run_suite(
|
||||
checks,
|
||||
platform_only("kde", kde_commands.setup, *args),
|
||||
platform_only("kde", kde_commands.layouts_by_count, *args),
|
||||
shared(commands.layouts, *args),
|
||||
platform_only("kde", kde.komorebic, "change-layout", "bsp"),
|
||||
shared(commands.sweep, *args, CONFIG),
|
||||
platform_only("kde", kde_commands.replace_configuration, *args),
|
||||
platform_only("kde", kde_commands.handover, *args),
|
||||
platform_only("kde", kde_commands.title_rule, *args),
|
||||
shared(commands.prints, platform, checks),
|
||||
platform_only("kde", kde_commands.queries, *args),
|
||||
platform_only("kde", kde_commands.at_the_end, *args),
|
||||
)
|
||||
|
||||
print("== 3b. minimize, unmanage and manage the focused window")
|
||||
focused = kde.active()
|
||||
title = kde.title(focused)
|
||||
before = kde.managed()
|
||||
kde.wait_for(lambda: kde.focused_title() == title, timeout=10)
|
||||
kde.komorebic("minimize")
|
||||
checks.check(
|
||||
f"minimize: komorebi lets go of {title} when KWin minimizes it",
|
||||
kde.wait_for(lambda: kde.managed() == before - 1, timeout=10)
|
||||
and kde.wid(title) != "",
|
||||
)
|
||||
kde.activate(focused)
|
||||
checks.check(
|
||||
f"{title}, restored, is tiled again",
|
||||
kde.wait_for(lambda: kde.managed() == before and healthy() == 0, timeout=10),
|
||||
)
|
||||
kde.wait_for(lambda: kde.focused_title() == title, timeout=10)
|
||||
kde.komorebic("unmanage")
|
||||
checks.check(
|
||||
f"unmanage: komorebi lets go of {title}, which stays open",
|
||||
kde.wait_for(lambda: kde.managed() == before - 1, timeout=10) and kde.wid(title) != "",
|
||||
)
|
||||
|
||||
|
||||
def centered() -> bool:
|
||||
r = kde.rect(focused)
|
||||
return abs(r.x + r.w // 2 - W // 2) <= CELL and abs(r.y + r.h // 2 - H // 2) <= CELL
|
||||
|
||||
|
||||
checks.check(f"{title} is in the middle of the work area", kde.wait_for(centered, timeout=10))
|
||||
kde.komorebic("manage")
|
||||
checks.check(
|
||||
f"manage: {title} is tiled again",
|
||||
kde.wait_for(lambda: kde.managed() == before and healthy() == 0, timeout=10),
|
||||
)
|
||||
|
||||
print("== 3c. a rule that matches a title decides when the window has its title")
|
||||
kde.komorebic("ignore-rule", "title", "pending-rule")
|
||||
before_all = kde.managed_all()
|
||||
|
||||
|
||||
def ignored(title: str) -> bool:
|
||||
"""True once the daemon has logged that it leaves the window titled `title` alone."""
|
||||
return any(
|
||||
"should not be managed" in line and f"title: {title}," in line
|
||||
for line in kde.komorebi_log().splitlines()
|
||||
)
|
||||
|
||||
|
||||
# foot is titled pending-rule when it opens, and gives itself another title after 4 seconds
|
||||
rename = 'sleep 4; printf "\\033]0;renamed-rule\\007"; sleep 3000'
|
||||
kde.spawn(f"foot -T pending-rule sh -c {shlex.quote(rename)}")
|
||||
checks.check(
|
||||
"a window the ignore rule matches by its title is left alone",
|
||||
kde.wait_for(lambda: ignored("pending-rule"), timeout=20) and kde.managed_all() == before_all,
|
||||
)
|
||||
checks.check(
|
||||
"once it has a title the rule does not match, it is tiled",
|
||||
kde.wait_for(
|
||||
lambda: kde.wid("renamed-rule") != "" and kde.managed_all() == before_all + 1,
|
||||
timeout=20,
|
||||
)
|
||||
and healthy() == 0,
|
||||
)
|
||||
# the daemon's log names a window by the title it had when it was focused, which for this one is
|
||||
# the first: close it, so that the last check finds every window it looks for in the log
|
||||
kde.close(kde.wid("renamed-rule"))
|
||||
checks.check(
|
||||
"closed, it leaves komorebi",
|
||||
kde.wait_for(lambda: kde.wid("renamed-rule") == "" and healthy() == 0, timeout=10),
|
||||
)
|
||||
|
||||
print("== 4. what komorebic only prints")
|
||||
commands.prints(platform, checks)
|
||||
for query in [
|
||||
"focused-monitor-index",
|
||||
"focused-workspace-index",
|
||||
"focused-container-index",
|
||||
"focused-window-index",
|
||||
"focused-workspace-name",
|
||||
"focused-workspace-layout",
|
||||
"focused-container-kind",
|
||||
"version",
|
||||
]:
|
||||
status, out = kde.run(f"komorebic query {query}")
|
||||
checks.check(f"query {query} answers", status == 0 and out.strip() != "")
|
||||
checks.check(
|
||||
"quick-save-resize and quick-load-resize",
|
||||
kde.run("komorebic quick-save-resize && komorebic quick-load-resize")[0] == 0,
|
||||
)
|
||||
checks.check(
|
||||
"save-resize and load-resize",
|
||||
kde.run(
|
||||
"komorebic save-resize /tmp/e2e-resize.json && komorebic load-resize /tmp/e2e-resize.json"
|
||||
)[0]
|
||||
== 0,
|
||||
)
|
||||
|
||||
print("== 5. still all there")
|
||||
checks.check("komorebi has every window KWin has, and answers", healthy() == 0)
|
||||
for wid in live_windows():
|
||||
print(f" {kde.title(wid)}: {kde.rect(wid)}")
|
||||
mismatch = [m for m in (where_asked(w) for w in live_windows()) if m is not None]
|
||||
for m in mismatch:
|
||||
print(f" {m}")
|
||||
checks.check("every window is where komorebi last asked for it", mismatch == [])
|
||||
kde.komorebic("stop")
|
||||
checks.check("a clean stop", kde.wait_for(lambda: not kde.komorebi_running()))
|
||||
|
||||
kde.stop()
|
||||
checks.finish()
|
||||
Reference in new issue
Block a user