App commands and --ha-remove (iteration 2, step 6)

The platform accepts app commands (D43): flags that get a
QCoreApplication like the manifest commands and run prepare() and then
the new hook runCommand(). The app's --ha-remove clears the device's
retained topics, so HA deletes the device; uninstall.sh runs it after
stopping the app and before unregistering from SteamVR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 16:32:51 +02:00
co-authored by Claude Opus 5.5
parent 148ae663cf
commit 0bdcaddcd5
12 changed files with 219 additions and 47 deletions
+7 -1
View File
@@ -41,6 +41,9 @@ Your app derives from it:
`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()`.
Its commands (flags such as `--ha-remove`) go there as a second list: like the manifest
commands they get a `QCoreApplication` (no display needed, works over SSH), and `exec()`
runs `prepare()` and then `runCommand(name)` in mode `Command` instead of a mode.
- **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`
@@ -48,10 +51,13 @@ Your app derives from it:
- `loading()`: the shared QML engine, the surfaces and their render backends exist; QML
isn't loaded yet. Set up what the QML needs here.
- `started()`: everything runs. Connect to surfaces and input here.
- `runCommand(name)`: one of the app's commands, after `prepare()`; no QML engine,
surfaces or SteamVR. Its result is the exit code.
- **Accessors:** `mode()`, `appKey()`, `appName()`, `isOptionSet()`, `optionValue()`,
`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` (`QCoreApplication` for
commands), so your members can be
QObjects with timers. All QML and surfaces are torn down at the end of `exec()`, while your
object and its members still exist.
+7 -1
View File
@@ -385,6 +385,8 @@ a `QGuiApplication` (no display needed, works over SSH), and `exec()` calls
- `exec()`: after the manifest check, an app command runs `prepare()` and then a new hook
`virtual int runCommand(const QString& name)`, whose result is the exit code. No engine,
surfaces or SteamVR.
- The mode is `DashboardMode::Command` (so `prepare()` can tell); the name of the first app
command given goes to `runCommand()`. Tested in `tst_surfaces` (`surfaces_command`).
- Moving the manifest commands onto this mechanism is possible later, not part of iteration 2.
### `--ha-remove`
@@ -396,7 +398,11 @@ delete).
- An app command (D43): loads the settings, connects, publishes **empty retained payloads**
to discovery (HA then deletes the device and its entities), device availability, and each
entity's state and availability topics; disconnects.
- Exit code 0 on success; ≠ 0 with a warning if the broker can't be reached within 10 s.
- Its client uses the client id `frameassistant_<device id>_remove` (doesn't knock a running
instance off the broker) and no will. The `HaDevice` is created only after the connection
is up: a device that sees the connection come up publishes itself first.
- Exit code 0 on success; 1 with an error if the broker can't be reached within 10 s, or the
removal isn't acknowledged within 10 s.
- `uninstall.sh`: `pkill` → `sleep 1` → `--ha-remove` (a failure doesn't stop the uninstall) →
`--uninstall-manifest`.
- Also usable by hand, e.g. after a device name change without `ha_unique_id`:
+8 -1
View File
@@ -25,7 +25,8 @@ CLAUDE.md ("Working rules").
- **Implementation** follows the order in DESIGN.md (steps 1–7). Steps 1 (libmosquitto in
CMake and the containers), 2 (`MqttClient`, `MosquittoClient`, `tst_mqtt`) 3
(`HaEntity`, `HaSensor`, `HaDevice`, unit tests) and 4 (app wiring, `--desktop-novr`,
UI-1) and 5 (TLS) done; next: step 6 (app commands, `--ha-remove`, `uninstall.sh`).
UI-1) 5 (TLS) and 6 (app commands, `--ha-remove`, `uninstall.sh`) done; next: step 7
(packaging check, docs).
## Next steps
@@ -178,6 +179,12 @@ Known limitation: the bundled-Qt package does not support WebEngine in this iter
validation messages.
- Still open: which CA store the Frame's OpenSSL uses by default (for `ca_file` empty);
check on the device.
- Step 6: app commands in the platform (D43: second option list, `QCoreApplication`, mode
`Command`, hook `runCommand()`; `tst_surfaces` `surfaces_command`); `--ha-remove` in the
app; `uninstall.sh` stops the app, runs `--ha-remove` (failure doesn't stop it) and
unregisters. Checked end to end against a local mosquitto, without a display: retained
discovery, availability, state and entity availability before, none after (exit 0);
broker down → exit 1 after 10 s; missing settings → exit 1.
### 2026-10-05: requirements approved