diff options
author | Robert Griebl <robert.griebl@qt.io> | 2022-08-18 22:17:40 +0200 |
---|---|---|
committer | Robert Griebl <robert.griebl@qt.io> | 2022-09-16 13:15:05 +0200 |
commit | ff7a6b71093d5b7c007eee0af998daffcc0504e1 (patch) | |
tree | 821ecb2c4723dd88d15bc7ea0442341469df2b11 | |
parent | 22929f522153d66187e4cb930b71a11c0b4166c6 (diff) | |
download | qtapplicationmanager-ff7a6b71093d5b7c007eee0af998daffcc0504e1.tar.gz |
Intents: allow intent injection via DBus when in developmentMode
This makes testing and debugging Intents a lot easier, especially
since you can even fake the requesting application id.
Change-Id: Ie5d46bd2e3a9962ca533bf75d7262c20f95d42b6
Reviewed-by: Dominik Holland <dominik.holland@qt.io>
-rw-r--r-- | doc/configuration.qdoc | 1 | ||||
-rw-r--r-- | doc/controller.qdoc | 34 | ||||
-rw-r--r-- | src/dbus-lib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/dbus-lib/applicationmanagerdbuscontextadaptor.cpp | 105 | ||||
-rw-r--r-- | src/dbus-lib/io.qt.applicationmanager.xml | 12 | ||||
-rw-r--r-- | src/tools/controller/controller.cpp | 60 |
6 files changed, 209 insertions, 4 deletions
diff --git a/doc/configuration.qdoc b/doc/configuration.qdoc index 8ee289f2..3a4e1f90 100644 --- a/doc/configuration.qdoc +++ b/doc/configuration.qdoc @@ -302,6 +302,7 @@ ui: \li Disables all security related checks. Use this option in a development setup only; never in production. (default: false) \row + \target development-mode \li \b --development-mode \br [\c flags/developmentMode] \li bool diff --git a/doc/controller.qdoc b/doc/controller.qdoc index 7986e1dc..68280efa 100644 --- a/doc/controller.qdoc +++ b/doc/controller.qdoc @@ -18,7 +18,7 @@ system-bus; don't run it with \c{--dbus none}. If you are running multiple application manager instances in the same system, you need to first \l{instance-id}{assign unique instance-ids} to each of them and then you can address them -individually from the \c appman-controller tool by using the \c instance-id command line option. +individually from the \c appman-controller tool by using the \c --instance-id command line option. The following commands are available: @@ -31,7 +31,7 @@ The following commands are available: \li \span {style="white-space: nowrap"} {\c start-application} \li \c{<application-id>} - \c{[document-url]} [optional] + \c{[document-url]} \li Starts the application with \c application-id within the application manager. \row \li \span {style="white-space: nowrap"} {\c debug-application} @@ -39,7 +39,7 @@ The following commands are available: \c{<application-id>} - \c{[document-url]} [optional] + \c{[document-url]} \li Starts the application with \c application-id within the application manager using a debug-wrapper. For more information, see \l{DebugWrappers}. \row @@ -94,6 +94,34 @@ The following commands are available: \li \c{<installation-location>} \li Shows details for the specified \c installation-location in YAML format. Alternatively, use \c{--json} to get the location details in JSON format instead. +\row + \li \span {style="white-space: nowrap"} {\c list-instances} + \li (none) + \li Lists all currently running \l{instance-id}{named} application manager instances. +\row + \li \span {style="white-space: nowrap"} {\c inject-intent-request} + \li \c{<intent-id>} + + \c{[parameters as json string]} + \li Injects an intent request into the application manager for testing or debugging purposes. + This only works, if the application manager is running in \l{development-mode}{developmentMode}. + + The parameters have to be supplied as a single argument JSON string - make sure to correctly + escape any quotation marks when running from a shell. + + By default, the injected intent request will have a requesting application id of \c{:sysui:} + (the System UI) and no handling application id. You can use the following command line + options to change this behavior: + + \c{--requesting-application-id} Fake the requesting application id. + + \c{--application-id} Specify the handling application id to prevent disambiguation. + + \c{--broadcast} Instead of a directed request, create a broadcast. + + Please note that \c{--application-id} and \c{--broadcast} are mutually exclusive. + + For successful non-broadcast requests, the result will be printed to the console as JSON. \endtable The \c{appman-controller} naturally supports the standard Unix \c{--help} command-line option. diff --git a/src/dbus-lib/CMakeLists.txt b/src/dbus-lib/CMakeLists.txt index df7a19d4..e34fcffc 100644 --- a/src/dbus-lib/CMakeLists.txt +++ b/src/dbus-lib/CMakeLists.txt @@ -18,6 +18,7 @@ qt_internal_add_module(AppManDBusPrivate Qt::AppManCommonPrivate Qt::AppManManagerPrivate Qt::AppManWindowPrivate + Qt::AppManIntentClientPrivate PUBLIC_LIBRARIES Qt::Core Qt::DBus diff --git a/src/dbus-lib/applicationmanagerdbuscontextadaptor.cpp b/src/dbus-lib/applicationmanagerdbuscontextadaptor.cpp index f9cab018..3ba9f76e 100644 --- a/src/dbus-lib/applicationmanagerdbuscontextadaptor.cpp +++ b/src/dbus-lib/applicationmanagerdbuscontextadaptor.cpp @@ -11,10 +11,12 @@ #include "applicationmanagerdbuscontextadaptor.h" #include "applicationmanager.h" #include "applicationmanager_adaptor.h" +#include "packagemanager.h" #include "dbuspolicy.h" #include "exception.h" #include "logging.h" - +#include "intentclient.h" +#include "intentclientrequest.h" QT_BEGIN_NAMESPACE_AM @@ -240,3 +242,104 @@ void ApplicationManagerAdaptor::stopApplication(const QString &id, bool forceKil AM_AUTHENTICATE_DBUS(void) ApplicationManager::instance()->stopApplication(id, forceKill); } + +QString ApplicationManagerAdaptor::sendIntentRequestAs(const QString &requestingApplicationId, + const QString &intentId, const QString &applicationId, + const QString &jsonParameters) +{ + AM_AUTHENTICATE_DBUS(QString) + + QDBusContext *dbusContext = AbstractDBusContextAdaptor::dbusContextFor(this); + dbusContext->setDelayedReply(true); + + if (!PackageManager::instance()->developmentMode()) { + dbusContext->sendErrorReply(qL1S("org.freedesktop.DBus.Error.Failed"), + qL1S("Only supported if 'developmentMode' is active")); + return { }; + } + + if (intentId.isEmpty()) { + dbusContext->sendErrorReply(qL1S("org.freedesktop.DBus.Error.Failed"), + qL1S("intentId cannot be empty")); + return { }; + } + + QVariantMap parameters; + if (!jsonParameters.trimmed().isEmpty()) { + QJsonParseError parseError; + auto jsDoc = QJsonDocument::fromJson(jsonParameters.toUtf8(), &parseError); + if (jsDoc.isNull()) { + dbusContext->sendErrorReply(qL1S("org.freedesktop.DBus.Error.Failed"), + qL1S("jsonParameters is not a valid JSON document: ") + + parseError.errorString()); + return { }; + } + parameters = jsDoc.toVariant().toMap(); + } + + auto dbusMsg = new QDBusMessage(dbusContext->message()); + auto dbusName = dbusContext->connection().name(); + + const QString reqAppId = requestingApplicationId.isEmpty() ? IntentClient::instance()->systemUiId() + : requestingApplicationId; + + auto icr = IntentClient::instance()->requestToSystem(reqAppId, intentId, applicationId, parameters); + icr->startTimeout(IntentClient::instance()->replyFromSystemTimeout()); + + // clang-code-model: this slot is always called (even on timeouts), so dbusMsg does not leak + connect(icr, &IntentClientRequest::replyReceived, this, [icr, dbusMsg, dbusName]() { + bool succeeded = icr->succeeded(); + QDBusConnection conn(dbusName); + + if (succeeded) { + auto jsonResult = QString::fromUtf8(QJsonDocument::fromVariant(icr->result()).toJson()); + conn.send(dbusMsg->createReply(jsonResult)); + } else { + conn.send(dbusMsg->createErrorReply(qL1S("org.freedesktop.DBus.Error.Failed"), + icr->errorMessage())); + } + delete dbusMsg; + icr->deleteLater(); + }); + + return { }; +} + +void ApplicationManagerAdaptor::broadcastIntentRequestAs(const QString &requestingApplicationId, + const QString &intentId, + const QString &jsonParameters) +{ + AM_AUTHENTICATE_DBUS(void) + + QDBusContext *dbusContext = AbstractDBusContextAdaptor::dbusContextFor(this); + + if (!PackageManager::instance()->developmentMode()) { + dbusContext->sendErrorReply(qL1S("org.freedesktop.DBus.Error.Failed"), + qL1S("Only supported if 'developmentMode' is active")); + return; + } + + if (intentId.isEmpty()) { + dbusContext->sendErrorReply(qL1S("org.freedesktop.DBus.Error.Failed"), + qL1S("intentId cannot be empty")); + return; + } + + QVariantMap parameters; + if (!jsonParameters.trimmed().isEmpty()) { + QJsonParseError parseError; + auto jsDoc = QJsonDocument::fromJson(jsonParameters.toUtf8(), &parseError); + if (jsDoc.isNull()) { + dbusContext->sendErrorReply(qL1S("org.freedesktop.DBus.Error.Failed"), + qL1S("jsonParameters is not a valid JSON document: ") + + parseError.errorString()); + return; + } + parameters = jsDoc.toVariant().toMap(); + } + + const QString reqAppId = requestingApplicationId.isEmpty() ? IntentClient::instance()->systemUiId() + : requestingApplicationId; + + IntentClient::instance()->requestToSystem(reqAppId, intentId, qSL(":broadcast:"), parameters); +} diff --git a/src/dbus-lib/io.qt.applicationmanager.xml b/src/dbus-lib/io.qt.applicationmanager.xml index c7a9d4fa..499500c2 100644 --- a/src/dbus-lib/io.qt.applicationmanager.xml +++ b/src/dbus-lib/io.qt.applicationmanager.xml @@ -107,5 +107,17 @@ <arg type="u" direction="out"/> <arg name="id" type="s" direction="in"/> </method> + <method name="sendIntentRequestAs"> + <arg type="s" direction="out"/> + <arg name="requestingApplicationId" type="s" direction="in"/> + <arg name="intentId" type="s" direction="in"/> + <arg name="applicationId" type="s" direction="in"/> + <arg name="jsonParameters" type="s" direction="in"/> + </method> + <method name="broadcastIntentRequestAs"> + <arg name="requestingApplicationId" type="s" direction="in"/> + <arg name="intentId" type="s" direction="in"/> + <arg name="jsonParameters" type="s" direction="in"/> + </method> </interface> </node> diff --git a/src/tools/controller/controller.cpp b/src/tools/controller/controller.cpp index 2fade30d..f7a5bc87 100644 --- a/src/tools/controller/controller.cpp +++ b/src/tools/controller/controller.cpp @@ -131,6 +131,7 @@ enum Command { ListInstallationLocations, ShowInstallationLocation, ListInstances, + InjectIntentRequest, }; // REMEMBER to update the completion file util/bash/appman-prompt, if you apply changes below! @@ -154,6 +155,7 @@ static struct { { ListInstallationLocations, "list-installation-locations", "List all installaton locations." }, { ShowInstallationLocation, "show-installation-location", "Show details for installation location." }, { ListInstances, "list-instances", "List all named application manager instances." }, + { InjectIntentRequest, "inject-intent-request", "Inject an intent request for testing." }, }; static Command command(QCommandLineParser &clp) @@ -188,6 +190,10 @@ static void cancelInstallationTask(bool all, const QString &taskId) Q_DECL_NOEXC static void listInstallationLocations() Q_DECL_NOEXCEPT_EXPR(false); static void showInstallationLocation(bool asJson = false) Q_DECL_NOEXCEPT_EXPR(false); static void listInstances() Q_DECL_NOEXCEPT_EXPR(false); +static void injectIntentRequest(const QString &intentId, bool isBroadcast, + const QString &applicationId, const QString &requestingApplicationId, + const QString &jsonParameters) Q_DECL_NOEXCEPT_EXPR(false); + class ThrowingApplication : public QCoreApplication // clazy:exclude=missing-qobject-macro { @@ -470,6 +476,32 @@ int main(int argc, char *argv[]) clp.process(a); a.runLater(listInstances); break; + + case InjectIntentRequest: + clp.addPositionalArgument(qSL("intent-id"), qSL("The id of the intent.")); + clp.addPositionalArgument(qSL("parameters"), qSL("The optional parameters for this request."), qSL("[json-parameters]")); + clp.addOption({ qSL("requesting-application-id"), qSL("Fake the requesting application id."), qSL("id"), qSL(":sysui:") }); + clp.addOption({ qSL("application-id"), qSL("Specify the handling application id."), qSL("id") }); + clp.addOption({ qSL("broadcast"), qSL("Create a broadcast request.") }); + clp.process(a); + + bool isBroadcast = clp.isSet(qSL("broadcast")); + QString appId = clp.value(qSL("application-id")); + QString requestingAppId = clp.value(qSL("requesting-application-id")); + + if (!appId.isEmpty() && isBroadcast) + throw Exception("You cannot use --application-id and --broadcast at the same time."); + + if (clp.positionalArguments().size() > 3) + clp.showHelp(1); + + QString jsonParams; + if (clp.positionalArguments().size() == 3) + jsonParams = clp.positionalArguments().at(2); + + a.runLater(std::bind(injectIntentRequest, clp.positionalArguments().at(1), + isBroadcast, requestingAppId, appId, jsonParams)); + break; } int result = a.exec(); @@ -912,3 +944,31 @@ void listInstances() } qApp->quit(); } + +void injectIntentRequest(const QString &intentId, bool isBroadcast, + const QString &requestingApplicationId, const QString &applicationId, + const QString &jsonParameters) Q_DECL_NOEXCEPT_EXPR(false) +{ + dbus.connectToManager(); + + if (isBroadcast) { + auto reply = dbus.manager()->broadcastIntentRequestAs(requestingApplicationId, + intentId, + jsonParameters); + reply.waitForFinished(); + if (reply.isError()) + throw Exception(Error::IO, "failed to call broadcastIntentRequest via DBus: %1").arg(reply.error().message()); + } else { + auto reply = dbus.manager()->sendIntentRequestAs(requestingApplicationId, + intentId, + applicationId, + jsonParameters); + reply.waitForFinished(); + if (reply.isError()) + throw Exception(Error::IO, "failed to call sendIntentRequest via DBus: %1").arg(reply.error().message()); + const auto jsonResult = reply.value(); + fprintf(stdout, "%s\n", qPrintable(jsonResult)); + } + + qApp->quit(); +} |