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
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` |
+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 -
```
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.
+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
"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)
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.
+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**
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`.
+1 -1
View File
@@ -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
+3 -1
View File
@@ -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]
+19 -20
View File
@@ -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)
+1 -1
View File
@@ -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