Commit Graph
72 Commits
Author SHA1 Message Date
saphid 5b35e9cc5d Merge current main and prepare Frame Control 0.4.7 2026-10-15 13:09:59 +11:00
saphid 8b9bd2119c Add searchable Steam Frame Control Center with live metrics 2026-10-14 18:40:59 +11:00
saphidandClaude Opus 5.5 d773babaa3 Meta sign-in: allow Instagram, and say where a stopped redirect went
A live sign-in with a real Meta account goes auth.oculus.com -> auth.meta.com
-> www.facebook.com -> www.instagram.com -> auth.meta.com. The window only
allowed oculus.com, meta.com and facebook.com, so it stopped the Instagram
step and sat blank. instagram.com is now allowed (same HTTPS-only checks;
the cookie is still read only from oculus.com). A page change the window
stops is remembered by host only, so closing the stuck window says why; it
is forgotten once the window loads a page again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:39:59 +11:00
saphidandClaude Opus 5.5 1f51cc89d2 Frame Control 0.4.6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:14:46 +11:00
saphidandClaude Opus 5.5 5db9631571 Usage statistics: send the time zone, and show the notice again when it lists something new
Opens, installs and updates carry the computer's time zone name (from
Electron, which knows it on every OS), so we can see roughly which
countries Frame Control is used in without ever seeing an address.

The first-run notice now has a version. Copies that saw the older notice
see it again, and keep sending their usual statistics, but leave the time
zone out until they've seen the new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:41:19 +11:00
saphidandClaude Opus 5.5 c7125bc3de My Meta library: Sign in with Meta closes two sign-out races
- cancel_signin marks an in-flight attempt and, independently, deletes the
  account if that attempt saved it, so a cancel landing after the save but
  before the connect's cleanup still removes the token.
- Sign-out runs one at a time (a second call joins it) and refuses new
  sign-ins while it runs; it goes round until no window, connect or cancel is
  left. The page keeps Sign in with Meta off from pressing Disconnect until
  the server has deleted the token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:10:59 +11:00
saphidandClaude Opus 5.5 9c33711e6f My Meta library: Sign in with Meta cancels only after its connect settles
When the sign-in window is closed, times out or is signed out of while a
connect is under way, the page is answered at once, but cancel-signin is
posted only after that connect settles (saved, refused or failed), so the
server always sees the cancel after the connect and deletes the token if
that connect saved it. Nothing sent means no cancel. Sign-out, Disconnect's
first step, waits for any such cancel (also from a window closed earlier)
before the page posts its disconnect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:00:36 +11:00
saphidandClaude Opus 5.5 9d92396d49 My Meta library: Sign in with Meta cancels by in-flight attempt and saved owner
- frame_meta keeps only in-flight sign-in attempts (removed when their connect
  finishes) instead of a bounded list of cancelled ids, so other cancels can't
  push one out, and records which attempt saved the account. Cancelling an
  attempt being checked stops its save; cancelling the attempt that just saved
  deletes that token; a later sign-in's or a pasted token's account is never
  touched; unknown attempts change nothing.
- will-redirect applies the per-frame rule: frames may be redirected to any
  https: page, the window only to Meta's sites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:54:38 +11:00
saphidandClaude Opus 5.5 be2eda8304 My Meta library: Sign in with Meta lets frames load any HTTPS page
Meta's login can embed captcha and CDN frames from other sites (Arkose Labs,
hCaptcha, reCAPTCHA, fbcdn), so frames inside the sign-in window may now go to
any https: address (or about:blank/about:srcdoc); other schemes (http:, data:,
blob:, javascript:, file:...) are still stopped. The window itself stays on
the Meta/Oculus/Facebook allow-list. Frames can't reach the cookie read or IPC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:44:32 +11:00
saphidandClaude Opus 5.5 f5bed08931 My Meta library: Sign in with Meta drops abandoned attempts, holds frames to the list
Review fixes for the sign-in window:
- Each sign-in has a random attempt id. Closing, timing out or signing out
  stops cookie intake first, then tells the server (new cancel-signin action)
  not to save a connect from that attempt, even one still waiting on Meta
  (frame_meta.connect/cancel_signin; 409 from the server). Disconnect signs
  out of the window before deleting the token.
- will-frame-navigate holds every frame to the allow-list; a frame going
  elsewhere is stopped, never opened outside.
- The window is destroyed, not closed, and will-prevent-unload is overridden,
  so beforeunload can't keep it alive; sign-out clears the session only after
  the window is gone.
- Permission checks refused as well as requests; no developer tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:39:25 +11:00
saphid e144325375 Merge branch 'feat/quest-import' into feat/meta-library 2026-10-14 09:26:42 +11:00
saphidandClaude Opus 5.5 291b8ec295 My Meta library: Sign in with Meta in a Frame Control window
The desktop app can now open Meta's own login page (https://secure.oculus.com,
as BSManager's "Log in with Meta") in a window with its own session
(persist:meta-signin: sandboxed, context isolation, no preload or Node,
permissions refused). Navigation stays on https oculus.com, meta.com and
facebook.com; other links open in the system browser and other redirects are
stopped. The oc_ac_at cookie is read only from that session and only for
oculus.com, sent once per value through the server's existing connect path
(check_token, who am I, 0600 file), and the window closes. The page only gets
the server's answer, never the token. Disconnect also signs out of Meta in
Frame Control by clearing that session. Pasting a token stays as the fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:26:18 +11:00
saphidandClaude Opus 5.5 ee7951e8d0 Release 0.4.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:55:41 +11:00
saphidandClaude Opus 5.5 b89538f506 Release 0.4.4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 15:36:27 +11:00
saphidandClaude Opus 5.5 218df4f312 Release 0.4.3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:48:49 +11:00
saphidandClaude Opus 5.5 81bf646b0e Release 0.4.2
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 20:20:54 +11:00
saphid 875e92ea7b Merge remote-tracking branch 'origin/main' into fix/apk-install-instant-failures 2026-10-08 19:03:54 +11:00
saphidandClaude Opus 5.5 f5f3a1f9ca Releases: exact draft match and a fixed release page (review fixes)
- publish-release.sh: the fallback takes only an untagged-... draft titled
  exactly "Frame Control X.Y.Z" (optionally ": subtitle"); a pre-release's
  draft ("9.8.7-rc.1") or one bound to another tag is refused.
- updater.js: ignore the page in update.json/the API entirely and always
  link to releases/tag/v<version> built from the validated version, so a
  path like tag/..\..\other/repo can't reach shell.openExternal.
- Tests for both, including backslash and %2e%2e traversal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:50:00 +11:00
saphidandClaude Opus 5.5 9d062b7ba0 Ship the OpenXR layer gzipped so the Windows installer installs it; CI installs and checks
electron-builder's 7-Zip compresses a bare arm64 ELF with its ARM64 branch
filter (method 0A), which the NSIS installer's extractor can't decode and
silently skips. Every installed Windows copy (0.4.0, 0.4.1) lacked
libXrApiLayer_FRAME_compat.so, so VR APK installs failed instantly.

- Commit and package the library as libXrApiLayer_FRAME_compat.so.gz
  (byte-identical when decompressed; SHA-256 checked against the README);
  frame_android decompresses it, and a corrupt file counts as missing.
- build.sh writes the .gz.
- check-resources.js also runs from the command line against an installed
  copy, comparing every file with the unpacked build.
- release.yml: the Windows job installs the NSIS installer silently and runs
  that check.
- Tests: no bare ELF under prebuilt/, packaging filter ships only the .gz.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:47:00 +11:00
saphidandClaude Opus 5.5 a41f635a7c Releases: publish drafts through REST and link update.json to the tag page
publish-release.sh took update.json's "page" from the draft's url, which is
releases/tag/untagged-... and dies on publishing; 0.4.0's manifest shipped
that dead link. It also looked the draft up with `gh release view <tag>`,
which reports "release not found" while a draft's tag_name still reads
untagged-... (and uses rate-limited GraphQL).

- publish-release.sh: REST only. Checks the tag exists, finds the release by
  tag_name or else the one untagged draft titled "Frame Control X.Y.Z",
  keeps the 8-installer digest check, replaces update.json via the uploads
  API, then PATCHes tag_name/draft/prerelease/make_latest. Adds --dry-run.
- updater.js: trust only this repo's releases/tag/<tag> pages (never
  untagged-...); otherwise link to releases/tag/v<version>.
- release.yml: one draft job before the matrix (no racing creates), with
  --verify-tag and tag_name set explicitly.
- Tests: tests/test_publish_release.py with a stand-in gh (tests/fakegh/gh),
  and an updater test for the page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:41:37 +11:00
saphidandClaude Opus 5.5 0b9a165d83 APK installs: install VR apps without a missing OpenXR layer, fail packaging without it, categorize failures
- frame_android: a missing, unreadable or empty OpenXR compat layer no longer
  aborts a VR install; it installs without the layer and says so (vr_issues),
  unless the layer was asked for explicitly. Patch failures are named, and any
  exception (not only FrameError) reaches the install hooks/telemetry.
- app/build/check-resources.js (electron-builder afterPack): the build fails if
  the layer's arm64-v8a library or other required resources are missing.
- frame_telemetry: new fixed error categories layer_missing, apk_repack_failed,
  tool_missing; ok=false without an error reports "other".
- server: install_finished carries xr_layer_missing when a VR APK installed
  without the layer. docs/privacy.md lists every category and the new field.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:38:15 +11:00
saphidandClaude Opus 5.5 551cc54bbc Release 0.4.1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 23:07:51 +11:00
saphidandClaude Opus 5.5 6abc765e22 App: Try Again also works when the server is up but its page failed to load
The button was accepted only while no server was known. If the server answered
and the page then failed to load, the error page showed with the server still
known, and the button did nothing. It's now accepted from the error page itself
(the window's only data: page).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 22:51:24 +10:00
saphid f73efe414c Merge main into fix/server-stdin-abort 2026-09-30 22:39:14 +10:00
saphidandClaude Opus 5.5 a0f810c018 App: restart the server by itself when it stops, and a Try Again button on the error page
A server that had been up for a minute starts again without asking. One that
stops sooner shows the error page, now headed "Frame Control stopped", with a
Try Again button (the menu item was the only way, and hard to find).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 20:17:24 +10:00
saphidandClaude Opus 5.5 83d74548cd Screenshots: Copy button, right-click menu, and new shots appear on their own
Each screenshot card and the viewer get a Copy button that puts the image on
the clipboard (natively in the desktop app, as PNG in a browser). Right-click
a screenshot to open, copy, save, show it in Finder, or copy its path or name;
right-click the viewer to copy or save. The shelf re-lists the Frame's
screenshots every 8 s while the window is visible and connected, redraws only
when something changed, and keeps thumbnails it already has. Switching
headsets clears the list and ignores answers still on their way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 15:08:36 +10:00
saphidandClaude Opus 5.5 02b9413f78 Merge main into devices: several headsets alongside the app store, comfort, panels and media
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 12:44:30 +10:00
saphidandGPT-6 Astra 8fca0fe0a2 Merge origin/main into apk-store-fixes, preserving store and headset features
Keep the union of server routes, desktop resources and responsive controls. Preserve OpenXR install defaults and telemetry hooks alongside library artwork. Adapt the resource test to single-file entries and avoid a completed-refresh race in the F-Droid test.

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-09-29 12:07:09 +10:00
saphidandClaude Opus 5.5 1203ec0a9e Merge main into family-comfort; a session state without 'started' can't crash status
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:09:35 +10:00
saphidandClaude Opus 5.5 08fbe730c6 Merge main into devices: switching headset stops the keyboard agent and retargets the Mac view
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:37:45 +10:00
saphidandClaude Opus 5.5 4bb7a1ee86 Merge main into frame-input: keyboard and trackpad alongside analytics, the Mac view and MCP
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:25:26 +10:00
saphid 215bc357ef Merge remote-tracking branch 'origin/main' into devices 2026-09-29 10:14:51 +10:00
saphidandClaude Opus 5.5 7d6ff7f919 Merge main into devices: MCP, analytics, Mac view alongside several headsets
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:14:50 +10:00
Alex Southwell 363497fdd5 Merge pull request #20 from saphid/vr-apks
Run Quest and other VR APKs on the Frame: VR detection, launcher fix, OpenXR 1.1 compatibility layer
2026-09-29 10:13:11 +10:00
saphidandClaude Opus 5.5 e288946b81 Merge main into vr-apks: VR installs report through install_hooks like every other install
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 09:58:55 +10:00
saphidandClaude Opus 5.5 d970107716 Merge main into mac-in-headset: the Mac view alongside analytics and Report a problem
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 09:57:35 +10:00
saphidandClaude Opus 5.5 53c51e1888 Devices: restarting during startup starts afresh; a headset capture keeps its headset through clean-up (review round 33)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 08:42:19 +10:00
saphidandClaude Opus 5.5 72e4ccf080 App: overlapping restarts and server starts share one (review round 32)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 08:35:19 +10:00
saphidandClaude Opus 5.5 a9740ad6f2 Devices: the app waits for its old server before starting the new one; clipboard sends keep their headset (review round 31)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 08:27:52 +10:00
saphidandClaude Opus 5.5 98ef6944c9 Devices: fixes from review round 12
- A reconnect while an install runs keeps the login it started with; a new
  one from ~/.ssh/config applies after.
- Frame > Open SSH goes through the server, so it uses the same headset and
  address as the app and refuses when there's none.
- A bare frame alias in use when a headset is set up stays selectable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:21:19 +10:00
saphidandClaude Opus 5.5 8315c7c3aa Merge vr-library (Steam library art, Play/Stop, refresh-art) into the store
Resolve CLI usage and POST table conflicts. Store installs now hand the
source's own image URLs (icon, banner, screenshots) to frame_android.install
as Steam artwork; before, they passed UI proxy paths (or nothing), so every
store install fell back to generated art.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:45:31 +10:00
saphid 0622afcae9 feat(ui): add family controls, casting and native notifications 2026-09-28 22:29:58 +10:00
saphidandGPT-6 Astra f695f398de Render complete Steam artwork for every sideload and fix VR shortcut identity
Add optional SteamGridDB settings, source-first fallbacks rendered with Steam canvas, backfill commands and shared APK/native library details. Verify live artwork and Open Saber Steam Play/Stop; document SuperTux's clipboard crash.

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
2026-09-28 22:20:27 +10:00
saphidandClaude Opus 5.5 f10b5fe159 Package ui/apk_sources in the desktop app
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:55:04 +10:00
saphidandClaude Opus 5.5 18334b5989 Devices: fixes from review round 2
- Switching headsets is serialized with the start of any install; background
  work counts as running from before its thread starts. use() reroutes every
  command at once and makes ensure() wait for the new headset.
- A new user or port reroutes commands even if the attempt then fails.
- ~/.ssh/config edits take a lock file shared with Set Up Connection
  (frame_connect.py and connect.sh, which now also writes atomically).
- A finished attempt no longer writes its older settings over a change Set
  Up Connection made meanwhile.
- Pin edits are locked and swapped atomically.
- A bare alias behind ProxyJump/ProxyCommand is left to ssh to reach.
- The page drops answers about the previous headset after a switch; the
  header switcher takes clicks in the macOS title bar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:38:41 +10:00
saphidandClaude Opus 5.5 a954fc83c9 Devices: several headsets, several addresses each, and live connection status
Frame Control can now manage more than one Steam Frame, and reach each at any
of several addresses (LAN IPs per network, its .local name, Tailscale). A
connector in the server tries them all at once, picks the best one that
answers, follows ssh -v through each stage (network, finding, SSH, identity,
login) and streams that to the page. The header shows it live; a new Devices
tab (key 5) manages headsets, addresses and network names.

- ui/frame_devices.py: registry in devices.json, imported from the managed
  ~/.ssh/config blocks; per-headset host key pinning; config block updates.
- ui/frame_network.py: gateway IP+MAC fingerprint, Wi-Fi name, Tailscale.
- ui/frame_link.py: the connector, Test now, Tailscale/mDNS discovery, API.
- server.py: ensure_master delegates to the connector; /api/connection,
  /api/connection/events (SSE), /api/devices.
- Electron: headset switcher and Devices item in the Frame menu.
- frame_connect.py --alias; FRAME_CONTROL_DATA_DIR / FRAME_CONTROL_SSH_DIR
  keep tests off real data.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:13:58 +10:00
saphidandClaude Opus 5.5 5bf1268c82 Ship KDE Connect inside Frame Control instead of downloading it on the Frame
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>
2026-09-28 21:07:20 +10:00
saphid 35ef3af54b Merge remote-tracking branch 'origin/main' into vr-apks
# Conflicts:
#	ui/frame_android.py
#	ui/index.html
2026-09-28 17:49:47 +10:00
saphidandClaude Opus 5.5 b9e31178f3 Keyboard and trackpad for the Frame, through its own KDE Connect
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>
2026-09-28 17:42:19 +10:00
saphidandClaude Opus 5.5 97d70d0c80 Merge origin/main: background install jobs and tabbed pages
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>
2026-09-28 17:37:28 +10:00