summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Griebl <robert.griebl@qt.io>2022-08-18 22:17:40 +0200
committerRobert Griebl <robert.griebl@qt.io>2022-09-16 13:15:05 +0200
commitff7a6b71093d5b7c007eee0af998daffcc0504e1 (patch)
tree821ecb2c4723dd88d15bc7ea0442341469df2b11
parent22929f522153d66187e4cb930b71a11c0b4166c6 (diff)
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.qdoc1
-rw-r--r--doc/controller.qdoc34
-rw-r--r--src/dbus-lib/CMakeLists.txt1
-rw-r--r--src/dbus-lib/applicationmanagerdbuscontextadaptor.cpp105
-rw-r--r--src/dbus-lib/io.qt.applicationmanager.xml12
-rw-r--r--src/tools/controller/controller.cpp60
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();
+}