{ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; flake-utils.url = "github:numtide/flake-utils"; rust-overlay = { url = "github:oxalica/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { nixpkgs, flake-utils, rust-overlay, ... }: flake-utils.lib.eachDefaultSystem ( system: let overlays = [ (import rust-overlay) ]; pkgs = import nixpkgs { inherit system overlays; }; runtimeLibs = with pkgs; [ libx11 libxcursor libxi libxkbcommon libxcb wayland vulkan-loader glfw ]; takeScreenshots = pkgs.writeShellApplication { name = "take-screenshots"; runtimeInputs = with pkgs; [ xorg-server imagemagick coreutils ]; text = '' set -euo pipefail if [ "$#" -lt 4 ]; then cat >&2 <<'USAGE' Usage: take-screenshots EXE NUM DELAY_START PAUSE_INBETWEEN [OUTPUT_DIR] EXE path to executable to launch NUM number of screenshots to take DELAY_START seconds to wait after launching EXE before first shot PAUSE_INBETWEEN seconds between consecutive shots OUTPUT_DIR output directory (default: current directory) USAGE exit 1 fi EXE=$1 NUM=$2 DELAY_START=$3 PAUSE=$4 OUTDIR=''${5:-.} mkdir -p "$OUTDIR" # Locate lavapipe (software Vulkan) ICD; needed because Xvfb has no GPU. LVP_ICD= for c in \ "${pkgs.mesa}/share/vulkan/icd.d/lvp_icd.x86_64.json" \ /run/opengl-driver/share/vulkan/icd.d/lvp_icd.x86_64.json do if [ -f "$c" ]; then LVP_ICD=$c; break; fi done if [ -z "$LVP_ICD" ]; then echo "take-screenshots: could not locate lavapipe Vulkan ICD" >&2 exit 1 fi # Pick a free X display. DISPLAY_NUM=99 while [ -e "/tmp/.X$DISPLAY_NUM-lock" ] || [ -e "/tmp/.X11-unix/X$DISPLAY_NUM" ]; do DISPLAY_NUM=$((DISPLAY_NUM + 1)) done Xvfb ":$DISPLAY_NUM" -screen 0 800x900x24 \ +extension GLX +extension RANDR +render -ac & XVFB_PID=$! GAME_PID= cleanup() { if [ -n "$GAME_PID" ]; then kill "$GAME_PID" 2>/dev/null || true wait "$GAME_PID" 2>/dev/null || true fi kill "$XVFB_PID" 2>/dev/null || true wait "$XVFB_PID" 2>/dev/null || true rm -f "/tmp/.X$DISPLAY_NUM-lock" } trap cleanup EXIT sleep 0.5 export DISPLAY=":$DISPLAY_NUM" export VK_ICD_FILENAMES=$LVP_ICD export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibs}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$EXE" & GAME_PID=$! sleep "$DELAY_START" for i in $(seq 1 "$NUM"); do if ! kill -0 "$GAME_PID" 2>/dev/null; then echo "take-screenshots: process exited before screenshot $i" >&2 exit 1 fi out=$(printf "%s/shot-%03d.png" "$OUTDIR" "$i") import -window root "$out" echo "$out" if [ "$i" -lt "$NUM" ]; then sleep "$PAUSE" fi done ''; }; in { devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ (rust-bin.stable.latest.default.override { extensions = [ "rust-src" "rust-analyzer" ]; }) pkg-config alsa-lib.dev udev.dev ] ++ runtimeLibs; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs; }; apps.take-screenshots = { type = "app"; program = "${takeScreenshots}/bin/take-screenshots"; }; } ); }