diff --git a/BUILD_STATUS.json b/BUILD_STATUS.json index b86dfc5..1491ef0 100644 --- a/BUILD_STATUS.json +++ b/BUILD_STATUS.json @@ -3,7 +3,7 @@ "do_not_publish": false, "date": "2026-10-15", "distribution_scope": "English documentation, selected project-authored source, metadata, stock-relative differences and offline utilities; no vendor/patched firmware or native listings", - "bundled_source_files": 135, + "bundled_source_files": 136, "full_build_tested": false, "hardware_actions": false, "stock_diff_variants": 24, diff --git a/CREDITS.md b/CREDITS.md index 4115e13..7747449 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -28,6 +28,6 @@ The original third party prose is not relicensed here. Public ledger entries ori AI assistants were used for coding, research organization, translation and review. Their output was treated as work to inspect, not independent hardware proof. Grok and Gemini performed bounded reviews; the exact limits are recorded in `verification/`. -The overlay depends on frame-mic-tuner at the recorded revision and retains its upstream MIT terms when acquired. OpenVR and other dependencies keep their own licenses. They are not bundled here. Published geometry and filtering methods are research foundations, not claims of inventing those algorithms. +The current dashboard uses a project authored OpenVR and OpenGL/EGL wrapper. It does not fetch or compile frame-mic-tuner code. Earlier research snapshots used that upstream overlay helper; those historical references remain part of the provenance. OpenVR and system dependencies retain their own licenses and are acquired separately. Published geometry and filtering methods are research foundations, not claims of inventing those algorithms. The banner is AI generated editorial artwork. The avatar screenshot was provided by L8OLOOE to identify the author, with no transfer of rights to any underlying avatar model or other creator assets. diff --git a/FROZEN_HANDOFF.md b/FROZEN_HANDOFF.md index 4ecd133..b29ef21 100644 --- a/FROZEN_HANDOFF.md +++ b/FROZEN_HANDOFF.md @@ -2,7 +2,7 @@ The documentation and selected source export are complete for this handoff revision. Start at [quick setup](QUICK_SETUP.md), then [source instructions](source/README.md). The [package report](verification/PACKAGE_VERIFICATION.json) contains the final manifest hash; independently verify with `python tools/verify_package.py` before use. -Scope: 768 historical Markdown versions, 1680 translated narrative blocks, 24 exact stock-relative firmware differences, and 135 selected source files. The source export includes game and service Roy SRAM modules with distinct version/status information. No vendor firmware image, native library, decompilation, SDK, private calibration or raw recording is included. +Scope: 768 historical Markdown versions, 1680 translated narrative blocks, 24 exact stock-relative firmware differences, and 136 selected source files. The source export includes game and service Roy SRAM modules with distinct version/status information. No vendor firmware image, native library, decompilation, SDK, private calibration or raw recording is included. Review: Grok independently inspected Roy, native UI and optics source provenance; findings were integrated and export changes are documented in [source review](verification/SOURCE_REVIEW.md) and [file manifest](verification/SOURCE_SUPPLEMENT.json). Earlier component reviews and corrections remain in [review disposition](verification/REVIEW_DISPOSITION.md). diff --git a/QUICK_SETUP.md b/QUICK_SETUP.md index e489240..297d0e8 100644 --- a/QUICK_SETUP.md +++ b/QUICK_SETUP.md @@ -55,7 +55,7 @@ $env:PYTHONPATH = Join-Path (Get-Location) 'pc' Linux equivalent, after changing to the received checkout: `python3 -m venv .venv`, then `.venv/bin/python -m pip --version`; use `export PYTHONUTF8=1` and `export PYTHONPATH="$PWD/pc"` for the historical Python layout. Confirm the received tree actually has that layout. Install the received component's pinned dependencies into that environment. Depending on the component, the historical stack uses numpy, openvr, python-osc and pytest, with optional image, video, binary-analysis and compression libraries. These are component requirements, not a claim that installing their latest versions reproduces the project. -Native Frame components have a distinct Linux ARM64 build/runtime environment: C++17, the matching OpenVR headers/runtime, and component-specific pkg-config, Cairo, libcurl and Vulkan dependencies. A Windows Python environment alone does not satisfy it. Follow the exact overlay dependency revision and license notices in the build chapter. Do not start services, install overlays or run device-facing scripts as part of initial setup. +Native Frame components have a distinct Linux ARM64 build/runtime environment: C++17, the matching OpenVR headers/runtime, and component-specific pkg-config, Cairo, libcurl, json-c, EGL and desktop OpenGL dependencies. A Windows Python environment alone does not satisfy it. Use the project authored overlay wrapper and separately supplied official OpenVR headers as described in the build chapter. No frame-mic-tuner checkout is needed. Do not start services, install overlays or run device-facing scripts as part of initial setup. Record the received source revision, file hashes, host/target architecture, compiler/interpreter and dependency versions. Run the received component's offline checks first; record checks that cannot run because inputs are missing. diff --git a/README.md b/README.md index a194008..35c78ac 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ If you are using an AI assistant, start with [AI_START_PROMPT.md](AI_START_PROMP | Path | Contents | | --- | --- | -| `source/pc/frame_app/` | Our native dashboard and device monitors. Upstream overlay code is acquired separately. | +| `source/pc/frame_app/` | Our native dashboard, project authored OpenVR/EGL overlay and device monitors. | | `source/pc/radio/` | Protocol, diagnostics, session control and tracker firmware tooling. | | `source/pc/radio/ram/` | Roy game and service modules, loader, runtime and recovery logic. | | `source/historical-original210/` | The protected historical Roy source, separate from newer copies. | @@ -62,7 +62,7 @@ If you are using an AI assistant, start with [AI_START_PROMPT.md](AI_START_PROMP | `tools/` | Offline package checks, catalog queries and diff verification. | | `verification/` | Hash manifests, review findings, exclusions and validation limits. | -There are 135 selected source files. Private calibration values were removed, personal device identifiers were replaced, and three legacy C candidates deliberately refuse compilation until their identity configuration is reviewed. Historical tests with unreviewed recorded fixtures are listed exclusions. This is not a fully reproducible build environment or a turnkey release. +There are 136 selected source files. Private calibration values were removed, personal device identifiers were replaced, and three legacy C candidates deliberately refuse compilation until their identity configuration is reviewed. Historical tests with unreviewed recorded fixtures are listed exclusions. This is not a fully reproducible build environment or a turnkey release. No PICO or Valve firmware images, SDKs, native vendor libraries, decompilations, private calibration, raw captures or credentials are included. You obtain permitted copies of the necessary external inputs yourself. Firmware diffs require the exact matching stock image. Instructions and old experiments do not authorize a new hardware operation. diff --git a/catalog/SOURCE_CODE_INDEX.json b/catalog/SOURCE_CODE_INDEX.json index 0076582..8efae6a 100644 --- a/catalog/SOURCE_CODE_INDEX.json +++ b/catalog/SOURCE_CODE_INDEX.json @@ -11919,7 +11919,7 @@ "ownership": "Not legally adjudicated by path; request rights-cleared source from owner", "selected_source_export_bundled": true, "bundled_path": "source/pc/frame_app/main.cpp", - "bundled_sha256": "de8428b4fcc69d13f2ad1913d15b7ebfd84c45f55b2a36782a3566a550c67956", + "bundled_sha256": "e4b144e3a2de3501e70dbd1467c3a326f975cd9fc1a7cc6debbdde8e8f70ad2a", "export_changes": [ "Publication added SPDX GPL-3.0-or-later and project attribution notice; original hashes remain provenance" ] diff --git a/docs/CONTRIBUTION_TASKS.md b/docs/CONTRIBUTION_TASKS.md index 6126f0f..a636441 100644 --- a/docs/CONTRIBUTION_TASKS.md +++ b/docs/CONTRIBUTION_TASKS.md @@ -2,7 +2,7 @@ ## Reproducible offline setup -Resolve missing imports against the exported source, document Python and native dependency versions, and produce component build instructions. Do not assume the broad historical requirements are a lockfile. The native UI needs json-c as well as its other listed libraries. Fetch third party code only into a separate development checkout with upstream notices intact. +Resolve missing imports against the exported source, document Python and native dependency versions, and produce component build instructions. Do not assume the broad historical requirements are a lockfile. The native UI needs json-c, EGL and desktop OpenGL as well as its other listed libraries. Fetch third party code only into a separate development checkout with upstream notices intact. ## Source export configuration diff --git a/docs/OVERLAY_BACKEND.md b/docs/OVERLAY_BACKEND.md new file mode 100644 index 0000000..a301e44 --- /dev/null +++ b/docs/OVERLAY_BACKEND.md @@ -0,0 +1,27 @@ +# Dashboard overlay backend + +The current dashboard no longer depends on frame-mic-tuner. The old dependency supplied a general overlay and Vulkan texture helper, not microphone functionality or tracker research. Historical references remain for provenance and do not describe the current build. + +`source/pc/frame_app/frame_overlay.cpp` and `.h` are a new project implementation. They create a SteamVR dashboard with the public OpenVR API, upload Cairo's converted RGBA pixels to desktop OpenGL textures in an EGL pbuffer context, and submit them through `SetOverlayTexture`. They do not use `SetOverlayRaw`, vendor overlay sources, or code copied from the previous helper. + +The wrapper supports panel and thumbnail creation, pointer events, dashboard selection, quit handling, an overlay presence probe and recreation when its handle disappears. It refuses a foreign owner of the same overlay key. Texture errors stop the UI rather than silently displaying a stale control panel. Closing the UI still does not stop the radio service. + +## Build + +Install component development packages for Cairo, libcurl, json-c, EGL and desktop OpenGL, a C++17 compiler and pkg-config. Acquire official OpenVR headers separately. On the Frame Linux ARM64 build host, in a separate copy of `source/`: + +```sh +export OPENVR_INCLUDE_DIR=/your/official/openvr/headers +export OPENVR_LIB_DIR=/opt/steamvr/bin/linuxarm64 +bash scripts/build_frame_app.sh +``` + +No dependency is downloaded by the script. It builds a local executable only. It does not install, connect to Roy or start a service. The context needs an EGL display compatible with SteamVR and desktop OpenGL support. This differs from the previous Vulkan path and must be validated on the actual runtime. + +## Validation boundary + +Offline compilation is distinct from target validation. Before calling this replacement equivalent to the previously installed UI, check it on the intended Frame session: dashboard visibility, image orientation and colors, pointer coordinates and click routing, thumbnail behavior, repeated open and close, SteamVR quit, overlay recreation and graceful failure when the EGL display is unavailable. A successful build does not prove those behaviors. + +A separate Grok review was requested but timed out without findings. It is not counted as a completed review of this replacement. The object compilation used official API headers; the complete application has not been linked or run on Frame in this change. + +No device operation is authorized by this document. Source and runtime dependencies remain under their respective licenses. The frozen October 15 handoff and previously installed private application are unchanged by this public source update. diff --git a/docs/SOFTWARE_AND_BUILD.md b/docs/SOFTWARE_AND_BUILD.md index fa9e458..f502343 100644 --- a/docs/SOFTWARE_AND_BUILD.md +++ b/docs/SOFTWARE_AND_BUILD.md @@ -28,7 +28,7 @@ Run only relevant offline checks after reproducing the actual old failure. A gre ## Native dashboard and dependencies -Build requires a C++17 compiler, pkg-config, Cairo, libcurl, Vulkan headers and installed SteamVR LinuxARM64/OpenVR runtime. Existing build scripts in the publication source are scripts/build_frame_app.sh and scripts/prepare_overlay_dependency.py. Obtain frame-mic-tuner separately from `https://github.com/sasaken1102r/frame-mic-tuner` at recorded commit`d1e8c830b00928ab94a44e1d5323613af95263c6` and retain its upstream MIT notice. Obtain official OpenVR headers separately from ValveSoftware/openvr; do not bundle their runtime here. +The current dashboard build uses C++17, pkg-config, Cairo, libcurl, json-c, desktop OpenGL/EGL and the installed SteamVR LinuxARM64/OpenVR runtime. Its project authored `pc/frame_app/frame_overlay.cpp` replaces the earlier fetched overlay dependency. No upstream overlay project is cloned or compiled. Obtain official OpenVR headers separately, then set `OPENVR_INCLUDE_DIR` to the directory containing `openvr.h`. Optionally set `OPENVR_LIB_DIR`; the default is `/opt/steamvr/bin/linuxarm64`. Run `bash scripts/build_frame_app.sh` from a separate development copy of `source/`. The script performs no downloads, installation or service start. A valid EGL desktop OpenGL display compatible with SteamVR is required. Frame runtime behavior of this replacement still needs validation. Publishing a native executable is separate from installing a device service, enabling radio or validating tracking. Do not auto-enable persistent startup before measured power-cycle reconnect, controller coexistence and pose quality. All actual deployment/execution requires the exact target/source/config/invocation and current authorization. diff --git a/source/README.md b/source/README.md index 0591702..f7e9ef1 100644 --- a/source/README.md +++ b/source/README.md @@ -2,7 +2,7 @@ This folder contains the selected project-authored implementation, not vendor SDK code or a firmware distribution. Read [the exact file and exclusion manifest](../verification/SOURCE_SUPPLEMENT.json). Each file has its original path/hash, bundled hash and any sanitization changes. The historical [source index](../catalog/SOURCE_CODE_INDEX.json) remains broader than this supplement. -Included components: native dashboard and monitors, Python diagnostics and transport, camera/timing/geometry/fusion, pose output and native driver source, Roy SRAM game/service C modules, loader/runtime and recovery logic, offline patch-generation scripts, requirements and two publication build scripts. Their presence is not proof of working full-body tracking or authorization to run device-facing tools. +Included components: native dashboard and monitors, Python diagnostics and transport, camera/timing/geometry/fusion, pose output and native driver source, Roy SRAM game/service C modules, loader/runtime and recovery logic, offline patch-generation scripts, requirements and the publication build script. Their presence is not proof of working full-body tracking or authorization to run device-facing tools. ## Roy variants @@ -26,7 +26,7 @@ All files were normalized to UTF-8 and LF; compare original and bundled hashes t Copy this source folder into a separate development checkout before installing dependencies, fetching upstream code or building. Keep this verified handoff unchanged. Follow [quick setup](../QUICK_SETUP.md) and [build notes](../docs/SOFTWARE_AND_BUILD.md). -`pc/requirements.txt` records broad historical minimums, not a reproducible dependency lock. Optional packages and system libraries remain component-specific. Native UI additionally includes `json-c/json.h` and needs json-c development headers/link flags. The historical `scripts/build_frame_app.sh` lacks explicit json-c pkg-config flags; review and add those flags in your development checkout if required by your toolchain. It fetches the pinned overlay dependency, so do not execute it as an offline verification command. Third-party notices must accompany separately obtained dependencies. +`pc/requirements.txt` records broad historical minimums, not a reproducible dependency lock. Optional packages and system libraries remain component-specific. The current native build links Cairo, libcurl, json-c, EGL, desktop OpenGL and the separately supplied OpenVR runtime. Set `OPENVR_INCLUDE_DIR` and optionally `OPENVR_LIB_DIR`, then run `bash scripts/build_frame_app.sh` in a separate source checkout. It does not fetch upstream overlay code. The project authored EGL wrapper is a new replacement, not hardware validated. See `docs/OVERLAY_BACKEND.md` in the repository root for its validation boundaries. No clean-machine full build has been demonstrated. Configuration files, profile approvals, clocks, private calibration and recorded datasets are obtained/prepared separately. Offline patch generators write images only in your private development area. Packaging them does not waive the firmware-only-as-diff rule for this handoff. diff --git a/source/pc/frame_app/frame_overlay.cpp b/source/pc/frame_app/frame_overlay.cpp new file mode 100644 index 0000000..331a2ef --- /dev/null +++ b/source/pc/frame_app/frame_overlay.cpp @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 L8OLOOE and contributors. + * Independent project implementation using the public OpenVR and EGL APIs. + */ +#include "frame_overlay.h" +#include +#include +#include +#include +#include +#include + +namespace { +constexpr const char* key="pico.frame-tracker"; +bool overlayError(vr::EVROverlayError result,const char* operation) { + if(result==vr::VROverlayError_None)return false; + std::fprintf(stderr,"%s: %s\n",operation,vr::VROverlay()->GetOverlayErrorNameFromEnum(result)); + return true; +} +} + +struct VrOverlay::State { + bool connected=false; + int width=0,height=0; + vr::VROverlayHandle_t panel=vr::k_ulOverlayHandleInvalid,thumbnail=vr::k_ulOverlayHandleInvalid; + EGLDisplay display=EGL_NO_DISPLAY; + EGLContext context=EGL_NO_CONTEXT; + EGLSurface surface=EGL_NO_SURFACE; + GLuint textures[2]={0,0}; + + bool create(std::string& error) { + auto* api=vr::VROverlay(); + auto result=api->CreateDashboardOverlay(key,"FramePicoTracker",&panel,&thumbnail); + if(result!=vr::VROverlayError_None){error=api->GetOverlayErrorNameFromEnum(result);return false;} + vr::HmdVector2_t scale={{static_cast(width),static_cast(height)}}; + if(overlayError(api->SetOverlayWidthInMeters(panel,1.6f),"overlay width") || + overlayError(api->SetOverlayInputMethod(panel,vr::VROverlayInputMethod_Mouse),"overlay input") || + overlayError(api->SetOverlayMouseScale(panel,&scale),"overlay mouse scale")) { + error="Could not configure dashboard overlay";return false; + } + return true; + } + + bool upload(vr::VROverlayHandle_t handle,GLuint texture,const uint8_t* rgba,int w,int h) { + if(!connected || !rgba || w<=0 || h<=0)return false; + if(!eglMakeCurrent(display,surface,surface,context))return false; + glBindTexture(GL_TEXTURE_2D,texture); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); + glPixelStorei(GL_UNPACK_ALIGNMENT,1); + glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,rgba); + if(glGetError()!=GL_NO_ERROR){std::fprintf(stderr,"Overlay texture upload failed\n");return false;} + // Complete writes before the runtime copies from this application's texture. + glFinish(); + vr::Texture_t data={reinterpret_cast(static_cast(texture)),vr::TextureType_OpenGL,vr::ColorSpace_Gamma}; + auto result=vr::VROverlay()->SetOverlayTexture(handle,&data); + return !overlayError(result,"SetOverlayTexture"); + } +}; + +VrOverlay::VrOverlay():state_(std::make_unique()){} +VrOverlay::~VrOverlay(){shutdown();} +VrOverlay::ConnectResult VrOverlay::connect(int width,int height,std::string& error) { + shutdown(); + if(width<=0 || height<=0){error="Invalid panel dimensions";return ConnectResult::Error;} + if(!vr::VR_IsRuntimeInstalled() || !vr::VR_IsHmdPresent()){error="SteamVR headset unavailable";return ConnectResult::NotRunning;} + vr::EVRInitError init=vr::VRInitError_None; + // Background initialization does not request launching SteamVR. + vr::VR_Init(&init,vr::VRApplication_Background); + if(init!=vr::VRInitError_None){error=vr::VR_GetVRInitErrorAsEnglishDescription(init);return ConnectResult::NotRunning;} + vr::VR_Shutdown(); + vr::VR_Init(&init,vr::VRApplication_Overlay); + if(init!=vr::VRInitError_None){error=vr::VR_GetVRInitErrorAsEnglishDescription(init);return ConnectResult::Error;} + state_->connected=true;state_->width=width;state_->height=height; + if(!vr::VROverlay()){error="OpenVR overlay interface unavailable";shutdown();return ConnectResult::Error;} + state_->display=eglGetDisplay(EGL_DEFAULT_DISPLAY); + EGLint major=0,minor=0; + if(state_->display==EGL_NO_DISPLAY || !eglInitialize(state_->display,&major,&minor) || !eglBindAPI(EGL_OPENGL_API)) { + error="Cannot initialize EGL desktop OpenGL on the current display";shutdown();return ConnectResult::Error; + } + EGLint attributes[]={EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,EGL_RENDERABLE_TYPE,EGL_OPENGL_BIT,EGL_RED_SIZE,8,EGL_GREEN_SIZE,8,EGL_BLUE_SIZE,8,EGL_ALPHA_SIZE,8,EGL_NONE}; + EGLConfig config=nullptr;EGLint count=0; + if(!eglChooseConfig(state_->display,attributes,&config,1,&count) || count!=1) { + error="No EGL OpenGL pbuffer configuration";shutdown();return ConnectResult::Error; + } + EGLint size[]={EGL_WIDTH,1,EGL_HEIGHT,1,EGL_NONE}; + state_->surface=eglCreatePbufferSurface(state_->display,config,size); + state_->context=eglCreateContext(state_->display,config,EGL_NO_CONTEXT,nullptr); + if(state_->surface==EGL_NO_SURFACE || state_->context==EGL_NO_CONTEXT || !eglMakeCurrent(state_->display,state_->surface,state_->surface,state_->context)) { + error="Cannot create current EGL OpenGL context";shutdown();return ConnectResult::Error; + } + glGenTextures(2,state_->textures); + if(!state_->textures[0] || !state_->textures[1] || !state_->create(error)){shutdown();return ConnectResult::Error;} + return ConnectResult::Ok; +} +void VrOverlay::shutdown() { + if(!state_)return; + auto& s=*state_; + if(s.connected && vr::VROverlay()) { + for(auto handle:{s.panel,s.thumbnail})if(handle!=vr::k_ulOverlayHandleInvalid) { + overlayError(vr::VROverlay()->ClearOverlayTexture(handle),"clear overlay"); + overlayError(vr::VROverlay()->DestroyOverlay(handle),"destroy overlay"); + } + // Let the runtime release its texture references before GL resources go away. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + if(s.connected)vr::VR_Shutdown(); + if(s.display!=EGL_NO_DISPLAY) { + if(s.context!=EGL_NO_CONTEXT && eglMakeCurrent(s.display,s.surface,s.surface,s.context)) { + glFinish();glDeleteTextures(2,s.textures); + } + eglMakeCurrent(s.display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT); + if(s.context!=EGL_NO_CONTEXT)eglDestroyContext(s.display,s.context); + if(s.surface!=EGL_NO_SURFACE)eglDestroySurface(s.display,s.surface); + eglTerminate(s.display); + } + state_=std::make_unique(); +} +VrEvents VrOverlay::pollEvents() { + VrEvents result; + if(!state_->connected || !vr::VROverlay()){result.quit=true;return result;} + vr::VREvent_t event{}; + while(vr::VRSystem()->PollNextEvent(&event,sizeof(event)))if(event.eventType==vr::VREvent_Quit){result.quit=true;vr::VRSystem()->AcknowledgeQuit_Exiting();} + while(vr::VROverlay()->PollNextOverlayEvent(state_->panel,&event,sizeof(event))) { + switch(event.eventType) { + case vr::VREvent_Quit:result.quit=true;vr::VRSystem()->AcknowledgeQuit_Exiting();break; + case vr::VREvent_OverlayClosed:result.closeRequested=true;break; + case vr::VREvent_MouseMove:case vr::VREvent_MouseButtonDown:case vr::VREvent_MouseButtonUp: + if(event.eventType!=vr::VREvent_MouseMove && event.data.mouse.button!=vr::VRMouseButton_Left)break; + result.pointer.push_back({event.eventType==vr::VREvent_MouseMove?PointerInput::Type::Move:event.eventType==vr::VREvent_MouseButtonDown?PointerInput::Type::Down:PointerInput::Type::Up,event.data.mouse.x,state_->height-event.data.mouse.y});break; + case vr::VREvent_FocusLeave:result.pointer.push_back({PointerInput::Type::Leave,-1,-1});break; + default:break; + } + } + return result; +} +bool VrOverlay::steamVrAlive() const {return state_->connected && vr::VRSystem() && vr::VROverlay();} +void VrOverlay::showPanel(){if(state_->connected)vr::VROverlay()->ShowDashboard(key);} +bool VrOverlay::submitThumbnail(const uint8_t* rgba,int size){return state_->upload(state_->thumbnail,state_->textures[1],rgba,size,size);} +bool VrOverlay::submitPanel(const uint8_t* rgba){return state_->upload(state_->panel,state_->textures[0],rgba,state_->width,state_->height);} +OverlayRepair VrOverlay::ensureOverlay() { + if(!state_->connected || !vr::VROverlay())return OverlayRepair::Failed; + vr::VROverlayHandle_t found=vr::k_ulOverlayHandleInvalid; + auto error=vr::VROverlay()->FindOverlay(key,&found); + if(error==vr::VROverlayError_None)return found==state_->panel?OverlayRepair::Unchanged:OverlayRepair::Failed; + if(error!=vr::VROverlayError_UnknownOverlay)return OverlayRepair::Failed; + // Remove only this instance's old handles. Never destroy a foreign key owner. + for(auto handle:{state_->panel,state_->thumbnail})if(handle!=vr::k_ulOverlayHandleInvalid)vr::VROverlay()->DestroyOverlay(handle); + state_->panel=state_->thumbnail=vr::k_ulOverlayHandleInvalid; + std::string message; + if(!state_->create(message)){std::fprintf(stderr,"Overlay repair failed: %s\n",message.c_str());return OverlayRepair::Failed;} + return OverlayRepair::Repaired; +} +int VrOverlay::probe() { + vr::EVRInitError error=vr::VRInitError_None;vr::VR_Init(&error,vr::VRApplication_Background); + if(error!=vr::VRInitError_None)return 1; + vr::VROverlayHandle_t handle=vr::k_ulOverlayHandleInvalid; + bool found=vr::VROverlay() && vr::VROverlay()->FindOverlay(key,&handle)==vr::VROverlayError_None; + std::printf("FramePicoTracker overlay: %s\n",found?"present":"absent");vr::VR_Shutdown();return found?0:1; +} diff --git a/source/pc/frame_app/frame_overlay.h b/source/pc/frame_app/frame_overlay.h new file mode 100644 index 0000000..a49ed93 --- /dev/null +++ b/source/pc/frame_app/frame_overlay.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 L8OLOOE and contributors. + */ +#pragma once +#include +#include +#include +#include + +struct PointerInput { + enum class Type { Move, Down, Up, Leave }; + Type type; + double x, y; +}; +struct VrEvents { + bool quit=false, closeRequested=false; + std::vector pointer; +}; +enum class OverlayRepair { Unchanged, Repaired, Failed }; + +class VrOverlay { +public: + enum class ConnectResult { Ok, NotRunning, Error }; + VrOverlay(); + ~VrOverlay(); + VrOverlay(const VrOverlay&)=delete; + VrOverlay& operator=(const VrOverlay&)=delete; + ConnectResult connect(int width,int height,std::string& error); + void shutdown(); + VrEvents pollEvents(); + bool steamVrAlive() const; + void showPanel(); + bool submitThumbnail(const uint8_t* rgba,int size); + bool submitPanel(const uint8_t* rgba); + OverlayRepair ensureOverlay(); + static int probe(); +private: + struct State; + std::unique_ptr state_; +}; diff --git a/source/pc/frame_app/main.cpp b/source/pc/frame_app/main.cpp index a564578..e7e210a 100644 --- a/source/pc/frame_app/main.cpp +++ b/source/pc/frame_app/main.cpp @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-3.0-or-later * Copyright (C) 2026 L8OLOOE and contributors. See CREDITS.md and NOTICE.md. */ -#include "vendor/vr_overlay.h" +#include "frame_overlay.h" #include #include #include @@ -61,12 +61,13 @@ int main(int argc,char** argv){ VrOverlay overlay;std::string error; if(overlay.connect(1200,700,error)!=VrOverlay::ConnectResult::Ok){fprintf(stderr,"%s\n",error.c_str());return 1;} std::vector rgba(1200*700*4),thumb(128*128*4,255); - overlay.submitThumbnail(thumb.data(),128);overlay.showPanel(); + if(!overlay.submitThumbnail(thumb.data(),128)){fprintf(stderr,"Could not submit dashboard thumbnail\n");overlay.shutdown();curl_global_cleanup();close(lock);return 1;} + overlay.showPanel(); auto* surface=cairo_image_surface_create(CAIRO_FORMAT_ARGB32,1200,700);auto* c=cairo_create(surface); std::string state="Connecting to diagnostic agent...",result=""; std::future pending=std::async(std::launch::async,api,std::string("/status")); bool recordRequest=false;auto refresh=std::chrono::steady_clock::now(); - bool savedPreview=false; + bool savedPreview=false,uiFailed=false; double mouseX=-1,mouseY=-1; std::string queuedRoute; std::vector updateRoutes; @@ -139,10 +140,12 @@ int main(int argc,char** argv){ cairo_surface_write_to_png(surface,"/home/steamos/pico-ram-work/frame-pico-ui-preview.png");savedPreview=true; } for(size_t i=0;i