From 6ccf5627566deef0e68ea26429d520a605f80095 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Fri, 25 Sep 2026 19:53:46 +1000 Subject: [PATCH] Add run-on-frame.sh to launch apps on the headset desktop Borrows the nested Plasma session's display and D-Bus variables from plasmashell and starts the app detached. Tested on the Frame: error paths, argument quoting, ~ expansion, and `mac-screen` opening a VNC connection. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 2 ++ scripts/run-on-frame.sh | 75 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100755 scripts/run-on-frame.sh diff --git a/README.md b/README.md index 44e92f1..d43aac3 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ Details: [docs/ssh.md](docs/ssh.md), [docs/streaming.md](docs/streaming.md), | `scripts/connect.sh` | Mac | Discover, set up key and `~/.ssh/config`, copy key, optional `--harden` (**verified**; `--harden` untested) | | `scripts/install-apps.sh` | Mac → Frame | Install Flatpaks (Remmina, Moonlight, …) on the Frame over SSH as `--user` (**verified** with Remmina) | | `scripts/paste-to-frame.sh` | Mac → Frame | Send the Mac clipboard (or stdin) to the Frame clipboard (**verified**) | +| `scripts/install-apk.sh` | Mac → Frame | Install APKs into Lepton with ADB tunnelled over SSH; starts Lepton Development if needed (**verified**; see [docs/apks.md](docs/apks.md) for app compatibility) | +| `scripts/run-on-frame.sh` | Mac → Frame | Start an app on the headset desktop, e.g. `mac-screen` opens Remmina straight into the Mac (**verified**) | | `scripts/push.sh` | Mac → Frame | `rsync` files to `~/Downloads` (or a given path) on the Frame (**verified**) | | `scripts/serve-bootstrap.sh` | Mac | Fallback: serve `bootstrap-on-frame.sh` with your public key embedded | | `scripts/bootstrap-on-frame.sh` | Frame | Fallback: install the key and enable `sshd` | diff --git a/scripts/run-on-frame.sh b/scripts/run-on-frame.sh new file mode 100755 index 0000000..3e0ed50 --- /dev/null +++ b/scripts/run-on-frame.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env zsh +# Mac-side: start a GUI app on the Steam Frame's in-headset desktop over SSH. +# +# The headset desktop is a nested Plasma session inside gamescope with its own +# runtime dir, Wayland socket, X display and D-Bus bus (verified 2026-09-25), so +# a plain `ssh frame some-app` can't find it. This copies those variables from +# plasmashell's environment, then starts the app detached so it outlives SSH. +# +# Usage: +# scripts/run-on-frame.sh remmina # Remmina main window +# scripts/run-on-frame.sh mac-screen # Remmina straight into the Mac profile +# scripts/run-on-frame.sh org.example.App [ARGS...] # any installed Flatpak +# scripts/run-on-frame.sh -- COMMAND [ARGS...] # any command on the Frame +# +# A leading "~/" in any argument is expanded on the Frame. +set -euo pipefail + +FRAME_ALIAS=${FRAME_ALIAS:-frame} +REMMINA_PROFILE="~/.var/app/org.remmina.Remmina/data/remmina/mac-screen-sharing.remmina" + +case "${1:-}" in + ""|-h|--help) sed -n '2,15p' "$0"; exit 0 ;; + remmina) cmd=(flatpak run org.remmina.Remmina) ;; + mac-screen) cmd=(flatpak run org.remmina.Remmina -c "$REMMINA_PROFILE") ;; + --) shift; (( $# )) || { print -u2 "run-on-frame: missing command after --"; exit 2; } + cmd=("$@") ;; + *) + if [[ "$1" =~ '^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+){2,}$' ]]; then + cmd=(flatpak run "$@") + else + print -u2 "run-on-frame: unknown app '$1' (use a Flatpak app ID, or -- COMMAND)" + exit 2 + fi ;; +esac + +# Runs on the Frame with the command as "$@". +remote=$(cat <<'EOF' +set -u +pid=$(pgrep -u "$(id -u)" -x plasmashell | head -n 1) +if [ -z "$pid" ]; then + echo "plasmashell is not running: open the desktop in the headset first." >&2 + exit 2 +fi +while IFS= read -r -d '' kv; do + case "$kv" in + WAYLAND_DISPLAY=*|XDG_RUNTIME_DIR=*|DISPLAY=*|XAUTHORITY=*|DBUS_SESSION_BUS_ADDRESS=*|\ + XDG_DATA_DIRS=*|XDG_CURRENT_DESKTOP=*|XDG_SESSION_TYPE=*) export "$kv" ;; + esac +done < "/proc/$pid/environ" +args=() +for a in "$@"; do + case "$a" in "~/"*) a="$HOME/${a#\~/}" ;; esac + args+=("$a") +done +log=$(mktemp /tmp/run-on-frame.XXXXXX) +setsid nohup "${args[@]}" > "$log" 2>&1 < /dev/null & +child=$! +sleep 3 +if kill -0 "$child" 2>/dev/null; then + echo "started: ${args[*]} (pid $child, log $log)" +elif wait "$child"; then + # Single-instance apps (e.g. Remmina) hand off to the running copy and exit 0. + echo "done: ${args[*]} (exited 0; single-instance apps hand off to the running copy)" + rm -f "$log" +else + rc=$? + echo "failed: ${args[*]} (exit $rc). Log:" >&2 + tail -n 20 "$log" >&2 + exit 1 +fi +EOF +) +b64=$(print -rn -- "$remote" | base64) + +ssh "$FRAME_ALIAS" "bash -c \"\$(echo $b64 | base64 -d)\" run-on-frame ${(j: :)${(@q)cmd}}"