refactor(nix): a platform-neutral core in the VM library

Checks, Rect and the pixel helpers depend on no compositor. They move to
komorebi_vm.core, which may not import komorebi_vm.kde, so that the package
can go to the Windows port unchanged. komorebi_vm re-exports them and no
suite changes. This is the first step of the cross-platform e2e plan.
This commit is contained in:
LGUG2Z committed 2026-10-30 12:02:55 -07:00
1 parent 9b90c508ee
commit a37a9faf3d
11 files changed
+54 -40

No files matched your search

+4
View File
@@ -139,6 +139,10 @@ check also runs `ruff` and `ty` over it. The driver type checks every
`test.py` with `ty` and `ruff` too, so a typo in a suite fails the build, not
the run.
- The package `komorebi_vm.core` is what does not depend on the platform: `Checks`,
`Rect` and the pixel helpers. It imports nothing of KDE, so that it can move to
the Windows port unchanged. `komorebi_vm` re-exports them, so that a suite
imports them from the same place as before.
- `Checks` counts checks and fails once, at the end, so that one failure does
not hide the ones after it. `finish()` prints `passed: N failed: M`, and its
error names the checks that failed, which `nix build` shows in the last lines
+7 -5
View File
@@ -2,7 +2,7 @@
A suite imports what it needs from here:
* [`Checks`](komorebi_vm.checks.Checks) counts checks and fails at the end.
* [`Checks`](komorebi_vm.core.checks.Checks) counts checks and fails at the end.
* [`Kde`](komorebi_vm.kde.Kde) is a KWin session inside a test machine, with
the tools to look at it (`kdotool`, `busctl`, `kscreen-doctor`, screenshots)
and the komorebi daemon that runs in it.
@@ -11,7 +11,7 @@ A suite imports what it needs from here:
* [`Keys`](komorebi_vm.keys.Keys) presses keys on the machine's keyboard.
* [`daemon`](komorebi_vm.daemon) reads what the daemon logs about the events it
handles and the frames it asks for.
* [`pixels`](komorebi_vm.pixels) reads a screenshot: the color of a pixel, and
* [`pixels`](komorebi_vm.core.pixels) reads a screenshot: the color of a pixel, and
the ring around a window.
* [`latency`](komorebi_vm.latency) times the daemon's commands from a recording
of the session bus.
@@ -25,9 +25,11 @@ protocol, not on the test driver, so `nix/tests/vm/lib/tests` can run it
against a fake machine (the `vm-lib` check does).
"""
from komorebi_vm import daemon, latency, pixels, uinput
from komorebi_vm.checks import Checks
from komorebi_vm.kde import Kde, Outputs, Rect
from komorebi_vm import daemon, latency, uinput
from komorebi_vm.core import pixels
from komorebi_vm.core.checks import Checks
from komorebi_vm.core.geometry import Rect
from komorebi_vm.kde import Kde, Outputs
from komorebi_vm.keys import Keys
from komorebi_vm.machine import Machine
from komorebi_vm.pointer import Pointer
@@ -0,0 +1,5 @@
"""What does not depend on the platform: the vocabulary and the helpers a scenario is made of.
Nothing in this package may import from `komorebi_vm.kde` or know the name of a compositor,
so that it can move to the Windows port unchanged.
"""
@@ -0,0 +1,32 @@
"""Rectangles, in the pixels of a screen."""
from dataclasses import dataclass
@dataclass(frozen=True)
class Rect:
"""A window's position and size, in pixels."""
x: int
y: int
w: int
h: int
def overlaps(self, other: "Rect") -> bool:
"""Returns true if and only if the two rectangles share at least one pixel."""
return (
self.x < other.x + other.w
and other.x < self.x + self.w
and self.y < other.y + other.h
and other.y < self.y + self.h
)
def inside(self, area: "Rect", slack: int = 0) -> bool:
"""Returns true if and only if every pixel of this rectangle is a pixel of `area`, give
or take `slack` pixels at each edge."""
return (
self.x >= area.x - slack
and self.y >= area.y - slack
and self.x + self.w <= area.x + area.w + slack
and self.y + self.h <= area.y + area.h + slack
)
@@ -2,7 +2,7 @@
from PIL import Image
from komorebi_vm.kde import Rect
from komorebi_vm.core.geometry import Rect
Color = tuple[int, int, int]
+1 -1
View File
@@ -10,7 +10,7 @@ and its title, and every frame the daemon asks KWin for is a debug line.
import re
from dataclasses import dataclass
from komorebi_vm.kde import Rect
from komorebi_vm.core.geometry import Rect
_EVENT = re.compile(r"processing event: (\w+) for window (\d+) with notification (\w+)")
_ACTIVATED = re.compile(r"KWin activated window (\d+) \((.*)\)$", re.MULTILINE)
+1 -30
View File
@@ -5,43 +5,14 @@ import re
import shlex
import time
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from PIL import Image
from komorebi_vm.core.geometry import Rect
from komorebi_vm.machine import Machine
@dataclass(frozen=True)
class Rect:
"""A window's position and size, in pixels."""
x: int
y: int
w: int
h: int
def overlaps(self, other: "Rect") -> bool:
"""Returns true if and only if the two rectangles share at least one pixel."""
return (
self.x < other.x + other.w
and other.x < self.x + self.w
and self.y < other.y + other.h
and other.y < self.y + self.h
)
def inside(self, area: "Rect", slack: int = 0) -> bool:
"""Returns true if and only if every pixel of this rectangle is a pixel of `area`, give
or take `slack` pixels at each edge."""
return (
self.x >= area.x - slack
and self.y >= area.y - slack
and self.x + self.w <= area.x + area.w + slack
and self.y + self.h <= area.y + area.h + slack
)
class Outputs:
"""The outputs of a KWin session, as `kscreen-doctor` lists and changes them.
+1 -1
View File
@@ -19,7 +19,7 @@ import re
from dataclasses import dataclass
from typing import Any
from komorebi_vm.kde import Rect
from komorebi_vm.core.geometry import Rect
_HEADER = re.compile(r"^(method call|method return|signal|error) time=(\d+\.\d+) (.*)$")
_STRING = ' string "'
+1 -1
View File
@@ -10,7 +10,7 @@ requires-python = ">=3.12"
dependencies = ["pillow"]
[tool.setuptools]
packages = ["komorebi_vm"]
packages = ["komorebi_vm", "komorebi_vm.core"]
[tool.setuptools.package-data]
komorebi_vm = ["py.typed"]
+1 -1
View File
@@ -1,7 +1,7 @@
from PIL import Image, ImageDraw
from komorebi_vm import Rect
from komorebi_vm.pixels import leftmost, near, pixel, ring
from komorebi_vm.core.pixels import leftmost, near, pixel, ring
BLUE = (66, 165, 245)
GRAY = (128, 128, 128)