Stop always calls systemctl and treats exit 5 (unit already collected)
as done, so there is no is-active/stop race. Cleanup never masks the
copy error, the play ssh timeout covers the remote worst case, and
tests cover stop exit codes and systemd-run stderr reporting.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- 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>
HTTPServer.server_bind calls socket.getfqdn, which stalled past the MCP
backend's 10-second startup window on GitHub's macOS runners.
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>
Review round 11: the cleanup's check and pkill now hold a lock that Show
takes to count itself in, so a new viewer can't start between them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review round 10: a Show replacing its own stream could have its new viewer
ended by the cleanup; a viewer reset left the delayed relay pending.
Verified: the relay delivers what's queued, then closes the agent side.
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>
Third review follow-up (PackageParser.buildClassName). Confirmed the
targetActivity resource id 0x01010202 in Open Saber Plus's manifest.
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>
Review follow-up. inspect() now returns 'repairable' and the filters it can
patch: the real activity's VR MAIN filter, or, when LAUNCHER/VR sits only on an
<activity-alias>, any real MAIN filter with a category to copy. install() and
patch() repair on 'repairable' instead of 'vr_activity', so a flat Godot 4
export is fixed and a VR category only on the alias no longer aborts install.
Tests cover both shapes, an already-launchable activity with an alias, and an
end-to-end patch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On a maintainer's Mac the compatibility-database key is in the Keychain, so a
test that reached install reporting published fake reports. Every test module
now imports tests/sandbox.py first, which points app data at a throwaway
directory (new FRAME_CONTROL_DATA_DIR), turns telemetry off and sends the
database nowhere.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lepton's apk-info-extractor ignores <activity-alias>, and Godot 4 exports put
LAUNCHER only on an alias (com.godot.game.GodotAppLauncher). Open Saber Plus
0.7.67 installed but Lepton exited with 'APP_ACTIVITY is empty'. Count only
real activities as launchable and patch the activity's VR intent filter.
Verified on the Frame: Open Saber Plus launches and renders.
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>