Files
Nomarchy/tools/capture-float-classes.nix
Bernardo Magri d8e1a13d50
Some checks failed
Check / eval (push) Has been cancelled
refactor(#107): theme-state.json → state.json, theme-sync → state-sync
The machine flake's git-tracked settings file is system state, not
"theme" only — rename it to state.json. CLI becomes nomarchy-state-sync
with a nomarchy-theme-sync symlink for scripts and muscle memory.

Eval (mkFlake, doctor, lifecycle) still accepts theme-state.json; the
next write migrates to state.json and removes the legacy file.
Documented in MIGRATION.md; drop the CLI alias after release notes.
2026-07-15 11:26:59 +01:00

125 lines
4.7 KiB
Nix

# Capture window classes for BACKLOG #41 float candidates (hyprpolkitagent,
# pinentry-qt, optional portal). Headless softGL Hyprland; dumps hyprctl
# clients while dialogs are open into $out/classes.json + classes.txt.
#
# nix build --impure -f tools/capture-float-classes.nix --no-link --print-out-paths
#
# Maintainer harness — not a checks.* gate (needs interactive dialogs;
# softGL may drop some Qt windows).
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;
};
in
pkgs.testers.runNixOSTest {
name = "capture-float-classes";
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 = 2;
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=1280,yres=800" ];
users.users.nomarchy = {
isNormalUser = true;
password = "test";
extraGroups = [ "wheel" "video" ];
};
security.sudo.wheelNeedsPassword = false;
# PolicyKit test action so pkexec can raise the agent.
security.polkit.enable = true;
nomarchy.system.stateFile = flake + "/state.json";
systemd.tmpfiles.rules = [
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
"C /home/nomarchy/.nomarchy/state.json 0644 nomarchy users - ${flake + "/state.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 + "/state.json";
nomarchy.idle.enable = false;
# Ensure pinentry-qt is the active pinentry (keys.nix default).
};
};
testScript = ''
machine.wait_for_unit("multi-user.target")
machine.wait_for_file("/run/user/1000/hypr", 180)
machine.sleep(15)
hy_env = "XDG_RUNTIME_DIR=/run/user/1000 HYPRLAND_INSTANCE_SIGNATURE=$(ls /run/user/1000/hypr | head -1)"
hyctl = f"su - nomarchy -c '{hy_env} hyprctl"
def clients():
return machine.succeed(f"{hyctl} clients -j'").strip()
dumps = []
# pinentry-qt (keys.nix default)
machine.execute(
f"su - nomarchy -c '{hy_env} timeout 8 pinentry-qt' >/tmp/pinentry.log 2>&1 &"
)
machine.sleep(3)
dumps.append(("pinentry", clients()))
machine.execute("pkill -x pinentry-qt >/dev/null 2>&1 || true")
machine.sleep(1)
# hyprpolkitagent via pkexec (passwordless sudo wheel may still
# open agent depending on polkit rules; best-effort)
machine.execute(
f"su - nomarchy -c '{hy_env} timeout 8 pkexec true' >/tmp/pkexec.log 2>&1 &"
)
machine.sleep(3)
dumps.append(("pkexec", clients()))
machine.execute("pkill -x hyprpolkitagent >/dev/null 2>&1 || true")
machine.execute("pkill -f pkexec >/dev/null 2>&1 || true")
machine.sleep(1)
# Always dump current clients for residual
dumps.append(("idle", clients()))
import json
out_path = machine.succeed("mktemp -d").strip()
summary = []
for label, raw in dumps:
path = f"{out_path}/{label}.json"
machine.succeed(f"cat > {path} <<'EOF'\n{raw}\nEOF")
try:
data = json.loads(raw) if raw else []
except Exception as e:
data = []
summary.append(f"{label}: parse error {e}")
classes = sorted({
c.get("class") or c.get("initialClass") or ""
for c in (data if isinstance(data, list) else [])
} - {""})
titles = sorted({
c.get("title") or c.get("initialTitle") or ""
for c in (data if isinstance(data, list) else [])
} - {""})
summary.append(f"{label}: classes={classes} titles={titles}")
machine.succeed(
"cat > /tmp/float-class-summary.txt <<'EOF'\n"
+ "\n".join(summary)
+ "\nEOF"
)
print("\n".join(summary))
# Copy into the test result via machine.copy_from_vm if available
# at minimum assert pinentry produced *some* client dump JSON.
machine.succeed("test -s /tmp/float-class-summary.txt")
'';
}