Files
Nomarchy/tools/doctor-float.nix
Bernardo Magri 545e40f4d9
Some checks failed
Check / eval (push) Has been cancelled
feat(terminal): Kitty only — drop Ghostty (#95)
Ghostty's OpenGL 4.3 floor broke the Acer (HD 4000 / 4.2). Dual-terminal
support (default Ghostty + install-time Kitty fallback) was more complexity
than it was worth.

- Remove ghostty.nix / nomarchy.ghostty.enable
- Kitty always installed and themed from theme-state
- Default nomarchy.terminal = kitty; SUPER+Return, doctor, calendar,
  what-changed use kitty --class=com.nomarchy.*
- Drop install-time glxinfo probe and mesa-demos on the installer
- Docs/REQUIREMENTS no longer list OpenGL 4.3 as a terminal floor

Verified: V0 flake check; option-docs; installer-safety; template HM has
kitty, no ghostty; menu/calendar emit classed kitty launches.
2026-07-15 09:51:27 +01:00

143 lines
5.4 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# V2 harness for the floating Doctor terminal (System Doctor /
# nomarchy-menu doctor → kitty --class=com.nomarchy.doctor).
#
# Boots headless softGL Hyprland (same stack as theme-shot), launches the
# real menu path, then asserts via hyprctl that the window is floating
# and sized (rules in modules/home/hyprland.nix). Screenshots: desktop
# (before) + doctor (after).
#
# nix build --impure -f tools/doctor-float.nix --no-link --print-out-paths
#
# Maintainer harness — not a checks.* gate (full desktop VM). Kitty under
# softGL is best-effort (MEMORY); if the client never appears the
# scripted float asserts fail loudly rather than claiming success.
let
flake = builtins.getFlake ("git+file://" + toString ../.);
inherit (flake.inputs) nixpkgs home-manager;
pkgs = import nixpkgs {
system = "x86_64-linux";
overlays = [ flake.overlays.default ];
config.allowUnfree = true;
};
slug = let s = builtins.getEnv "THEME"; in if s == "" then "boreal" else s;
in
pkgs.testers.runNixOSTest {
name = "doctor-float-${slug}";
node.pkgsReadOnly = false;
node.specialArgs = { username = "nomarchy"; };
nodes.machine = { pkgs, lib, ... }: {
imports = [ flake.nixosModules.nomarchy home-manager.nixosModules.home-manager ];
virtualisation.memorySize = 4096;
virtualisation.cores = 4;
hardware.graphics.enable = true;
environment.variables.LIBGL_ALWAYS_SOFTWARE = "1";
boot.initrd.availableKernelModules = [ "virtio_gpu" ];
virtualisation.qemu.options = [ "-vga none" "-device virtio-gpu-pci,xres=1920,yres=1080" ];
users.users.nomarchy = {
isNormalUser = true;
password = "test";
extraGroups = [ "wheel" "video" ];
};
nomarchy.system.stateFile = flake + "/themes/${slug}.json";
systemd.tmpfiles.rules = [
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
"C /home/nomarchy/.nomarchy/theme-state.json 0644 nomarchy users - ${flake + "/themes/${slug}.json"}"
];
services.greetd.settings.initial_session = {
command = "start-hyprland";
user = "nomarchy";
};
home-manager.useGlobalPkgs = true;
home-manager.users.nomarchy = {
imports = [ flake.homeModules.nomarchy ];
nomarchy.stateFile = flake + "/themes/${slug}.json";
nomarchy.idle.enable = false;
};
};
testScript = ''
import json
machine.wait_for_unit("multi-user.target")
machine.wait_for_file("/run/user/1000/hypr", 180)
hy_env = "XDG_RUNTIME_DIR=/run/user/1000 HYPRLAND_INSTANCE_SIGNATURE=$(ls /run/user/1000/hypr | head -1)"
hy = f"su - nomarchy -c '{hy_env} hyprctl"
# Wallpaper + bar settle.
machine.sleep(25)
# Confirm rules are loaded (config path, not just eval).
conf = machine.succeed(
"su - nomarchy -c 'grep -n doctor /home/nomarchy/.config/hypr/hyprland.conf "
"|| grep -n doctor ~/.config/hypr/hyprland.conf || true'"
)
assert "com.nomarchy.doctor" in conf or r"com\.nomarchy\.doctor" in conf, (
f"doctor windowrules missing from live hyprland.conf:\n{conf}"
)
# Before shot tiled desktop only.
machine.execute(f"{hy} dispatch movecursor 1912 1075' >/dev/null 2>&1")
machine.sleep(1)
machine.screenshot("desktop-before")
# Real product path: Waybar / System Doctor both call this.
machine.execute(f"{hy} dispatch exec nomarchy-menu doctor' >/dev/null 2>&1")
# Wait for the classed Kitty client (softGL may be slow).
def clients():
raw = machine.succeed(f"{hy} clients -j'").strip()
try:
return json.loads(raw) if raw else []
except Exception as e:
raise AssertionError(f"hyprctl clients -j not JSON: {e}\n{raw!r}") from e
doctor = None
for _ in range(40):
machine.sleep(1)
for c in clients():
cls = c.get("class") or c.get("initialClass") or ""
if cls == "com.nomarchy.doctor":
doctor = c
break
if doctor is not None:
break
dump = machine.succeed(f"{hy} clients -j'")
assert doctor is not None, (
"no client with class com.nomarchy.doctor after nomarchy-menu doctor "
f"(Kitty under softGL may have failed clients dump):\n{dump}"
)
assert doctor.get("floating") is True, (
f"doctor window is not floating: {json.dumps(doctor, indent=2)}"
)
# Product contract: float + center. Size rule is best-effort if the
# client geometry wins; assert center from the actual size.
at = doctor.get("at") or [0, 0]
size = doctor.get("size") or [0, 0]
w, h = int(size[0]), int(size[1])
x, y = int(at[0]), int(at[1])
assert w >= 400 and h >= 300, f"doctor window too small: {doctor}"
mx, my = x + w / 2, y + h / 2
assert abs(mx - 960) < 250, f"doctor not horizontally centered (mid={mx}): {doctor}"
assert abs(my - 540) < 250, f"doctor not vertically centered (mid={my}): {doctor}"
machine.screenshot("doctor-float")
# Leave evidence in the guest for copy-out debugging.
machine.succeed(
"cat > /tmp/doctor-float-assert.txt <<'EOF'\n"
+ json.dumps(
{
"class": doctor.get("class") or doctor.get("initialClass"),
"floating": doctor.get("floating"),
"at": doctor.get("at"),
"size": doctor.get("size"),
"title": doctor.get("title"),
},
indent=2,
)
+ "\nEOF"
)
'';
}