* Add bounded desktop power and heat observations with offline downloads
* Fix desktop power tests on Python 3.9
---------
Co-authored-by: SAPHID <saphid@users.noreply.github.com>
#38 built and bundled the native host into the Windows and both Linux
installers (adding a Linux ARM64 installer). Only the Windows installer
gets it now; the Linux and macOS installer jobs are as on main.
x264 is GPL and Windows encodes with Media Foundation, so the shipped
bundle leaves it out. desktop/build.py --with-x264 adds it for the
native CI jobs' headless protocol tests, which no longer upload their
bundles. The release job tests the packaged app's own bundle with its
bundled Python and checks x264 isn't in it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
host_view now picks the PC host on Windows only; Linux and macOS keep
the Mac view as on main, so #38's Linux adapter is never offered. The PC
host starts nothing, loads no native library and lists no windows until
the person ticks "Turn on PC in the headset" on the card (action
"enable" on /api/macview). Turning it off ends streams, viewers and the
helper. The choice is a client preference, like mvSeparate.
Tools lists "PC in the headset" on Windows only, beside the macOS-only
Mac link, with an Experimental tag and a short note on what hasn't been
tried. The Mac card's wording and Linux-portal tweaks from #38 go back
to main's, apart from the shared "your computer" hint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #38's native PC host. The only conflict was ui/index.html: main
moved the macview card under Tools, so this keeps main's layout; #38's
loadMacView changes applied cleanly. Scoping to Windows and the
Experimental opt-in follow in separate commits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home, Tools and the Control Center now call the keyboard "Type on the
headset", and T opens it from anywhere. The clipboard moves from Send to
Frame to sit beside the keyboard, with a new Copy the headset's clipboard
button: the server reads Klipper's getClipboardContents on the headset
desktop's bus (base64 back, 1 MB limit, empty and image clipboards say so),
and the desktop app, the iPhone app or frame_host.set_clipboard_text puts
it on the clipboard here. Clipboard text is never logged.
Not tested on a headset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's Import Android game panel joins Quest and Meta under Apps > Browse,
and its Import button (androidBackupOpen, which lived in the old Android
store header and would have thrown at startup here) is now an Add / Install
choice. docs/android-backups.md points to the new places.
Home's shortcuts (Type on the Frame and the rest) now show their tool before
focusing it, so typing works on the first tap and phones open the keyboard
(Sol review P3). A tool panel already in its workspace isn't moved again, which
also dropped focus on the follow-up hashchange.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps the four primary destinations and ports main's newer work into them:
the Home "Right now" panel (volume, cast, quick actions) replaces the old
device block, whose readings now live in the header's headset popover; the
install queue sits above the Apps views; Import from my Quest and My Meta
library sit with Browse; the pad, panel switcher, Mac view and media panels
take main's markup. Scrolling follows main's #scroll container. Settings
stays a tab, so main's header gear is not added. The old .home-status
toggle is gone (main's startup test caught it touching a missing node).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Primary tabs become Apps (Installed / Browse / Linux apps), Games (Installed /
Owned / Store), Tools and Settings, with shortcuts 1-4 and one Add / Install
chooser for catalogs, Steam, Linux apps, files and install links. Installed
rows show identity, one state and Open/Stop; the rest moves to app details.
Review fixes (#106): input, touch, approvals, artwork repair, keyboard helper
delivery and catalogue jobs stay bound to the headset they were meant for;
report preview follows the latest consent; failed Store reads are not an empty
library; keyboard access to device details and the Store sheet; the iOS
bundle ships the artwork renderer and OpenXR resources; typed addresses and
custom SSH ports survive setup; WebKit crashes recover; native preferences
survive relaunch; APK update rollback and repair stay retryable.
Refs #101, #104, #106
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
- 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>
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>
- 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>
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>
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>
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>
- 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>
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>
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>
- 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>
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>
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>
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>
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>