# Themed-desktop capture harness (item 28 slice c) — boots the full # desktop headlessly (software-GL Hyprland) at 1920x1080 and QMP-dumps # screenshots into $out: desktop.png, menu.png (rofi root open), and # btop.png (Kitty running btop, when the terminal starts under softGL). # Also asserts nomarchy-doctor-status JSON (class:bad) — the harness # seeds an untracked theme-state.json, so doctor always reports ✖. # Maintainer tool, NOT a checks.* gate (a full-desktop VM is far too # heavy for CI). Impure: THEME env picks the theme slug. # THEME= nix build --impure -f tools/theme-shot.nix --no-link --print-out-paths # The guest gets a minimal ~/.nomarchy (just the theme JSON as # theme-state.json), seeded via tmpfiles BEFORE greetd starts the # session, so the session's own exec-once wallpaper paint works — # assets come from the themesDir baked into the tool. Bar, menus AND # wallpaper render. 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 = "theme-shot-${slug}"; node.pkgsReadOnly = false; # nomarchy sets nixpkgs.config node.specialArgs = { username = "nomarchy"; }; nodes.machine = { pkgs, lib, ... }: { imports = [ flake.nixosModules.nomarchy home-manager.nixosModules.home-manager ]; virtualisation.memorySize = 4096; virtualisation.cores = 4; # Software GL for Hyprland (GLES/aquamarine) without a host GPU. 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"; # Seed the runtime state (theme-sync reads $NOMARCHY_PATH, default # ~/.nomarchy) before greetd auto-starts the session, so the # exec-once `nomarchy-theme-sync wallpaper` finds it and paints. # NixOS activation creates /home/nomarchy before systemd runs. 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; # HM must see the overlay'd node pkgs home-manager.users.nomarchy = { imports = [ flake.homeModules.nomarchy ]; nomarchy.stateFile = flake + "/themes/${slug}.json"; nomarchy.idle.enable = false; # no lock/dpms during the shot }; }; testScript = '' machine.wait_for_unit("multi-user.target") machine.wait_for_file("/run/user/1000/hypr", 180) # hyprctl dispatch helper (session env resolved from the runtime dir). hy = "su - nomarchy -c 'XDG_RUNTIME_DIR=/run/user/1000 HYPRLAND_INSTANCE_SIGNATURE=$(ls /run/user/1000/hypr | head -1) hyprctl dispatch" # The session exec-once paints the wallpaper (softGL first paint at # 1920x1080 is slow) — give it a generous settle. machine.sleep(30) # Doctor indicator contract: untracked theme-state → ✖ → status JSON # with class:bad and the 󰒡 glyph (Waybar self-shows the module). status = machine.succeed("su - nomarchy -c 'nomarchy-doctor-status'") assert '"class":"bad"' in status, f"doctor-status not bad: {status!r}" assert ("󰀨" in status or "󰒡" in status), f"doctor glyph missing: {status!r}" # Baked btop theme must be present (asset or generated) for the slug. machine.succeed("test -f /home/nomarchy/.config/btop/themes/nomarchy.theme") machine.succeed("grep -q 'theme\\[main_bg\\]' /home/nomarchy/.config/btop/themes/nomarchy.theme") # Poke custom/doctor (signal 10) so a first-poll miss at session start # doesn't leave the tripwire blank for the full 300s interval. machine.execute("su - nomarchy -c 'pkill -RTMIN+10 waybar' >/dev/null 2>&1 || true") machine.sleep(3) # Park the cursor in the bottom-right corner so it doesn't sit in the # middle of the desktop shot (that frame doubles as the theme-picker # preview.png source). machine.execute(hy + " movecursor 1912 1075' >/dev/null 2>&1") machine.sleep(1) machine.screenshot("desktop") # Open the root menu for the polish review. machine.execute(hy + " exec nomarchy-menu' >/dev/null 2>&1") machine.sleep(8) machine.screenshot("menu") # Close menu, launch btop in Kitty for the per-theme btop.theme shot. # softGL may still fail to start a GPU terminal — btop.png is best-effort; # file assertions above still prove the theme is baked into the guest. machine.execute("su - nomarchy -c 'pkill -x rofi' >/dev/null 2>&1 || true") machine.sleep(1) machine.execute( hy + " exec kitty -e btop' >/dev/null 2>&1" ) machine.sleep(12) machine.screenshot("btop") ''; }