feat(nix): placement as shared steps and KDE extras

This commit is contained in:
LGUG2Z committed 2026-10-30 12:03:01 -07:00
1 parent 0e79d4d009
commit 92aaaeb65f
2 files changed
+115 -117

No files matched your search

@@ -0,0 +1,95 @@
"""What only KDE can tell about the placement rules of the config, beside the shared `placement`.
The rules match a window by what it is called: its Wayland app id (`Class`) and its program
(`Exe`), as well as its title. The shared scenario checks the places, by number and by title; these
check that the names a rule matches are the ones a real KWin window has, and that `foot.exe` is
`foot`.
The suite runs the shared scenario, then `by_app_id` and `by_program`, with no window open.
"""
from typing import Any
from komorebi_vm.core.checks import Checks
from komorebi_vm.core.platform import Client
from komorebi_vm.kde_platform import KdePlatform
def _config(rules: Any) -> dict[str, Any]:
return {
"default_workspace_padding": 10,
"default_container_padding": 10,
"monitors": [
{
"workspaces": [
{"name": "a", "layout": "BSP", "initial_window_placement_rules": rules}
]
}
],
}
def _order(platform: KdePlatform) -> list[str]:
return [titles[0] for titles in platform.state().containers()]
def _open(platform: KdePlatform, opened: list[Client], title: str, app_id: str | None) -> None:
"""Opens a window, and waits until the daemon has put it in a container and given it the focus:
"after the one in focus" depends on it."""
opened.append(platform.spawn(title, app_id=app_id))
platform.wait_for(lambda: platform.state().managed() == len(opened), timeout=20)
platform.wait_for(lambda: platform.state().focused_title() == title, timeout=20)
def _close(platform: KdePlatform, opened: list[Client]) -> None:
for client in opened:
platform.close(client)
platform.wait_for(lambda: platform.state().managed() == 0, timeout=20)
def by_app_id(platform: KdePlatform, checks: Checks, clients: list[Client]) -> None:
"""A window with the app id `special` goes first, one titled `chat` goes last, and one no rule
matches goes after the one in focus."""
platform.restart_with(
_config(
{
"Primary": {"kind": "Class", "id": "special", "matching_strategy": "Equals"},
"Last": [{"kind": "Title", "id": "chat", "matching_strategy": "Contains"}],
}
)
)
opened: list[Client] = []
for title in ("one", "two"):
_open(platform, opened, title, None)
_open(platform, opened, "three", "special")
checks.check(
f"the window with the app id special is first ({_order(platform)})",
_order(platform) == ["three", "one", "two"],
)
_open(platform, opened, "a chat room", None)
checks.check(
f"the chat room is last ({_order(platform)})",
_order(platform) == ["three", "one", "two", "a chat room"],
)
_open(platform, opened, "four", None)
checks.check(
f"a window no rule matches goes after the one in focus, the chat room ({_order(platform)})",
_order(platform) == ["three", "one", "two", "a chat room", "four"],
)
_close(platform, opened)
def by_program(platform: KdePlatform, checks: Checks, clients: list[Client]) -> None:
"""A rule for the program `foot.exe`, the name of a Windows executable, matches `foot`, which
is what opens every window here, so every window goes to the primary tile."""
platform.restart_with(
_config({"Primary": {"kind": "Exe", "id": "foot.exe", "matching_strategy": "Equals"}})
)
opened: list[Client] = []
for title in ("a", "b", "c"):
_open(platform, opened, title, None)
checks.check(
f"foot.exe is foot, so every window is a primary one: c, b, a ({_order(platform)})",
_order(platform) == ["c", "b", "a"],
)
_close(platform, opened)
+20 -117
View File
@@ -1,17 +1,15 @@
# `initial_window_placement_rules` in a real (headless) KWin: where in the layout a window that
# opens goes. The daemon reads the rules from a `komorebi.json` it is started with, and the rules
# match a window by what it is called: its program (`Exe`), its Wayland app id (`Class`) and its
# title. `komorebi/tests/placement.rs` checks the same against the fake; this checks that the names
# the rules match are the ones a real window has, and that the places of a file are the places the
# windows get.
import json
import shlex
import time
from typing import Any
# opens goes. The daemon reads the rules from a `komorebi.json` it is started with. The shared
# scenario (`komorebi_vm.core.scenarios.placement`, which runs on Windows too) checks the places, by
# number and by title. What only a real KWin can tell is in
# `komorebi_vm.platforms.kde.extras.placement`: that the names the rules match are the ones a real
# window has (its Wayland app id, its program, and `foot.exe` being `foot`).
# `komorebi/tests/placement.rs` checks the same against the fake.
from komorebi_vm import Checks, Kde
CONFIG = "/tmp/placement.json"
from komorebi_vm.core.scenarios import placement
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 placement as kde_placement
start_all()
machine.wait_for_unit("multi-user.target")
@@ -19,113 +17,18 @@ machine.wait_for_unit("user@1000.service")
checks = Checks()
kde = Kde.headless(machine, "main", size=(1920, 1080), scale=SCALE)
opened: list[str] = []
platform = KdePlatform(kde, (1920, 1080))
platform.start_komorebi()
def order() -> list[str]:
"""The title of the window of each container of the first workspace, in the layout's order."""
return [titles[0] for titles in kde.containers()]
def open_window(title: str, app_id: str = "foot") -> None:
"""Opens a window, and waits until the daemon has put it in a container."""
kde.spawn(f"foot -a {shlex.quote(app_id)} -T {shlex.quote(title)} sleep 3000")
opened.append(title)
kde.wait_for(lambda: kde.wid(title) != "", timeout=30)
kde.wait_for(lambda: kde.managed() == len(opened), timeout=20)
# the window that has just opened has the focus once the daemon has told KWin so
kde.wait_for(lambda: kde.focused_title() == title, timeout=20)
time.sleep(0.5 * SCALE)
def fresh(rules: Any) -> None:
"""Starts the daemon again, with `rules` as the placement rules of the workspace's windows.
The windows of the run before are closed. `None` leaves the key out of the file.
"""
if kde.komorebi_running():
kde.komorebic("stop")
kde.wait_for(lambda: not kde.komorebi_running(), timeout=30)
for title in opened:
wid = kde.wid(title)
if wid != "":
kde.close(wid)
kde.wait_for(lambda: all(kde.wid(title) == "" for title in opened), timeout=30)
opened.clear()
workspace: dict[str, Any] = {"name": "a", "layout": "BSP"}
if rules is not None:
workspace["initial_window_placement_rules"] = rules
config = {
"default_workspace_padding": 10,
"default_container_padding": 10,
"monitors": [{"workspaces": [workspace]}],
}
kde.sh(f"printf %s {shlex.quote(json.dumps(config))} > {CONFIG}")
kde.start_komorebi("--config", CONFIG)
checks.check(
"the daemon answers komorebic",
kde.wait_for(lambda: kde.run("komorebic state")[0] == 0, timeout=30),
)
print("== 1. no rules: after the one in focus")
fresh(None)
for title in ("a", "b", "c"):
open_window(title)
checks.check(f"a, b, c ({order()})", order() == ["a", "b", "c"])
print("== 2. every window to the primary tile")
fresh("Primary")
for title in ("a", "b", "c"):
open_window(title)
checks.check(f"c, b, a ({order()})", order() == ["c", "b", "a"])
print("== 3. by the number of the place, counted from 1, as a number and as text")
fresh(2)
for title in ("a", "b", "c"):
open_window(title)
checks.check(f"a, c, b ({order()})", order() == ["a", "c", "b"])
fresh("2")
for title in ("a", "b", "c"):
open_window(title)
checks.check(
f"text that is a number is the same place: a, c, b ({order()})", order() == ["a", "c", "b"]
args = (platform, checks, [])
run_suite(
checks,
shared(placement.run, platform, checks),
platform_only("kde", kde_placement.by_app_id, *args),
platform_only("kde", kde_placement.by_program, *args),
)
print("== 4. by the app id and the title")
fresh(
{
"Primary": {"kind": "Class", "id": "special", "matching_strategy": "Equals"},
"Last": [{"kind": "Title", "id": "chat", "matching_strategy": "Contains"}],
}
)
for title in ("one", "two"):
open_window(title)
open_window("three", "special")
checks.check(
f"the window with the app id special is first ({order()})", order() == ["three", "one", "two"]
)
open_window("a chat room")
checks.check(
f"the chat room is last ({order()})", order() == ["three", "one", "two", "a chat room"]
)
open_window("four")
checks.check(
f"a window no rule matches goes after the one in focus, the chat room ({order()})",
order() == ["three", "one", "two", "a chat room", "four"],
)
print("== 5. by the program, with the name of a Windows executable")
fresh({"Primary": {"kind": "Exe", "id": "foot.exe", "matching_strategy": "Equals"}})
for title in ("a", "b", "c"):
open_window(title)
checks.check(
f"foot.exe is foot, so every window is a primary one: c, b, a ({order()})",
order() == ["c", "b", "a"],
)
kde.komorebic("stop")
checks.check("a clean stop", kde.wait_for(lambda: not kde.komorebi_running(), timeout=30))
kde.stop_komorebi()
checks.check("a clean stop", not kde.komorebi_running())
kde.stop()
checks.finish()