From c5c176644a5dda62611f97c6201bb833406302a6 Mon Sep 17 00:00:00 2001 From: Matzman Date: Mon, 5 Oct 2026 23:04:24 +0200 Subject: [PATCH] Remove the HA Dashboard tab and the Qt WebEngine dependency (D40) DSH-1 ended with a negative result: the Frame's system WebEngine can't be loaded, and bundling our own failed (no sandbox, ICU resources, Vulkan abort, empty page). Reverts the tab (60deb43), the container packages (0676998) and the bundler's WebEngine support (b823ff3, e2cfa8e), and updates the docs. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 23 +++++++--------- CMakePresets.json | 3 +-- README.md | 20 +++++--------- THIRD_PARTY_NOTICES.md | 1 - cmake/BundleQt.cmake | 16 ------------ cmake/bundle-install.cmake.in | 42 ++---------------------------- docker/cross-arm64.Dockerfile | 9 ------- docker/native.Dockerfile | 1 - docs/DESIGN.md | 23 +++++++--------- docs/PROGRESS.md | 19 +++++++++----- docs/REQUIREMENTS.md | 11 ++++---- docs/STEAM-FRAME.md | 10 ++++++- src/CMakeLists.txt | 10 ++----- src/Dashboard.cpp | 5 ---- src/Dashboard.h | 4 --- src/ha/HaWebUi.cpp | 49 ----------------------------------- src/ha/HaWebUi.h | 46 -------------------------------- src/main.cpp | 5 ---- src/qml/HaDashboard.qml | 40 ---------------------------- 19 files changed, 59 insertions(+), 278 deletions(-) delete mode 100644 src/ha/HaWebUi.cpp delete mode 100644 src/ha/HaWebUi.h delete mode 100644 src/qml/HaDashboard.qml diff --git a/CLAUDE.md b/CLAUDE.md index 8dacb34..823f6cb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co 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 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). +(implementation iteration 1: settings file; the web UI tab was dropped, see REQUIREMENTS.md D40). **Start every session by reading `docs/PROGRESS.md`** (current state, next steps, log). @@ -61,7 +61,7 @@ cmake --preset native-debug && cmake --build --preset native-debug # system Qt ctest --preset native-debug # all tests 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 --snapshot x.png # x-0.png (main tab), x-dashboard.png +build/native-debug/bin/frame-assistant --snapshot x.png # one frame via the real offscreen path 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 cross-bundle # fallback: self-contained, Qt bundled (also native-bundle) @@ -72,9 +72,8 @@ build/native-debug/bin/frame-assistant --graphics-api vulkan --benchmark 600 # ``` - Use `--snapshot` and then read the PNGs to check UI or rendering changes without a headset. - With several surfaces it writes `-.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. + With several surfaces it writes `-.png` per surface (`-0` for an empty id). + Snapshot and benchmark don't load the settings. - Overlay and desktop mode need a settings file; pass a test one with `--config `. - `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 @@ -198,16 +197,16 @@ into Qt events. `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()`: + (`QML_ELEMENT`) 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. +- **No Qt WebEngine** (REQUIREMENTS.md, D40): the Frame's system WebEngine can't be loaded, and bundling our own + failed (STEAM-FRAME.md). Don't link it: the whole app would fail to start on the Frame. +- **Check Frame libraries by loading, not by file:** before relying on a system library, check + `ldd | grep "not found"` and that its package version matches the rest of Qt + (`pacman -Q | grep qt6-`). - **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 @@ -333,5 +332,3 @@ Check these when testing on the device, and update this list: - The restructured app code (base class `DashboardApplication`, `Surface`, `Overlay` base, 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). -- 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). diff --git a/CMakePresets.json b/CMakePresets.json index b83f485..dd55c75 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -41,8 +41,7 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo", "VRDASH_DEPLOY_QT": "ON", - "VRDASH_BUILD_TESTS": "OFF", - "VRDASH_WEBENGINE_PROCESS": "/opt/qt6-arm64/usr/lib/qt6/libexec/QtWebEngineProcess" + "VRDASH_BUILD_TESTS": "OFF" } }, { diff --git a/README.md b/README.md index 97c5386..517dc06 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,7 @@ the headset to Home Assistant in both directions: - **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 -> 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), +> parts are being built: so far the settings file. See [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md), > [docs/DESIGN.md](docs/DESIGN.md) and [docs/PROGRESS.md](docs/PROGRESS.md). ### Platform @@ -34,8 +33,8 @@ FrameAssistant grew out of a SteamVR dashboard-overlay template, which it still ## Quick start Requirements: -- CMake ≥ 3.22, Ninja, a C++20 compiler, and Qt ≥ 6.5 (Quick, QuickControls2, OpenGL, - WebEngineQuick; Test for the tests) +- CMake ≥ 3.22, Ninja, a C++20 compiler, and Qt ≥ 6.5 (Quick, QuickControls2, OpenGL; + Test for the tests) - Vulkan headers (optional; without them the build is OpenGL only) - Docker or Podman, for the Frame builds @@ -47,7 +46,7 @@ cmake --preset native-debug && cmake --build --preset native-debug ctest --preset native-debug build/native-debug/bin/frame-assistant --desktop # UI in windows, no SteamVR -build/native-debug/bin/frame-assistant --snapshot out.png # one frame per tab through the VR path +build/native-debug/bin/frame-assistant --snapshot out.png # one frame through the VR path build/native-debug/bin/frame-assistant # as dashboard tab (SteamVR running) build/native-debug/bin/frame-assistant --install-manifest # register + auto-start with SteamVR ``` @@ -89,15 +88,12 @@ FrameAssistant reads its settings from `~/.config/frameassistant/config.json` | `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 | +| `homeassistant.url` | no | | Home Assistant's address; reserved for the dashboard, not used yet | | `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 - **App identity** (app key `net.matzman.frameassistant`, tab name, executable name @@ -149,7 +145,6 @@ VK_DRIVER=lvp scripts/container-test.sh # same on the lavapipe software driver |------|---------| | `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/render/` | Off-screen Qt Quick renderer; Vulkan and OpenGL backends | | `src/vr/` | SteamVR connection, overlays (dashboard tab) | @@ -162,6 +157,5 @@ VK_DRIVER=lvp scripts/container-test.sh # same on the lavapipe software driver ## License -MIT, see [LICENSE](LICENSE). Third-party components (OpenVR SDK, Qt incl. Qt WebEngine; in -bundled builds also their dependencies) are listed in -[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). +MIT, see [LICENSE](LICENSE). Third-party components (OpenVR SDK, Qt; in bundled builds also +their dependencies) are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 0a21361..a25d79f 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -7,7 +7,6 @@ This project is MIT-licensed (see `LICENSE`). `` 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//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 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, taken unmodified from Debian. Their copyright and license files are in diff --git a/cmake/BundleQt.cmake b/cmake/BundleQt.cmake index 5abb177..f26d070 100644 --- a/cmake/BundleQt.cmake +++ b/cmake/BundleQt.cmake @@ -30,14 +30,6 @@ set(VRDASH_BUNDLE_PLUGINS wayland-shell-integration/*.so wayland-graphics-integration-client/*.so wayland-decoration-client/*.so xcbglintegrations/*.so) -# Qt WebEngine (bundled only if a dashboard needs it): its helper process, resources and -# locales. Cross builds point VRDASH_WEBENGINE_PROCESS at the target's QtWebEngineProcess -# (Debian can't install the arm64 one next to the host's; see docker/cross-arm64.Dockerfile). -set(VRDASH_WEBENGINE_PROCESS "" CACHE FILEPATH - "QtWebEngineProcess to bundle (default: the one of the Qt used for the build)") -# Locales shipped with WebEngine (the full set is about 35 MB). -set(VRDASH_WEBENGINE_LOCALES en-US de) - function(vrdash_bundle_qt) get_property(targets GLOBAL PROPERTY VRDASH_DASHBOARD_TARGETS) get_property(qml_dirs GLOBAL PROPERTY VRDASH_DASHBOARD_QML_DIRS) @@ -60,14 +52,6 @@ function(vrdash_bundle_qt) set(qt_libdir ${qt_prefix}/lib/${CMAKE_LIBRARY_ARCHITECTURE}) # Debian multiarch endif() - # Used by the install script only if a bundled library is Qt WebEngine. - set(webengine_process ${VRDASH_WEBENGINE_PROCESS}) - if(NOT webengine_process) - set(webengine_process ${QT6_INSTALL_PREFIX}/${QT6_INSTALL_LIBEXECS}/QtWebEngineProcess) - endif() - set(webengine_resources ${QT6_INSTALL_PREFIX}/${QT6_INSTALL_DATA}/resources) - set(webengine_locales ${QT6_INSTALL_PREFIX}/${QT6_INSTALL_TRANSLATIONS}/qtwebengine_locales) - # The regexes end up inside a quoted string in the generated script: escape backslashes. string(REPLACE "\\" "\\\\" bundle_exclude "${VRDASH_BUNDLE_EXCLUDE}") diff --git a/cmake/bundle-install.cmake.in b/cmake/bundle-install.cmake.in index 4be871c..839033a 100644 --- a/cmake/bundle-install.cmake.in +++ b/cmake/bundle-install.cmake.in @@ -14,9 +14,6 @@ set(qt_plugins "@QT6_INSTALL_PREFIX@/@QT6_INSTALL_PLUGINS@") set(qt_qml "@QT6_INSTALL_PREFIX@/@QT6_INSTALL_QML@") set(patchelf "@VRDASH_PATCHELF@") set(exclude "@bundle_exclude@") -set(webengine_process "@webengine_process@") -set(webengine_resources "@webengine_resources@") -set(webengine_locales "@webengine_locales@") message(STATUS "Bundling Qt into ${prefix}") @@ -81,35 +78,6 @@ file(GET_RUNTIME_DEPENDENCIES RESOLVED_DEPENDENCIES_VAR resolved UNRESOLVED_DEPENDENCIES_VAR unresolved) -# --- Qt WebEngine: helper process (and its libraries), resources, locales ---------------- -# Only if a dashboard uses it. Found through qt.conf (LibraryExecutables, Data, -# Translations; written below). -set(helper_executables "") -if(resolved MATCHES "libQt6WebEngineCore") - message(STATUS "Bundling Qt WebEngine (${webengine_process})") - # TYPE PROGRAM: a plain file(INSTALL) would drop the executable bit. - file(INSTALL "${webengine_process}" DESTINATION "${prefix}/libexec" TYPE PROGRAM - FOLLOW_SYMLINK_CHAIN) - list(APPEND helper_executables "${prefix}/libexec/QtWebEngineProcess") - file(GET_RUNTIME_DEPENDENCIES - EXECUTABLES ${helper_executables} - DIRECTORIES "@qt_libdir@" "${libdir}" - PRE_EXCLUDE_REGEXES ${exclude} - POST_EXCLUDE_REGEXES "^${libdir}/" - RESOLVED_DEPENDENCIES_VAR helper_resolved - UNRESOLVED_DEPENDENCIES_VAR helper_unresolved) - list(APPEND resolved ${helper_resolved}) - list(APPEND unresolved ${helper_unresolved}) - list(REMOVE_DUPLICATES resolved) - list(REMOVE_DUPLICATES unresolved) - file(GLOB files LIST_DIRECTORIES false "${webengine_resources}/*") - file(INSTALL ${files} DESTINATION "${prefix}/resources") - foreach(locale @VRDASH_WEBENGINE_LOCALES@) - file(INSTALL "${webengine_locales}/${locale}.pak" - DESTINATION "${prefix}/translations/qtwebengine_locales") - endforeach() -endif() - if(unresolved) message(WARNING "Unresolved (expected on target system): ${unresolved}") endif() @@ -131,7 +99,7 @@ foreach(lib IN LISTS bundled_libs) set_rpath("${lib}" "\$ORIGIN") endif() endforeach() -foreach(file IN LISTS qt_plugin_files qml_plugins helper_executables) +foreach(file IN LISTS qt_plugin_files qml_plugins) get_filename_component(dir "${file}" DIRECTORY) file(RELATIVE_PATH rel "${dir}" "${libdir}") set_rpath("${file}" "\$ORIGIN/${rel}") @@ -144,14 +112,11 @@ find_program(dpkg dpkg) if(dpkg) set(docdir "${prefix}/@CMAKE_INSTALL_DOCDIR@/third-party") set(packages "") - foreach(file IN LISTS resolved qt_plugin_files qml_plugins helper_executables) + foreach(file IN LISTS resolved qt_plugin_files qml_plugins) # Ask dpkg about the original file: map installed plugins back, resolve # symlinks and /lib -> /usr/lib (usrmerge: packages register /usr paths). string(REPLACE "${plugindir}" "${qt_plugins}" original "${file}") string(REPLACE "${qmldir}" "${qt_qml}" original "${original}") - if(file STREQUAL "${prefix}/libexec/QtWebEngineProcess") - set(original "${webengine_process}") - endif() file(REAL_PATH "${original}" original) execute_process(COMMAND "${dpkg}" -S "${original}" OUTPUT_VARIABLE owner ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) @@ -193,9 +158,6 @@ Prefix = .. Libraries = @CMAKE_INSTALL_LIBDIR@ Plugins = plugins QmlImports = qml -LibraryExecutables = libexec -Data = . -Translations = translations ") list(LENGTH resolved n_libs) diff --git a/docker/cross-arm64.Dockerfile b/docker/cross-arm64.Dockerfile index 51088e3..26a66fe 100644 --- a/docker/cross-arm64.Dockerfile +++ b/docker/cross-arm64.Dockerfile @@ -17,13 +17,4 @@ RUN dpkg --add-architecture arm64 \ qml6-module-qtquick-layouts:arm64 qml6-module-qtquick-templates:arm64 \ qml6-module-qtqml-workerscript:arm64 qml6-module-qtquick-window:arm64 \ qt6-qpa-plugins:arm64 qt6-wayland:arm64 \ - qt6-webengine-dev:arm64 qml6-module-qtwebengine:arm64 \ && rm -rf /var/lib/apt/lists/* - -# Qt WebEngine's helper process for arm64 (bundled builds): its package is Multi-Arch: foreign -# and can't be installed next to the amd64 one, so unpack it into its own directory. -RUN apt-get update \ - && cd /tmp && apt-get download libqt6webenginecore6-bin:arm64 \ - && dpkg-deb -x libqt6webenginecore6-bin_*_arm64.deb /opt/qt6-arm64 \ - && rm -f libqt6webenginecore6-bin_*_arm64.deb && rm -rf /var/lib/apt/lists/* \ - && test -x /opt/qt6-arm64/usr/lib/qt6/libexec/QtWebEngineProcess diff --git a/docker/native.Dockerfile b/docker/native.Dockerfile index f0a582f..9e517e5 100644 --- a/docker/native.Dockerfile +++ b/docker/native.Dockerfile @@ -13,5 +13,4 @@ RUN apt-get update \ qml6-module-qtquick qml6-module-qtquick-controls qml6-module-qtquick-layouts \ qml6-module-qtquick-templates qml6-module-qtqml-workerscript qml6-module-qtquick-window \ qml6-module-qttest qt6-qpa-plugins qt6-wayland \ - qt6-webengine-dev qml6-module-qtwebengine \ && rm -rf /var/lib/apt/lists/* diff --git a/docs/DESIGN.md b/docs/DESIGN.md index c6f6e3d..0893887 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -57,18 +57,15 @@ Three layers; dependencies only go downward (D17). - **MQTT is the only channel for everything device-related:** entities, services, availability (D24). -- MQTT can't show HA as a whole (other entities' states, service calls for them), which the - dashboard (DSH-1) needs. The dashboard gets **its own channel, separate from MQTT**: +- MQTT can't show HA as a whole (other entities' states, service calls for them), which a + dashboard needs. The dashboard gets **its own channel, separate from MQTT**: - Q2a, HA web UI in Qt WebEngine: the browser talks to HA itself (HTTP/WebSocket, HA's own - login); config: HA URL. + login); config: HA URL. **Failed on the Frame (D40)**: the system WebEngine can't be + loaded, a bundled one didn't work (STEAM-FRAME.md). - Q2b, native QML dashboard: HA's WebSocket API with a token (Q3) as another I/O component; config: HA URL and token. -- Both fit the three layers. The MVP contains the web UI test (DSH-1, D28), which also serves - as the spike on WebEngine in the off-screen renderer; Q2/Q3 are decided for DSH-2. -- Known DSH-1 risks: WebEngine must be initialised before the `QGuiApplication` (created by the - platform's base constructor), so the platform needs a small change; WebEngine with our - off-screen renderer, especially Vulkan, is untested; text fields inside the page probably - don't open the SteamVR keyboard (fallback: log in once in a desktop mode). +- Both fit the three layers. The web UI test (DSH-1, D28) answered Q2a with "no"; Q2/Q3 are + decided for DSH-2, with the native dashboard as the candidate. ## Start-up and modes @@ -102,7 +99,7 @@ Two overlay kinds in the platform (D25): | Main tab "Frame Assistant" (UI-1, later NOT-7, do-not-disturb) | dashboard tab | always there; SteamVR shows/hides it | ✓ | | Notifications | world overlay | the app (notification manager) shows/hides it | ✓ | | Status tab (SRV-6) | dashboard tab | exists only while cards exist (D9) | ✗ | -| "HA Dashboard" (DSH-1, web UI test) | dashboard tab | always there | ✓ | +| HA dashboard (DSH-2) | dashboard tab | always there | ✗ (web UI test DSH-1 removed, D40) | - **World overlays (MVP):** a new overlay kind next to the dashboard tab (CLAUDE.md, "Planned extension"). The surface gets a kind and a placement; renders only while visible (NFR-3). @@ -150,7 +147,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 ✗ | | **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 ✓ | -| **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) | +| **HA web UI** | Was: Qt WebEngine for the dashboard tab (DSH-1). Dropped (D40); a native dashboard (DSH-2) would need its own object class. | ✗ | | **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. | ✓ | @@ -307,7 +304,7 @@ Layout follows common practice (e.g. Zigbee2MQTT's `…//set`); one topi | `mqtt.tls.ca_file` | no | system certificates | own CA, e.g. a self-signed broker certificate | | `mqtt.tls.cert_file` / `key_file` | no | none | client certificate, if the broker requires one | | `homeassistant.discovery_prefix` | no | `homeassistant` | | -| `homeassistant.url` | for DSH-1 only | — | missing: the dashboard tab shows a hint instead of the web UI | +| `homeassistant.url` | no | — | reserved for the dashboard (DSH-2); not used yet (D40) | | `notifications.timeout_s` | no | 10 | D33 | - Errors (missing required values, invalid JSON, wrong types): exit with a message and exit @@ -447,4 +444,4 @@ Collected 2026-10-05 for the notification design. | D36 | 2026-10-05 | NFR-3 measured as own cost (CPU idle, frames while hidden, GPU per frame) plus an A/B comparison of the game's compositor statistics; manual in the MVP, a `--vr-stats` mode later | Measurable acceptance for NFR-3 without extra code in the MVP | | D37 | 2026-10-05 | The MVP is implemented in four iterations: 1 DSH-1 + configuration, 2 MQTT/HA device/UI-1, 3 sensors, 4 notifications | Small iterations (working rules); DSH-1 first because the user wants to see it early and it doesn't depend on MQTT | | D38 | 2026-10-05 | App-specific command-line options: the platform's base constructor takes an optional list of extra options, parses them with its own and exposes their values | The command line is parsed in the base constructor, before the derived app can contribute (`--config`); backward compatible, `--help` stays complete | -| D39 | 2026-10-05 | DSH-1: `QtWebEngineQuick::initialize()` in `main.cpp` (no platform change); a named, persistent WebEngine profile under `~/.local/share/frameassistant/`; tab name "HA Dashboard" | WebEngine must be initialised before the `QGuiApplication`; the HA login must survive restarts | +| D39 | 2026-10-05 | *Superseded by D40 (REQUIREMENTS.md): tab and WebEngine removed.* DSH-1: `QtWebEngineQuick::initialize()` in `main.cpp` (no platform change); a named, persistent WebEngine profile under `~/.local/share/frameassistant/`; tab name "HA Dashboard" | WebEngine must be initialised before the `QGuiApplication`; the HA login must survive restarts | diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 68e1bcf..f72b290 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -5,12 +5,11 @@ CLAUDE.md ("Working rules"). ## Current state -- **Implementation iteration 1 (DSH-1 and configuration), phase 3 (implementation) done, - waiting for the user's approval.** All plan steps are implemented and committed; next is - phase 4, testing on the Frame (plan item 10). -- Started by SteamVR, the app now logs to `logs/frame-assistant.log` in the install folder: - the manifest passes `--log-file` (CFG-2 wants the settings error in a log file). Whether - SteamVR on the Frame honours the manifest's `arguments` is checked in phase 4. +- **Implementation iteration 1, phase 4 (testing on the Frame).** DSH-1 is done with a + negative result: HA's web UI via Qt WebEngine doesn't work on the Frame, so the "HA + Dashboard" tab and the WebEngine dependency were removed (D40). What remains of iteration 1 + (platform hooks, settings file, `--config`, log file for SteamVR-started instances) still + needs its test on the Frame with the system-Qt package. ## Next steps @@ -19,7 +18,7 @@ Requirements and design cover the whole MVP; it is **implemented in four small i | Iteration | Content | Platform change | Checked by | |---|---|---|---| -| 1 | DSH-1: HA web UI (Qt WebEngine) in a dashboard tab; configuration (D34) with start-up hook and `--config` | start-up hook (D21), WebEngine initialisation before the `QGuiApplication` | HA web UI in the tab on the Frame; result documented | +| 1 | ~~DSH-1: HA web UI in a dashboard tab~~ (failed, removed: D40); configuration (D34) with start-up hook and `--config`; log file for SteamVR-started instances | start-up hook (D21), app command-line options (D38) | DSH-1 result documented; app starts on the Frame with the settings file, settings errors in the log | | 2 | MQTT client (own thread), HA device with discovery and availability, libmosquitto in the package, status display UI-1 | modes (D22) | device appears in HA, online/offline right, reconnect works | | 3 | OpenVR wrappers and the 13 sensor entities | `--desktop` with optional SteamVR (D22) | sensor values in HA on the Frame | | 4 | notification manager, `notify` entity, world overlay | world overlay, premultiplied flag (D25) | notification from HA in VR, also in a game | @@ -80,6 +79,12 @@ Known limitation: the bundled-Qt package does not support WebEngine in this iter present (relevant for Q2/Q3). - 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. +- DSH-1 closed (D40): the bundling experiment failed (sandbox, ICU resources, Vulkan abort, + empty page with OpenGL and in a desktop window; details in STEAM-FRAME.md). At the user's + decision the "HA Dashboard" tab, `HaWebUi`, the WebEngine build dependency and the + bundler's WebEngine support were removed (reverts of the corresponding commits). Qt WebView + is no alternative (WebEngine backend on Linux). `homeassistant.url` stays reserved for + DSH-2, where a native dashboard is the candidate. - Phase 4 (testing on the Frame), first findings: - `deploy-to-frame.sh` exited silently without a bundled package in `dist/` (fixed). - The system-Qt package doesn't start: the Frame's Qt WebEngine is 6.7.0 (rest of Qt 6.8.0) diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 22ef7b1..3557ba7 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -103,7 +103,7 @@ FrameAssistant. | ID | Requirement | Prio | MVP | Acceptance criteria | |----|-------------|------|-----|---------------------| -| DSH-1 | A dashboard tab shows HA's web UI (Qt WebEngine) at a configured URL, as a "does it work out of the box" test. | M | ✓ | The page loads in VR and can be operated with the laser; whatever doesn't work out of the box is documented and moved to a later iteration. | +| DSH-1 | A dashboard tab shows HA's web UI (Qt WebEngine) at a configured URL, as a "does it work out of the box" test. | M | ✓ | The page loads in VR and can be operated with the laser; whatever doesn't work out of the box is documented and moved to a later iteration. **Result (D40):** fails on the Frame; tab removed. | | DSH-2 | The full dashboard to control HA entities in VR: everything beyond DSH-1, e.g. login comfort, opening a given HA view (SRV-4), possibly a native approach (Q2, Q3). | S | | Defined after design exploration. | ### Non-functional (NFR) @@ -133,7 +133,7 @@ FrameAssistant. | # | Question | Status | |---|----------|--------| | Q1 | Several notifications at once: stack, queue, replace by tag? | open, design exploration | -| Q2 | Dashboard: HA's own web UI vs. native VR UI? | open, design exploration (Qt WebEngine exists on the Frame, Appendix C) | +| Q2 | Dashboard: HA's own web UI vs. native VR UI? | open; HA's web UI via Qt WebEngine failed on the Frame (D40), so native is the candidate | | Q3 | Dashboard data: HA WebSocket API (token) in addition to MQTT, or MQTT only? | open, design | | Q4 | Which MQTT client library? Licence, availability on SteamOS, TLS/login support. | answered: libmosquitto, shipped in the package (D14, Appendix B) | | Q5 | Status card: own tab or part of the main tab? | answered: own tab, on demand (D9) | @@ -163,6 +163,7 @@ FrameAssistant. | D15 | 2026-10-05 | HA MQTT discovery: **own small layer on Qt's JSON classes**, using device-based discovery (one `/device//config` topic with all entities under `cmps`) | No suitable library (Appendix B); HA recommends device-based discovery for multi-entity devices (NFR-7) | | D20 | 2026-10-05 | New requirement UI-1 (status display in the main tab), MVP | Helps testing on the device; little effort | | D28 | 2026-10-05 | DSH-1 split: DSH-1 (MVP) = HA web UI in a tab as an out-of-the-box test; DSH-2 (Should) = the full dashboard. The MVP (D8) now also contains DSH-1 | The user wants to see early whether the simple variant works | +| D40 | 2026-10-05 | DSH-1 done with a negative result: HA's web UI via Qt WebEngine doesn't work on the Frame (system WebEngine not loadable; bundling our own failed). The "HA Dashboard" tab and the WebEngine dependency are removed; the MVP no longer contains a dashboard. `homeassistant.url` stays reserved for DSH-2 | The out-of-the-box test answered its question; a WebEngine dependency would keep the whole app from starting on the Frame | Design decisions (D16 onward) are in [DESIGN.md](DESIGN.md). @@ -273,7 +274,7 @@ use Qt's private API, which requires the exact Qt version. | Sounds, audio from URL | NOT-4, SRV-8 | Qt Multimedia + QtNetwork | ✓ `libQt6Multimedia`, FFmpeg plugin | no | | HTTPS (downloads, WebSocket) | SRV-8, Q3 | Qt TLS plugin | ✓ `libqopensslbackend.so` | no | | Translations | NFR-4 | `QTranslator`, `lupdate`/`lrelease` | ✓ (build-time tools) | no | -| Dashboard, HA web UI | DSH-1, DSH-2, Q2 | Qt WebEngine (Quick) | **✗ present but not loadable**: 6.7.0 next to Qt 6.8.0, built against FFmpeg 6, which the Frame lacks (STEAM-FRAME.md) | yes, only bundled with all of Qt (experiment, see PROGRESS.md) | +| Dashboard, HA web UI | DSH-1, DSH-2, Q2 | Qt WebEngine (Quick); **dropped (D40)** | **✗ present but not loadable**: 6.7.0 next to Qt 6.8.0, built against FFmpeg 6, which the Frame lacks (STEAM-FRAME.md) | bundling it with all of Qt was tried and failed (STEAM-FRAME.md) | | Dashboard, native UI | DSH-2, Q2/Q3 | HA WebSocket API via Qt WebSockets | ✓ `libQt6WebSockets` | no | | Haptics, fade, apps | SRV-1/2 | OpenVR | ✓ | no | | Tests | NFR-8 | QtTest | ✓ (host only) | no | @@ -286,5 +287,5 @@ Also present and possibly useful later: `libQt6TextToSpeech`, Qt's `networkinfor Open points: - The Debian build container needs the matching `-dev` packages (amd64 and arm64) for every module used. Not checked yet. -- WebEngine and Multimedia come with SteamOS's desktop (KDE) packages; a SteamOS update could - remove them. +- Multimedia comes with SteamOS's desktop (KDE) packages; a SteamOS update could remove it. + (WebEngine likewise, but it isn't usable anyway, D40.) diff --git a/docs/STEAM-FRAME.md b/docs/STEAM-FRAME.md index 504bfc3..87ae562 100644 --- a/docs/STEAM-FRAME.md +++ b/docs/STEAM-FRAME.md @@ -70,7 +70,8 @@ Without the deploy script: copy the `.tar.gz`, unpack it on the Frame, run `./in Checked 2026-10-05; details per requirement in [REQUIREMENTS.md](REQUIREMENTS.md), Appendix C. - **System Qt 6.8.0** under `/usr/lib` includes, besides the base modules: DBus, Network, - Multimedia (FFmpeg plugin), WebSockets, WebEngine (Core, Quick), WebView, TextToSpeech, + Multimedia (FFmpeg plugin), WebSockets, WebEngine (Core, Quick; not loadable, see below), + WebView, TextToSpeech, Positioning, Sensors, Svg, VirtualKeyboard. TLS plugins: `libqopensslbackend.so`, `libqcertonlybackend.so`. - It apparently comes with the KDE Plasma desktop mode (plugins for plasma, kwin, discover). @@ -84,6 +85,13 @@ Checked 2026-10-05; details per requirement in [REQUIREMENTS.md](REQUIREMENTS.md depend on it. A program linking WebEngine fails to start ("libavcodec.so.60: cannot open shared object file"). Other Qt modules are 6.8.0 (`pacman -Q | grep qt6-`). Qt Multimedia's FFmpeg plugin resolves all its libraries (`ldd`, no "not found"). +- **Bundling our own Qt WebEngine failed** (experiment, 2026-10-05; Debian's Qt 6.8.2 incl. + WebEngine with its own FFmpeg, 141 MB package): the helper process (`QtWebEngineProcess`, + needs the executable bit) only starts without Chromium's sandbox (`--no-sandbox`); without + `QTWEBENGINE_RESOURCES_PATH` its child processes find no ICU data ("Invalid file descriptor + to ICU data"); with Vulkan the app aborts ("free(): invalid pointer"); with OpenGL the + dashboard tab stays empty; in a desktop window the page stays white and Chromium's + remote-debugging port never opens. FrameAssistant uses no web engine (D40). - **Qt add-on modules must come from the system Qt.** Shipping one fails: they use Qt's private API, and QtCore aborts on any version mismatch ("Cannot mix incompatible Qt library"). - **Not present:** any MQTT library (no `libmosquitto`, no Qt MQTT). FrameAssistant ships diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1f48f40..5892416 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,20 +58,14 @@ target_link_libraries(frameassistant_lib PUBLIC Qt6::Core) target_compile_options(frameassistant_lib PRIVATE -Wall -Wextra) # The app: its UI is the QML module "FrameAssistant" (src/qml/). -# Qt WebEngine shows HA's web UI (DSH-1); on the Frame it comes with the system Qt. -find_package(Qt6 REQUIRED COMPONENTS WebEngineQuick) vrdash_add_dashboard(frameassistant OUTPUT_NAME ${VRDASH_EXECUTABLE_NAME} QML_URI FrameAssistant SOURCES main.cpp Dashboard.h Dashboard.cpp - QML_SOURCES ha/HaWebUi.h ha/HaWebUi.cpp QML_FILES qml/Main.qml - qml/Card.qml - qml/HaDashboard.qml) -target_link_libraries(frameassistant PRIVATE frameassistant_lib Qt6::WebEngineQuick) -# The generated QML type registration includes QML_SOURCES headers by file name. -target_include_directories(frameassistant PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ha) + qml/Card.qml) +target_link_libraries(frameassistant PRIVATE frameassistant_lib) install(FILES ${OPENVR_LIBRARY} DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/Dashboard.cpp b/src/Dashboard.cpp index 7279887..81ade1c 100644 --- a/src/Dashboard.cpp +++ b/src/Dashboard.cpp @@ -17,9 +17,6 @@ Dashboard::Dashboard(int& argc, char** argv) .arg(Config::defaultPath()), QStringLiteral("path"))}) { addSurface({.qmlRootType = QStringLiteral("Main")}); - addSurface({.id = QStringLiteral("dashboard"), - .qmlRootType = QStringLiteral("HaDashboard"), - .displayName = QStringLiteral("HA Dashboard")}); } bool Dashboard::prepare(QString* error) { @@ -37,5 +34,3 @@ bool Dashboard::prepare(QString* error) { << m_config.deviceName << "(" + m_config.deviceId + ")"; return true; } - -void Dashboard::loading() { m_haWebUi.setUrl(m_config.haUrl); } diff --git a/src/Dashboard.h b/src/Dashboard.h index 16fede5..10d6eed 100644 --- a/src/Dashboard.h +++ b/src/Dashboard.h @@ -2,7 +2,6 @@ #include "app/DashboardApplication.h" #include "config/Config.h" -#include "ha/HaWebUi.h" // The dashboard app. Its UI is the QML module set up in src/CMakeLists.txt // (QML_URI FrameAssistant, files in src/qml/). @@ -17,10 +16,7 @@ class Dashboard : public DashboardApplication { protected: // Loads the settings (overlay and desktop mode; docs/DESIGN.md, D22). bool prepare(QString* error) override; - void loading() override; private: Config m_config; - // QML singleton (HaWebUi); a member, so it outlives all QML. - HaWebUi m_haWebUi{nullptr}; }; diff --git a/src/ha/HaWebUi.cpp b/src/ha/HaWebUi.cpp deleted file mode 100644 index f765ccb..0000000 --- a/src/ha/HaWebUi.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "ha/HaWebUi.h" - -#include -#include -#include - -namespace { - -HaWebUi* g_instance = nullptr; - -} // namespace - -HaWebUi::HaWebUi(QObject* parent) : QObject(parent) { - Q_ASSERT(!g_instance); - g_instance = this; -} - -HaWebUi::~HaWebUi() { g_instance = nullptr; } - -QQuickWebEngineProfile* HaWebUi::profile() { - if (!m_profile) { - m_profile = new QQuickWebEngineProfile(this); - m_profile->setStorageName(QStringLiteral("frameassistant")); - m_profile->setOffTheRecord(false); - m_profile->setPersistentStoragePath(storagePath()); - m_profile->setCachePath(cachePath()); - m_profile->setPersistentCookiesPolicy( - QQuickWebEngineProfile::AllowPersistentCookies); - } - return m_profile; -} - -QString HaWebUi::storagePath() { - return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + - QStringLiteral("/frameassistant/webengine"); -} - -QString HaWebUi::cachePath() { - return QStandardPaths::writableLocation( - QStandardPaths::GenericCacheLocation) + - QStringLiteral("/frameassistant/webengine"); -} - -HaWebUi* HaWebUi::create(QQmlEngine*, QJSEngine*) { - Q_ASSERT(g_instance); - // Owned by the app, not by the QML engine. - QJSEngine::setObjectOwnership(g_instance, QJSEngine::CppOwnership); - return g_instance; -} diff --git a/src/ha/HaWebUi.h b/src/ha/HaWebUi.h deleted file mode 100644 index 4cd02eb..0000000 --- a/src/ha/HaWebUi.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -class QJSEngine; -class QQmlEngine; - -// HA's web UI in the "HA Dashboard" tab (DSH-1): the URL from the settings -// and a persistent WebEngine profile, so the HA login survives restarts -// (docs/DESIGN.md, D39). A QML singleton owned by the app (Dashboard). -class HaWebUi : public QObject { - Q_OBJECT - QML_ELEMENT - QML_SINGLETON - // Empty: no URL configured (the tab shows a hint). - Q_PROPERTY(QUrl url READ url CONSTANT) - Q_PROPERTY(QQuickWebEngineProfile* profile READ profile CONSTANT) - - public: - // Not default-constructible, so QML uses create() instead of making its - // own instance. - explicit HaWebUi(QObject* parent); - ~HaWebUi() override; - - // Call before the QML is loaded. - void setUrl(const QUrl& url) { m_url = url; } - - QUrl url() const { return m_url; } - // Created on first use: only a tab that shows the web UI needs it. - QQuickWebEngineProfile* profile(); - - // Where the profile keeps its data (cookies, local storage) and cache. - static QString storagePath(); - static QString cachePath(); - - // QML singleton factory: the instance the app created. - static HaWebUi* create(QQmlEngine*, QJSEngine*); - - private: - QUrl m_url; - QQuickWebEngineProfile* m_profile = nullptr; -}; diff --git a/src/main.cpp b/src/main.cpp index 49fe89d..b2aa26b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,6 @@ -#include - #include "Dashboard.h" int main(int argc, char* argv[]) { - // Must run before the Qt application object exists (the Dashboard - // constructor creates it). - QtWebEngineQuick::initialize(); Dashboard app(argc, argv); return app.exec(); } diff --git a/src/qml/HaDashboard.qml b/src/qml/HaDashboard.qml deleted file mode 100644 index 7657ca4..0000000 --- a/src/qml/HaDashboard.qml +++ /dev/null @@ -1,40 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Controls.Material -import QtWebEngine - -// "HA Dashboard" tab (DSH-1): HA's own web UI at the URL from the settings. -// Without a URL it shows a hint instead. -Pane { - id: root - - Material.theme: Material.Dark - padding: 0 - font.pixelSize: 24 - - Loader { - anchors.fill: parent - sourceComponent: HaWebUi.url.toString() !== "" ? webUi : hint - } - - Component { - id: webUi - - WebEngineView { - profile: HaWebUi.profile - url: HaWebUi.url - } - } - - Component { - id: hint - - Label { - padding: 48 - wrapMode: Text.WordWrap - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: qsTr("No Home Assistant URL configured.\n\nSet \"homeassistant.url\" in the settings file and restart.") - } - } -}