Document iteration 1: settings file, HA Dashboard tab, new hooks
README: settings file, --config, the new tab. ARCHITECTURE.md and CLAUDE.md: prepare() hook, app command-line options, app code layout, WebEngine notes, the moc raw-string pitfall. THIRD_PARTY_NOTICES: Qt WebEngine. Ship config.example.json; the package README explains the set-up. PROGRESS.md and DESIGN.md: phase 3 results and deviations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
**FrameAssistant**, "Steam Frame companion for Home Assistant" (in the spirit of HASS.Agent):
|
**FrameAssistant**, "Steam Frame companion for Home Assistant" (in the spirit of HASS.Agent):
|
||||||
it reports headset/environment sensors to Home Assistant, offers services Home Assistant can
|
it reports headset/environment sensors to Home Assistant, offers services Home Assistant can
|
||||||
call (e.g. notifications in the headset), and shows a Home Assistant dashboard as a SteamVR
|
call (e.g. notifications in the headset), and shows a Home Assistant dashboard as a SteamVR
|
||||||
dashboard tab. Status: the overlay platform works; the Home Assistant parts are being planned.
|
dashboard tab. Status: the overlay platform works; the Home Assistant parts are being built
|
||||||
|
(implementation iteration 1: settings file and the "HA Dashboard" web UI tab).
|
||||||
|
|
||||||
**Start every session by reading `docs/PROGRESS.md`** (current state, next steps, log).
|
**Start every session by reading `docs/PROGRESS.md`** (current state, next steps, log).
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ cmake --preset native-debug && cmake --build --preset native-debug # system Qt
|
|||||||
ctest --preset native-debug # all tests
|
ctest --preset native-debug # all tests
|
||||||
build/native-debug/bin/tst_inputbridge clickUsesGlCoordinates # one QtTest function
|
build/native-debug/bin/tst_inputbridge clickUsesGlCoordinates # one QtTest function
|
||||||
build/native-debug/bin/frame-assistant --desktop # UI preview in a window, no SteamVR
|
build/native-debug/bin/frame-assistant --desktop # UI preview in a window, no SteamVR
|
||||||
build/native-debug/bin/frame-assistant --snapshot x.png # one frame via the real offscreen path
|
build/native-debug/bin/frame-assistant --snapshot x.png # x-0.png (main tab), x-dashboard.png
|
||||||
scripts/container-build.sh cross # x86_64 → aarch64 against the Frame's system Qt (default)
|
scripts/container-build.sh cross # x86_64 → aarch64 against the Frame's system Qt (default)
|
||||||
scripts/container-build.sh native # same for the host arch (use on the Frame)
|
scripts/container-build.sh native # same for the host arch (use on the Frame)
|
||||||
scripts/container-build.sh cross-bundle # fallback: self-contained, Qt bundled (also native-bundle)
|
scripts/container-build.sh cross-bundle # fallback: self-contained, Qt bundled (also native-bundle)
|
||||||
@@ -70,7 +71,11 @@ VK_DRIVER=lvp scripts/container-test.sh # same on lavapipe (software Vulkan)
|
|||||||
build/native-debug/bin/frame-assistant --graphics-api vulkan --benchmark 600 # frame ms incl. GPU
|
build/native-debug/bin/frame-assistant --graphics-api vulkan --benchmark 600 # frame ms incl. GPU
|
||||||
```
|
```
|
||||||
|
|
||||||
- Use `--snapshot` and then read the PNG to check UI or rendering changes without a headset.
|
- Use `--snapshot` and then read the PNGs to check UI or rendering changes without a headset.
|
||||||
|
With several surfaces it writes `<name>-<id>.png` per surface (`-0` for the main tab's
|
||||||
|
empty id). Snapshot and benchmark don't load the settings, so the "HA Dashboard" tab shows
|
||||||
|
its "no URL" hint there.
|
||||||
|
- Overlay and desktop mode need a settings file; pass a test one with `--config <path>`.
|
||||||
- `tst_render` runs once per backend (`VRDASH_TEST_API`, separate CTest entries, because the
|
- `tst_render` runs once per backend (`VRDASH_TEST_API`, separate CTest entries, because the
|
||||||
Qt graphics API is process-global). It QSKIPs without a GPU context or when Vulkan isn't
|
Qt graphics API is process-global). It QSKIPs without a GPU context or when Vulkan isn't
|
||||||
compiled in.
|
compiled in.
|
||||||
@@ -106,11 +111,14 @@ into Qt events.
|
|||||||
modes (overlay, `--desktop`, `--snapshot`, `--benchmark`, manifest install).
|
modes (overlay, `--desktop`, `--snapshot`, `--benchmark`, manifest install).
|
||||||
- **Inheritance for the app, components for the surfaces** (the user's decision, after
|
- **Inheritance for the app, components for the surfaces** (the user's decision, after
|
||||||
trying composition): the app derives from `DashboardApplication`. In its constructor it
|
trying composition): the app derives from `DashboardApplication`. In its constructor it
|
||||||
configures (`addSurface`, optional `setAppKey`/`setDisplayName`) and it
|
configures (`addSurface`, optional `setAppKey`/`setDisplayName`) and passes its own
|
||||||
overrides the hooks `loading()` (backends + shared engine exist, QML not loaded) and
|
command-line options (`QCommandLineOption` list) to the base constructor. It overrides
|
||||||
`started()` (everything runs). In the hooks, protected accessors give the running state:
|
the hooks `prepare(QString*)` (first; mode and app key known, no engine/surfaces/SteamVR
|
||||||
`mode()`, `appKey()`, `appName()`, `engine()`, `surfaces()`, `surface()` (primary),
|
yet; returning false ends `exec()` with exit code 1), `loading()` (backends + shared
|
||||||
`surface(id)`, `vrRuntime()`, `navigation()`. The app is `src/Dashboard.h/.cpp`.
|
engine exist, QML not loaded) and `started()` (everything runs). In the hooks, protected
|
||||||
|
accessors give the running state: `mode()`, `appKey()`, `appName()`, `isOptionSet()`,
|
||||||
|
`optionValue()`, `engine()`, `surfaces()`, `surface()` (primary), `surface(id)`,
|
||||||
|
`vrRuntime()`, `navigation()`. The app is `src/Dashboard.h/.cpp`.
|
||||||
- The base constructor creates the `QGuiApplication` (`QCoreApplication` for manifest
|
- The base constructor creates the `QGuiApplication` (`QCoreApplication` for manifest
|
||||||
commands), so derived members can be QObjects with timers.
|
commands), so derived members can be QObjects with timers.
|
||||||
- All QML and surfaces are torn down **at the end of `exec()`**, while the derived object
|
- All QML and surfaces are torn down **at the end of `exec()`**, while the derived object
|
||||||
@@ -188,6 +196,22 @@ into Qt events.
|
|||||||
- **QML singletons owned by the app:** keep them as members of the app class. A singleton
|
- **QML singletons owned by the app:** keep them as members of the app class. A singleton
|
||||||
must not be default-constructible, or QML builds its own instance instead of calling
|
must not be default-constructible, or QML builds its own instance instead of calling
|
||||||
`create()` (e.g. give the constructor a required `QObject* parent` and init it `{nullptr}`).
|
`create()` (e.g. give the constructor a required `QObject* parent` and init it `{nullptr}`).
|
||||||
|
- **App code layout:** logic without SteamVR or rendering goes into the static library
|
||||||
|
`frameassistant_lib` (`src/config/`, …), which the app and its tests link. QML types
|
||||||
|
(`QML_ELEMENT`, e.g. `src/ha/HaWebUi`) go into `QML_SOURCES` of `vrdash_add_dashboard()`:
|
||||||
|
they must be part of the QML module. The generated type registration includes their
|
||||||
|
headers by file name only, so their directory is on the app's include path.
|
||||||
|
- **Settings** (`src/config/Config`) are loaded once in `Dashboard::prepare()` (overlay and
|
||||||
|
desktop mode) and passed on as an immutable value (DESIGN.md, D19).
|
||||||
|
- **Qt WebEngine** ("HA Dashboard" tab): `QtWebEngineQuick::initialize()` runs in `main()`
|
||||||
|
before the app object, because it must precede the `QGuiApplication`. `HaWebUi` creates a
|
||||||
|
persistent profile under `~/.local/share/frameassistant/webengine` with the Qt 6.8 API
|
||||||
|
(setters on `QQuickWebEngineProfile`). Qt ≥ 6.9 logs a hint to use
|
||||||
|
`WebEngineProfilePrototype` instead, which the Frame's Qt 6.8 doesn't have.
|
||||||
|
- **moc and raw string literals:** a raw string containing `//` (e.g. a URL) makes moc treat
|
||||||
|
the rest as a comment; it then misses `Q_OBJECT` classes in that file (link error "vtable
|
||||||
|
for …"). In moc'ed files, write such text as normal string literals (`tst_config` uses
|
||||||
|
JSON with `'` quotes and converts them).
|
||||||
- Publish per-frame values (frame stats etc.) to QML at most about once per second. Binding
|
- Publish per-frame values (frame stats etc.) to QML at most about once per second. Binding
|
||||||
per-frame values in QML re-triggers the on-demand renderer forever.
|
per-frame values in QML re-triggers the on-demand renderer forever.
|
||||||
- **The executable is `frame-assistant` (`VRDASH_EXECUTABLE_NAME`), never `vrdashboard`.**
|
- **The executable is `frame-assistant` (`VRDASH_EXECUTABLE_NAME`), never `vrdashboard`.**
|
||||||
@@ -307,3 +331,5 @@ Check these when testing on the device, and update this list:
|
|||||||
- The restructured app code (base class `DashboardApplication`, `Surface`, `Overlay` base,
|
- The restructured app code (base class `DashboardApplication`, `Surface`, `Overlay` base,
|
||||||
active-surface navigation) has only been tested off the device so far.
|
active-surface navigation) has only been tested off the device so far.
|
||||||
- Several dashboard tabs from one process (works in `tst_surfaces`, not tried in SteamVR).
|
- Several dashboard tabs from one process (works in `tst_surfaces`, not tried in SteamVR).
|
||||||
|
- Qt WebEngine in the off-screen renderer (Vulkan and OpenGL), the SteamVR keyboard in web
|
||||||
|
page input fields, and the persistent login ("HA Dashboard" tab, DSH-1).
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ the headset to Home Assistant in both directions:
|
|||||||
- **Services:** Home Assistant can call it, e.g. to show notifications in the headset.
|
- **Services:** Home Assistant can call it, e.g. to show notifications in the headset.
|
||||||
- **Dashboard:** a SteamVR dashboard tab to view and control Home Assistant from inside VR.
|
- **Dashboard:** a SteamVR dashboard tab to view and control Home Assistant from inside VR.
|
||||||
|
|
||||||
> **Status:** early development. The overlay platform works on the Frame; the Home Assistant
|
> **Status:** early development. The overlay platform works on the Frame. The Home Assistant
|
||||||
> parts are being planned; see [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md),
|
> parts are being built: so far a settings file and an "HA Dashboard" tab with Home
|
||||||
|
> Assistant's web UI. See [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md),
|
||||||
> [docs/DESIGN.md](docs/DESIGN.md) and [docs/PROGRESS.md](docs/PROGRESS.md).
|
> [docs/DESIGN.md](docs/DESIGN.md) and [docs/PROGRESS.md](docs/PROGRESS.md).
|
||||||
|
|
||||||
### Platform
|
### Platform
|
||||||
@@ -33,8 +34,8 @@ FrameAssistant grew out of a SteamVR dashboard-overlay template, which it still
|
|||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- CMake ≥ 3.22, Ninja, a C++20 compiler, and Qt ≥ 6.5 (Quick, QuickControls2, OpenGL;
|
- CMake ≥ 3.22, Ninja, a C++20 compiler, and Qt ≥ 6.5 (Quick, QuickControls2, OpenGL,
|
||||||
Test for the tests)
|
WebEngineQuick; Test for the tests)
|
||||||
- Vulkan headers (optional; without them the build is OpenGL only)
|
- Vulkan headers (optional; without them the build is OpenGL only)
|
||||||
- Docker or Podman, for the Frame builds
|
- Docker or Podman, for the Frame builds
|
||||||
|
|
||||||
@@ -45,12 +46,14 @@ The OpenVR SDK files are downloaded automatically when CMake configures the buil
|
|||||||
cmake --preset native-debug && cmake --build --preset native-debug
|
cmake --preset native-debug && cmake --build --preset native-debug
|
||||||
ctest --preset native-debug
|
ctest --preset native-debug
|
||||||
|
|
||||||
build/native-debug/bin/frame-assistant --desktop # UI in a window, no SteamVR
|
build/native-debug/bin/frame-assistant --desktop # UI in windows, no SteamVR
|
||||||
build/native-debug/bin/frame-assistant --snapshot out.png # one frame through the VR path
|
build/native-debug/bin/frame-assistant --snapshot out.png # one frame per tab through the VR path
|
||||||
build/native-debug/bin/frame-assistant # as dashboard tab (SteamVR running)
|
build/native-debug/bin/frame-assistant # as dashboard tab (SteamVR running)
|
||||||
build/native-debug/bin/frame-assistant --install-manifest # register + auto-start with SteamVR
|
build/native-debug/bin/frame-assistant --install-manifest # register + auto-start with SteamVR
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Overlay and desktop mode need a settings file (below); `--snapshot` and `--benchmark` don't.
|
||||||
|
|
||||||
On the Steam Frame:
|
On the Steam Frame:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -60,6 +63,41 @@ scripts/deploy-to-frame.sh <user>@<frame> # copy over SSH and install (SteamVR
|
|||||||
|
|
||||||
Details are in [docs/STEAM-FRAME.md](docs/STEAM-FRAME.md).
|
Details are in [docs/STEAM-FRAME.md](docs/STEAM-FRAME.md).
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
FrameAssistant reads its settings from `~/.config/frameassistant/config.json`
|
||||||
|
(`$XDG_CONFIG_HOME` is respected; `--config <path>` picks another file). Start from
|
||||||
|
[`resources/config.example.json`](resources/config.example.json), which packages ship as
|
||||||
|
`share/frameassistant/config.example.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"device": { "name": "Steam Frame" },
|
||||||
|
"mqtt": { "host": "homeassistant.local", "username": "frameassistant", "password": "…" },
|
||||||
|
"homeassistant": { "url": "http://homeassistant.local:8123" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Required | Default | Meaning |
|
||||||
|
|-----|----------|---------|---------|
|
||||||
|
| `device.name` | yes | | Name of this headset in Home Assistant |
|
||||||
|
| `device.ha_unique_id` | no | derived from the name | Fixed id, so the name can change without creating a new device |
|
||||||
|
| `mqtt.host` | yes | | MQTT broker |
|
||||||
|
| `mqtt.port` | no | 1883, 8883 with TLS | |
|
||||||
|
| `mqtt.username`, `mqtt.password` | no | no login | |
|
||||||
|
| `mqtt.tls.enabled` | no | `false` | |
|
||||||
|
| `mqtt.tls.ca_file` | no | system certificates | CA of the broker's certificate |
|
||||||
|
| `mqtt.tls.cert_file`, `mqtt.tls.key_file` | no | | Client certificate |
|
||||||
|
| `homeassistant.discovery_prefix` | no | `homeassistant` | |
|
||||||
|
| `homeassistant.url` | no | | Home Assistant web UI for the "HA Dashboard" tab |
|
||||||
|
| `notifications.timeout_s` | no | 10 | How long notifications stay |
|
||||||
|
|
||||||
|
Invalid or missing settings stop the app with an error message and exit code 1. Unknown keys
|
||||||
|
only produce a warning. Keep the file private (`chmod 600`) when it holds a password.
|
||||||
|
|
||||||
|
The "HA Dashboard" tab keeps the Home Assistant login in
|
||||||
|
`~/.local/share/frameassistant/webengine`.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
- **App identity** (app key `net.matzman.frameassistant`, tab name, executable name
|
- **App identity** (app key `net.matzman.frameassistant`, tab name, executable name
|
||||||
@@ -68,8 +106,10 @@ Details are in [docs/STEAM-FRAME.md](docs/STEAM-FRAME.md).
|
|||||||
- **UI:** `src/qml/`, with `Main.qml` as the root. List new QML files under `QML_FILES` in
|
- **UI:** `src/qml/`, with `Main.qml` as the root. List new QML files under `QML_FILES` in
|
||||||
`vrdash_add_dashboard()` in `src/CMakeLists.txt`. Iterate with `--desktop` and `--snapshot`.
|
`vrdash_add_dashboard()` in `src/CMakeLists.txt`. Iterate with `--desktop` and `--snapshot`.
|
||||||
- **C++ logic:** the app class `src/Dashboard.h/.cpp` derives from `DashboardApplication`.
|
- **C++ logic:** the app class `src/Dashboard.h/.cpp` derives from `DashboardApplication`.
|
||||||
It adds views in its constructor (`addSurface()`), keeps its objects as members and wires
|
It adds views in its constructor (`addSurface()`) and its own command-line options,
|
||||||
them up in the `loading()`/`started()` overrides.
|
keeps its objects as members, loads the settings in `prepare()` and wires things up in
|
||||||
|
the `loading()`/`started()` overrides. Logic without SteamVR or rendering goes into the
|
||||||
|
static library `frameassistant_lib` (e.g. `src/config/`), which the tests link.
|
||||||
- **Controller buttons:** actions and default bindings are in `resources/input/`.
|
- **Controller buttons:** actions and default bindings are in `resources/input/`.
|
||||||
- **Ship it:** `scripts/package-release.sh` and `scripts/deploy-to-frame.sh`, see
|
- **Ship it:** `scripts/package-release.sh` and `scripts/deploy-to-frame.sh`, see
|
||||||
[docs/STEAM-FRAME.md](docs/STEAM-FRAME.md).
|
[docs/STEAM-FRAME.md](docs/STEAM-FRAME.md).
|
||||||
@@ -84,6 +124,7 @@ How the pieces fit together: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|||||||
|--------|---------|
|
|--------|---------|
|
||||||
| *(none)* | Run as SteamVR dashboard overlay (SteamVR must be running) |
|
| *(none)* | Run as SteamVR dashboard overlay (SteamVR must be running) |
|
||||||
| `--desktop` | Show the UI in desktop windows, one per surface; no SteamVR |
|
| `--desktop` | Show the UI in desktop windows, one per surface; no SteamVR |
|
||||||
|
| `--config <path>` | Settings file (default `~/.config/frameassistant/config.json`) |
|
||||||
| `--snapshot <png>` | Render one frame per surface off-screen and save it (`<name>-<id>.png` with several surfaces) |
|
| `--snapshot <png>` | Render one frame per surface off-screen and save it (`<name>-<id>.png` with several surfaces) |
|
||||||
| `--benchmark <frames>` | Render frames back to back with a full GPU wait each and print frame times |
|
| `--benchmark <frames>` | Render frames back to back with a full GPU wait each and print frame times |
|
||||||
| `--graphics-api vulkan\|opengl` | Render backend. Default: Vulkan, falling back to OpenGL if Vulkan doesn't start; an explicit choice never falls back |
|
| `--graphics-api vulkan\|opengl` | Render backend. Default: Vulkan, falling back to OpenGL if Vulkan doesn't start; an explicit choice never falls back |
|
||||||
@@ -107,17 +148,20 @@ VK_DRIVER=lvp scripts/container-test.sh # same on the lavapipe software driver
|
|||||||
| Path | Content |
|
| Path | Content |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `src/main.cpp`, `src/Dashboard.*`, `src/qml/` | **The app** |
|
| `src/main.cpp`, `src/Dashboard.*`, `src/qml/` | **The app** |
|
||||||
|
| `src/config/` | Settings file (`frameassistant_lib`) |
|
||||||
|
| `src/ha/` | Home Assistant parts: `HaWebUi` (web UI tab) |
|
||||||
| `src/app/` | `DashboardApplication` (app base class), `Surface` (one view) |
|
| `src/app/` | `DashboardApplication` (app base class), `Surface` (one view) |
|
||||||
| `src/render/` | Off-screen Qt Quick renderer; Vulkan and OpenGL backends |
|
| `src/render/` | Off-screen Qt Quick renderer; Vulkan and OpenGL backends |
|
||||||
| `src/vr/` | SteamVR connection, overlays (dashboard tab) |
|
| `src/vr/` | SteamVR connection, overlays (dashboard tab) |
|
||||||
| `src/input/` | Laser/scroll/keyboard → Qt events; SteamVR Input navigation |
|
| `src/input/` | Laser/scroll/keyboard → Qt events; SteamVR Input navigation |
|
||||||
| `resources/` | SteamVR manifest template, input actions and bindings |
|
| `resources/` | SteamVR manifest template, input actions and bindings, example settings |
|
||||||
| `tests/` | Unit and render tests |
|
| `tests/` | Unit and render tests |
|
||||||
| `cmake/` | `vrdash_add_dashboard()`, OpenVR download, Qt bundling, toolchain |
|
| `cmake/` | `vrdash_add_dashboard()`, OpenVR download, Qt bundling, toolchain |
|
||||||
| `docker/`, `scripts/` | Build containers; build, package, deploy and test scripts |
|
| `docker/`, `scripts/` | Build containers; build, package, deploy and test scripts |
|
||||||
| `docs/` | Requirements, progress, architecture, Steam Frame notes |
|
| `docs/` | Requirements, design, progress, architecture, Steam Frame notes |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT, see [LICENSE](LICENSE). Third-party components (OpenVR SDK, Qt; in bundled builds also
|
MIT, see [LICENSE](LICENSE). Third-party components (OpenVR SDK, Qt incl. Qt WebEngine; in
|
||||||
their dependencies) are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
bundled builds also their dependencies) are listed in
|
||||||
|
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ This project is MIT-licensed (see `LICENSE`). `<project>` below is the CMake pro
|
|||||||
|-----------|---------|----------------|
|
|-----------|---------|----------------|
|
||||||
| [OpenVR SDK](https://github.com/ValveSoftware/openvr) (`libopenvr_api.so`, `openvr.h`) | BSD-3-Clause | Shipped in `lib/`; license in `share/doc/<project>/third-party/openvr.LICENSE` |
|
| [OpenVR SDK](https://github.com/ValveSoftware/openvr) (`libopenvr_api.so`, `openvr.h`) | BSD-3-Clause | Shipped in `lib/`; license in `share/doc/<project>/third-party/openvr.LICENSE` |
|
||||||
| [Qt 6](https://www.qt.io/) (Core, Gui, Qml, Quick, Quick Controls, Wayland/XCB plugins) | LGPL-3.0 (Qt open-source license) | Linked dynamically. Default builds use the system's Qt (not shipped). Bundled builds ship Qt's shared libraries unmodified in `lib/`, `plugins/`, `qml/`: replace them to use another compatible Qt build |
|
| [Qt 6](https://www.qt.io/) (Core, Gui, Qml, Quick, Quick Controls, Wayland/XCB plugins) | LGPL-3.0 (Qt open-source license) | Linked dynamically. Default builds use the system's Qt (not shipped). Bundled builds ship Qt's shared libraries unmodified in `lib/`, `plugins/`, `qml/`: replace them to use another compatible Qt build |
|
||||||
|
| [Qt WebEngine](https://doc.qt.io/qt-6/qtwebengine-index.html) (Quick), for the "HA Dashboard" tab | LGPL-3.0; contains Chromium (BSD-3-Clause and further licenses, listed by Qt WebEngine) | Linked dynamically, from the system's Qt; not shipped. Bundled builds don't support it yet |
|
||||||
|
|
||||||
**Bundled builds** (`--bundle-qt`) also ship further shared libraries that Qt depends on,
|
**Bundled builds** (`--bundle-qt`) also ship further shared libraries that Qt depends on,
|
||||||
taken unmodified from Debian. Their copyright and license files are in
|
taken unmodified from Debian. Their copyright and license files are in
|
||||||
|
|||||||
+23
-10
@@ -37,13 +37,19 @@ the order in which all of that is created and destroyed.
|
|||||||
|
|
||||||
Your app derives from it:
|
Your app derives from it:
|
||||||
- **Constructor:** configuration (`addSurface()`, optionally `setAppKey()`,
|
- **Constructor:** configuration (`addSurface()`, optionally `setAppKey()`,
|
||||||
`setDisplayName()`, `setQuickStyle()`, `setQmlModule()`).
|
`setDisplayName()`, `setQuickStyle()`, `setQmlModule()`). The app's own command-line
|
||||||
|
options go to the base constructor as a list of `QCommandLineOption`s; they are parsed and
|
||||||
|
listed in `--help` with the built-in ones, and read with `isOptionSet()`/`optionValue()`.
|
||||||
- **Hooks:**
|
- **Hooks:**
|
||||||
|
- `prepare(QString* error)`: first, once mode and app key are known, before the QML
|
||||||
|
engine, the surfaces and SteamVR exist (not for manifest commands). Returning `false`
|
||||||
|
ends `exec()` with the error and exit code 1, e.g. for invalid settings.
|
||||||
- `loading()`: the shared QML engine, the surfaces and their render backends exist; QML
|
- `loading()`: the shared QML engine, the surfaces and their render backends exist; QML
|
||||||
isn't loaded yet. Set up what the QML needs here.
|
isn't loaded yet. Set up what the QML needs here.
|
||||||
- `started()`: everything runs. Connect to surfaces and input here.
|
- `started()`: everything runs. Connect to surfaces and input here.
|
||||||
- **Accessors:** `mode()`, `appKey()`, `appName()`, `engine()`, `surfaces()`, `surface()`
|
- **Accessors:** `mode()`, `appKey()`, `appName()`, `isOptionSet()`, `optionValue()`,
|
||||||
(the first), `surface(id)`, `vrRuntime()`, `navigation()`.
|
`engine()`, `surfaces()`, `surface()` (the first), `surface(id)`, `vrRuntime()`,
|
||||||
|
`navigation()`.
|
||||||
- **Lifetime:** the base constructor creates the `QGuiApplication`, so your members can be
|
- **Lifetime:** the base constructor creates the `QGuiApplication`, so your members can be
|
||||||
QObjects with timers. All QML and surfaces are torn down at the end of `exec()`, while your
|
QObjects with timers. All QML and surfaces are torn down at the end of `exec()`, while your
|
||||||
object and its members still exist.
|
object and its members still exist.
|
||||||
@@ -121,15 +127,16 @@ sequenceDiagram
|
|||||||
## Start and shutdown (overlay mode)
|
## Start and shutdown (overlay mode)
|
||||||
|
|
||||||
1. **Start:**
|
1. **Start:**
|
||||||
1. Connect to SteamVR (`VR_Init`).
|
1. Call `prepare()`; stop with exit code 1 if it fails.
|
||||||
2. Create one render backend per surface. The first surface decides the API, including a
|
2. Connect to SteamVR (`VR_Init`).
|
||||||
|
3. Create one render backend per surface. The first surface decides the API, including a
|
||||||
fallback to OpenGL; the others use the same API, because Qt's graphics API is
|
fallback to OpenGL; the others use the same API, because Qt's graphics API is
|
||||||
process-wide.
|
process-wide.
|
||||||
3. Call `loading()`.
|
4. Call `loading()`.
|
||||||
4. Load each surface's QML.
|
5. Load each surface's QML.
|
||||||
5. Create the overlays.
|
6. Create the overlays.
|
||||||
6. Set up controller navigation.
|
7. Set up controller navigation.
|
||||||
7. Call `started()`, then run the event loop.
|
8. Call `started()`, then run the event loop.
|
||||||
2. **Shutdown runs in reverse**, with one rule found on the Frame: **overlays first, then
|
2. **Shutdown runs in reverse**, with one rule found on the Frame: **overlays first, then
|
||||||
SteamVR, then the graphics devices.** OpenVR requires shutting down before the devices it
|
SteamVR, then the graphics devices.** OpenVR requires shutting down before the devices it
|
||||||
got textures from are destroyed. Otherwise its client library frees objects on a dead
|
got textures from are destroyed. Otherwise its client library frees objects on a dead
|
||||||
@@ -160,3 +167,9 @@ For e.g. a notification app: a `WorldOverlay` (another `Overlay` subclass, creat
|
|||||||
(`VROverlayFlags_MakeOverlaysInteractiveIfVisible`).
|
(`VROverlayFlags_MakeOverlaysInteractiveIfVisible`).
|
||||||
|
|
||||||
The surface structure is ready for it: `SurfaceConfig` would gain a kind and a placement.
|
The surface structure is ready for it: `SurfaceConfig` would gain a kind and a placement.
|
||||||
|
|
||||||
|
## FrameAssistant on top
|
||||||
|
|
||||||
|
The app itself (`src/Dashboard.*`, `src/config/`, `src/ha/`, `src/qml/`) builds on this
|
||||||
|
platform; its design (Home Assistant connection, entities, overlays, threads) is in
|
||||||
|
[DESIGN.md](DESIGN.md).
|
||||||
|
|||||||
+6
-4
@@ -150,7 +150,7 @@ and signals across threads are delivered through the receiver's event loop.
|
|||||||
| **I/O wrappers** | Data sources and actuators; know nothing about HA. One wrapper object per OpenVR interface used (e.g. `IVRSystem`, `IVRApplications`, `IVRChaperone`, `IVRSettings`). System wrappers (network, audio) later. | OpenVR ✓, system ✗ |
|
| **I/O wrappers** | Data sources and actuators; know nothing about HA. One wrapper object per OpenVR interface used (e.g. `IVRSystem`, `IVRApplications`, `IVRChaperone`, `IVRSettings`). System wrappers (network, audio) later. | OpenVR ✓, system ✗ |
|
||||||
| **Overlays** | View and controller of MVC (QML), as dashboard tabs or world overlays. Get their data (the model) from other objects. | ✓ |
|
| **Overlays** | View and controller of MVC (QML), as dashboard tabs or world overlays. Get their data (the model) from other objects. | ✓ |
|
||||||
| **Managers** | One object per stateful feature that manages and coordinates it, using entities, services and overlays. Notification manager (MVP); status card manager (SRV-6/7) later. | notifications ✓ |
|
| **Managers** | One object per stateful feature that manages and coordinates it, using entities, services and overlays. Notification manager (MVP); status card manager (SRV-6/7) later. | notifications ✓ |
|
||||||
| **HA web UI** | Encapsulates Qt WebEngine for the dashboard tab (DSH-1; Q2 for DSH-2). | ✓ (DSH-1) |
|
| **HA web UI** | Encapsulates Qt WebEngine for the dashboard tab (DSH-1; Q2 for DSH-2). `src/ha/HaWebUi`, a QML singleton in the app itself (not `frameassistant_lib`: QML types must be part of the QML module). | ✓ (DSH-1) |
|
||||||
| **Configuration** | Settings from the JSON file, loaded once by the app as an immutable value. Each component gets the part it needs through its constructor (D19). | ✓ |
|
| **Configuration** | Settings from the JSON file, loaded once by the app as an immutable value. Each component gets the part it needs through its constructor (D19). | ✓ |
|
||||||
| **App** | The managing object: owns all of the above as components and wires them together. | ✓ |
|
| **App** | The managing object: owns all of the above as components and wires them together. | ✓ |
|
||||||
|
|
||||||
@@ -314,10 +314,12 @@ Layout follows common practice (e.g. Zigbee2MQTT's `…/<device>/set`); one topi
|
|||||||
code ≠ 0 (CFG-2, via the start-up hook D21).
|
code ≠ 0 (CFG-2, via the start-up hook D21).
|
||||||
- Unknown keys: a warning in the log, no abort (catches typos without breaking across
|
- Unknown keys: a warning in the log, no abort (catches typos without breaking across
|
||||||
versions).
|
versions).
|
||||||
- Password in plain text: warn if the file is readable by others; otherwise file permissions
|
- Password in plain text: warn if the file holds a password and is readable by others;
|
||||||
(`chmod 600`).
|
otherwise file permissions (`chmod 600`). (Implemented this way: a readable file without a
|
||||||
|
password is harmless.)
|
||||||
- Throttle intervals (SEN-14) are fixed per entity in the code for the MVP.
|
- Throttle intervals (SEN-14) are fixed per entity in the code for the MVP.
|
||||||
- A `config.example.json` ships in the package.
|
- A `config.example.json` ships in the package (`share/frameassistant/`); the package's
|
||||||
|
README.txt explains how to set it up.
|
||||||
- To check on the Frame when TLS is implemented: where the system certificates are
|
- To check on the Frame when TLS is implemented: where the system certificates are
|
||||||
(`/etc/ssl/certs`?), for the `ca_file` default.
|
(`/etc/ssl/certs`?), for the `ca_file` default.
|
||||||
|
|
||||||
|
|||||||
+23
-3
@@ -5,9 +5,12 @@ CLAUDE.md ("Working rules").
|
|||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
|
|
||||||
- **Implementation iteration 1 (DSH-1 and configuration), phase 3 (implementation).**
|
- **Implementation iteration 1 (DSH-1 and configuration), phase 3 (implementation) done,
|
||||||
Requirements (D13) and design ([DESIGN.md](DESIGN.md)) are approved; the plan below was
|
waiting for the user's approval.** All plan steps are implemented and committed; next is
|
||||||
approved on 2026-10-05.
|
phase 4, testing on the Frame (plan item 10).
|
||||||
|
- **Open point found:** started by SteamVR, the app writes no log file (the manifest passes no
|
||||||
|
`--log-file`, stderr goes to SteamVR). CFG-2 wants the settings error "in the log file and
|
||||||
|
on stderr". To be decided.
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
@@ -77,6 +80,23 @@ Known limitation: the bundled-Qt package does not support WebEngine in this iter
|
|||||||
present (relevant for Q2/Q3).
|
present (relevant for Q2/Q3).
|
||||||
- libmosquitto ABI check: Debian's 2.0.21 needs at most `OPENSSL_3.0.0`/`GLIBC_2.34`; the
|
- libmosquitto ABI check: Debian's 2.0.21 needs at most `OPENSSL_3.0.0`/`GLIBC_2.34`; the
|
||||||
Frame has OpenSSL 3.2.1 and glibc 2.39. No libcjson dependency. D14 holds.
|
Frame has OpenSSL 3.2.1 and glibc 2.39. No libcjson dependency. D14 holds.
|
||||||
|
- Phase 3 of implementation iteration 1:
|
||||||
|
- Platform: `prepare()` hook and app-specific command-line options, tested in
|
||||||
|
`tst_surfaces` (incl. `surfaces_prepare_fail`).
|
||||||
|
- Settings file (`src/config/`, new library `frameassistant_lib`), `--config`; `tst_config`
|
||||||
|
(37 cases). `config.example.json` in the package; README.txt explains the set-up.
|
||||||
|
- "HA Dashboard" tab: `WebEngineView`, `HaWebUi` with a persistent profile, WebEngine
|
||||||
|
initialisation in `main()`. Host: desktop mode loads a local test page, profile data
|
||||||
|
lands in `~/.local/share/frameassistant/webengine`; snapshot renders both tabs.
|
||||||
|
- Build containers with Qt WebEngine. Cross build: the aarch64 binary needs no private Qt
|
||||||
|
symbols, at most `Qt_6.8` and `GLIBC_2.34`. Container tests 8/8 (incl. Vulkan with
|
||||||
|
validation layers, no messages).
|
||||||
|
- Snags: moc misses classes in files with raw strings containing `//` (CLAUDE.md);
|
||||||
|
generated QML type registration includes headers by file name; Qt ≥ 6.9 hints at
|
||||||
|
`WebEngineProfilePrototype` (not in the Frame's 6.8). Deviations: `HaWebUi` lives in the
|
||||||
|
app, not the library; the password warning needs a password in the file.
|
||||||
|
- Not verified yet: WebEngine with a URL in the off-screen pipeline (Frame, phase 4); the
|
||||||
|
bundled package (not supported for WebEngine in this iteration).
|
||||||
- Plan for implementation iteration 1 approved; phase 3 started.
|
- Plan for implementation iteration 1 approved; phase 3 started.
|
||||||
- Plan for implementation iteration 1 written; new tab "HA Dashboard" (D38, D39).
|
- Plan for implementation iteration 1 written; new tab "HA Dashboard" (D38, D39).
|
||||||
- MVP implemented in four iterations; DSH-1 first, at the user's wish (D37).
|
- MVP implemented in four iterations; DSH-1 first, at the user's wish (D37).
|
||||||
|
|||||||
@@ -20,3 +20,7 @@ endforeach()
|
|||||||
|
|
||||||
install(FILES ${_share}/manifest.vrmanifest DESTINATION ${VRDASH_SHARE_SUBDIR})
|
install(FILES ${_share}/manifest.vrmanifest DESTINATION ${VRDASH_SHARE_SUBDIR})
|
||||||
install(FILES ${_input_files} DESTINATION ${VRDASH_SHARE_SUBDIR}/input)
|
install(FILES ${_input_files} DESTINATION ${VRDASH_SHARE_SUBDIR}/input)
|
||||||
|
|
||||||
|
# Example settings file (copy to ~/.config/frameassistant/config.json and edit).
|
||||||
|
configure_file(config.example.json ${_share}/config.example.json COPYONLY)
|
||||||
|
install(FILES config.example.json DESTINATION ${VRDASH_SHARE_SUBDIR})
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"device": {
|
||||||
|
"name": "Steam Frame"
|
||||||
|
},
|
||||||
|
"mqtt": {
|
||||||
|
"host": "homeassistant.local",
|
||||||
|
"port": 1883,
|
||||||
|
"username": "frameassistant",
|
||||||
|
"password": "change-me",
|
||||||
|
"tls": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"homeassistant": {
|
||||||
|
"url": "http://homeassistant.local:8123"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"timeout_s": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,6 +81,14 @@ chmod +x "$out/install.sh" "$out/uninstall.sh"
|
|||||||
echo "SteamOS update, use the -bundled package instead."
|
echo "SteamOS update, use the -bundled package instead."
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
|
echo "Settings (required before the first start):"
|
||||||
|
echo " mkdir -p ~/.config/$project"
|
||||||
|
echo " cp share/$project/config.example.json ~/.config/$project/config.json"
|
||||||
|
echo " chmod 600 ~/.config/$project/config.json"
|
||||||
|
echo " then edit it (device name, MQTT broker, Home Assistant URL)."
|
||||||
|
echo " Without valid settings the app exits; to see why, run"
|
||||||
|
echo " ./bin/$exe --desktop in a terminal."
|
||||||
|
echo
|
||||||
echo "Install / update: ./install.sh (SteamVR must be running)"
|
echo "Install / update: ./install.sh (SteamVR must be running)"
|
||||||
echo " SteamVR remembers this folder's location: after moving it,"
|
echo " SteamVR remembers this folder's location: after moving it,"
|
||||||
echo " run ./install.sh again."
|
echo " run ./install.sh again."
|
||||||
|
|||||||
Reference in New Issue
Block a user