From a7ae41b94fc6296a5caabb2365eee6f802638efa Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Fri, 25 Sep 2026 19:35:16 +1000 Subject: [PATCH] Verify against a real Frame; switch clipboard to Klipper The headset desktop is nested Plasma in gamescope with no wl-copy/xclip, so paste-to-frame.sh now calls Klipper over plasmashell's D-Bus bus. connect.sh, push.sh, paste-to-frame.sh and install-apps.sh were exercised on SteamOS 0.3.0 (build 20260922); docs record what was confirmed. Cross-provider review skipped at Alex's request (Astra quota exhausted). Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 21 +++++++++++---------- docs/file-transfer.md | 14 +++++++------- docs/open-questions.md | 38 ++++++++++++++++++++++++++++++++++---- docs/ssh.md | 6 ++---- scripts/connect.sh | 2 +- scripts/install-apps.sh | 4 +++- scripts/paste-to-frame.sh | 39 +++++++++++++++++++-------------------- scripts/push.sh | 2 +- 8 files changed, 78 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 57bffbf..44e92f1 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,10 @@ This repo holds notes and Mac-side helpers for controlling a Valve Steam Frame (standalone VR headset: SteamOS 3, Arch-based, arm64, Snapdragon 8 Gen 3) from this Mac, with as little typing on the headset's virtual keyboard as possible. -Status: research written 2026-09-25. **None of the scripts have been run -against a real Frame yet.** Treat them as untested until the checklist below has -been done once. +Status: research written 2026-09-25, then checked against a real Frame the same +day (SteamOS 0.3.0, variant `vr`, build 20260922). `connect.sh`, `push.sh`, +`paste-to-frame.sh` and `install-apps.sh` work; the rest are still untested. See +[docs/open-questions.md](docs/open-questions.md#verified-on-device-2026-09-25). ## Minimum typing on the headset @@ -61,21 +62,21 @@ enables `sshd`. See [docs/ssh.md](docs/ssh.md#fallback-bootstrap-one-liner). | Shell on the Frame | `ssh frame` (user `steamos`) | Confirmed (Valve docs) | | **See/control the Frame from the Mac** | **Steam Link for macOS → connect to `frame`** (Valve names this). Alternatives: RDP to `xrdp` with Microsoft *Windows App* for the Linux desktop, or `adb`/`scrcpy` for the Android (Lepton) layer only | Steam Link and xrdp confirmed on Frame; the Mac RDP client is inferred | | **Show the Mac's desktop inside the Frame** | **macOS Screen Sharing (built-in VNC) → Remmina (Flatpak, aarch64) on the Frame's Linux desktop**, installed over SSH | Inferred: each piece is documented, but the combination hasn't been tested on a Frame | -| File transfer | `scp` / `rsync` over the `frame` alias (`scripts/push.sh`) | Inferred from confirmed SSH | -| Paste Mac clipboard into the headset | `scripts/paste-to-frame.sh` (`pbpaste` → `ssh` → `wl-copy`), or the clipboard sync in an RDP session | Inferred / untested | +| File transfer | `scp` / `rsync` over the `frame` alias (`scripts/push.sh`) | **Verified** (rsync is on the image) | +| Paste Mac clipboard into the headset | `scripts/paste-to-frame.sh` (`pbpaste` → `ssh` → Klipper over D-Bus), or the clipboard sync in an RDP session | **Verified** (script); RDP untested | Details: [docs/ssh.md](docs/ssh.md), [docs/streaming.md](docs/streaming.md), [docs/file-transfer.md](docs/file-transfer.md), [docs/open-questions.md](docs/open-questions.md). -## Scripts (all untested against hardware) +## Scripts | Script | Runs on | Purpose | |---|---|---| -| `scripts/connect.sh` | Mac | Discover, set up key and `~/.ssh/config`, copy key, optional `--harden` | -| `scripts/install-apps.sh` | Mac → Frame | Install Flatpaks (Remmina, Moonlight, …) on the Frame over SSH as `--user` | -| `scripts/paste-to-frame.sh` | Mac → Frame | Send the Mac clipboard (or stdin) to the Frame clipboard | -| `scripts/push.sh` | Mac → Frame | `rsync` files to `~/Downloads` (or a given path) on the Frame | +| `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/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/docs/file-transfer.md b/docs/file-transfer.md index 6d6c831..e64df42 100644 --- a/docs/file-transfer.md +++ b/docs/file-transfer.md @@ -26,13 +26,13 @@ virtual keyboard's paste key or a right-click → Paste. echo "https://example.com" | ./scripts/paste-to-frame.sh - ``` -How it works (untested). Over SSH, the script finds the logged-in Plasma -session's `XDG_RUNTIME_DIR` and `wayland-*` socket, then runs `wl-copy`. If -there's no Wayland socket or no `wl-copy`, it tries `xclip` with `DISPLAY=:0`. -It assumes the in-headset desktop is a normal Plasma session owned by -`steamos`. That isn't known yet: the headset desktop may be a KWin session -nested inside SteamVR. If both methods fail, the script prints what it found so -the approach can be adjusted. +How it works (verified 2026-09-25). The headset's desktop is a Plasma Wayland +session nested inside gamescope, with its own runtime dir +(`/run/user/1000/nested_plasma`) and its own D-Bus bus. `wl-copy` and `xclip` +aren't installed. The script reads the bus address from `plasmashell`'s +environment and calls Klipper's `setClipboardContents` with `qdbus6`. The +desktop has to be running in the headset. It's text only, and pastes over about +100 KB hit the argument limit, so send big things with `push.sh`. A simpler fallback: `ssh frame 'cat > ~/clip.txt'` < file, then open it in the headset. diff --git a/docs/open-questions.md b/docs/open-questions.md index e67e82c..325933a 100644 --- a/docs/open-questions.md +++ b/docs/open-questions.md @@ -6,6 +6,38 @@ pages. Searches of Reddit and the Steam forums turned up **almost no end-user reports** about SSH, desktop streaming, or macOS. Treat that as "not documented yet", not "doesn't work". +## Verified on device (2026-09-25) + +Checked over SSH from the Mac, read-only, on SteamOS 0.3.0 (`VARIANT_ID=vr`, +build 20260922.6101926, kernel 6.18, aarch64): + +- **1–2.** Developer Mode + Set User Password gave working SSH with no terminal + steps. `sshd` is enabled and active. The user is `steamos` (in `wheel`) and + the hostname is `frame`. +- **3.** `frame.local` resolves from the Mac; `avahi-daemon` is active. +- **5.** `/etc/ssh/sshd_config` has `Include /etc/ssh/sshd_config.d/*.conf`. + The existing drop-ins are `20-systemd-userdb.conf` and `99-archlinux.conf`, so + `01-frame-keys-only.conf` would sort first as intended. (`--harden` itself + hasn't been run.) +- **8.** The in-headset desktop is `kwin_wayland` + `plasmashell` nested + inside gamescope (1280×800), with `XDG_RUNTIME_DIR=/run/user/1000/nested_plasma`, + `WAYLAND_DISPLAY=wayland-0`, `DISPLAY=:2` and a private D-Bus bus. SteamVR + (`vrserver`, `vrcompositor`) and `xrdp` are running. +- **9.** `rsync`, `flatpak`, `python3`, `git`, `qdbus6` and `xrdp` are present. + `wl-copy`, `xclip`, `xsel`, `kdeconnect-cli`, `tailscale`, `krfb` and `wayvnc` + are **not**. `paste-to-frame.sh` now uses Klipper over D-Bus and round-trips + text correctly. +- Flathub is already configured as a **system** remote; Chromium is the only + installed Flatpak. `/` is 10 GB (42% used); `/home` is 929 GB. +- `push.sh` copied a test file with rsync. +- **10.** `install-apps.sh remmina --vnc-host .local` installed Remmina as + a `--user` Flatpak over SSH and wrote the profile. The desktop's + `XDG_DATA_DIRS` includes the user Flatpak exports, so it shows up in the menu. + The Frame can reach the Mac's Screen Sharing port (5900). The Remmina + connection itself hasn't been tried in the headset yet (part of 11). + +Still open: 4, 6, 7, 11 (in-headset connect), 12–16. + ## Check on the headset (in order) 1. **Is Developer Mode available on a retail unit?** Valve's pages are aimed at @@ -51,13 +83,11 @@ end-user reports** about SSH, desktop streaming, or macOS. Treat that as ## Unconfirmed claims made in these docs -- `frame.local` works. This comes from one secondary search summary, with no - primary source found. - `/home` and `/etc` persist across Frame OS updates. This is inferred from Steam Deck behaviour. - The whole Mac → Frame desktop path (VNC → Remmina). Each part is documented separately, but the combination is untested. - Steam Remote Play with a Mac as host is broken. That's based on community reports, not tested with the Frame. -- None of the `scripts/` have run against real hardware. They were only - syntax-checked on the Mac (see the commit message). +- `connect.sh --harden`, `serve-bootstrap.sh` and + `bootstrap-on-frame.sh` haven't run against real hardware. diff --git a/docs/ssh.md b/docs/ssh.md index 1c209c0..0b25cce 100644 --- a/docs/ssh.md +++ b/docs/ssh.md @@ -31,10 +31,8 @@ Valve's examples use a bare `frame`. That works on Windows through LLMNR/NetBIOS. **On macOS, a bare single-label name usually doesn't resolve** unless your router's DNS registers DHCP client names. -- A secondary source says `frame.local`, a DNS alias, or the IP all work - (search-result summary only, no primary source found). SteamOS on Deck - normally answers `steamdeck.local` over mDNS (Avahi). **Inferred**: the Frame - probably answers `frame.local`. +- **Verified on device (2026-09-25):** `avahi-daemon` is running on the Frame + and `frame.local` resolves from the Mac over mDNS. - `scripts/connect.sh` tries `frame.local`, then `frame`. If neither works, it tells you to re-run it with the IP. Once you have a working address, the `Host frame` alias means you just type `ssh frame`. diff --git a/scripts/connect.sh b/scripts/connect.sh index 79ef887..9ce6cd4 100755 --- a/scripts/connect.sh +++ b/scripts/connect.sh @@ -2,7 +2,7 @@ # Mac-side: find the Steam Frame, create a key, add a `Host frame` alias to # ~/.ssh/config, copy the key, and optionally disable SSH password logins. # -# UNTESTED against real hardware. Idempotent: safe to re-run. +# Verified on a Frame 2026-09-25 (except --harden). Idempotent: safe to re-run. # # Usage: # scripts/connect.sh [HOST_OR_IP] # set up key + alias diff --git a/scripts/install-apps.sh b/scripts/install-apps.sh index e6d9c46..0a7afec 100755 --- a/scripts/install-apps.sh +++ b/scripts/install-apps.sh @@ -2,7 +2,9 @@ # Mac-side: install Flatpaks on the Steam Frame over SSH (per-user, so they # survive SteamOS updates and need no sudo / steamos-readonly changes). # -# UNTESTED against real hardware. Idempotent. +# Verified on a Frame 2026-09-25 (remmina + --vnc-host). Idempotent. +# The "exports/share is not in the search path" warning only applies to the +# SSH shell; the headset desktop's XDG_DATA_DIRS already includes it. # # Usage: # scripts/install-apps.sh remmina [--vnc-host my-mac.local] diff --git a/scripts/paste-to-frame.sh b/scripts/paste-to-frame.sh index 78322cd..fbc407c 100755 --- a/scripts/paste-to-frame.sh +++ b/scripts/paste-to-frame.sh @@ -1,8 +1,8 @@ #!/usr/bin/env zsh # Mac-side: put text on the Steam Frame desktop clipboard. # -# UNTESTED against real hardware. Assumes the in-headset desktop is a Plasma -# session owned by the SSH user; prints diagnostics if that assumption fails. +# Needs the headset's desktop (Plasma) to be running. Text only; very large +# pastes (over ~100 KB) exceed the argument limit, so use push.sh for those. # # Usage: # scripts/paste-to-frame.sh # sends the Mac clipboard (pbpaste) @@ -11,28 +11,27 @@ set -euo pipefail FRAME_ALIAS=${FRAME_ALIAS:-frame} -# Runs on the Frame. Clipboard text arrives on stdin. setsid keeps the -# clipboard-serving process alive after the SSH session closes. +# Runs on the Frame. Clipboard text arrives on stdin. +# Verified 2026-09-25 (SteamOS 0.3.0 vr, build 20260922): the headset desktop is +# a nested Plasma Wayland session inside gamescope with its own D-Bus bus, and +# wl-copy/xclip are not installed. Klipper (org.kde.klipper, served by +# plasmashell) is reachable with qdbus6, so we borrow plasmashell's bus address. remote=$(cat <<'EOF' set -u -tmp=$(mktemp) -cat > "$tmp" -rt=/run/user/$(id -u) -sock=$(ls "$rt" 2>/dev/null | grep -E '^wayland-[0-9]+$' | head -n 1) -if [ -n "$sock" ] && command -v wl-copy >/dev/null 2>&1 \ - && XDG_RUNTIME_DIR=$rt WAYLAND_DISPLAY=$sock setsid wl-copy < "$tmp" >/dev/null 2>&1; then - echo "copied via wl-copy ($sock)" -elif command -v xclip >/dev/null 2>&1 \ - && DISPLAY=:0 setsid xclip -selection clipboard -i < "$tmp" >/dev/null 2>&1; then - echo "copied via xclip (DISPLAY=:0)" +text=$(cat; printf x); text=${text%x} +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 +bus=$(tr '\0' '\n' < "/proc/$pid/environ" | sed -n 's/^DBUS_SESSION_BUS_ADDRESS=//p') +if DBUS_SESSION_BUS_ADDRESS=$bus qdbus6 org.kde.klipper /klipper \ + org.kde.klipper.klipper.setClipboardContents "$text" >/dev/null; then + echo "copied via Klipper (${#text} chars)" else - echo "clipboard copy failed; diagnostics:" >&2 - echo " runtime dir: $(ls "$rt" 2>&1 | tr '\n' ' ')" >&2 - echo " wl-copy: $(command -v wl-copy || echo missing) xclip: $(command -v xclip || echo missing)" >&2 - loginctl list-sessions --no-legend 2>&1 | sed 's/^/ session: /' >&2 - rm -f "$tmp"; exit 2 + echo "Klipper call failed (bus: ${bus:-none})" >&2 + exit 2 fi -rm -f "$tmp" EOF ) b64=$(print -rn -- "$remote" | base64) diff --git a/scripts/push.sh b/scripts/push.sh index 2fc4676..bcbbceb 100755 --- a/scripts/push.sh +++ b/scripts/push.sh @@ -1,7 +1,7 @@ #!/usr/bin/env zsh # Mac-side: copy a file or folder to the Steam Frame. # -# UNTESTED against real hardware. +# Verified on a Frame 2026-09-25. # # Usage: scripts/push.sh SOURCE [REMOTE_DEST] (default dest: ~/Downloads/) set -euo pipefail