mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 17:00:41 +02:00
Package the Frame Control web UI as an installable Electron Mac app and bring in the tooling built alongside it. - app/: Electron wrapper that starts ui/server.py on a free loopback port, hardened window (sandbox, no navigation, runAsNode fuse off), login-shell PATH so Homebrew tools work from Finder, first-run offer to run connect.sh, ad-hoc signed DMG/zip via electron-builder. - ui/: headset view (OpenVR screenshots), device status, library, Steam "Get games" (owned games, install, store search), Android apps as persistent Lepton instances with a rated F-Droid catalogue and a private compatibility database, Android display controls over ADB, file and clipboard transfer, Flatpaks, remote and power actions. - apk-catalog/, compat-db/, frame/: catalogue build pipeline, Lakebed capsule for compatibility reports, Frame-side launchers. - tests/ and CI: server guard and validation tests plus Steam helper tests, run on Python 3.9 with script and app syntax checks. - Docs: README leads with the Mac app; new Android, panels, Steam games and field-notes docs; security notes on LAN-exposed ADB ports. Screenshot values for the headset's IP and Wi-Fi name are placeholders. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
101 lines
3.4 KiB
Bash
Executable File
101 lines
3.4 KiB
Bash
Executable File
#!/usr/bin/env zsh
|
|
# Mac-side: install APKs on the Frame.
|
|
#
|
|
# Default: each APK becomes its own app, in its own persistent Lepton
|
|
# instance with a Steam library shortcut (ui/frame_android.py). Nothing is
|
|
# lost when it closes.
|
|
#
|
|
# --dev: the old way, ADB into Lepton Development over an SSH tunnel. Apps
|
|
# installed like this are deleted when Lepton Development exits.
|
|
#
|
|
# Verified on a Frame 2026-09-25 (--split untested).
|
|
#
|
|
# Usage:
|
|
# scripts/install-apk.sh APP.apk [APP2.apk ...] # own instance each
|
|
# scripts/install-apk.sh --dev APP.apk [APP2.apk ...] # into Lepton Development
|
|
# scripts/install-apk.sh --dev --split BASE.apk SPLIT.apk ...
|
|
#
|
|
# Env: FRAME_ALIAS (default frame), LOCAL_PORT (default: first free port from 15555).
|
|
set -euo pipefail
|
|
|
|
FRAME_ALIAS=${FRAME_ALIAS:-frame}
|
|
LEPTON_APPID=3056000
|
|
if [[ -z "${LOCAL_PORT:-}" ]]; then
|
|
for LOCAL_PORT in {15555..15575}; do
|
|
lsof -nP -iTCP:$LOCAL_PORT -sTCP:LISTEN >/dev/null 2>&1 || break
|
|
done
|
|
fi
|
|
SERIAL="127.0.0.1:$LOCAL_PORT"
|
|
CTL="${TMPDIR:-/tmp}/frame-adb-$$.sock"
|
|
|
|
split=0
|
|
dev=0
|
|
apks=()
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--split) split=1 ;;
|
|
--dev) dev=1 ;;
|
|
-h|--help) sed -n '2,18p' "$0"; exit 0 ;;
|
|
*) apks+=("$arg") ;;
|
|
esac
|
|
done
|
|
(( ${#apks} )) || { sed -n '14,16p' "$0" >&2; exit 2; }
|
|
if (( ! dev )); then
|
|
(( split )) && { print -u2 "--split needs --dev for now"; exit 2; }
|
|
for apk in "${apks[@]}"; do
|
|
print "==> Installing $apk as its own app"
|
|
FRAME_ALIAS=$FRAME_ALIAS python3 "${0:A:h}/../ui/frame_android.py" install "$apk"
|
|
done
|
|
exit 0
|
|
fi
|
|
command -v adb >/dev/null || { print -u2 "adb missing: brew install android-platform-tools"; exit 1; }
|
|
|
|
for apk in "${apks[@]}"; do
|
|
[[ -f "$apk" ]] || { print -u2 "not a file: $apk"; exit 1; }
|
|
# The Frame is ARM64: native code must include lib/arm64-v8a/.
|
|
libs=$(unzip -Z1 "$apk" 2>/dev/null | grep -E '^lib/[^/]+/' | cut -d/ -f2 | sort -u || true)
|
|
if [[ -n "$libs" && "$libs" != *arm64-v8a* ]]; then
|
|
print -u2 "!! $apk has native code for ${(j:, :)${(f)libs}} only; the Frame needs arm64-v8a"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
lepton_listening() { ssh "$FRAME_ALIAS" 'ss -ltn | grep -q ":5555 "'; }
|
|
|
|
if ! lepton_listening; then
|
|
print "==> Starting Lepton Development on the Frame"
|
|
ssh "$FRAME_ALIAS" "steam steam://rungameid/$LEPTON_APPID >/dev/null 2>&1"
|
|
for i in {1..30}; do
|
|
lepton_listening && break
|
|
(( i == 30 )) && { print -u2 "Lepton didn't open port 5555 within 60s. Is Lepton Development installed?"; exit 1; }
|
|
sleep 2
|
|
done
|
|
fi
|
|
|
|
print "==> Tunnelling ADB over SSH (localhost:$LOCAL_PORT -> $FRAME_ALIAS:5555)"
|
|
ssh -f -N -M -S "$CTL" -o ExitOnForwardFailure=yes \
|
|
-L "127.0.0.1:$LOCAL_PORT:127.0.0.1:5555" "$FRAME_ALIAS"
|
|
cleanup() {
|
|
adb disconnect "$SERIAL" >/dev/null 2>&1 || true
|
|
ssh -S "$CTL" -O exit "$FRAME_ALIAS" >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
adb connect "$SERIAL" | grep -q "connected to" || { print -u2 "adb connect $SERIAL failed"; exit 1; }
|
|
# A freshly started Lepton accepts ADB before Android has finished booting.
|
|
for i in {1..45}; do
|
|
[[ "$(adb -s "$SERIAL" shell getprop sys.boot_completed 2>/dev/null)" == 1 ]] && break
|
|
(( i == 45 )) && { print -u2 "Android in Lepton didn't finish booting within 90s"; exit 1; }
|
|
sleep 2
|
|
done
|
|
|
|
if (( split )); then
|
|
print "==> Installing split APK set (${#apks} files)"
|
|
adb -s "$SERIAL" install-multiple -r "${apks[@]}"
|
|
else
|
|
for apk in "${apks[@]}"; do
|
|
print "==> Installing $apk"
|
|
adb -s "$SERIAL" install -r "$apk"
|
|
done
|
|
fi
|