From 22929f522153d66187e4cb930b71a11c0b4166c6 Mon Sep 17 00:00:00 2001 From: Robert Griebl Date: Thu, 18 Aug 2022 22:17:05 +0200 Subject: Intents: add broadcasts This commit adds a new intent type: a broadcast. Broadcasts will be delivered to all applications (*), but they do not have the possibility to return a reply back to the sender. Implementation wise, they are just treated as normal requests that are multiplexed for every application. (*) This can be limited with the new handleOnlyWhenRunning flag. Change-Id: If9f954cf5e52707624b95c80c8e984dfd6c4315a Reviewed-by: Qt CI Bot Reviewed-by: Dominik Holland --- src/application-lib/intentinfo.cpp | 13 ++- src/application-lib/intentinfo.h | 4 + src/application-lib/yamlpackagescanner.cpp | 3 + src/intent-client-lib/intentclient.cpp | 59 +++++++++- src/intent-client-lib/intentclient.h | 9 +- src/intent-client-lib/intentclientrequest.cpp | 29 ++++- src/intent-client-lib/intentclientrequest.h | 8 +- src/intent-server-lib/intent.cpp | 20 +++- src/intent-server-lib/intent.h | 10 +- src/intent-server-lib/intentserver.cpp | 121 +++++++++++++-------- src/intent-server-lib/intentserver.h | 2 +- src/intent-server-lib/intentserverrequest.cpp | 31 ++++-- src/intent-server-lib/intentserverrequest.h | 14 ++- .../intentserversysteminterface.cpp | 5 +- .../intentserversysteminterface.h | 4 +- .../intentclientdbusimplementation.cpp | 3 + src/manager-lib/intentaminterface.cpp | 77 +++++++------ src/manager-lib/intentaminterface.h | 16 +-- 18 files changed, 307 insertions(+), 121 deletions(-) (limited to 'src') diff --git a/src/application-lib/intentinfo.cpp b/src/application-lib/intentinfo.cpp index b4854c13..f57a5688 100644 --- a/src/application-lib/intentinfo.cpp +++ b/src/application-lib/intentinfo.cpp @@ -64,8 +64,13 @@ QString IntentInfo::icon() const return m_icon.isEmpty() ? m_packageInfo->icon() : m_icon; } +bool IntentInfo::handleOnlyWhenRunning() const +{ + return m_handleOnlyWhenRunning; +} + -const quint32 IntentInfo::DataStreamVersion = 2; +const quint32 IntentInfo::DataStreamVersion = 3; void IntentInfo::writeToDataStream(QDataStream &ds) const @@ -80,7 +85,8 @@ void IntentInfo::writeToDataStream(QDataStream &ds) const << m_categories << m_names << m_descriptions - << m_icon; + << m_icon + << m_handleOnlyWhenRunning; } IntentInfo *IntentInfo::readFromDataStream(PackageInfo *pkg, QDataStream &ds) @@ -98,7 +104,8 @@ IntentInfo *IntentInfo::readFromDataStream(PackageInfo *pkg, QDataStream &ds) >> intent->m_categories >> intent->m_names >> intent->m_descriptions - >> intent->m_icon; + >> intent->m_icon + >> intent->m_handleOnlyWhenRunning; intent->m_visibility = (visibilityStr == qSL("public")) ? Public : Private; intent->m_categories.sort(); diff --git a/src/application-lib/intentinfo.h b/src/application-lib/intentinfo.h index 08df4315..94b9d2b7 100644 --- a/src/application-lib/intentinfo.h +++ b/src/application-lib/intentinfo.h @@ -44,6 +44,8 @@ public: QMap descriptions() const; QString icon() const; + bool handleOnlyWhenRunning() const; + void writeToDataStream(QDataStream &ds) const; static IntentInfo *readFromDataStream(PackageInfo *pkg, QDataStream &ds); @@ -60,6 +62,8 @@ private: QMap m_descriptions; // language -> description QString m_icon; // relative to the manifest's location + bool m_handleOnlyWhenRunning = false; + friend class YamlPackageScanner; Q_DISABLE_COPY(IntentInfo) }; diff --git a/src/application-lib/yamlpackagescanner.cpp b/src/application-lib/yamlpackagescanner.cpp index a34bfb80..f092172b 100644 --- a/src/application-lib/yamlpackagescanner.cpp +++ b/src/application-lib/yamlpackagescanner.cpp @@ -318,6 +318,9 @@ PackageInfo *YamlPackageScanner::scan(QIODevice *source, const QString &fileName intentInfo->m_categories = p->parseStringOrStringList(); intentInfo->m_categories.sort(); }); + intentFields.emplace_back("handleOnlyWhenRunning", false, YamlParser::Scalar, [&intentInfo](YamlParser *p) { + intentInfo->m_handleOnlyWhenRunning = p->parseScalar().toBool(); + }); p->parseFields(intentFields); diff --git a/src/intent-client-lib/intentclient.cpp b/src/intent-client-lib/intentclient.cpp index 17f2ac25..4078108f 100644 --- a/src/intent-client-lib/intentclient.cpp +++ b/src/intent-client-lib/intentclient.cpp @@ -71,6 +71,20 @@ IntentClient *IntentClient::instance() return s_instance; } +/*! \qmlproperty string IntentClient::systemUiId + + The hardcoded, special application id for targeting the System UI with an intent request. +*/ +QString IntentClient::systemUiId() const +{ + return qSL(":sysui:"); +} + +int IntentClient::replyFromSystemTimeout() const +{ + return m_replyFromSystemTimeout; +} + void IntentClient::setReplyFromSystemTimeout(int timeout) { m_replyFromSystemTimeout = timeout; @@ -145,6 +159,9 @@ IntentClientRequest *IntentClient::sendIntentRequest(const QString &intentId, co it. The request will fail, if this specified application doesn't exist or can't handle this specific request, even though other applications would be able to do it. + There is the special application id \c IntentClient.systemUiId which can be used to target the + System UI. + \sa sendIntentRequest */ IntentClientRequest *IntentClient::sendIntentRequest(const QString &intentId, const QString &applicationId, @@ -152,6 +169,8 @@ IntentClientRequest *IntentClient::sendIntentRequest(const QString &intentId, co { if (intentId.isEmpty()) return nullptr; + if (applicationId == qSL(":broadcast:")) // reserved + return nullptr; //TODO: check that parameters only contains basic datatypes. convertFromJSVariant() does most of // this already, but doesn't bail out on unconvertible types (yet) @@ -162,13 +181,37 @@ IntentClientRequest *IntentClient::sendIntentRequest(const QString &intentId, co return icr; } +/*! \qmlmethod bool IntentClient::broadcastIntentRequest(string intentId, var parameters) + \since 6.5 + + Broadcasts an intent request with the given \a intentId to the system. The additional + \a parameters are specific to the requested \a intentId, but the format is always the same: a + standard JavaScript object, which can also be just empty if the requested intent doesn't + require any parameters. + + Broadcast requests do not generate replies. The return value is only ever \c false, if you + call this function with invalid arguments. +*/ +bool IntentClient::broadcastIntentRequest(const QString &intentId, const QVariantMap ¶meters) +{ + if (intentId.isEmpty()) + return false; + + //TODO: check that parameters only contains basic datatypes. convertFromJSVariant() does most of + // this already, but doesn't bail out on unconvertible types (yet) + + requestToSystem(m_systemInterface->currentApplicationId(this), intentId, qSL(":broadcast:"), parameters); + return true; +} + IntentClientRequest *IntentClient::requestToSystem(const QString &requestingApplicationId, const QString &intentId, const QString &applicationId, const QVariantMap ¶meters) { IntentClientRequest *ir = new IntentClientRequest(IntentClientRequest::Direction::ToSystem, requestingApplicationId, QUuid(), - intentId, applicationId, parameters); + intentId, applicationId, parameters, + applicationId == qSL(":broadcast:")); qCDebug(LogIntents) << "Application" << requestingApplicationId << "created an intent request for" << intentId << "(application:" << applicationId << ")"; @@ -181,6 +224,11 @@ void IntentClient::requestToSystemFinished(IntentClientRequest *icr, const QUuid if (!icr) return; + if (icr->isBroadcast()) { + icr->deleteLater(); + return; + } + if (error) { icr->setErrorMessage(errorMessage); } else if (newRequestId.isNull()) { @@ -228,17 +276,20 @@ void IntentClient::requestToApplication(const QUuid &requestId, const QString &i const QString &requestingApplicationId, const QString &applicationId, const QVariantMap ¶meters) { + bool broadcast = (requestingApplicationId == qSL(":broadcast:")); + qCDebug(LogIntents) << "Client: Incoming intent request" << requestId << "to application" << applicationId - << "for intent" << intentId << "parameters" << parameters; + << "for intent" << intentId << (broadcast ? "(broadcast)" : "") << "parameters" << parameters; IntentClientRequest *icr = new IntentClientRequest(IntentClientRequest::Direction::ToApplication, requestingApplicationId, requestId, intentId, - applicationId, parameters); + applicationId, parameters, broadcast); IntentHandler *handler = m_handlers.value(qMakePair(intentId, applicationId)); if (handler) { QQmlEngine::setObjectOwnership(icr, QQmlEngine::JavaScriptOwnership); - icr->startTimeout(m_replyFromApplicationTimeout); + if (!broadcast) + icr->startTimeout(m_replyFromApplicationTimeout); emit handler->requestReceived(icr); } else { diff --git a/src/intent-client-lib/intentclient.h b/src/intent-client-lib/intentclient.h index ecc224e8..a405b8c3 100644 --- a/src/intent-client-lib/intentclient.h +++ b/src/intent-client-lib/intentclient.h @@ -26,13 +26,17 @@ class IntentClientSystemInterface; class IntentClient : public QObject { Q_OBJECT - Q_CLASSINFO("AM-QmlType", "QtApplicationManager/IntentClient 2.0 SINGLETON") + Q_CLASSINFO("AM-QmlType", "QtApplicationManager/IntentClient 2.1 SINGLETON") + Q_PROPERTY(QString systemUiId READ systemUiId CONSTANT REVISION 1) public: ~IntentClient() override; static IntentClient *createInstance(IntentClientSystemInterface *systemInterface); static IntentClient *instance(); + QString systemUiId() const; + + int replyFromSystemTimeout() const; void setReplyFromSystemTimeout(int timeout); void setReplyFromApplicationTimeout(int timeout); @@ -50,6 +54,9 @@ public: const QString &applicationId, const QVariantMap ¶meters); + Q_REVISION(1) Q_INVOKABLE bool broadcastIntentRequest(const QString &intentId, + const QVariantMap ¶meters); + private: void requestToSystemFinished(IntentClientRequest *icr, const QUuid &newRequestId, bool error, const QString &errorMessage); diff --git a/src/intent-client-lib/intentclientrequest.cpp b/src/intent-client-lib/intentclientrequest.cpp index 09836df2..e077fe35 100644 --- a/src/intent-client-lib/intentclientrequest.cpp +++ b/src/intent-client-lib/intentclientrequest.cpp @@ -126,6 +126,15 @@ QT_BEGIN_NAMESPACE_AM \sa succeeded */ +/*! \qmlproperty bool IntentRequest::broadcast + \readonly + \since 6.5 + + Only Set to \c true, if the received request is a broadcast. + + \note Valid only on received requests. +*/ + /*! \qmlsignal IntentRequest::replyReceived() This signal gets emitted when a reply to an intent request is available. The signal handler @@ -145,7 +154,7 @@ IntentClientRequest::Direction IntentClientRequest::direction() const IntentClientRequest::~IntentClientRequest() { // the incoming request was gc'ed on the JavaScript side, but no reply was sent yet - if ((direction() == Direction::ToApplication) && !m_finished) + if ((direction() == Direction::ToApplication) && !m_finished && !m_broadcast) sendErrorReply(qSL("Request not handled")); } @@ -174,6 +183,11 @@ QVariantMap IntentClientRequest::parameters() const return m_parameters; } +bool IntentClientRequest::isBroadcast() const +{ + return m_broadcast; +} + bool IntentClientRequest::succeeded() const { return m_succeeded; @@ -210,6 +224,11 @@ void IntentClientRequest::sendReply(const QVariantMap &result) qmlWarning(this) << "Calling IntentRequest::sendReply on requests originating from this application is a no-op."; return; } + if (m_broadcast) { + qmlWarning(this) << "Calling IntentRequest::sendReply on broadcast requests is a no-op."; + return; + } + IntentClient *ic = IntentClient::instance(); if (QThread::currentThread() != ic->thread()) { @@ -241,6 +260,10 @@ void IntentClientRequest::sendErrorReply(const QString &errorMessage) qmlWarning(this) << "Calling IntentRequest::sendErrorReply on requests originating from this application is a no-op."; return; } + if (m_broadcast) { + qmlWarning(this) << "Calling IntentRequest::sendErrorReply on broadcast requests is a no-op."; + return; + } IntentClient *ic = IntentClient::instance(); if (QThread::currentThread() != ic->thread()) { @@ -286,7 +309,8 @@ void IntentClientRequest::connectNotify(const QMetaMethod &signal) IntentClientRequest::IntentClientRequest(Direction direction, const QString &requestingApplicationId, const QUuid &id, const QString &intentId, - const QString &applicationId, const QVariantMap ¶meters) + const QString &applicationId, const QVariantMap ¶meters, + bool broadcast) : QObject() , m_direction(direction) , m_id(id) @@ -294,6 +318,7 @@ IntentClientRequest::IntentClientRequest(Direction direction, const QString &req , m_requestingApplicationId(requestingApplicationId) , m_applicationId(applicationId) , m_parameters(parameters) + , m_broadcast(broadcast) { } void IntentClientRequest::setRequestId(const QUuid &requestId) diff --git a/src/intent-client-lib/intentclientrequest.h b/src/intent-client-lib/intentclientrequest.h index 02d7b39c..016ab349 100644 --- a/src/intent-client-lib/intentclientrequest.h +++ b/src/intent-client-lib/intentclientrequest.h @@ -19,7 +19,7 @@ class IntentClient; class IntentClientRequest : public QObject { Q_OBJECT - Q_CLASSINFO("AM-QmlType", "QtApplicationManager/IntentRequest 2.0 UNCREATABLE") + Q_CLASSINFO("AM-QmlType", "QtApplicationManager/IntentRequest 2.1 UNCREATABLE") Q_PROPERTY(QUuid requestId READ requestId NOTIFY requestIdChanged) Q_PROPERTY(Direction direction READ direction CONSTANT) @@ -30,6 +30,7 @@ class IntentClientRequest : public QObject Q_PROPERTY(bool succeeded READ succeeded NOTIFY replyReceived) Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY replyReceived) Q_PROPERTY(QVariantMap result READ result NOTIFY replyReceived) + Q_PROPERTY(bool broadcast READ isBroadcast CONSTANT REVISION 1) public: enum class Direction { ToSystem, ToApplication }; @@ -43,6 +44,7 @@ public: QString applicationId() const; QString requestingApplicationId() const; QVariantMap parameters() const; + bool isBroadcast() const; const QVariantMap result() const; bool succeeded() const; @@ -62,7 +64,8 @@ protected: private: IntentClientRequest(Direction direction, const QString &requestingApplicationId, const QUuid &id, - const QString &intentId, const QString &applicationId, const QVariantMap ¶meters); + const QString &intentId, const QString &applicationId, const QVariantMap ¶meters, + bool broadcast); void setRequestId(const QUuid &requestId); void setResult(const QVariantMap &result); @@ -83,6 +86,7 @@ private: // ToSystem: we have received the final result or errorMessage via replyReceived() // ToApplication: the request was handled and send(Error)Reply was called bool m_finished = false; + bool m_broadcast = false; Q_DISABLE_COPY(IntentClientRequest) diff --git a/src/intent-server-lib/intent.cpp b/src/intent-server-lib/intent.cpp index af4a4549..95992a8b 100644 --- a/src/intent-server-lib/intent.cpp +++ b/src/intent-server-lib/intent.cpp @@ -142,6 +142,18 @@ QT_BEGIN_NAMESPACE_AM If the intent does not specify a \c categories list, this will return the same as the containing PackageObject::categories. */ +/*! + \qmlproperty bool IntentObject::handleOnlyWhenRunning + \readonly + \since 6.5 + + By default, applications are automatically started when a request is targeted at them, but + they are not currently running. If this property is set to \c true, then any requests for this + intent will only be forwarded to its handling application, if the application is actuallly + running. + This is useful for system-wide broadcasts that are only relevant if an application is active + (e.g. changes in internet availability). +*/ Intent::Intent() @@ -151,7 +163,7 @@ Intent::Intent(const QString &id, const QString &packageId, const QString &appli const QStringList &capabilities, Intent::Visibility visibility, const QVariantMap ¶meterMatch, const QMap &names, const QMap &descriptions, const QUrl &icon, - const QStringList &categories) + const QStringList &categories, bool handleOnlyWhenRunning) : m_intentId(id) , m_visibility(visibility) , m_requiredCapabilities(capabilities) @@ -162,6 +174,7 @@ Intent::Intent(const QString &id, const QString &packageId, const QString &appli , m_descriptions(descriptions) , m_categories(categories) , m_icon(icon) + , m_handleOnlyWhenRunning(handleOnlyWhenRunning) { } @@ -273,6 +286,11 @@ QStringList Intent::categories() const return m_categories; } +bool Intent::handleOnlyWhenRunning() const +{ + return m_handleOnlyWhenRunning; +} + QT_END_NAMESPACE_AM #include "moc_intent.cpp" diff --git a/src/intent-server-lib/intent.h b/src/intent-server-lib/intent.h index 5ccdc6d6..dc9a7350 100644 --- a/src/intent-server-lib/intent.h +++ b/src/intent-server-lib/intent.h @@ -17,7 +17,7 @@ QT_BEGIN_NAMESPACE_AM class Intent : public QObject { Q_OBJECT - Q_CLASSINFO("AM-QmlType", "QtApplicationManager.SystemUI/IntentObject 2.0 UNCREATABLE") + Q_CLASSINFO("AM-QmlType", "QtApplicationManager.SystemUI/IntentObject 2.1 UNCREATABLE") Q_PROPERTY(QString intentId READ intentId CONSTANT) Q_PROPERTY(QString packageId READ packageId CONSTANT) @@ -33,6 +33,8 @@ class Intent : public QObject Q_PROPERTY(QVariantMap descriptions READ descriptions CONSTANT) Q_PROPERTY(QStringList categories READ categories CONSTANT) + Q_PROPERTY(bool handleOnlyWhenRunning READ handleOnlyWhenRunning CONSTANT REVISION 1) + public: enum Visibility { Public, @@ -59,12 +61,14 @@ public: QVariantMap descriptions() const; QStringList categories() const; + bool handleOnlyWhenRunning() const; + private: Intent(const QString &intentId, const QString &packageId, const QString &applicationId, const QStringList &capabilities, Intent::Visibility visibility, const QVariantMap ¶meterMatch, const QMap &names, const QMap &descriptions, const QUrl &icon, - const QStringList &categories); + const QStringList &categories, bool handleOnlyWhenRunning); QString m_intentId; Visibility m_visibility = Private; @@ -79,6 +83,8 @@ private: QStringList m_categories; QUrl m_icon; + bool m_handleOnlyWhenRunning = false; + friend class IntentServer; friend class IntentServerHandler; friend class TestPackageLoader; // for auto tests only diff --git a/src/intent-server-lib/intentserver.cpp b/src/intent-server-lib/intentserver.cpp index 3813e5db..476e0c77 100644 --- a/src/intent-server-lib/intentserver.cpp +++ b/src/intent-server-lib/intentserver.cpp @@ -122,6 +122,7 @@ IntentServer *IntentServer::createInstance(IntentServerSystemInterface *systemIn systemInterface->initialize(is.get()); qmlRegisterType("QtApplicationManager.SystemUI", 2, 0, "IntentObject"); + qmlRegisterType("QtApplicationManager.SystemUI", 2, 1, "IntentObject"); qmlRegisterType("QtApplicationManager.SystemUI", 2, 0, "IntentModel"); qmlRegisterSingletonType("QtApplicationManager.SystemUI", 2, 0, "IntentServer", @@ -211,7 +212,7 @@ Intent *IntentServer::addIntent(const QString &id, const QString &packageId, const QStringList &capabilities, Intent::Visibility visibility, const QVariantMap ¶meterMatch, const QMap &names, const QMap &descriptions, const QUrl &icon, - const QStringList &categories) + const QStringList &categories, bool handleOnlyWhenRunning) { try { if (id.isEmpty()) @@ -233,7 +234,8 @@ Intent *IntentServer::addIntent(const QString &id, const QString &packageId, } auto intent = new Intent(id, packageId, handlingApplicationId, capabilities, visibility, - parameterMatch, names, descriptions, icon, categories); + parameterMatch, names, descriptions, icon, categories, + handleOnlyWhenRunning); QQmlEngine::setObjectOwnership(intent, QQmlEngine::CppOwnership); beginInsertRows(QModelIndex(), rowCount(), rowCount()); @@ -502,12 +504,12 @@ void IntentServer::processRequestQueue() qCDebug(LogIntents) << "Processing intent request" << isr << isr->requestId() << "in state" << isr->state(); if (isr->state() == IntentServerRequest::State::ReceivedRequest) { // step 1) disambiguate - if (isr->handlingApplicationId().isEmpty()) { + if (!isr->isBroadcast() && !isr->selectedIntent()) { // not disambiguated yet if (!isSignalConnected(QMetaMethod::fromSignal(&IntentServer::disambiguationRequest))) { // If the System UI does not react to the signal, then just use the first match. - isr->setHandlingApplicationId(isr->potentialIntents().constFirst()->packageId()); + isr->setSelectedIntent(isr->potentialIntents().constFirst()); } else { m_disambiguationQueue.enqueue(isr); isr->setState(IntentServerRequest::State::WaitingForDisambiguation); @@ -524,67 +526,80 @@ void IntentServer::processRequestQueue() isr->parameters()); } } - if (!isr->handlingApplicationId().isEmpty()) { + if (isr->isBroadcast() || isr->selectedIntent()) { qCDebug(LogIntents) << "No disambiguation necessary/required for intent" << isr->intentId(); isr->setState(IntentServerRequest::State::Disambiguated); } } if (isr->state() == IntentServerRequest::State::Disambiguated) { // step 2) start app - auto handlerIPC = m_systemInterface->findClientIpc(isr->handlingApplicationId()); + auto handlerIPC = m_systemInterface->findClientIpc(isr->selectedIntent()->applicationId()); if (!handlerIPC) { - qCDebug(LogIntents) << "Intent handler" << isr->handlingApplicationId() << "is not running"; - m_startingAppQueue.enqueue(isr); - isr->setState(IntentServerRequest::State::WaitingForApplicationStart); - if (m_startingAppTimeout > 0) { - QTimer::singleShot(m_startingAppTimeout, this, [this, isr]() { - if (m_startingAppQueue.removeOne(isr)) { - isr->setRequestFailed(qSL("Starting handler application timed out after %1 ms").arg(m_startingAppTimeout)); - enqueueRequest(isr); - } - }); + qCDebug(LogIntents) << "Intent handler" << isr->selectedIntent()->applicationId() << "is not running"; + + if (isr->potentialIntents().constFirst()->handleOnlyWhenRunning()) { + qCDebug(LogIntents) << " * skipping, because 'handleOnlyWhenRunning' is set"; + isr->setRequestFailed(qSL("Skipping delivery due to handleOnlyWhenRunning")); + } else { + m_startingAppQueue.enqueue(isr); + isr->setState(IntentServerRequest::State::WaitingForApplicationStart); + if (m_startingAppTimeout > 0) { + QTimer::singleShot(m_startingAppTimeout, this, [this, isr]() { + if (m_startingAppQueue.removeOne(isr)) { + isr->setRequestFailed(qSL("Starting handler application timed out after %1 ms").arg(m_startingAppTimeout)); + enqueueRequest(isr); + } + }); + } + m_systemInterface->startApplication(isr->selectedIntent()->applicationId()); } - m_systemInterface->startApplication(isr->handlingApplicationId()); } else { - qCDebug(LogIntents) << "Intent handler" << isr->handlingApplicationId() << "is already running"; + qCDebug(LogIntents) << "Intent handler" << isr->selectedIntent()->applicationId() << "is already running"; isr->setState(IntentServerRequest::State::StartedApplication); } } if (isr->state() == IntentServerRequest::State::StartedApplication) { // step 3) send request out - auto clientIPC = m_systemInterface->findClientIpc(isr->handlingApplicationId()); + auto clientIPC = m_systemInterface->findClientIpc(isr->selectedIntent()->applicationId()); if (!clientIPC) { qCWarning(LogIntents) << "Could not find an IPC connection for application" - << isr->handlingApplicationId() << "to forward the intent request" + << isr->selectedIntent()->applicationId() << "to forward the intent request" << isr->requestId(); isr->setRequestFailed(qSL("No IPC channel to reach target application.")); } else { qCDebug(LogIntents) << "Sending intent request to handler application" - << isr->handlingApplicationId(); - m_sentToAppQueue.enqueue(isr); - isr->setState(IntentServerRequest::State::WaitingForReplyFromApplication); - if (m_sentToAppTimeout > 0) { - QTimer::singleShot(m_sentToAppTimeout, this, [this, isr]() { - if (m_sentToAppQueue.removeOne(isr)) { - isr->setRequestFailed(qSL("Waiting for reply from handler application timed out after %1 ms").arg(m_sentToAppTimeout)); - enqueueRequest(isr); - } - }); + << isr->selectedIntent()->applicationId(); + if (!isr->isBroadcast()) { + m_sentToAppQueue.enqueue(isr); + isr->setState(IntentServerRequest::State::WaitingForReplyFromApplication); + if (m_sentToAppTimeout > 0) { + QTimer::singleShot(m_sentToAppTimeout, this, [this, isr]() { + if (m_sentToAppQueue.removeOne(isr)) { + isr->setRequestFailed(qSL("Waiting for reply from handler application timed out after %1 ms").arg(m_sentToAppTimeout)); + enqueueRequest(isr); + } + }); + } + } else { + // there are no replies for broadcasts, so we simply skip this step + isr->setState(IntentServerRequest::State::ReceivedReplyFromApplication); } m_systemInterface->requestToApplication(clientIPC, isr); } } if (isr->state() == IntentServerRequest::State::ReceivedReplyFromApplication) { // step 5) send reply to requesting app - auto clientIPC = m_systemInterface->findClientIpc(isr->requestingApplicationId()); - if (!clientIPC) { - qCWarning(LogIntents) << "Could not find an IPC connection for application" - << isr->requestingApplicationId() << "to forward the Intent reply" - << isr->requestId(); - } else { - qCDebug(LogIntents) << "Forwarding intent reply" << isr->requestId() - << "to requesting application" << isr->requestingApplicationId(); - m_systemInterface->replyFromSystem(clientIPC, isr); + if (!isr->isBroadcast()) { + auto clientIPC = m_systemInterface->findClientIpc(isr->requestingApplicationId()); + if (!clientIPC) { + qCWarning(LogIntents) << "Could not find an IPC connection for application" + << isr->requestingApplicationId() << "to forward the Intent reply" + << isr->requestId(); + } else { + qCDebug(LogIntents) << "Forwarding intent reply" << isr->requestId() + << "to requesting application" << isr->requestingApplicationId(); + m_systemInterface->replyFromSystem(clientIPC, isr); + } } QMetaObject::invokeMethod(this, [isr]() { delete isr; }, Qt::QueuedConnection); // aka deleteLater for non-QObject isr = nullptr; @@ -595,6 +610,9 @@ void IntentServer::processRequestQueue() QList IntentServer::convertToQml(const QVector &intents) { + //TODO: we really should copy here, because the Intent pointers may die: a disambiguation might + // be active, while one of the apps involved is removed or updated + QList ol; for (auto intent : intents) ol << intent; @@ -675,7 +693,7 @@ void IntentServer::internalDisambiguateRequest(const QUuid &requestId, bool reje if (reject) { isr->setRequestFailed(qSL("Disambiguation was rejected")); } else if (isr->potentialIntents().contains(selectedIntent)) { - isr->setHandlingApplicationId(selectedIntent->applicationId()); + isr->setSelectedIntent(selectedIntent); isr->setState(IntentServerRequest::State::Disambiguated); } else { qCWarning(LogIntents) << "IntentServer::acknowledgeDisambiguationRequest for intent" @@ -694,7 +712,7 @@ void IntentServer::applicationWasStarted(const QString &applicationId) bool foundOne = false; for (auto it = m_startingAppQueue.cbegin(); it != m_startingAppQueue.cend(); ) { auto isr = *it; - if (isr->handlingApplicationId() == applicationId) { + if (isr->selectedIntent()->applicationId() == applicationId) { qCDebug(LogIntents) << "Intent request" << isr->intentId() << "can now be forwarded to application" << applicationId; @@ -726,10 +744,10 @@ void IntentServer::replyFromApplication(const QString &replyingApplicationId, co qCWarning(LogIntents) << "Got a reply for intent" << requestId << "from application" << replyingApplicationId << "but no reply was expected for this intent"; } else { - if (isr->handlingApplicationId() != replyingApplicationId) { + if (isr->selectedIntent() && (isr->selectedIntent()->applicationId() != replyingApplicationId)) { qCWarning(LogIntents) << "Got a reply for intent" << isr->requestId() << "from application" << replyingApplicationId << "but expected a reply from" - << isr->handlingApplicationId() << "instead"; + << isr->selectedIntent()->applicationId() << "instead"; isr->setRequestFailed(qSL("Request reply received from wrong application")); } else { QString errorMessage; @@ -762,7 +780,8 @@ IntentServerRequest *IntentServer::requestToSystem(const QString &requestingAppl } QVector intents; - if (applicationId.isEmpty()) { + bool broadcast = (applicationId == qSL(":broadcast:")); + if (applicationId.isEmpty() || broadcast) { intents = filterByIntentId(m_intents, intentId, parameters); } else { if (Intent *intent = this->applicationIntent(intentId, applicationId, parameters)) @@ -783,9 +802,17 @@ IntentServerRequest *IntentServer::requestToSystem(const QString &requestingAppl return nullptr; } - auto isr = new IntentServerRequest(requestingApplicationId, intentId, intents, parameters); - enqueueRequest(isr); - return isr; + if (broadcast) { + for (auto intent : qAsConst(intents)) { + auto isr = new IntentServerRequest(requestingApplicationId, intentId, { intent }, parameters, broadcast); + enqueueRequest(isr); + } + return nullptr; // this is not an error condition for broadcasts - there simply is no return value for the sender + } else { + auto isr = new IntentServerRequest(requestingApplicationId, intentId, intents, parameters, broadcast); + enqueueRequest(isr); + return isr; + } } QT_END_NAMESPACE_AM diff --git a/src/intent-server-lib/intentserver.h b/src/intent-server-lib/intentserver.h index cd0828ec..5419f175 100644 --- a/src/intent-server-lib/intentserver.h +++ b/src/intent-server-lib/intentserver.h @@ -47,7 +47,7 @@ public: const QStringList &capabilities, Intent::Visibility visibility, const QVariantMap ¶meterMatch, const QMap &names, const QMap &descriptions, const QUrl &icon, - const QStringList &categories); + const QStringList &categories, bool handleOnlyWhenRunning); void removeIntent(Intent *intent); diff --git a/src/intent-server-lib/intentserverrequest.cpp b/src/intent-server-lib/intentserverrequest.cpp index 7880007e..9641ccbb 100644 --- a/src/intent-server-lib/intentserverrequest.cpp +++ b/src/intent-server-lib/intentserverrequest.cpp @@ -9,18 +9,22 @@ QT_BEGIN_NAMESPACE_AM IntentServerRequest::IntentServerRequest(const QString &requestingApplicationId, const QString &intentId, const QVector &potentialIntents, - const QVariantMap ¶meters) + const QVariantMap ¶meters, bool broadcast) : m_id(QUuid::createUuid()) , m_state(State::ReceivedRequest) + , m_broadcast(broadcast) , m_intentId(intentId) , m_requestingApplicationId(requestingApplicationId) - , m_potentialIntents(potentialIntents) , m_parameters(parameters) { Q_ASSERT(!potentialIntents.isEmpty()); + Q_ASSERT(!broadcast || (potentialIntents.size() == 1)); + + for (auto *intent : potentialIntents) + m_potentialIntents << intent; if (potentialIntents.size() == 1) - setHandlingApplicationId(potentialIntents.first()->applicationId()); + setSelectedIntent(potentialIntents.constFirst()); } IntentServerRequest::State IntentServerRequest::state() const @@ -43,14 +47,19 @@ QString IntentServerRequest::requestingApplicationId() const return m_requestingApplicationId; } -QString IntentServerRequest::handlingApplicationId() const +Intent *IntentServerRequest::selectedIntent() const { - return m_handlingApplicationId; + return m_selectedIntent.get(); } QVector IntentServerRequest::potentialIntents() const { - return m_potentialIntents; + QVector out; + for (auto &intent : m_potentialIntents) { + if (intent) + out << intent.get(); + } + return out; } QVariantMap IntentServerRequest::parameters() const @@ -68,6 +77,11 @@ QVariantMap IntentServerRequest::result() const return m_result; } +bool IntentServerRequest::isBroadcast() const +{ + return m_broadcast; +} + void IntentServerRequest::setRequestFailed(const QString &errorMessage) { m_succeeded = false; @@ -88,9 +102,10 @@ void IntentServerRequest::setState(IntentServerRequest::State newState) m_state = newState; } -void IntentServerRequest::setHandlingApplicationId(const QString &applicationId) +void IntentServerRequest::setSelectedIntent(Intent *intent) { - m_handlingApplicationId = applicationId; + if (m_potentialIntents.contains(intent)) + m_selectedIntent = intent; } QT_END_NAMESPACE_AM diff --git a/src/intent-server-lib/intentserverrequest.h b/src/intent-server-lib/intentserverrequest.h index eb8004f9..2b2bd27b 100644 --- a/src/intent-server-lib/intentserverrequest.h +++ b/src/intent-server-lib/intentserverrequest.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -23,7 +24,8 @@ class IntentServerRequest public: IntentServerRequest(const QString &requestingApplicationId, const QString &intentId, - const QVector &potentialIntents, const QVariantMap ¶meters); + const QVector &potentialIntents, const QVariantMap ¶meters, + bool broadcast); enum class State { ReceivedRequest, @@ -41,14 +43,15 @@ public: QUuid requestId() const; QString intentId() const; QString requestingApplicationId() const; - QString handlingApplicationId() const; + Intent *selectedIntent() const; QVector potentialIntents() const; QVariantMap parameters() const; bool succeeded() const; QVariantMap result() const; + bool isBroadcast() const; void setState(State newState); - void setHandlingApplicationId(const QString &applicationId); + void setSelectedIntent(Intent *intent); void setRequestFailed(const QString &errorMessage); void setRequestSucceeded(const QVariantMap &result); @@ -57,10 +60,11 @@ private: QUuid m_id; State m_state; bool m_succeeded = false; + bool m_broadcast = false; QString m_intentId; QString m_requestingApplicationId; - QString m_handlingApplicationId; - QVector m_potentialIntents; + QPointer m_selectedIntent; + QVector> m_potentialIntents; QVariantMap m_parameters; QVariantMap m_result; }; diff --git a/src/intent-server-lib/intentserversysteminterface.cpp b/src/intent-server-lib/intentserversysteminterface.cpp index f17038d5..08945c12 100644 --- a/src/intent-server-lib/intentserversysteminterface.cpp +++ b/src/intent-server-lib/intentserversysteminterface.cpp @@ -14,8 +14,11 @@ void IntentServerSystemInterface::initialize(IntentServer *intentServer) connect(this, &IntentServerSystemInterface::replyFromApplication, m_is, &IntentServer::replyFromApplication); + + // we need to explicitly decouple here via QueuedConnection. Otherwise the request queue + // handling in IntentServer can get out of sync connect(this, &IntentServerSystemInterface::applicationWasStarted, - m_is, &IntentServer::applicationWasStarted); + m_is, &IntentServer::applicationWasStarted, Qt::QueuedConnection); } IntentServer *IntentServerSystemInterface::intentServer() const diff --git a/src/intent-server-lib/intentserversysteminterface.h b/src/intent-server-lib/intentserversysteminterface.h index d317cc96..8b2f299c 100644 --- a/src/intent-server-lib/intentserversysteminterface.h +++ b/src/intent-server-lib/intentserversysteminterface.h @@ -37,9 +37,9 @@ public: IntentServerRequest *requestToSystem(const QString &requestingApplicationId, const QString &intentId, const QString &applicationId, const QVariantMap ¶meters); - virtual void replyFromSystem(IpcConnection *clientIPC, IntentServerRequest *irs) = 0; + virtual void replyFromSystem(IpcConnection *clientIPC, IntentServerRequest *isr) = 0; - virtual void requestToApplication(IpcConnection *clientIPC, IntentServerRequest *irs) = 0; + virtual void requestToApplication(IpcConnection *clientIPC, IntentServerRequest *isr) = 0; signals: void applicationWasStarted(const QString &appId); diff --git a/src/launcher-lib/intentclientdbusimplementation.cpp b/src/launcher-lib/intentclientdbusimplementation.cpp index c4056a31..57451388 100644 --- a/src/launcher-lib/intentclientdbusimplementation.cpp +++ b/src/launcher-lib/intentclientdbusimplementation.cpp @@ -40,9 +40,12 @@ void IntentClientDBusImplementation::initialize(IntentClient *intentClient) Q_DE QQmlEngine::setObjectOwnership(IntentClient::instance(), QQmlEngine::CppOwnership); return IntentClient::instance(); }); + qmlRegisterRevision("QtApplicationManager", 2, 1); qmlRegisterUncreatableType("QtApplicationManager", 2, 0, "IntentRequest", qSL("Cannot create objects of type IntentRequest")); + qmlRegisterUncreatableType("QtApplicationManager", 2, 1, "IntentRequest", + qSL("Cannot create objects of type IntentRequest")); qmlRegisterType("QtApplicationManager.Application", 2, 0, "IntentHandler"); diff --git a/src/manager-lib/intentaminterface.cpp b/src/manager-lib/intentaminterface.cpp index 3aeb5b1d..28bc19ae 100644 --- a/src/manager-lib/intentaminterface.cpp +++ b/src/manager-lib/intentaminterface.cpp @@ -41,9 +41,6 @@ QT_BEGIN_NAMESPACE_AM -static QString sysUiId() { return qSL(":sysui:"); } - - ////////////////////////////////////////////////////////////////////////// // vvv IntentAMImplementation vvv @@ -105,7 +102,7 @@ IntentServer *IntentAMImplementation::createIntentServerAndClientInstance(Packag intentInfo->parameterMatch(), intentInfo->names(), intentInfo->descriptions(), QUrl::fromLocalFile(package->info()->baseDir().absoluteFilePath(intentInfo->icon())), - intentInfo->categories())) { + intentInfo->categories(), intentInfo->handleOnlyWhenRunning())) { throw Exception(Error::Intents, "could not add intent %1 for package %2") .arg(intentInfo->id()).arg(package->id()); } @@ -196,7 +193,7 @@ void IntentServerAMImplementation::initialize(IntentServer *server) bool IntentServerAMImplementation::checkApplicationCapabilities(const QString &applicationId, const QStringList &requiredCapabilities) { - if (applicationId == sysUiId()) // The System UI bypasses the capabilities check + if (applicationId == IntentClient::instance()->systemUiId()) // The System UI bypasses the capabilities check return true; const auto app = ApplicationManager::instance()->application(applicationId); @@ -223,15 +220,15 @@ void IntentServerAMImplementation::startApplication(const QString &appId) } void IntentServerAMImplementation::requestToApplication(IntentServerSystemInterface::IpcConnection *clientIPC, - IntentServerRequest *irs) + IntentServerRequest *isr) { - reinterpret_cast(clientIPC)->requestToApplication(irs); + reinterpret_cast(clientIPC)->requestToApplication(isr); } void IntentServerAMImplementation::replyFromSystem(IntentServerSystemInterface::IpcConnection *clientIPC, - IntentServerRequest *irs) + IntentServerRequest *isr) { - reinterpret_cast(clientIPC)->replyFromSystem(irs); + reinterpret_cast(clientIPC)->replyFromSystem(isr); } @@ -251,7 +248,7 @@ QString IntentClientAMImplementation::currentApplicationId(QObject *hint) { QmlInProcessRuntime *runtime = QmlInProcessRuntime::determineRuntime(hint); - return runtime ? runtime->application()->info()->id() : sysUiId(); + return runtime ? runtime->application()->info()->id() : IntentClient::instance()->systemUiId(); } void IntentClientAMImplementation::initialize(IntentClient *intentClient) Q_DECL_NOEXCEPT_EXPR(false) @@ -263,9 +260,12 @@ void IntentClientAMImplementation::initialize(IntentClient *intentClient) Q_DECL QQmlEngine::setObjectOwnership(IntentClient::instance(), QQmlEngine::CppOwnership); return IntentClient::instance(); }); + qmlRegisterRevision("QtApplicationManager", 2, 1); qmlRegisterUncreatableType("QtApplicationManager", 2, 0, "IntentRequest", qSL("Cannot create objects of type IntentRequest")); + qmlRegisterUncreatableType("QtApplicationManager", 2, 1, "IntentRequest", + qSL("Cannot create objects of type IntentRequest")); qmlRegisterType("QtApplicationManager.Application", 2, 0, "IntentHandler"); qmlRegisterType("QtApplicationManager.SystemUI", 2, 0, "IntentServerHandler"); @@ -334,7 +334,8 @@ void IntentServerIpcConnection::setReady(Application *application) return; m_application = application; m_ready = true; - emit applicationIsReady((isInProcess() && !application) ? sysUiId() : application->id()); + emit applicationIsReady((isInProcess() && !application) ? IntentClient::instance()->systemUiId() + : application->id()); } IntentServerIpcConnection *IntentServerIpcConnection::find(const QString &appId) @@ -395,28 +396,29 @@ IntentServerInProcessIpcConnection *IntentServerInProcessIpcConnection::createSy QString IntentServerInProcessIpcConnection::applicationId() const { - return m_isSystemUi ? sysUiId() : IntentServerIpcConnection::applicationId(); + return m_isSystemUi ? IntentClient::instance()->systemUiId() + : IntentServerIpcConnection::applicationId(); } -void IntentServerInProcessIpcConnection::requestToApplication(IntentServerRequest *irs) +void IntentServerInProcessIpcConnection::requestToApplication(IntentServerRequest *isr) { // we need decouple the server/client interface at this point to have a consistent // behavior in single- and multi-process mode - QMetaObject::invokeMethod(this, [this, irs]() { + QMetaObject::invokeMethod(this, [this, isr]() { auto clientInterface = m_interface->intentClientSystemInterface(); - emit clientInterface->requestToApplication(irs->requestId(), irs->intentId(), - irs->requestingApplicationId(), - irs->handlingApplicationId(), irs->parameters()); + emit clientInterface->requestToApplication(isr->requestId(), isr->intentId(), + isr->isBroadcast() ? qSL(":broadcast:") : isr->requestingApplicationId(), + isr->selectedIntent()->applicationId(), isr->parameters()); }, Qt::QueuedConnection); } -void IntentServerInProcessIpcConnection::replyFromSystem(IntentServerRequest *irs) +void IntentServerInProcessIpcConnection::replyFromSystem(IntentServerRequest *isr) { // we need decouple the server/client interface at this point to have a consistent // behavior in single- and multi-process mode - QMetaObject::invokeMethod(this, [this, irs]() { + QMetaObject::invokeMethod(this, [this, isr]() { auto clientInterface = m_interface->intentClientSystemInterface(); - emit clientInterface->replyFromSystem(irs->requestId(), !irs->succeeded(), irs->result()); + emit clientInterface->replyFromSystem(isr->requestId(), !isr->succeeded(), isr->result()); }, Qt::QueuedConnection); } @@ -465,17 +467,21 @@ IntentServerDBusIpcConnection *IntentServerDBusIpcConnection::find(QDBusConnecti return nullptr; } -void IntentServerDBusIpcConnection::requestToApplication(IntentServerRequest *irs) +void IntentServerDBusIpcConnection::requestToApplication(IntentServerRequest *isr) { - emit m_adaptor->requestToApplication(irs->requestId().toString(), irs->intentId(), - irs->handlingApplicationId(), - convertFromJSVariant(irs->parameters()).toMap()); + Q_ASSERT(isr && isr->selectedIntent()); + + emit m_adaptor->requestToApplication(isr->requestId().toString(), isr->intentId(), + isr->selectedIntent()->applicationId(), + convertFromJSVariant(isr->parameters()).toMap()); } -void IntentServerDBusIpcConnection::replyFromSystem(IntentServerRequest *irs) +void IntentServerDBusIpcConnection::replyFromSystem(IntentServerRequest *isr) { - emit m_adaptor->replyFromSystem(irs->requestId().toString(), !irs->succeeded(), - convertFromJSVariant(irs->result()).toMap()); + Q_ASSERT(isr); + + emit m_adaptor->replyFromSystem(isr->requestId().toString(), !isr->succeeded(), + convertFromJSVariant(isr->result()).toMap()); } QString IntentServerDBusIpcConnection::requestToSystem(const QString &intentId, @@ -483,13 +489,13 @@ QString IntentServerDBusIpcConnection::requestToSystem(const QString &intentId, const QVariantMap ¶meters) { auto requestingApplicationId = application() ? application()->id() : QString(); - auto irs = m_interface->requestToSystem(requestingApplicationId, intentId, applicationId, + auto isr = m_interface->requestToSystem(requestingApplicationId, intentId, applicationId, convertFromDBusVariant(parameters).toMap()); - if (!irs) { + if (!isr) { sendErrorReply(QDBusError::NotSupported, qL1S("No matching intent handler registered.")); return QString(); } else { - return irs->requestId().toString(); + return isr->requestId().toString(); } } @@ -733,9 +739,11 @@ void IntentServerHandler::componentComplete() return; } + QString sysUiId = IntentClient::instance()->systemUiId(); + IntentServer *is = IntentServer::instance(); - is->addPackage(sysUiId()); - is->addApplication(sysUiId(), sysUiId()); + is->addPackage(sysUiId); + is->addApplication(sysUiId, sysUiId); const auto ids = intentIds(); for (const auto &intentId : ids) { @@ -748,9 +756,10 @@ void IntentServerHandler::componentComplete() for (auto it = qvm_descriptions.cbegin(); it != qvm_descriptions.cend(); ++it) descriptions.insert(it.key(), it.value().toString()); - auto intent = is->addIntent(intentId, sysUiId(), sysUiId(), m_intent->requiredCapabilities(), + auto intent = is->addIntent(intentId, sysUiId, sysUiId, m_intent->requiredCapabilities(), m_intent->visibility(), m_intent->parameterMatch(), names, - descriptions, m_intent->icon(), m_intent->categories()); + descriptions, m_intent->icon(), m_intent->categories(), + m_intent->handleOnlyWhenRunning()); if (intent) m_registeredIntents << intent; else diff --git a/src/manager-lib/intentaminterface.h b/src/manager-lib/intentaminterface.h index 81d7fd3f..f72418b3 100644 --- a/src/manager-lib/intentaminterface.h +++ b/src/manager-lib/intentaminterface.h @@ -54,8 +54,8 @@ public: IpcConnection *findClientIpc(const QString &appId) override; void startApplication(const QString &appId) override; - void requestToApplication(IpcConnection *clientIPC, IntentServerRequest *irs) override; - void replyFromSystem(IpcConnection *clientIPC, IntentServerRequest *irs) override; + void requestToApplication(IpcConnection *clientIPC, IntentServerRequest *isr) override; + void replyFromSystem(IpcConnection *clientIPC, IntentServerRequest *isr) override; private: IntentClientSystemInterface *m_icsi = nullptr; @@ -94,8 +94,8 @@ public: bool isReady() const; void setReady(Application *application); - virtual void replyFromSystem(IntentServerRequest *irs) = 0; - virtual void requestToApplication(IntentServerRequest *irs) = 0; + virtual void replyFromSystem(IntentServerRequest *isr) = 0; + virtual void requestToApplication(IntentServerRequest *isr) = 0; signals: void applicationIsReady(const QString &applicationId); @@ -122,8 +122,8 @@ public: QString applicationId() const override; - void replyFromSystem(IntentServerRequest *irs) override; - void requestToApplication(IntentServerRequest *irs) override; + void replyFromSystem(IntentServerRequest *isr) override; + void requestToApplication(IntentServerRequest *isr) override; private: IntentServerInProcessIpcConnection(Application *application, IntentServerAMImplementation *iface); @@ -146,8 +146,8 @@ public: ~IntentServerDBusIpcConnection() override; QString requestToSystem(const QString &intentId, const QString &applicationId, const QVariantMap ¶meters); - void replyFromSystem(IntentServerRequest *irs) override; - void requestToApplication(IntentServerRequest *irs) override; + void replyFromSystem(IntentServerRequest *isr) override; + void requestToApplication(IntentServerRequest *isr) override; void replyFromApplication(const QString &requestId, bool error, const QVariantMap &result); -- cgit v1.2.1