Files
punktfunk/clients/apple/tools/screenshots.sh
T
enricobuehler 32879f45bf
apple / swift (push) Successful in 54s
release / apple (push) Successful in 8m1s
apple / screenshots (push) Failing after 6m42s
ci / rust (push) Successful in 1m25s
ci / web (push) Successful in 42s
android / android (push) Successful in 3m27s
ci / docs-site (push) Successful in 53s
ci / bench (push) Failing after 3m1s
deb / build-publish (push) Successful in 2m33s
decky / build-publish (push) Successful in 12s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m13s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m26s
docker / deploy-docs (push) Successful in 6s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m7s
feat(apple): App Store screenshot harness + CI zip artifact
A DEBUG-only "shot mode" renders one mock-populated screen full-bleed
(PUNKTFUNK_SHOT_SCENE=<name> -> ScreenshotHostView instead of ContentView),
so the OS can screenshot the REAL, fully-rendered UI. tools/screenshots.sh
drives it: screencapture for the mac window, `simctl io booted screenshot`
for the iOS/iPad/tvOS Simulators, at exactly the App Store Connect sizes.

ImageRenderer was tried first and rejected: it can't rasterize this app's
chrome (NavigationStack, Form/TabView, Liquid-Glass/NSVisualEffect all render
black or the "can't render" placeholder). Capturing the live window/Simulator
avoids that. Only the stream hero is synthetic (StreamView needs a live
connection) - a synthwave frame + the real glass HUD, overridable via
PUNKTFUNK_SHOT_HERO.

CI: a new `screenshots` job in apple.yml builds the iOS (+ tvOS best-effort)
xcframework slices, runs the harness per platform best-effort, and attaches
the result as a single zip artifact (punktfunk-appstore-screenshots). It is
isolated from the build/test job and skipped on PRs, so a capture gap (missing
Simulator runtime, or no Screen Recording grant for the mac window capture)
never reds the core signal.

Generated PNGs (clients/apple/screenshots/) are gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:44:03 +02:00

154 lines
6.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# App Store screenshot driver for the Punktfunk Apple client.
#
# Launches the app in "shot mode" (PUNKTFUNK_SHOT_SCENE=<name> → one mock-populated screen,
# full-bleed; see Sources/PunktfunkClient/Screenshots/) once per scene per device, and lets the OS
# capture the REAL rendered UI:
# • macOS → `screencapture` of the app's borderless window.
# • iOS/iPadOS/tvOS → a booted Simulator + `xcrun simctl io booted screenshot` (native pixels =
# the exact App Store size for that device).
#
# The captured pixels are exactly App Store Connect's required sizes:
# mac 2880×1800 (a 1× display yields 1440×900 — also accepted)
# iphone-6.9 1320×2868 (portrait) / 2868×1320 (the landscape hero)
# ipad-13 2064×2752 (portrait) / 2752×2064 (the landscape hero)
# appletv 1920×1080
#
# Requirements:
# • macOS target: just the Swift toolchain (`swift build`) + a one-time Screen Recording grant
# for your terminal (System Settings → Privacy & Security → Screen Recording).
# • iOS/iPadOS/tvOS targets: full Xcode (xcodebuild + Simulators), not just Command Line Tools.
#
# Usage:
# tools/screenshots.sh all # every platform this machine can build
# tools/screenshots.sh macos # just macOS
# tools/screenshots.sh ios ipad tvos # specific platforms
# OUT=~/Desktop/shots tools/screenshots.sh all
# PUNKTFUNK_SHOT_HERO=~/frame.png tools/screenshots.sh ios # real captured frame for the hero
#
# Keep SCENES in sync with ShotScenes.all.
set -euo pipefail
APPLE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$APPLE_DIR"
OUT="${OUT:-$APPLE_DIR/screenshots}"
BUNDLE_ID="io.unom.punktfunk"
SCENES=(01-stream 02-hosts 03-pair 04-trust 05-settings)
SETTLE="${SETTLE:-4}" # seconds to let a scene lay out before capturing
mkdir -p "$OUT"
log() { printf '\033[1;36m[shots]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[shots]\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31m[shots]\033[0m %s\n' "$*" >&2; exit 1; }
require_xcode() {
xcrun --find simctl >/dev/null 2>&1 \
|| die "Full Xcode required for simulator capture (have Command Line Tools only).
Install Xcode, then: sudo xcode-select -s /Applications/Xcode.app"
}
# ---------------------------------------------------------------------------- macOS
shoot_macos() {
log "macOS — building (swift build -c release)…"
swift build -c release >/dev/null
local bin=".build/release/PunktfunkClient"
[ -x "$bin" ] || die "build produced no $bin"
for scene in "${SCENES[@]}"; do
local logf; logf="$(mktemp)"
PUNKTFUNK_SHOT_SCENE="$scene" "$bin" >"$logf" 2>&1 &
local pid=$!
# Wait for the window to exist and the scene to settle.
local win=""
for _ in $(seq 1 50); do
win="$(grep -o 'PF_SHOT_WINDOW=[0-9]*' "$logf" | head -1 | cut -d= -f2 || true)"
[ -n "$win" ] && grep -q PF_SHOT_READY "$logf" && break
sleep 0.2
done
if [ -z "$win" ]; then
kill -9 "$pid" 2>/dev/null || true
warn "macOS/$scene: app never reported a window — skipping"; cat "$logf" >&2; continue
fi
local dest="$OUT/mac-$scene.png"
if screencapture -x -o -l"$win" "$dest" 2>/dev/null && [ -s "$dest" ]; then
log "macOS/$scene$dest ($(pixels "$dest"))"
else
warn "macOS/$scene: screencapture failed — grant your terminal Screen Recording permission
(System Settings → Privacy & Security → Screen Recording), then re-run."
fi
kill -9 "$pid" 2>/dev/null || true
rm -f "$logf"
done
}
# ------------------------------------------------------------------ iOS / iPadOS / tvOS
# $1 device-name regex $2 scheme $3 sdk $4 file prefix $5 runtime-grep
shoot_sim() {
require_xcode
local match="$1" scheme="$2" sdk="$3" prefix="$4" runtime="$5"
local udid
udid="$(xcrun simctl list devices available | grep -E "$match" | grep -oE '[0-9A-F-]{36}' | head -1 || true)"
[ -n "$udid" ] || die "$prefix: no available Simulator matching /$match/.
Create one in Xcode → Settings → Components, or: xcrun simctl create …"
log "$prefix — Simulator $udid"
xcrun simctl boot "$udid" 2>/dev/null || true
xcrun simctl bootstatus "$udid" -b >/dev/null 2>&1 || true
log "$prefix — building ($scheme)…"
local dd; dd="$(mktemp -d)"
xcodebuild -project Punktfunk.xcodeproj -scheme "$scheme" -configuration Debug \
-sdk "$sdk" -destination "id=$udid" -derivedDataPath "$dd" \
CODE_SIGNING_ALLOWED=NO build >/dev/null \
|| die "$prefix: xcodebuild failed"
local app; app="$(find "$dd/Build/Products" -maxdepth 2 -name '*.app' -type d | head -1)"
[ -n "$app" ] || die "$prefix: no .app built"
xcrun simctl install "$udid" "$app"
for scene in "${SCENES[@]}"; do
xcrun simctl terminate "$udid" "$BUNDLE_ID" 2>/dev/null || true
SIMCTL_CHILD_PUNKTFUNK_SHOT_SCENE="$scene" \
${PUNKTFUNK_SHOT_HERO:+SIMCTL_CHILD_PUNKTFUNK_SHOT_HERO="$PUNKTFUNK_SHOT_HERO"} \
xcrun simctl launch "$udid" "$BUNDLE_ID" >/dev/null
sleep "$SETTLE"
local dest="$OUT/$prefix-$scene.png"
xcrun simctl io "$udid" screenshot "$dest" >/dev/null
log "$prefix/$scene$dest ($(pixels "$dest"))"
done
xcrun simctl terminate "$udid" "$BUNDLE_ID" 2>/dev/null || true
rm -rf "$dd"
}
pixels() { sips -g pixelWidth -g pixelHeight "$1" 2>/dev/null | awk '/pixel/{print $2}' | paste -sd× -; }
# ---------------------------------------------------------------------------- dispatch
[ $# -gt 0 ] || set -- all
for target in "$@"; do
case "$target" in
macos) shoot_macos ;;
ios) shoot_sim 'iPhone 16 Pro Max' Punktfunk-iOS iphonesimulator iphone-6.9 iOS ;;
ipad) shoot_sim 'iPad Pro 13|iPad Pro .*M4|iPad Pro \(13' Punktfunk-iOS iphonesimulator ipad-13 iOS ;;
tvos) shoot_sim 'Apple TV' Punktfunk-tvOS appletvsimulator appletv tvOS ;;
all)
shoot_macos
if xcrun --find simctl >/dev/null 2>&1; then
shoot_sim 'iPhone 16 Pro Max' Punktfunk-iOS iphonesimulator iphone-6.9 iOS
shoot_sim 'iPad Pro 13|iPad Pro .*M4|iPad Pro \(13' Punktfunk-iOS iphonesimulator ipad-13 iOS
shoot_sim 'Apple TV' Punktfunk-tvOS appletvsimulator appletv tvOS
else
warn "Skipping iOS/iPadOS/tvOS — full Xcode not found (Command Line Tools only)."
fi
;;
*) die "unknown target '$target' (use: all macos ios ipad tvos)" ;;
esac
done
log "Done. Screenshots in $OUT"
ls -1 "$OUT" 2>/dev/null || true