- Stop is a no-op when the collected player unit is already gone
(raw systemctl stop exits 5 on the Frame; verified 2026-09-29).
- Surface systemd-run stderr when the player can't start.
- Keep the copy error if the cleanup ssh also fails; reject upload
names that the play path can never accept.
- Allow 60 s for play (ffprobe 30 s + systemd-run 15 s remote).
- Docs: four-hour cap is unconditional; no delete action yet; fix a
garbled timing sentence.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review round 12: a failed USB-C tunnel now retries the normal path; an
existing HostKeyAlias wins; --host with --usb is rejected; the Steam
desktop-streaming claim is now 'untested' (Valve documents the desktop
showing when a game loses focus); the Show/cleanup overlap test blocks
for real (it fails without the lock). Verified live: with the cable out,
the route is the normal path.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Plugged into the Mac, the Frame is a USB network device ("Steam Frame",
usb0 at ~0.9 ms). The tunnel uses it when the Frame's usb0 answers,
with the usual host key; otherwise the normal path. Interleaved runs:
content p50 7 vs 10 ms, click to drawn 17 vs 27 ms, scroll p95 23 vs
31-37 ms. FRAME_MACVIEW_USB=0 turns it off; the card says "over USB-C".
- Bench: --usb, and the route is recorded per run.
- Docs: Steam's own streaming (no SteamVR host on macOS; Remote Play pairs
but streams games, not windows; test blocked); the Frame's USB network;
the 2026-09-28 health-check boot-loop recurrence.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Chromium on the Frame outlived its last viewer window (verified: 11
processes left after a run). Once nothing is shown, Stop ends it, unless
Show was pressed again meanwhile; its profile is Frame Control's own.
- Relay --delay: if the agent side fails, close the viewer side too
(review round 9).
- Docs: the final scroll run captured 57 fps; don't blame ScreenCaptureKit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The keyboard and trackpad no longer fetch KDE Connect from Valve's package
repository on the Frame. The desktop apps and the iPhone app's Frame bundle
carry Valve's arm64 build of kdeconnect 24.02.2-1 and the five libraries it
links (kcontacts, kpeople, modemmanager-qt, pulseaudio-qt, libfakekey),
pinned by SHA-256 in frame/kdeconnect/packages.json and downloaded at build
time from the kdeconnect-frame-24.02.2-1 release, which also holds Valve's
complete source package for each.
On first use the computer copies them over its SSH connection (the iPhone
bundle already has them on the Frame); the agent checks each SHA-256,
unpacks them and stamps which build it is, so later starts copy nothing.
No internet on the Frame, 3.6 MB instead of 8 MB, 18 MB unpacked instead of
82 MB (ModemManager and friends were packaging-only dependencies).
GPL/LGPL compliance: frame/kdeconnect/NOTICE.md names each exact version,
licence and source; per-project licence texts in frame/kdeconnect/LICENSES;
THIRD_PARTY_NOTICES.md; an About and licences dialog in the app.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in #4, #5, #8 (fbl100's verified Remmina/VNC mirror), APK
alternatives and the website. docs/streaming.md: Mac in the headset stays
the recommendation (now verified on the Frame); Remmina keeps #8's verified
evidence as the whole-screen fallback. docs/mac-in-headset.md cites #8's
lag finding.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review round 8 (GPT-6 Astra xhigh): --delay paused upstream reads, so busy
streams saw 30-60 ms instead of 30. Verified locally: 60-62 ms round trip
with 30 ms each way under load. No saved result used --delay.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Per-frame timing on the Mac's clock (capture, encode, network, decode,
draw), viewer clock sync and reports, input echo, /stats and a HUD.
- scripts/macview-bench.py: repeatable runs on the real Frame, a shaping
relay (no sudo), interleaved A/B between agent settings; results in
bench/results/.
- Adaptive controller: ack-based send gate with jitter-aware slack, AIMD
bitrate that knows when a stream is app-limited, fps then size tiers.
On a 50->3->50 Mbit/s step, scroll p95 went from 4.7 s to 72 ms; no cost
on a clean link.
- Separate mode: real AppKit event loop (HiDPI and NSScreen now work),
cropped capture for fixed-size windows, windows kept on their display,
graceful quit restores windows; stop/start races fixed.
- Encoder timeline clamp (no oversized frame after a pause).
- Frame Control shows each live stream's fps, delay, bitrate and tier.
Reviewed by GPT-6 Astra xhigh (read-only), 7 rounds; findings fixed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Second review follow-up: with several activities (e.g. a splash activity ahead
of the game), the alias fallback now prefers the real activity named by the
alias's android:targetActivity. Reads targetActivity by resource id, updates
the error text and docs/vr-apks.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Analytics go to the maintainer's PostHog US project 343535, tagged
$lib = frame-control. Every event carries $ip 0.0.0.0, since PostHog
stores the sender's address otherwise (checked live), including events
queued by earlier versions.
- Report a problem sends a private problem_report event to PostHog instead
of a public GitHub issue, with its own random id so a contact address
can't be linked to analytics. The dialog asks how to reach the person and
shows a reference. Maintainers read reports on the PostHog dashboard or
with `python3 ui/frame_report.py inbox`.
- Community sync pages by timestamp in UTC: PostHog refuses OFFSET for
personal API keys.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Home → Keyboard and trackpad, in every version of Frame Control (Mac,
Windows, Linux, iPhone, iPad) with nothing to install on the device in
your hand. On a phone: a trackpad (drag, tap, two-finger scroll and
right-click) and a field that types on the Frame. On a computer: click
the pad to pass the mouse and keyboard through; Esc to stop.
First-party route: ui/frame_input_agent.py runs on the Frame and talks
KDE Connect's LAN protocol (v7) to kdeconnectd as if it were a phone.
KDE Connect isn't on the image, but Valve's package repository has it;
the agent fetches it and four libraries into ~ (no root, survives
updates), starts it, pairs by itself (accepting over D-Bus), and stops
it again when the last device disconnects. Each device has its own
identity; a stuck KDE Connect is restarted once.
Verified against the real Frame (SteamOS 0.4.1): first-time install,
pairing, pointer moves from the Mac's server and the iPhone app
(Simulator), Mac and iPhone at once, two installs at once, a frozen
daemon replaced, and the daemon stopping when the app quits.
Reviewed by GPT-6 Astra (xhigh) over seven rounds; all findings fixed
except per-event delivery acknowledgement (documented known limit).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Flatpak installs record their outcome inside main's background job; failed
jobs are diagnostics too. The Privacy panel lives on the Tools page (#privacy
opens it), tab analytics use the four page names, and "Test it now?" reads the
install job's result.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Anonymous PostHog analytics (ui/frame_telemetry.py): usage on by default
after a first-run notice; compatibility results and error details opt-in,
offered together by the notice's "Share more to help fix problems" button.
Random id, no person profiles or GeoIP, scrubbed text, an offline outbox,
and "Show what's been sent" in the new Privacy panel. Inert without a
project key, from a source checkout, or with DO_NOT_TRACK=1.
- APK installs now record install_failed when the APK itself won't install,
and offer a 20-second test after installing. Opted-in reports reach the
shared database through PostHog and `frame_compat_db.py sync`.
- The desktop app updates itself from published releases (app/updater.js):
update.json from releases/latest/download, SHA-256 checked, no downgrades;
macOS bundle swap, Windows NSIS, Linux AppImage, otherwise the release page.
scripts/publish-release.sh publishes a tested draft with its manifest.
- Report a problem (header button, Privacy panel, Help menu) files a GitHub
issue through the website's feedback API, with a previewed, scrubbed
diagnostics snapshot; activity and logs only when asked for.
Reviewed by GPT-6 Astra (xhigh, read-only) three times; all findings fixed.
Docs: docs/privacy.md, docs/releasing.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Steam's own idle timer (60 min on AC, 15 on battery) suspends the Frame,
and SSH work doesn't count as activity. scripts/keep-awake.sh on sets both
timers to Never through Steam's DevTools (reusing ui/frame_steam.py) and
holds a logind sleep inhibitor as a user unit; off releases the inhibitor
and restores the saved timers. Findings recorded in how-the-frame-works.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main now runs Android installs as background jobs and maps SSH failures to
one offline message. Alternative-version installs go through the same job,
the alternatives dialog waits on it with runJob, and send_error_json keeps
the apk blocker that opens the dialog.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
VR apps with an arm64 OpenXR loader get frame/openxr-compat's layer unless
--no-xr-compat; the app bundle ships the layer. Verified on the Frame: Wolvic's
Quest build gets through OpenXR start-up, Open Brush still reaches FOCUSED.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>