8 Commits
Author SHA1 Message Date
matzmanandClaude Opus 5.5 5a10cdf7ca Wrap-up documentation after the first tests on the Steam Frame
README: status, new Steam Frame facts (Steam's generated app manifest,
OpenVR library, display and font metrics) and open points. PROGRESS:
partial results of T1, T2 and T4, and what is open next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 00:21:52 +02:00
matzmanandClaude Opus 5.5 7f14551a6a Record the Frame test of the Fusion style fix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 00:20:05 +02:00
matzmanandClaude Opus 5.5 ff4ea8f455 Use the Fusion Quick Controls style; drop the Material import
On the Steam Frame, the fixed 1200x800 layout lost about its bottom
quarter. Measured with --snapshot: fonts and DPI are as intended, but
the controls ran in the Material style. The qt6 branch changed
mainwidget.qml's "import QtQuick.Controls" into
"import QtQuick.Controls.Material", which fixes Material for all
controls and even overrides QT_QUICK_CONTROLS_STYLE. Material's insets
and padding make the navigation buttons 65 px tall instead of 45 px
(Fusion) or 49 px (Basic, Qt 5's default style renamed); nothing in the
file uses Material.

mainwidget.qml imports QtQuick.Controls again, and main.cpp sets Fusion
explicitly, so the sizes are the same on every platform instead of
depending on Qt 6's per-platform default. With Basic the last navigation
button is still cut off on the Frame; with Fusion everything fits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 00:14:57 +02:00
matzmanandClaude Opus 5.5 d664f86945 Add layout diagnosis: font metrics in the log, --snapshot option
On the Steam Frame, the fixed 1200x800 QML layout overflows: about the
bottom quarter is cut off and the navigation icons overlap their text.
Forcing 96 DPI and switching the Quick Controls style didn't help, so the
cause has to be measured rather than guessed.

- SetWidget() logs the application font (family, pt, px), what 20 pt
  (MyText) is in px, the screen's DPI and the device pixel ratio.
- --snapshot <file> writes the rendered overlay (RenderBackend::readback)
  as PNG at most once per second while it is visible. A time interval,
  because OVRAS only renders when the scene changes. Written to a
  temporary file and renamed, so readers never see a partial file.
- run.sh passes arguments after the graphics API to the program.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 00:06:09 +02:00
matzmanandClaude Opus 5.5 bbb3d1ba0e Log debug output only in builds with debugSymbolsAndLogs
On master, easylogging's Debug level was disabled unless OVRAS was built
with debugSymbolsAndLogs (ENABLE_DEBUG_LOGGING). The qt6 branch switched
to qDebug() and lost that: debugLoggingEnabled was left unused, and all
debug output reached the log. On the Steam Frame it filled with
"updateAllPulseData done." about every 11 ms, as the audio tab reads the
microphone's mute state on every tick.

setUpLogging() now disables debug output through a logging filter rule.
QT_LOGGING_RULES="*.debug=true" takes precedence and turns it on again
for troubleshooting without a rebuild.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:32:00 +02:00
matzmanandClaude Opus 5.5 ec82b5fead Register the manifest when Steam already lists the app key
Steam writes every VR app the user owns into its generated
steamapps.vrmanifest, installed or not. Whoever owns OVRAS on Steam
therefore always has steam.overlay.1009850 registered, as a steam:// URL
without a working directory, and --force-install-manifest failed with
"Could not find working directory of already installed application:
VRApplicationError_PropertyNotSet" (found on the Steam Frame).

Such an entry has no manifest file to remove, so OVRAS now leaves it
alone and adds its own manifest. Whether SteamVR accepts a second
manifest for the key, and which entry it then uses, is not documented:
logApplicationEntry() logs what SteamVR reports for the key after
installing, so a test on the Frame shows it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:25:57 +02:00
matzmanandClaude Opus 5.5 317db6831d Use the OpenVR SDK's bin/linuxarm64 library on aarch64
On the Steam Frame, VR_Init failed with "Installation path could not be
located": the SDK's lib/linuxarm64/libopenvr_api.so (2.15.6) reads the VR
path registry from the fixed path /data/work/openvrpaths.vrpath instead
of ~/.config/openvr. The SDK's bin/linuxarm64 library of the same version
uses ~/.config/openvr; the vrdashboard template ships it and was verified
on the Frame. It is added as a new file (third-party/openvr had no
bin/linuxarm64, and "bin" is gitignored, hence force-added), and aarch64
builds link against and ship it. x86_64 and Windows are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:13:47 +02:00
matzmanandClaude Opus 5.5 537539ab5e Run the manifest commands without a display
The first deploy to the Steam Frame aborted in install.sh: OVRAS creates
its QApplication before it handles --force-install-manifest, and Qt's
default platform plugin (xcb) aborts when there is no display, as over
SSH. Registering and unregistering draw nothing, so the generated
install.sh and uninstall.sh now run them with QT_QPA_PLATFORM=offscreen.
The normal start keeps DISPLAY=:0, as rendering needs a real context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:08:10 +02:00
12 changed files with 447 additions and 30 deletions
+6 -1
View File
@@ -343,7 +343,12 @@ endif ()
if (WIN32)
set(OPENVR_LIB_DIR ${CMAKE_SOURCE_DIR}/third-party/openvr/lib/win64)
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
set(OPENVR_LIB_DIR ${CMAKE_SOURCE_DIR}/third-party/openvr/lib/linuxarm64)
# bin/, not lib/: the SDK's lib/linuxarm64/libopenvr_api.so looks for the
# VR path registry in the fixed path /data/work/openvrpaths.vrpath instead
# of ~/.config/openvr, so on the Steam Frame it never finds SteamVR
# ("Installation path could not be located"). bin/linuxarm64 (SDK 2.15.6)
# uses ~/.config/openvr.
set(OPENVR_LIB_DIR ${CMAKE_SOURCE_DIR}/third-party/openvr/bin/linuxarm64)
else ()
set(OPENVR_LIB_DIR ${CMAKE_SOURCE_DIR}/third-party/openvr/lib/linux64)
endif ()
+14 -6
View File
@@ -45,7 +45,10 @@ cat > "$out/install.sh" <<EOF
cd "\$(dirname "\$0")" || exit 1
# An update replaces the files of a running instance: stop it first.
pkill -f "/$exe( |\\\$)" 2>/dev/null && sleep 1
./$exe --force-install-manifest || exit 1
# OVRAS creates its QApplication before handling the manifest options, and
# Qt's default platform plugin (xcb) aborts without a display, e.g. over SSH.
# Registering draws nothing, so use the display-less "offscreen" plugin.
QT_QPA_PLATFORM=offscreen ./$exe --force-install-manifest || exit 1
# Start now. Over SSH there is no display; Qt needs one even for off-screen
# rendering, so use the Frame's (:0).
DISPLAY="\${DISPLAY:-:0}" nohup ./$exe >/dev/null 2>&1 &
@@ -58,17 +61,22 @@ cat > "$out/uninstall.sh" <<EOF
# be deleted afterwards.
cd "\$(dirname "\$0")" || exit 1
pkill -f "/$exe( |\\\$)" 2>/dev/null
exec ./$exe --force-remove-manifest
# No display needed for unregistering (see install.sh).
exec env QT_QPA_PLATFORM=offscreen ./$exe --force-remove-manifest
EOF
cat > "$out/run.sh" <<EOF
#!/bin/sh
# Restarts OVR Advanced Settings with the given graphics API, for testing:
# ./run.sh [opengl|vulkan] (default: opengl)
# Restarts OVR Advanced Settings with the given graphics API, for testing.
# Further arguments go to the program:
# ./run.sh [opengl|vulkan] [options] (default: opengl)
# e.g. ./run.sh opengl --snapshot /tmp/ovras.png
cd "\$(dirname "\$0")" || exit 1
api="\${1:-opengl}"
[ \$# -gt 0 ] && shift
pkill -f "/$exe( |\\\$)" 2>/dev/null && sleep 1
DISPLAY="\${DISPLAY:-:0}" nohup ./$exe --graphics-api "\${1:-opengl}" >/dev/null 2>&1 &
echo "Started with \${1:-opengl}; log: \$(pwd)/AdvancedSettings.log"
DISPLAY="\${DISPLAY:-:0}" nohup ./$exe --graphics-api "\$api" "\$@" >/dev/null 2>&1 &
echo "Started with \$api; log: \$(pwd)/AdvancedSettings.log"
EOF
chmod +x "$out/install.sh" "$out/uninstall.sh" "$out/run.sh"
+75
View File
@@ -75,6 +75,36 @@ Private Qt symbols in the binary built that way (`objdump -T`, `Qt_6_PRIVATE_API
- It calls `VR_Init` directly, without `initializeOpenVR`'s retry loop. That loop waits
and retries on every error, including the one we expect here.
### 2a. App key already registered by Steam (approved 2026-10-07)
**Finding (Frame test T1):** Steam writes `~/.local/share/Steam/config/steamapps.vrmanifest`
(`"source": "steam"`) with every VR app the user **owns**, installed or not (Frame: 154
entries, 25 installed). It is regenerated by Steam. Whoever owns OVRAS on Steam therefore
always has `steam.overlay.1009850` registered, as `launch_type: url`
(`steam://launch/1009850/openvroverlay`), `is_hidden`. Such an entry has no working
directory. `reinstallApplicationManifest()` treats that as an error ("Could not find working
directory of already installed application: VRApplicationError_PropertyNotSet") and stops.
**Open question:** does SteamVR accept a second manifest for the same key, and which entry
wins? `AddApplicationManifest` may return `VRApplicationError_AppKeyAlreadyExists`. If
Steam's entry wins, auto-start and the dashboard would launch the Steam (Proton) version.
**Change (`setup.cpp`, platform-neutral, also useful upstream):**
- `reinstallApplicationManifest()`: if the key is installed but has no working directory
(`VRApplicationError_PropertyNotSet`), there is no manifest file to remove. Log it with
the entry's source and don't remove anything. Then call `AddApplicationManifest` directly
(`installApplicationManifest()` skips installed keys) and fail with the error's name if it
is refused.
- New helper `logApplicationEntry()`, called at the end of `--force-install-manifest`: logs
what SteamVR now reports for the key (source, launch type, working directory, binary path,
URL, auto-launch). This answers the open question on the Frame.
- Unchanged: the path for a regular earlier installation (with working directory), and
`installApplicationManifest()` on normal starts.
**Follow-up depending on the result:** if SteamVR refuses our manifest or Steam's entry
wins, the native build needs its own app key (option 2, separate design: the key is also in
the manifest, 7 default action manifests and `SteamVRTabController.h`).
## 3. Rendering through public API (R1, R3)
**From the template** (`4a5b090`, verified on the Frame), adopted largely unchanged:
@@ -230,6 +260,51 @@ existing code:
private API.
- RPATH is only `$ORIGIN` (`BUILD_WITH_INSTALL_RPATH`): the build tree gets packaged.
## 10. Layout diagnosis: font log and `--snapshot` (approved 2026-10-08)
**Why:** on the Frame the fixed 1200×800 layout overflows: the bottom quarter is cut off
(left: Statistics, Bindings, Settings; right: Microphone, Push-to-Talk), and the navigation
icons overlap their text. `QT_FONT_DPI=96`, `QT_QUICK_CONTROLS_STYLE=Basic` and the Material
"Dense" variant didn't fix it. Font metrics (Noto Sans instead of Segoe UI) are the main
suspect; measuring needs the unwarped rendered image.
**Changes (platform-neutral):**
- `OverlayController::SetWidget()`: logs once the application font (family, pt, px), what
20 pt (`MyText`) is in px, the screen's logical/physical DPI and the window's device pixel
ratio.
- Option `--snapshot <file>` (`setup.*`, `main.cpp`, `overlaycontroller.*`): while the
overlay is visible and rendered, `renderOverlay()` writes `RenderBackend::readback()` as
PNG to `<file>`, at most once per second (OVRAS only renders when the scene changes, so a
frame count could never trigger on a still page). Written to `<file>.tmp` and renamed.
`main.cpp` passes it through a setter, not a new constructor parameter, to keep the diff
small. Without the option nothing changes.
- `run.sh`: arguments after the graphics API go to the program
(`./run.sh opengl --snapshot /tmp/ovras.png`).
## 11. Quick Controls style: Fusion, no Material import (approved 2026-10-08)
**Finding (measured with `--snapshot`, DESIGN.md section 10):** the font is as intended (20 pt
= 27 px, 96 DPI, device pixel ratio 1). The controls run in the Material style: `qt6`
(`93c0851`) changed `mainwidget.qml`'s `import QtQuick.Controls` into
`import QtQuick.Controls.Material`. That import fixes Material for all controls and even
overrides `QT_QUICK_CONTROLS_STYLE`. Material's insets and padding make the navigation
buttons 65 px tall (75 px pitch), so the fixed 1200×800 root page loses about its bottom
quarter. Nothing in the file uses Material.
| Style (root page, navigation buttons) | Button | Pitch | Result |
|---|---|---|---|
| Material (`qt6` as is) | 65 px | 75 px | 9 of 12 buttons visible |
| Basic (Qt 5's "Default" renamed) | 49 px | 59 px | last button cut off (18 of 49 px) |
| Fusion (Qt 6's Linux default) | 45 px | 55 px | everything fits |
**Changes:**
- `mainwidget.qml`: `import QtQuick.Controls` again, as before `93c0851`, with a comment.
- `main.cpp`: `QQuickStyle::setStyle("Fusion")` after `QQuickWindow::setGraphicsApi()`,
before any QML is loaded. Explicit, so the look and sizes are the same on every platform
(without it Qt 6 picks Windows' own style there). Links `Qt6QuickControls2` directly
(already a dependency; present on the Frame: `/usr/lib/libQt6QuickControls2.so.6.8.0`).
- Upstream note: with Material, the layout overflows on Windows too.
## Tests
- **x86_64 locally:** the build succeeds; `objdump` check (R1).
+165 -3
View File
@@ -172,16 +172,156 @@
- **Result:** `APPLICATION_VERSION="5.8.17-release"`, `CMAKE_PROJECT_VERSION=5.8.17`, package
`dist/AdvancedSettings-5.8.17-linux-arm64.tar.gz` (3.0 MB).
### Fix: manifest commands without a display (2026-10-07)
- **Problem (first deploy to the Frame):** `install.sh` aborted with a core dump in
`./AdvancedSettings_app --force-install-manifest`. Log: "could not connect to display",
then "no Qt platform plugin could be initialized" (fatal).
- **Cause:** `main.cpp` creates the `QApplication` before it handles the manifest options.
Qt's default platform plugin (`xcb`) aborts without a display, and over SSH there is
none. `install.sh` set `DISPLAY=:0` only for the start, not for registering.
`uninstall.sh` (`--force-remove-manifest`) had the same problem.
- **Fix (`package-release.sh`, generated scripts only):** both manifest calls run with
`QT_QPA_PLATFORM=offscreen`. Registering draws nothing, so it needs no display at all.
The normal start keeps `DISPLAY=:0` (`xcb`), because rendering needs a real OpenGL/Vulkan
context. No change to the program.
- **Result:** package rebuilt (`--no-build`), generated scripts checked with `sh -n`.
- **Not tested yet:** the fix itself on the Frame (T1, T11). No local x86 build was at hand
for a test without a display.
### Fix: OpenVR library for aarch64 (2026-10-07)
- **Problem (second deploy):** with the display fix, `--force-install-manifest` got as far
as `VR_Init` and failed there: "Installation path could not be located (110)", and on
stderr "Unable to read VR Path Registry from /data/work/openvrpaths.vrpath".
`~/.config/openvr/openvrpaths.vrpath` on the Frame is correct (runtime `/opt/steamvr`).
- **Cause:** the package shipped `third-party/openvr/lib/linuxarm64/libopenvr_api.so`. It is
byte-identical to the OpenVR SDK 2.15.6's `lib/linuxarm64` and looks for the path registry
in the fixed path `/data/work/openvrpaths.vrpath`. The SDK's `bin/linuxarm64` library
(used by the template, verified on the Frame) and SteamVR's own one in
`/opt/steamvr/bin/linuxarm64` don't contain that path. OVRAS's `third-party/openvr` had no
`bin/linuxarm64`.
- **Fix:**
- New file `third-party/openvr/bin/linuxarm64/libopenvr_api.so` from the SDK v2.15.6
(`raw.githubusercontent.com/ValveSoftware/openvr/v2.15.6/bin/linuxarm64/…`, SHA-256
`cc3671d2…47e9`).
- `CMakeLists.txt`: `OPENVR_LIB_DIR` = `bin/linuxarm64` for aarch64 only. x86_64 and
Windows are unchanged.
- Rejected: setting `VR_PATHREG_OVERRIDE` in the scripts. It wouldn't help when SteamVR
autostarts OVRAS.
- **Result:** cross build and package succeed. The package contains the new library (no
`/data/work`), `Qt_6_PRIVATE_API` without `QVulkan*`: 0, RUNPATH `$ORIGIN`. Package
1.6 MB (was 3.0 MB, the old library was 5.4 MB).
- **Not tested yet:** on the Frame (T1).
### Finding: app key already registered by Steam (2026-10-07)
- **Third try of T1** (by hand on the Frame, with the two fixes above): `VR_Init` works, as
`Utility` (vrserver.txt: "New Connect message … (VRApplication_Utility)"; SteamVR did
**not** refuse Utility this time, unlike with the template). Then: "Manifest Previously
Installed", "Could not find working directory of already installed application:
VRApplicationError_PropertyNotSet".
- **Cause:** Steam's generated `steamapps.vrmanifest` lists every owned VR app, including
OVRAS (`steam.overlay.1009850`, launch via `steam://` URL), although the Steam version is
uninstalled. Not a leftover of the uninstallation; it can't be removed permanently.
- **Next:** design DESIGN.md section 2a (approved 2026-10-07).
- **Implementation (`setup.cpp`, `setup.h`):** as designed: key without working directory →
nothing removed, `AddApplicationManifest` directly (error name on refusal);
`logApplicationEntry()` after "Manifest reinstalled." logs source, launch type, working
directory, binary, URL and auto-launch for the key.
- **Result:** cross build and package succeed, `Qt_6_PRIVATE_API` without `QVulkan*`: 0.
- **Not tested yet:** on the Frame. The log decides: `AppKeyAlreadyExists` or Steam's entry
(`steam://` URL) → own app key needed (option 2); our working directory and binary →
enough.
**Result on the Frame (2026-10-07, deploy with `ec82b5f`):** SteamVR accepts our manifest
(no error) but keeps using Steam's entry for the key:
`source: "steam" launch type: "url" working dir: PropertyNotSet binary: PropertyNotSet
URL: "steam://launch/1009850/openvroverlay" auto-launch: true`.
- **Consequence:** the native build needs its own app key (option 2, DESIGN.md 2a,
follow-up). Design pending.
- **Side effect:** `--force-install-manifest` switched auto-launch on for Steam's key. At the
next SteamVR start, SteamVR may launch `steam://launch/1009850` (the uninstalled Steam
version). To be switched off again.
- OVRAS itself was started by `install.sh` and keeps running (not yet checked in the
dashboard, T2).
### Fix: debug output only with debugSymbolsAndLogs (2026-10-07)
- **Problem:** the log filled with `[D] updateAllPulseData done.` about every 11 ms (8399
lines in 6 s). `AudioTabController::eventLoopTick()` reads the microphone's mute state on
every tick, and every read calls `updateAllPulseData()`
(`AudioManagerPulse_internal.h:312`).
- **Cause (regression in `qt6`):** on `master` the message was easylogging's `LOG(DEBUG)`,
and the Debug level was disabled unless built with `debugSymbolsAndLogs`
(`ENABLE_DEBUG_LOGGING`). `qt6` switched to `qDebug()` and lost that; the constant
`debugLoggingEnabled` in `setup.cpp` was left unused, so all 46 `qDebug()` calls were
always active.
- **Fix (`setup.cpp`, `setUpLogging()`):** without `ENABLE_DEBUG_LOGGING`,
`QLoggingCategory::setFilterRules("*.debug=false")`. `QT_LOGGING_RULES="*.debug=true"`
takes precedence and turns debug output on again without a rebuild.
- **Result:** cross build and package succeed, `Qt_6_PRIVATE_API` without `QVulkan*`: 0.
- **Not tested yet:** on the Frame (log without `[D]` lines; with `QT_LOGGING_RULES` with
them).
- **Open for R6/T10:** each tick makes three synchronous PulseAudio queries (server info,
all sinks, all sources) for the mute state. Not changed now.
### Layout cut off on the Frame; diagnosis tools (2026-10-08)
- **Observed (headset photo):** the dashboard tab appears, navigation and laser work. The
fixed 1200×800 layout overflows: about the bottom quarter is cut off (left: Statistics,
Bindings, Settings; right: Microphone half, Push-to-Talk). Navigation icons overlap their
text. The right edge is complete.
- **Ruled out by experiment on the Frame:** `QT_FONT_DPI=96` (verified in the process
environment): no change. `QT_QUICK_CONTROLS_STYLE=Basic` (Qt 5's "Default" style; `qt6`
sets no style, so Qt 6 uses Fusion on Linux; only `mainwidget.qml` imports Material): no
visible change. `QT_QUICK_CONTROLS_MATERIAL_VARIANT=Dense`: slightly better, still cut
off.
- **Facts:** Frame X screen 1920×1080, `Xft.dpi: 144`; default font Noto Sans
(`fc-match sans-serif`). Template renders 1600×1000.
- **Also seen:** after removing the Steam license, Steam's `steamapps.vrmanifest` still
lists `steam.overlay.1009850` (not yet regenerated); the debug-output fix works (0 `[D]`
lines).
- **Diagnosis tools (DESIGN.md section 10):** font/screen metrics in the log, option
`--snapshot <file>`, `run.sh` passes further options.
- **Result:** cross build and package succeed, `Qt_6_PRIVATE_API` without `QVulkan*`: 0,
generated `run.sh` checked with `sh -n`. No new warnings in the changed lines.
- **Not tested yet:** on the Frame (log line "Font: …", PNG written).
### Fix: Quick Controls style Fusion (2026-10-08)
- **Measurements on the Frame** (snapshots of the root page, OpenGL):
- Log: `Font: "Noto Sans" 9 pt = 12 px (requested "Sans Serif" 9 pt); 20 pt = 27 px;
screen "gamescope" logical DPI 96 physical DPI 63.972 device pixel ratio 1`. Fonts, DPI
and scaling are as intended.
- The PNG itself is cut off: OVRAS renders the content too large; SteamVR doesn't crop.
- Navigation buttons: as `qt6` is, 65 px (pitch 75) = Material. With
`QT_QUICK_CONTROLS_STYLE=Basic` (verified in the process environment): identical, the
import overrides it. Test build without the Material import (not committed): 45 px
(pitch 55) = Fusion, **everything fits**. Same build with Basic: 49 px (pitch 59), the
last button is cut off.
- **Fix (DESIGN.md section 11):** `mainwidget.qml` imports `QtQuick.Controls` again;
`main.cpp` sets `QQuickStyle::setStyle("Fusion")`.
- **Result:** cross build and package succeed, `Qt_6_PRIVATE_API` without `QVulkan*`: 0.
- **Frame test of the committed build (`ff4ea8f`, 2026-10-08, OpenGL):** root page fits
completely (12 navigation buttons 45 px, pitch 55, Microphone, Push-to-Talk, version
visible), same as the test build; "Chaperone Settings" fits completely too. No `QT_`
variables set, so Fusion comes from the code. Log: no new errors.
- **Not tested yet:** the other pages, Vulkan.
- **Seen, not changed:** on "Chaperone" and "Space Fix" the icon still overlaps the text
slightly (`MyPushButton`: icon as wide as the button is high, text only indented by
spaces).
### Test checklist on the Frame (step 5)
Deploy: `build_scripts/steamframe/deploy-to-frame.sh <user>@<frame>` (SteamVR running).
| # | Check | Criterion | Result |
|---|---|---|---|
| T1 | `install.sh`: registration works (log: "Manifest reinstalled", via Background) | R2 | open |
| T2 | Tab appears in the dashboard, UI correct (OpenGL) | R2, R3 | open |
| T1 | `install.sh`: registration works (log: "Manifest reinstalled", via Background) | R2 | partly: "Manifest reinstalled" after three fixes (display, OpenVR library, Steam's entry), but SteamVR keeps Steam's entry for the key (see "Finding: app key …") |
| T2 | Tab appears in the dashboard, UI correct (OpenGL) | R2, R3 | partly: tab appears; root page and "Chaperone Settings" correct after the style fix; other pages not checked |
| T3 | `./run.sh vulkan`: UI correct | R3 | open |
| T4 | Laser right way up, click, scrolling on several pages (both APIs) | R4 | open |
| T4 | Laser right way up, click, scrolling on several pages (both APIs) | R4 | partly: laser and navigation work (OpenGL, user's observation); scrolling and Vulkan not checked |
| T5 | Text field (e.g. new profile name): SteamVR keyboard opens, input arrives | R4 | open |
| T6 | An OVRAS action arrives (e.g. push-to-talk, log/notification) | R4 | open |
| T7 | Play area before/after start unchanged; log: "CommitWorkingCopy skipped" | R7 | open |
@@ -198,3 +338,25 @@ Deploy: `build_scripts/steamframe/deploy-to-frame.sh <user>@<frame>` (SteamVR ru
(working rules, conventions), `docs/steamframe/OVRAS-FEATURES.md` (feature analysis).
- **Still open in iteration 1:** phase 4 (tests on the Frame, T1–T11) and phase 5
(assessment).
### Wrap-up (2026-10-08)
- **First tests on the Frame (2026-10-07/08):** six fixes, each its own commit:
`537539a` manifest commands without a display, `317db68` OpenVR library
`bin/linuxarm64`, `ec82b5f` registration when Steam lists the key, `bbb3d1b` no debug
output by default, `d664f86` layout diagnosis (font log, `--snapshot`), `ff4ea8f` Fusion
style instead of Material.
- **State on the Frame:** package of `ff4ea8f` deployed, running with
`--snapshot /tmp/ovras.png` (OpenGL).
- **Open, in this order:**
1. Page-by-page layout check with `--snapshot` (SteamVR, Offsets, Motion, Rotation,
Space Fix, Audio, Video, Utilities, Statistics, Settings, Chaperone sub-pages). Known:
icon slightly overlaps the text on "Chaperone" and "Space Fix".
2. Rest of T1–T11, especially T3 (Vulkan), T5 (keyboard), T8 (quit), T9 (auto-start),
T10 (CPU), T11 (uninstall).
3. App key: Steam's entry still wins (see README "Known open points"); auto-launch is
switched on for Steam's key (`~/.config/openvr/config/vrappconfig/steam.overlay.1009850.vrappconfig`,
created by our `install.sh`). Decide on an own app key (option 2).
4. R6: three synchronous PulseAudio queries per tick for the microphone mute state.
- **Not in the repository:** the headset photo `frame-headset-2026-10-07T22-00-14-104Z.png`
in the working tree (untracked).
+32 -12
View File
@@ -12,16 +12,22 @@ point for continuing the work on any computer.
| [OVRAS-FEATURES.md](OVRAS-FEATURES.md) | OVRAS's features and how they are implemented; issues found while reading |
| [`/CLAUDE.md`](../../CLAUDE.md) | Working rules and conventions (for people and for Claude Code) |
## Status (2026-10-07)
## Status (2026-10-08)
- **Iteration 1 ("native start on the Frame"): implemented, not yet tested on the Frame.**
- **Iteration 1 ("native start on the Frame"): implemented; testing on the Frame has
started** (phase 4). OVRAS starts natively on the Frame, the dashboard tab appears, laser
and navigation work, the root page and "Chaperone Settings" fit (OpenGL). Five problems
found on the first deployment were fixed (details in [PROGRESS.md](PROGRESS.md)):
manifest commands without a display, wrong aarch64 OpenVR library, app key already
registered by Steam, debug log flood, layout cut off (Material style).
- Tested without a headset:
- Linux x86_64 build.
- aarch64 cross build: no private Qt API except `QVulkan*`, Qt 6.8, GLIBC ≤ 2.38.
- Render tests with OpenGL and Vulkan; Vulkan with validation layers and no messages.
- Release package (3.0 MB).
- **Next step:** test checklist T1–T11 on the Frame ([PROGRESS.md](PROGRESS.md)), record
the results there, then assessment.
- Release package (1.6 MB).
- **Next step:** continue on the Frame: page-by-page layout check with `--snapshot`, then the
rest of the test checklist T1–T11 ([PROGRESS.md](PROGRESS.md), "Wrap-up (2026-10-08)"
lists what is open), then assessment.
- **After that:** iteration 2. R5 hides the features that are native in SteamVR or don't
work on the Frame. Then the remaining features are checked one by one.
@@ -124,11 +130,12 @@ docker run --rm -v "$PWD:$PWD" -w "$PWD" ovras-steamframe-cross-arm64 \
| Command | Effect |
|---|---|
| `./install.sh` | Registers with SteamVR (auto-start) and starts |
| `./run.sh opengl\|vulkan` | Restarts with that graphics API |
| `./run.sh opengl\|vulkan [options]` | Restarts with that graphics API; further options go to the program |
| `./uninstall.sh` | Unregisters |
| `AdvancedSettings.log` | Log (in the working directory, i.e. this folder) |
Program options (new): `--graphics-api opengl|vulkan` (default `opengl`). Existing ones
Program options (new): `--graphics-api opengl|vulkan` (default `opengl`); `--snapshot <file>`
(diagnosis: writes the rendered overlay as PNG at most once per second while visible). Existing ones
include `--force-install-manifest` and `--force-remove-manifest`; see `--help`.
## Steam Frame facts the code relies on
@@ -143,14 +150,23 @@ Verified with the template on SteamOS holo 0.4.3; details in FrameAssistant's
- **Manifest and SteamVR:**
- The manifest needs `binary_path_linux_arm`; entries without it are skipped silently.
- SteamVR refuses `VRApplication_Utility`. Manifest commands therefore fall back to
`Background`, which needs a running SteamVR.
`Background`, which needs a running SteamVR. (2026-10-07: with OVRAS, SteamVR accepted
`Utility`; the fallback stays.)
- Steam's generated `~/.local/share/Steam/config/steamapps.vrmanifest` lists every VR app
the user owns, installed or not, with a `steam://` URL. For a key listed there, SteamVR
keeps using Steam's entry even after our manifest is added (seen 2026-10-07).
- Use the OpenVR SDK's `bin/linuxarm64/libopenvr_api.so`; the SDK's `lib/linuxarm64` one
looks for the path registry in `/data/work` and never finds SteamVR.
- `VR_Shutdown` must run before the graphics device is destroyed (crash with Vulkan
otherwise).
- **Rendering and input:**
- OpenGL runs through zink (no native GL driver); Vulkan is about 1.9× faster per frame.
- SteamVR reports overlay mouse coordinates with y from the bottom.
- **Processes and display:**
- Over SSH there is no display: set `DISPLAY=:0` (the scripts do).
- Over SSH there is no display: set `DISPLAY=:0` (the scripts do). Commands that draw
nothing run with `QT_QPA_PLATFORM=offscreen`.
- X screen 1920×1080 with `Xft.dpi: 144`, yet Qt reports logical DPI 96 and device pixel
ratio 1 for OVRAS; default font Noto Sans (20 pt = 27 px).
- Process names are cut to 15 characters: find instances by command line.
- An executable must not be named like a SteamVR process (e.g. `vrdashboard`).
- **Private package repositories:** the Frame's `/etc/pacman.conf` points to private Valve
@@ -160,9 +176,13 @@ Verified with the template on SteamOS holo 0.4.3; details in FrameAssistant's
- **SteamVR keyboard:** text fields may not get focus. The template had to send the offscreen
window an explicit `FocusIn`; `qt6` doesn't (T5).
- **Unverified on the Frame:**
- QML modules: Controls/Material, Layouts, Dialogs, Multimedia.
- libXt/libXtst present? libstdc++ ≥ GLIBCXX_3.4.32?
- **App key taken by Steam's entry:** whoever owns OVRAS on Steam has
`steam.overlay.1009850` in Steam's `steamapps.vrmanifest`, and SteamVR keeps using that
entry (auto-start would launch the Steam version). On the author's Frame it is still
listed after removing the license from the account. Possible fix: an own app key for the
native build (option 2, DESIGN.md section 2a; needs its own design).
- **Unverified on the Frame:** QML modules Dialogs and Multimedia (Controls, Layouts and
the binary's libraries load, since OVRAS runs).
- **Remaining play-area commit:** "auto-apply chaperone" (opt-in, default off) still commits
at start-up; this belongs to R5.
- **Windows:** the Windows build is broken on `steamframe`. There is no D3D11 render path
+11
View File
@@ -3,6 +3,7 @@
#include "openvr/ovr_settings_wrapper.h"
#include "render/RenderBackend.h"
#include <QQuickStyle>
#include <QtLogging>
#include <QtDebug>
#include <memory>
@@ -80,6 +81,15 @@ int main( int argc, char* argv[] )
}
QQuickWindow::setGraphicsApi( renderBackend->graphicsApi() );
// The QML layout has a fixed size (1200x800) and was made for Qt 5's
// default style. Without an explicit style, Qt 6 picks one per platform
// (Linux: Fusion, Windows: Windows), so the sizes would differ by
// platform. Fusion was measured on the Steam Frame: the root page fits
// (navigation buttons 45 px; Basic, Qt 5's style renamed: 49 px, the last
// button is cut off; Material: 65 px, a quarter is cut off). Must be set
// before any QML is loaded.
QQuickStyle::setStyle( "Fusion" );
try
{
QQmlEngine qmlEngine;
@@ -97,6 +107,7 @@ int main( int argc, char* argv[] )
commandLineArgs.forceNoSound,
qmlEngine,
std::move( renderBackend ) ) );
controller->setSnapshotPath( commandLineArgs.snapshotPath );
//const auto url
// = QUrl( ":/qt/qml/AdvancedSettings/src/res/qml/common/mainwidget.qml" );
QQmlComponent component( &qmlEngine, ":/qt/qml/AdvancedSettings/src/res/qml/common/mainwidget.qml" );
+34
View File
@@ -16,6 +16,9 @@
#include <QCursor>
#include <QProcess>
#include <QMessageBox>
#include <QFile>
#include <QFontInfo>
#include <QScreen>
#include <QtLogging>
#include <QtDebug>
#include <iostream>
@@ -456,6 +459,20 @@ void OverlayController::SetWidget( QQuickItem* quickItem,
static_cast<int>( quickItem->width() ),
static_cast<int>( quickItem->height() ) );
// Font and screen metrics that decide how big the fixed-size
// (1200x800) QML layout comes out, e.g. on the Steam Frame (Noto Sans,
// Xft.dpi 144) compared to Windows (Segoe UI, 96 DPI).
const QFont font = QGuiApplication::font();
const QFontInfo fontInfo( font );
qInfo() << "Font:" << fontInfo.family() << fontInfo.pointSizeF()
<< "pt =" << fontInfo.pixelSize() << "px (requested"
<< font.family() << font.pointSizeF() << "pt); 20 pt ="
<< QFontInfo( QFont( font.family(), 20 ) ).pixelSize()
<< "px; screen" << m_window.screen()->name() << "logical DPI"
<< m_window.screen()->logicalDotsPerInch() << "physical DPI"
<< m_window.screen()->physicalDotsPerInch()
<< "device pixel ratio" << m_window.effectiveDevicePixelRatio();
vr::HmdVector2_t vecWindowSize
= { static_cast<float>( quickItem->width() ),
static_cast<float>( quickItem->height() ) };
@@ -572,6 +589,23 @@ void OverlayController::renderOverlay()
m_renderControl.endFrame();
m_renderBackend->finishFrame();
// --snapshot: writes the rendered overlay to a PNG at most once per
// second while it is visible, so the layout can be checked without a
// headset photo. Written to a temporary file and renamed, so readers
// never see a partial file.
if ( !m_snapshotPath.isEmpty()
&& ( !m_snapshotTimer.isValid()
|| m_snapshotTimer.elapsed() >= 1000 ) )
{
m_snapshotTimer.start();
const QString tmp = m_snapshotPath + ".tmp";
if ( m_renderBackend->readback().save( tmp, "PNG" ) )
{
QFile::remove( m_snapshotPath );
QFile::rename( tmp, m_snapshotPath );
}
}
vr::VRTextureBounds_t bounds = m_renderBackend->vrTextureBounds();
vr::VROverlay()->SetOverlayTextureBounds( m_ulOverlayHandle, &bounds );
vr::Texture_t texture = m_renderBackend->vrTexture();
+10
View File
@@ -23,6 +23,7 @@
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QQuickRenderTarget>
#include <QElapsedTimer>
#include <memory>
#include <optional>
#include "render/RenderBackend.h"
@@ -117,6 +118,9 @@ private:
QQuickWindow m_window{ &m_renderControl };
std::optional<vr::ETextureType> m_cached_vr_texture_type;
// --snapshot (see renderOverlay()): target file and time of the last one.
QString m_snapshotPath;
QElapsedTimer m_snapshotTimer;
// Set by Shutdown(), which must run only once (see there).
bool m_shutdownDone = false;
@@ -210,6 +214,12 @@ public:
std::unique_ptr<RenderBackend> renderBackend );
virtual ~OverlayController();
// Diagnosis (--snapshot): see renderOverlay(). Empty: off.
void setSnapshotPath( const QString& path )
{
m_snapshotPath = path;
}
void Shutdown();
Q_INVOKABLE void exitApp();
Q_INVOKABLE void setAutoChapProfileName( int index );
+5 -1
View File
@@ -1,5 +1,9 @@
import QtQuick
import QtQuick.Controls.Material
// Not QtQuick.Controls.Material: importing a style module fixes that style
// for all controls and overrides the one set in main.cpp. Material's
// controls are too tall for the fixed-size layout (cut off on the Steam
// Frame).
import QtQuick.Controls
import QtQuick.Layouts
import ".."
import "../utilities_page"
+85 -7
View File
@@ -1,6 +1,7 @@
#include "setup.h"
#include <QtLogging>
#include <QtDebug>
#include <QLoggingCategory>
#ifdef ENABLE_DEBUG_LOGGING
constexpr auto debugLoggingEnabled = true;
#else
@@ -64,6 +65,10 @@ CommandLineOptions returnCommandLineParser( const MyQApplication& application )
k_graphicsApiDefault );
parser.addOption( graphicsApi );
QCommandLineOption snapshot(
k_snapshot, k_snapshotDescription, k_snapshotValueName );
parser.addOption( snapshot );
parser.process( application );
const bool desktopModeEnabled = parser.isSet( desktopMode );
@@ -93,13 +98,16 @@ CommandLineOptions returnCommandLineParser( const MyQApplication& application )
const QString graphicsApiValue = parser.value( graphicsApi );
qInfo() << "Graphics API:" << graphicsApiValue;
const CommandLineOptions commandLineArgs{ desktopModeEnabled,
forceNoSoundEnabled,
forceNoManifestEnabled,
forceInstallManifestEnabled,
forceRemoveManifestEnabled,
resetSettingsEnabled,
graphicsApiValue };
const QString snapshotValue = parser.value( snapshot );
if ( !snapshotValue.isEmpty() )
qInfo() << "Overlay snapshots to:" << snapshotValue;
const CommandLineOptions commandLineArgs{
desktopModeEnabled, forceNoSoundEnabled,
forceNoManifestEnabled, forceInstallManifestEnabled,
forceRemoveManifestEnabled, resetSettingsEnabled,
graphicsApiValue, snapshotValue
};
qInfo() << "Command line arguments processed.";
@@ -191,6 +199,29 @@ void reinstallApplicationManifest( const std::string manifestPath )
kStringSize,
&app_error );
// Steam lists every VR app the user owns in its generated
// steamapps.vrmanifest, installed or not, so whoever owns OVRAS on
// Steam has this key registered with a steam:// URL and no working
// directory. There is no manifest file to remove then: add ours next
// to it (installApplicationManifest() would skip the installed key).
if ( app_error == vr::VRApplicationError_PropertyNotSet )
{
qInfo() << "Key registered without a manifest file (e.g. by "
"Steam), not removing it";
const auto add_error = vr::VRApplications()->AddApplicationManifest(
manifestPath.c_str() );
if ( add_error != vr::VRApplicationError_None )
{
throw std::runtime_error(
"Could not add application manifest: "
+ std::string(
vr::VRApplications()->GetApplicationsErrorNameFromEnum(
add_error ) ) );
}
qInfo() << "Manifest Installed at: " << manifestPath;
return;
}
if ( app_error != vr::VRApplicationError_None )
{
throw std::runtime_error(
@@ -251,6 +282,43 @@ void forceRemoveApplicationManifest()
}
}
// Logs what SteamVR reports for OVRAS's app key, to show which entry it uses
// when several register the key (e.g. Steam's own, see
// reinstallApplicationManifest()).
void logApplicationEntry()
{
const auto property = []( vr::EVRApplicationProperty prop )
{
constexpr auto kStringSize = 1024;
char value[kStringSize] = { 0 };
auto error = vr::VRApplicationError_None;
vr::VRApplications()->GetApplicationPropertyString(
application_strings::applicationKey,
prop,
value,
kStringSize,
&error );
return error == vr::VRApplicationError_None
? QString( value )
: QString(
vr::VRApplications()->GetApplicationsErrorNameFromEnum(
error ) );
};
qInfo() << "SteamVR entry for" << application_strings::applicationKey
<< "- source:"
<< property( vr::VRApplicationProperty_Source_String )
<< "launch type:"
<< property( vr::VRApplicationProperty_LaunchType_String )
<< "working dir:"
<< property( vr::VRApplicationProperty_WorkingDirectory_String )
<< "binary:"
<< property( vr::VRApplicationProperty_BinaryPath_String )
<< "URL:" << property( vr::VRApplicationProperty_URL_String )
<< "auto-launch:"
<< vr::VRApplications()->GetApplicationAutoLaunch(
application_strings::applicationKey );
}
// Initializes OpenVR and calls the relevant manifest functions.
// The .vrmanifest is used by SteamVR however the exact functiontionality is not
// documented in the official documentation.
@@ -298,6 +366,7 @@ void forceRemoveApplicationManifest()
reinstallApplicationManifest( *manifestPath );
enableApplicationAutostart();
qInfo() << "Manifest reinstalled.";
logApplicationEntry();
}
else if ( removeManifest )
{
@@ -321,4 +390,13 @@ void setUpLogging()
f = fopen( "AdvancedSettings.log", "a" );
qSetMessagePattern("[%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{time process}: %{message}");
originalMessageHandler = qInstallMessageHandler( mainQtMessageHandler );
// Debug output only in builds with debugSymbolsAndLogs, as on master
// (easylogging's Debug level was disabled otherwise). Some qDebug() calls
// run on every tick (e.g. "updateAllPulseData done."). The environment
// variable QT_LOGGING_RULES="*.debug=true" takes precedence over this and
// turns them on again without a rebuild.
if constexpr ( !debugLoggingEnabled )
{
QLoggingCategory::setFilterRules( "*.debug=false" );
}
}
+10
View File
@@ -30,6 +30,8 @@ struct CommandLineOptions
const bool resetSettings = false;
// Value of --graphics-api; checked in main() with parseGraphicsApi().
const QString graphicsApi = "opengl";
// Value of --snapshot; empty: no snapshots.
const QString snapshotPath;
};
// Manages the programs control flow and main settings.
@@ -98,6 +100,13 @@ constexpr auto k_graphicsApiDescription
= "Graphics API for rendering the overlay: opengl (default) or vulkan.";
constexpr auto k_graphicsApiValueName = "api";
constexpr auto k_graphicsApiDefault = "opengl";
// Diagnosis: writes the rendered overlay to a PNG while it is visible, to check
// the layout without a headset photo (OverlayController::renderOverlay()).
constexpr auto k_snapshot = "snapshot";
constexpr auto k_snapshotDescription
= "Writes the rendered overlay to <file> (PNG) at most once per second "
"while it is visible.";
constexpr auto k_snapshotValueName = "file";
constexpr auto k_resetSettings = "reset-steamvr-settings";
constexpr auto k_resetSettingsDescription
@@ -117,6 +126,7 @@ void installApplicationManifest( const std::string manifestPath );
void removeApplicationManifest( const std::string manifestPath );
void reinstallApplicationManifest( const std::string manifestPath );
void forceRemoveApplicationManifest();
void logApplicationEntry();
[[noreturn]] void handleManifests( const bool installManifest,
const bool removeManifest );
Binary file not shown.