mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 01:00:18 +02:00
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:
1 parent
1f6115b789
commit
a7ae41b94f
8 files changed
+78
-48
No files matched your search
@@ -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` |
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user