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
+2
View File
@@ -33,6 +33,8 @@ add_test(NAME surfaces_cli_key COMMAND tst_surfaces --graphics-api opengl)
set_tests_properties(surfaces_cli_key PROPERTIES ENVIRONMENT "VRDASH_TEST_CLI_KEY=1")
add_test(NAME surfaces_prepare_fail COMMAND tst_surfaces --graphics-api opengl)
set_tests_properties(surfaces_prepare_fail PROPERTIES ENVIRONMENT "VRDASH_TEST_PREPARE_FAIL=1")
add_test(NAME surfaces_command COMMAND tst_surfaces)
set_tests_properties(surfaces_command PROPERTIES ENVIRONMENT "VRDASH_TEST_COMMAND=1")
add_test(NAME surfaces_desktop_novr COMMAND tst_surfaces)
set_tests_properties(surfaces_desktop_novr PROPERTIES
ENVIRONMENT "VRDASH_TEST_DESKTOP_NOVR=1;QT_QPA_PLATFORM=offscreen")
+32 -1
View File
@@ -6,10 +6,14 @@
// without calling the other hooks.
// VRDASH_TEST_DESKTOP_NOVR=1: --desktop-novr instead of --snapshot; prepare()
// checks the mode and then fails (no window, no event loop).
// VRDASH_TEST_COMMAND=1: the app command --test-command instead of
// --snapshot: prepare(), then runCommand() without GUI, engine or surfaces;
// its result is exec()'s.
// Exit codes: 0 pass, 1 fail, 77 skipped (no GPU context).
#include <QCommandLineOption>
#include <QDir>
#include <QGuiApplication>
#include <QImage>
#include <QTemporaryDir>
#include <cstdio>
@@ -34,7 +38,9 @@ class TwoSurfaceApp : public DashboardApplication {
QStringLiteral("An app option."),
QStringLiteral("value")),
QCommandLineOption(QStringLiteral("test-flag"),
QStringLiteral("An app flag, never set."))}) {
QStringLiteral("An app flag, never set."))},
{QCommandLineOption(QStringLiteral("test-command"),
QStringLiteral("An app command."))}) {
setQmlModule(QStringLiteral("SurfacesTest"));
setDisplayName(QStringLiteral("Test App"));
setAppKey(QStringLiteral("config.key"));
@@ -53,6 +59,7 @@ class TwoSurfaceApp : public DashboardApplication {
int prepareCalls = 0;
int loadingCalls = 0;
int startedCalls = 0;
QStringList commands;
bool ok = true;
protected:
@@ -94,6 +101,15 @@ class TwoSurfaceApp : public DashboardApplication {
"texture size from config");
ok &= check(vrRuntime() == nullptr, "no VR outside overlay mode");
}
int runCommand(const QString& name) override {
commands.append(name);
ok &= check(mode() == DashboardMode::Command, "mode in runCommand()");
ok &= check(engine() == nullptr && surfaces().isEmpty(),
"no engine and no surfaces for a command");
ok &= check(!qobject_cast<QGuiApplication*>(QCoreApplication::instance()),
"no GUI application for a command");
return 7;
}
void started() override {
++startedCalls;
const QList<Surface*> all = surfaces();
@@ -113,11 +129,14 @@ int main(int argc, char* argv[]) {
const bool cliKey = qEnvironmentVariableIntValue("VRDASH_TEST_CLI_KEY");
const bool desktopNoVr =
qEnvironmentVariableIntValue("VRDASH_TEST_DESKTOP_NOVR");
const bool command = qEnvironmentVariableIntValue("VRDASH_TEST_COMMAND");
const bool failPrepare =
qEnvironmentVariableIntValue("VRDASH_TEST_PREPARE_FAIL") || desktopNoVr;
std::vector<std::string> args = {argv[0], "--test-value", "42"};
if (desktopNoVr) {
args.emplace_back("--desktop-novr");
} else if (command) {
args.emplace_back("--test-command");
} else {
args.emplace_back("--snapshot");
args.emplace_back(snapshot);
@@ -136,7 +155,19 @@ int main(int argc, char* argv[]) {
cliKey ? QStringLiteral("cli.key") : QStringLiteral("config.key");
app.failPrepare = failPrepare;
if (desktopNoVr) app.expectedMode = DashboardMode::DesktopNoVr;
if (command) app.expectedMode = DashboardMode::Command;
const int result = app.exec();
if (command) {
bool ok = app.ok;
ok &= check(result == 7, "exec() returns runCommand()'s result");
ok &= check(
app.prepareCalls == 1 && app.loadingCalls == 0 && app.startedCalls == 0,
"prepare(), but no other hooks for a command");
ok &= check(app.commands == QStringList{QStringLiteral("test-command")},
"runCommand() once with the command's name");
fprintf(stderr, ok ? "PASS\n" : "FAILED\n");
return ok ? 0 : 1;
}
if (failPrepare) {
bool ok = app.ok;
ok &= check(result == 1, "exec() returns 1 when prepare() fails");