test+fix: close #14, V2 #57 live install entry, #41 float slice
All checks were successful
Check / eval (push) Successful in 2m57s

- #14: first scheduled lock bump confirmed (8fded63 nomarchy-bump Mon
  05:20 UTC 2026-07-06); remove queue item.
- #57 V2: checks.live-install-entry builds nomarchy-live HM gen and
  asserts Install Nomarchy.desktop + installer bin + menu gate.
- #41: float/center hyprpolkitagent + pinentry-qt from package app-ids;
  capture-float-classes.nix harness (softGL empty); residual portal
  class stays NEXT [blocked:hw]. HARDWARE-QUEUE confirm steps.

Verified: live-install-entry + windowrule-syntax green.
This commit is contained in:
Bernardo Magri
2026-07-10 09:20:15 +01:00
parent 5f3124d160
commit 14558ad296
6 changed files with 205 additions and 34 deletions

View File

@@ -0,0 +1,124 @@
# 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 + "/theme-state.json";
systemd.tmpfiles.rules = [
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
"C /home/nomarchy/.nomarchy/theme-state.json 0644 nomarchy users - ${flake + "/theme-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 + "/theme-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")
'';
}