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) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-25 19:35:16 +10:00
1 parent 1f6115b789
commit a7ae41b94f
8 files changed
+78 -48

No files matched your search

+11 -10
View File
@@ -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 (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. 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 Status: research written 2026-09-25, then checked against a real Frame the same
against a real Frame yet.** Treat them as untested until the checklist below has day (SteamOS 0.3.0, variant `vr`, build 20260922). `connect.sh`, `push.sh`,
been done once. `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 ## 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) | | 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 | | **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 | | **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 | | 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` → `wl-copy`), or the clipboard sync in an RDP session | Inferred / untested | | 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), Details: [docs/ssh.md](docs/ssh.md), [docs/streaming.md](docs/streaming.md),
[docs/file-transfer.md](docs/file-transfer.md), [docs/file-transfer.md](docs/file-transfer.md),
[docs/open-questions.md](docs/open-questions.md). [docs/open-questions.md](docs/open-questions.md).
## Scripts (all untested against hardware) ## Scripts
| Script | Runs on | Purpose | | Script | Runs on | Purpose |
|---|---|---| |---|---|---|
| `scripts/connect.sh` | Mac | Discover, set up key and `~/.ssh/config`, copy key, optional `--harden` | | `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` | | `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 | | `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 | | `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/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` | | `scripts/bootstrap-on-frame.sh` | Frame | Fallback: install the key and enable `sshd` |
+7 -7
View File
@@ -26,13 +26,13 @@ virtual keyboard's paste key or a right-click → Paste.
echo "https://example.com" | ./scripts/paste-to-frame.sh - echo "https://example.com" | ./scripts/paste-to-frame.sh -
``` ```
How it works (untested). Over SSH, the script finds the logged-in Plasma How it works (verified 2026-09-25). The headset's desktop is a Plasma Wayland
session's `XDG_RUNTIME_DIR` and `wayland-*` socket, then runs `wl-copy`. If session nested inside gamescope, with its own runtime dir
there's no Wayland socket or no `wl-copy`, it tries `xclip` with `DISPLAY=:0`. (`/run/user/1000/nested_plasma`) and its own D-Bus bus. `wl-copy` and `xclip`
It assumes the in-headset desktop is a normal Plasma session owned by aren't installed. The script reads the bus address from `plasmashell`'s
`steamos`. That isn't known yet: the headset desktop may be a KWin session environment and calls Klipper's `setClipboardContents` with `qdbus6`. The
nested inside SteamVR. If both methods fail, the script prints what it found so desktop has to be running in the headset. It's text only, and pastes over about
the approach can be adjusted. 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 A simpler fallback: `ssh frame 'cat > ~/clip.txt'` < file, then open it in the
headset. headset.
+34 -4
View File
@@ -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 end-user reports** about SSH, desktop streaming, or macOS. Treat that as
"not documented yet", not "doesn't work". "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 <mac>.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) ## Check on the headset (in order)
1. **Is Developer Mode available on a retail unit?** Valve's pages are aimed at 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 ## 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 - `/home` and `/etc` persist across Frame OS updates. This is inferred from
Steam Deck behaviour. Steam Deck behaviour.
- The whole Mac → Frame desktop path (VNC → Remmina). Each part is documented - The whole Mac → Frame desktop path (VNC → Remmina). Each part is documented
separately, but the combination is untested. separately, but the combination is untested.
- Steam Remote Play with a Mac as host is broken. That's based on community - Steam Remote Play with a Mac as host is broken. That's based on community
reports, not tested with the Frame. reports, not tested with the Frame.
- None of the `scripts/` have run against real hardware. They were only - `connect.sh --harden`, `serve-bootstrap.sh` and
syntax-checked on the Mac (see the commit message). `bootstrap-on-frame.sh` haven't run against real hardware.
+2 -4
View File
@@ -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** LLMNR/NetBIOS. **On macOS, a bare single-label name usually doesn't resolve**
unless your router's DNS registers DHCP client names. unless your router's DNS registers DHCP client names.
- A secondary source says `frame.local`, a DNS alias, or the IP all work - **Verified on device (2026-09-25):** `avahi-daemon` is running on the Frame
(search-result summary only, no primary source found). SteamOS on Deck and `frame.local` resolves from the Mac over mDNS.
normally answers `steamdeck.local` over mDNS (Avahi). **Inferred**: the Frame
probably answers `frame.local`.
- `scripts/connect.sh` tries `frame.local`, then `frame`. If neither works, it tells you to re-run it with the IP. - `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 Once you have a working address, the `Host frame` alias means you just type
`ssh frame`. `ssh frame`.
+1 -1
View File
@@ -2,7 +2,7 @@
# Mac-side: find the Steam Frame, create a key, add a `Host frame` alias to # 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. # ~/.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: # Usage:
# scripts/connect.sh [HOST_OR_IP] # set up key + alias # scripts/connect.sh [HOST_OR_IP] # set up key + alias
+3 -1
View File
@@ -2,7 +2,9 @@
# Mac-side: install Flatpaks on the Steam Frame over SSH (per-user, so they # Mac-side: install Flatpaks on the Steam Frame over SSH (per-user, so they
# survive SteamOS updates and need no sudo / steamos-readonly changes). # 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: # Usage:
# scripts/install-apps.sh remmina [--vnc-host my-mac.local] # scripts/install-apps.sh remmina [--vnc-host my-mac.local]
+19 -20
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env zsh #!/usr/bin/env zsh
# Mac-side: put text on the Steam Frame desktop clipboard. # Mac-side: put text on the Steam Frame desktop clipboard.
# #
# UNTESTED against real hardware. Assumes the in-headset desktop is a Plasma # Needs the headset's desktop (Plasma) to be running. Text only; very large
# session owned by the SSH user; prints diagnostics if that assumption fails. # pastes (over ~100 KB) exceed the argument limit, so use push.sh for those.
# #
# Usage: # Usage:
# scripts/paste-to-frame.sh # sends the Mac clipboard (pbpaste) # scripts/paste-to-frame.sh # sends the Mac clipboard (pbpaste)
@@ -11,28 +11,27 @@ set -euo pipefail
FRAME_ALIAS=${FRAME_ALIAS:-frame} FRAME_ALIAS=${FRAME_ALIAS:-frame}
# Runs on the Frame. Clipboard text arrives on stdin. setsid keeps the # Runs on the Frame. Clipboard text arrives on stdin.
# clipboard-serving process alive after the SSH session closes. # 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' remote=$(cat <<'EOF'
set -u set -u
tmp=$(mktemp) text=$(cat; printf x); text=${text%x}
cat > "$tmp" pid=$(pgrep -u "$(id -u)" -x plasmashell | head -n 1)
rt=/run/user/$(id -u) if [ -z "$pid" ]; then
sock=$(ls "$rt" 2>/dev/null | grep -E '^wayland-[0-9]+$' | head -n 1) echo "plasmashell is not running: open the desktop in the headset first." >&2
if [ -n "$sock" ] && command -v wl-copy >/dev/null 2>&1 \ exit 2
&& XDG_RUNTIME_DIR=$rt WAYLAND_DISPLAY=$sock setsid wl-copy < "$tmp" >/dev/null 2>&1; then fi
echo "copied via wl-copy ($sock)" bus=$(tr '\0' '\n' < "/proc/$pid/environ" | sed -n 's/^DBUS_SESSION_BUS_ADDRESS=//p')
elif command -v xclip >/dev/null 2>&1 \ if DBUS_SESSION_BUS_ADDRESS=$bus qdbus6 org.kde.klipper /klipper \
&& DISPLAY=:0 setsid xclip -selection clipboard -i < "$tmp" >/dev/null 2>&1; then org.kde.klipper.klipper.setClipboardContents "$text" >/dev/null; then
echo "copied via xclip (DISPLAY=:0)" echo "copied via Klipper (${#text} chars)"
else else
echo "clipboard copy failed; diagnostics:" >&2 echo "Klipper call failed (bus: ${bus:-none})" >&2
echo " runtime dir: $(ls "$rt" 2>&1 | tr '\n' ' ')" >&2 exit 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
fi fi
rm -f "$tmp"
EOF EOF
) )
b64=$(print -rn -- "$remote" | base64) b64=$(print -rn -- "$remote" | base64)
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env zsh #!/usr/bin/env zsh
# Mac-side: copy a file or folder to the Steam Frame. # 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/) # Usage: scripts/push.sh SOURCE [REMOTE_DEST] (default dest: ~/Downloads/)
set -euo pipefail set -euo pipefail