docs: add validation guide, hardware and Windows scripts, CI

Documents what the rig proved and what it found, adds the hardware sign-off
script for the Hyprland machine and an (untested) Windows validation script,
and updates CI to build the cached OBS runtime, run tests and lint the Windows
code paths.
This commit is contained in:
2026-10-08 14:31:54 +02:00
parent 9178a303ca
commit 3588d5b212
5 changed files with 362 additions and 19 deletions
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Hardware sign-off for Linux: run from a real desktop session (Hyprland,
# another Wayland compositor, or X11) on the machine being validated.
#
# scripts/validate/linux-hardware.sh [output-dir]
#
# Records at 1440p60 with every usable hardware codec, checks the segments,
# confirms the portal restores its selection without asking again, and
# records a window. Writes report.txt in the output directory.
set -uo pipefail
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
out="${1:-${TMPDIR:-/tmp}/clipbin-hardware}"
probe="${repo}/target/debug/clipbin-probe"
report="${out}/report.txt"
rm -rf "${out}" && mkdir -p "${out}"
format=(--width 2560 --height 1440 --fps 60 --bitrate 40000)
cargo build --locked -p clipbin-probe --manifest-path "${repo}/Cargo.toml" || exit 1
log() { echo "$*" | tee -a "${report}"; }
ask() {
local answer
read -r -p "$1 [y/n] " answer
log "answer: $1 -> ${answer}"
}
log "Clipbin hardware validation $(date -Is)"
log "host: $(uname -r) session=${XDG_SESSION_TYPE:-?} desktop=${XDG_CURRENT_DESKTOP:-?}"
log "gpu: $(lspci 2>/dev/null | grep -iE 'vga|display' | cut -d: -f3- | xargs)"
"${probe}" encoders --verify "${format[@]}" --out "${out}/encoders" > "${out}/encoders.log" 2> "${out}/encoders.obs.log"
log "$(grep -E '^(obs_version|usable\.|verify\.)' "${out}/encoders.log")"
token=""
for codec in h264 hevc av1; do
if grep -q "^usable\.${codec}=none" "${out}/encoders.log"; then
log "SKIP ${codec}: no usable hardware encoder"
continue
fi
if [[ -z "${token}" ]]; then
echo
echo "Recording a monitor for 20 seconds with ${codec}. The portal will ask"
echo "you to pick a monitor: pick one and keep some motion on screen."
fi
dir="${out}/monitor-${codec}"
restore=()
[[ -n "${token}" ]] && restore=(--restore-token "${token}")
"${probe}" record --target monitor --codec "${codec}" "${restore[@]}" --seconds 20 "${format[@]}" \
--out "${dir}" > "${dir}.log" 2> "${dir}.obs.log"
record_exit=$?
"${probe}" check --codec "${codec}" --out "${dir}" >> "${dir}.log" 2>&1
check_exit=$?
log "monitor-${codec}: record_exit=${record_exit} check_exit=${check_exit} $(grep -E '^(encoder|recorded|check)=' "${dir}.log" | tr '\n' ' ')"
log "$(grep -E 'status=' "${dir}.log" | tr '\n' ' ')"
if [[ -z "${token}" ]]; then
token="$(sed -n 's/^restore_token=//p' "${dir}.log")"
log "restore token received: $([[ -n "${token}" ]] && echo yes || echo no)"
else
ask "Did ${codec} start WITHOUT showing the portal picker again?"
fi
done
echo
echo "Now recording a window for 20 seconds. Start a game (or any moving"
echo "window) first, then pick it in the portal."
read -r -p "Press Enter when ready. " _
dir="${out}/window-h264"
"${probe}" record --target window --seconds 20 "${format[@]}" --out "${dir}" > "${dir}.log" 2> "${dir}.obs.log"
record_exit=$?
"${probe}" check --out "${dir}" >> "${dir}.log" 2>&1
log "window-h264: record_exit=${record_exit} check_exit=$? $(grep -E '^(recorded|check)=' "${dir}.log" | tr '\n' ' ')"
echo
echo "Last: source loss. Record a window again, then close that window"
echo "about 5 seconds in. The probe should report a failure."
read -r -p "Press Enter when ready. " _
dir="${out}/window-closed"
"${probe}" record --target window --seconds 20 "${format[@]}" --out "${dir}" > "${dir}.log" 2> "${dir}.obs.log"
log "window-closed: exit=$? $(grep -E 'status=failed' "${dir}.log" | head -1)"
echo
echo "Report: ${report}"
echo "Recordings and logs: ${out}"
+80
View File
@@ -0,0 +1,80 @@
# Hardware validation for Windows. Not yet run on Windows: it is the first
# job for the Windows implementor, who should fix what it finds.
#
# powershell -ExecutionPolicy Bypass -File scripts\validate\windows.ps1 [-Out <dir>] [-GameWindow "<title>:<class>:<exe>"]
#
# Needs: Rust (rustup, MSVC toolchain), Visual Studio Build Tools (C++),
# Git for Windows (its bash runs scripts/obs/assemble-windows.sh) and FFmpeg
# on PATH for the segment checks (winget install Gyan.FFmpeg).
#
# -GameWindow is OBS's window identifier for a running game, as shown in OBS
# Studio's Game Capture "Window" list: "<title>:<window class>:<executable>".
param(
[string]$Out = "$env:TEMP\clipbin-windows",
[string]$GameWindow = ""
)
$ErrorActionPreference = "Stop"
$repo = Resolve-Path "$PSScriptRoot\..\.."
$report = Join-Path $Out "report.txt"
Remove-Item -Recurse -Force $Out -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force $Out | Out-Null
function Log($text) { $text | Tee-Object -FilePath $report -Append }
Log "Clipbin Windows validation $(Get-Date -Format o)"
Log "os: $((Get-CimInstance Win32_OperatingSystem).Caption) $([Environment]::OSVersion.Version)"
Log "gpu: $((Get-CimInstance Win32_VideoController).Name -join ', ')"
# 1. The pinned official OBS runtime, with the Game Capture hook still signed.
& bash "$repo/scripts/obs/assemble-windows.sh"
if ($LASTEXITCODE -ne 0) { throw "assembling the OBS runtime failed" }
$runtime = Join-Path $repo "target\obs-runtime\windows-x86_64"
Get-ChildItem "$runtime\data\obs-plugins\win-capture\*" -Include *.exe, *.dll | ForEach-Object {
$signature = Get-AuthenticodeSignature $_.FullName
Log ("signature {0}: {1} ({2})" -f $_.Name, $signature.Status, $signature.SignerCertificate.Subject)
}
# 2. Build the probe (also checks the Windows code paths compile and link).
Push-Location $repo
cargo build --locked -p clipbin-probe
if ($LASTEXITCODE -ne 0) { throw "building clipbin-probe failed" }
Pop-Location
$probe = Join-Path $repo "target\debug\clipbin-probe.exe"
$format = @("--width", "2560", "--height", "1440", "--fps", "60", "--bitrate", "40000")
function Record($name, $codec, [string[]]$arguments) {
$dir = Join-Path $Out $name
& $probe record @arguments --codec $codec @format --seconds 20 --out $dir > "$dir.log" 2> "$dir.obs.log"
$recordExit = $LASTEXITCODE
& $probe check --codec $codec --out $dir >> "$dir.log" 2>&1
$checkExit = $LASTEXITCODE
Log "${name}: record_exit=$recordExit check_exit=$checkExit $((Select-String -Path "$dir.log" -Pattern '^(encoder|recorded|check)=').Line -join ' ')"
Log ((Select-String -Path "$dir.log" -Pattern 'status=').Line -join ' ')
}
# 3. Hardware encoders: NVENC, AMF or QSV for H.264, HEVC and AV1.
& $probe encoders --verify @format --out (Join-Path $Out "encoders") > "$Out\encoders.log" 2> "$Out\encoders.obs.log"
Log ((Select-String -Path "$Out\encoders.log" -Pattern '^(obs_version|usable\.|verify\.)').Line -join "`n")
# 4. Monitor capture with each usable codec.
foreach ($codec in "h264", "hevc", "av1") {
if (Select-String -Path "$Out\encoders.log" -Pattern "^usable\.$codec=none" -Quiet) {
Log "SKIP ${codec}: no usable hardware encoder"
continue
}
Record "monitor-$codec" $codec @("--target", "monitor")
}
# 5. Game Capture (signed hook) and Window Capture of a running game.
if ($GameWindow) {
Record "game-capture" "h264" @("--target", "game", "--window", $GameWindow)
Record "window-capture" "h264" @("--target", "window", "--window", $GameWindow)
Write-Host "Last: start a recording, then quit the game about 5 seconds in."
Read-Host "Press Enter when ready"
$dir = Join-Path $Out "game-closed"
& $probe record --target game --window $GameWindow @format --seconds 20 --out $dir > "$dir.log" 2> "$dir.obs.log"
Log "game-closed: exit=$LASTEXITCODE $((Select-String -Path "$dir.log" -Pattern 'status=failed').Line | Select-Object -First 1)"
} else {
Log "SKIP game capture: pass -GameWindow with a running game's window identifier"
}
Write-Host "Report: $report"