diff --git a/autotests/dbusrunnertest.cpp b/autotests/dbusrunnertest.cpp index 3535688..fb261f8 100644 --- a/autotests/dbusrunnertest.cpp +++ b/autotests/dbusrunnertest.cpp @@ -1,128 +1,176 @@ /* * Copyright (C) 2017 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2 as * published by the Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "runnermanager.h" #include #include #include #include using namespace Plasma; Q_DECLARE_METATYPE(Plasma::QueryMatch); Q_DECLARE_METATYPE(QList); class DBusRunnerTest : public QObject { Q_OBJECT public: DBusRunnerTest(); ~DBusRunnerTest(); private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testMatch(); + void testMulti(); private: - QProcess *m_process; QStringList m_filesForCleanup; }; DBusRunnerTest::DBusRunnerTest() - : QObject(), - m_process(new QProcess(this)) + : QObject() { - m_process->start(QFINDTESTDATA("testremoterunner")); - QVERIFY(m_process->waitForStarted()); qRegisterMetaType >(); } DBusRunnerTest::~DBusRunnerTest() { - m_process->kill(); - m_process->waitForFinished(); } void DBusRunnerTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); QDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)).mkpath(QStringLiteral("kservices5")); - const QString fakeServicePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kservices5/dbusrunnertest.desktop"); - QFile::copy(QFINDTESTDATA("dbusrunnertest.desktop"), fakeServicePath); - m_filesForCleanup << fakeServicePath; + { + const QString fakeServicePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kservices5/dbusrunnertest.desktop"); + QFile::copy(QFINDTESTDATA("dbusrunnertest.desktop"), fakeServicePath); + m_filesForCleanup << fakeServicePath; + + } + { + const QString fakeServicePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kservices5/dbusrunnertestmulti.desktop"); + QFile::copy(QFINDTESTDATA("dbusrunnertestmulti.desktop"), fakeServicePath); + m_filesForCleanup << fakeServicePath; + } KSycoca::self()->ensureCacheValid(); } void DBusRunnerTest::cleanupTestCase() { for(const QString path: m_filesForCleanup) { QFile::remove(path); } } void DBusRunnerTest::testMatch() { + QProcess process; + process.start(QFINDTESTDATA("testremoterunner"), QStringList({"net.krunnertests.dave"})); + QVERIFY(process.waitForStarted()); + + QTest::qSleep(500); + RunnerManager m; auto s = KService::serviceByDesktopPath(QStringLiteral("dbusrunnertest.desktop")); QVERIFY(s); m.loadRunner(s); m.launchQuery(QStringLiteral("foo")); QSignalSpy spy(&m, &RunnerManager::matchesChanged); QVERIFY(spy.wait()); //verify matches QCOMPARE(m.matches().count(), 1); auto result = m.matches().first(); //see testremoterunner.cpp QCOMPARE(result.id(), QStringLiteral("dbusrunnertest_id1")); //note the runner name is prepended QCOMPARE(result.text(), QStringLiteral("Match 1")); QCOMPARE(result.iconName(), QStringLiteral("icon1")); QCOMPARE(result.type(), Plasma::QueryMatch::ExactMatch); //relevance can't be compared easily becuase RunnerContext meddles with it //verify actions auto actions = m.actionsForMatch(result); QCOMPARE(actions.count(), 1); auto action = actions.first(); QCOMPARE(action->text(), QStringLiteral("Action 1")); - QSignalSpy processSpy(m_process, &QProcess::readyRead); + QSignalSpy processSpy(&process, &QProcess::readyRead); m.run(result); processSpy.wait(); - QCOMPARE(m_process->readAllStandardOutput().trimmed(), QByteArray("Running:id1:")); + QCOMPARE(process.readAllStandardOutput().trimmed(), QByteArray("Running:id1:")); result.setSelectedAction(action); m.run(result); processSpy.wait(); - QCOMPARE(m_process->readAllStandardOutput().trimmed(), QByteArray("Running:id1:action1")); + QCOMPARE(process.readAllStandardOutput().trimmed(), QByteArray("Running:id1:action1")); + + process.kill(); + process.waitForFinished(); +} + +void DBusRunnerTest::testMulti() +{ + QProcess process1; + process1.start(QFINDTESTDATA("testremoterunner"), QStringList({"net.krunnertests.multi.a1"})); + QVERIFY(process1.waitForStarted()); + + QProcess process2; + process2.start(QFINDTESTDATA("testremoterunner"), QStringList({"net.krunnertests.multi.a2"})); + QVERIFY(process2.waitForStarted()); + + QTest::qSleep(500); + + RunnerManager m; + auto s = KService::serviceByDesktopPath(QStringLiteral("dbusrunnertestmulti.desktop")); + QVERIFY(s); + m.loadRunner(s); + m.launchQuery(QStringLiteral("foo")); + + QSignalSpy spy(&m, &RunnerManager::matchesChanged); + QVERIFY(spy.wait()); + + //verify matches, must be one from each + QCOMPARE(m.matches().count(), 2); + + QString first = m.matches().at(0).data().toString(); + QString second = m.matches().at(1).data().toString(); + QVERIFY(first != second); + QVERIFY(first == "net.krunnertests.multi.a1" || first == "net.krunnertests.multi.a2"); + QVERIFY(second == "net.krunnertests.multi.a1" || second == "net.krunnertests.multi.a2"); + + process1.kill(); + process2.kill(); + process1.waitForFinished(); + process2.waitForFinished(); } QTEST_MAIN(DBusRunnerTest) #include "dbusrunnertest.moc" diff --git a/autotests/dbusrunnertest.desktop b/autotests/dbusrunnertest.desktop index 78c5b21..f0c8671 100644 --- a/autotests/dbusrunnertest.desktop +++ b/autotests/dbusrunnertest.desktop @@ -1,15 +1,15 @@ [Desktop Entry] Name=DBus runner test Comment=DBus runner test X-KDE-ServiceTypes=Plasma/Runner Type=Service Icon=internet-web-browser X-KDE-PluginInfo-Author=Some Developer X-KDE-PluginInfo-Email=kde@example.com X-KDE-PluginInfo-Name=dbusrunnertest X-KDE-PluginInfo-Version=1.0 X-KDE-PluginInfo-License=LGPL X-KDE-PluginInfo-EnabledByDefault=true X-Plasma-API=DBus -X-Plasma-DBusRunner-Service=net.dave +X-Plasma-DBusRunner-Service=net.krunnertests.dave X-Plasma-DBusRunner-Path=/dave diff --git a/autotests/dbusrunnertest.desktop b/autotests/dbusrunnertestmulti.desktop similarity index 88% copy from autotests/dbusrunnertest.desktop copy to autotests/dbusrunnertestmulti.desktop index 78c5b21..4374943 100644 --- a/autotests/dbusrunnertest.desktop +++ b/autotests/dbusrunnertestmulti.desktop @@ -1,15 +1,15 @@ [Desktop Entry] Name=DBus runner test Comment=DBus runner test X-KDE-ServiceTypes=Plasma/Runner Type=Service Icon=internet-web-browser X-KDE-PluginInfo-Author=Some Developer X-KDE-PluginInfo-Email=kde@example.com X-KDE-PluginInfo-Name=dbusrunnertest X-KDE-PluginInfo-Version=1.0 X-KDE-PluginInfo-License=LGPL X-KDE-PluginInfo-EnabledByDefault=true X-Plasma-API=DBus -X-Plasma-DBusRunner-Service=net.dave +X-Plasma-DBusRunner-Service=net.krunnertests.multi.* X-Plasma-DBusRunner-Path=/dave diff --git a/autotests/testremoterunner.cpp b/autotests/testremoterunner.cpp index 8a86e00..8324f9b 100644 --- a/autotests/testremoterunner.cpp +++ b/autotests/testremoterunner.cpp @@ -1,78 +1,79 @@ /* * Copyright (C) 2017 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2 as * published by the Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "testremoterunner.h" #include "krunner1adaptor.h" //Test DBus runner, if the search term contains "foo" it returns a match, otherwise nothing //Run prints a line to stdout -TestRemoteRunner::TestRemoteRunner() +TestRemoteRunner::TestRemoteRunner(const QString &serviceName) { new Krunner1Adaptor(this); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); - QDBusConnection::sessionBus().registerService(QStringLiteral("net.dave")); + QDBusConnection::sessionBus().registerService(serviceName); QDBusConnection::sessionBus().registerObject(QStringLiteral("/dave"), this); } RemoteMatches TestRemoteRunner::Match(const QString& searchTerm) { RemoteMatches ms; if (searchTerm.contains(QLatin1String("foo"))) { RemoteMatch m; m.id = QStringLiteral("id1"); m.text = QStringLiteral("Match 1"); m.iconName = QStringLiteral("icon1"); m.type = Plasma::QueryMatch::ExactMatch; m.relevance = 0.8; ms << m; } return ms; } RemoteActions TestRemoteRunner::Actions() { RemoteAction action; action.id = QStringLiteral("action1"); action.text = QStringLiteral("Action 1"); action.iconName = QStringLiteral("document-browser"); return RemoteActions({action}); } void TestRemoteRunner::Run(const QString &id, const QString &actionId) { std::cout << "Running:" << qPrintable(id) << ":" << qPrintable(actionId) << std::endl; std::cout.flush(); } int main(int argc, char ** argv) { QCoreApplication app(argc, argv); - TestRemoteRunner r; + Q_ASSERT(app.arguments().count() == 2); + TestRemoteRunner r(app.arguments()[1]); app.exec(); } diff --git a/autotests/testremoterunner.h b/autotests/testremoterunner.h index 6ad7c79..bf25ad3 100644 --- a/autotests/testremoterunner.h +++ b/autotests/testremoterunner.h @@ -1,16 +1,16 @@ #pragma once #include #include "../src/dbusutils_p.h" class TestRemoteRunner : public QObject { Q_OBJECT public: - TestRemoteRunner(); + TestRemoteRunner(const QString &serviceName); public Q_SLOTS: RemoteActions Actions(); RemoteMatches Match(const QString &searchTerm); void Run(const QString &id, const QString &actionId); }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f7377a..2bbf544 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,109 +1,108 @@ add_subdirectory(declarative) set (KF5Runner_SRCS abstractrunner.cpp dbusrunner.cpp runnerjobs.cpp querymatch.cpp runnercontext.cpp runnermanager.cpp runnersyntax.cpp) ecm_qt_declare_logging_category(KF5Runner_SRCS HEADER krunner_debug.h IDENTIFIER KRUNNER CATEGORY_NAME org.kde.krunner) set_property(SOURCE "data/org.kde.krunner1.xml" PROPERTY INCLUDE dbusutils_p.h) -qt5_add_dbus_interface(KF5Runner_SRCS "data/org.kde.krunner1.xml" krunner_iface) add_library(KF5Runner ${KF5Runner_SRCS}) generate_export_header(KF5Runner BASE_NAME KRunner) add_library(KF5::Runner ALIAS KF5Runner) target_include_directories(KF5Runner INTERFACE "$") target_include_directories(KF5Runner PUBLIC "$") target_link_libraries(KF5Runner PUBLIC Qt5::Core KF5::Plasma # Must be public because abstractrunner.h needs plasma/version.h PRIVATE Qt5::DBus Qt5::Gui Qt5::Widgets KF5::ConfigCore KF5::Service KF5::I18n KF5::ThreadWeaver KF5::CoreAddons #KShell KF5::KIOCore #KProtocolInfo ) set_target_properties(KF5Runner PROPERTIES VERSION ${KRUNNER_VERSION_STRING} SOVERSION 5 EXPORT_NAME "Runner" ) ecm_generate_headers(KRunner_CamelCase_HEADERS HEADER_NAMES AbstractRunner RunnerContext RunnerManager RunnerSyntax QueryMatch PREFIX KRunner REQUIRED_HEADERS KRunner_HEADERS ) # Install files install(TARGETS KF5Runner EXPORT KF5RunnerTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${KRunner_CamelCase_HEADERS} DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KRunner/KRunner COMPONENT Devel) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/krunner_export.h ${KRunner_HEADERS} DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KRunner/krunner COMPONENT Devel) install(FILES data/servicetypes/plasma-runner.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) if(BUILD_QCH) ecm_add_qch( KF5Runner_QCH NAME KRunner BASE_NAME KF5Runner VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KRunner_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS Qt5Core_QCH BLANK_MACROS KRUNNER_EXPORT KRUNNER_DEPRECATED KRUNNER_DEPRECATED_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KRunner LIB_NAME KF5Runner DEPS "core" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KF5_INCLUDE_INSTALL_DIR}/KRunner) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) install(FILES "data/org.kde.krunner1.xml" DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.krunner1.xml) diff --git a/src/dbusrunner.cpp b/src/dbusrunner.cpp index b8607d0..f89b05e 100644 --- a/src/dbusrunner.cpp +++ b/src/dbusrunner.cpp @@ -1,128 +1,193 @@ /* - * Copyright (C) 2017 David Edmundson + * Copyright (C) 2017,2018 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2 as * published by the Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dbusrunner_p.h" #include #include #include #include #include #include +#include #include #include #include +#include #include #include "krunner_debug.h" -#include "krunner_iface.h" +#include "dbusutils_p.h" + +#define IFACE_NAME "org.kde.krunner1" DBusRunner::DBusRunner(const KService::Ptr service, QObject *parent) : Plasma::AbstractRunner(service, parent) + , m_mutex(QMutex::NonRecursive) { qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); - QString serviceName = service->property(QStringLiteral("X-Plasma-DBusRunner-Service")).toString(); - QString path = service->property(QStringLiteral("X-Plasma-DBusRunner-Path")).toString(); + QString requestedServiceName = service->property(QStringLiteral("X-Plasma-DBusRunner-Service")).toString(); + m_path = service->property(QStringLiteral("X-Plasma-DBusRunner-Path")).toString(); - if (serviceName.isEmpty() || path.isEmpty()) { + if (requestedServiceName.isEmpty() || m_path.isEmpty()) { qCWarning(KRUNNER) << "Invalid entry:" << service->name(); return; } - m_interface = new OrgKdeKrunner1Interface(serviceName, path, QDBusConnection::sessionBus(), this); + if (requestedServiceName.endsWith(QLatin1Char('*'))) { + requestedServiceName.chop(1); + //find existing matching names + auto names = QDBusConnection::sessionBus().interface()->registeredServiceNames(); + if (names.isValid()) { + for(const QString serviceName : names.value()) { + if (serviceName.startsWith(requestedServiceName)) { + m_matchingServices << serviceName; + } + } + } + //and watch for changes + connect(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::serviceOwnerChanged, + this, [this, requestedServiceName](const QString &serviceName, const QString &oldOwner, const QString &newOwner) { + if (!serviceName.startsWith(requestedServiceName)) { + return; + } + if (!oldOwner.isEmpty() && !newOwner.isEmpty()) { + //changed owner, but service still exists. Don't need to adjust anything + return; + } + QMutexLocker lock(&m_mutex); + if (!newOwner.isEmpty()) { + m_matchingServices.insert(serviceName); + } + if (!oldOwner.isEmpty()) { + m_matchingServices.remove(serviceName); + } + }); + } else { + //don't check when not wildcarded, as it could be used with DBus-activation + m_matchingServices << requestedServiceName; + } connect(this, &AbstractRunner::prepare, this, &DBusRunner::requestActions); } DBusRunner::~DBusRunner() = default; void DBusRunner::requestActions() { - auto reply = m_interface->Actions(); - auto watcher = new QDBusPendingCallWatcher(reply); - connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher]() { - watcher->deleteLater(); - clearActions(); - QDBusReply reply = *watcher; - if (!reply.isValid()) { - return; - } - for(const RemoteAction &action: reply.value()) { - auto a = addAction(action.id, QIcon::fromTheme(action.iconName), action.text); - a->setData(action.id); - } - }); + clearActions(); + m_actions.clear(); + + //in the multi-services case, register separate actions from each plugin in case they happen to be somehow different + //then match together in matchForAction() + + for (const QString &service: m_matchingServices) { + auto getActionsMethod = QDBusMessage::createMethodCall(service, m_path, QStringLiteral(IFACE_NAME), QStringLiteral("Actions")); + QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(getActionsMethod); + + auto watcher = new QDBusPendingCallWatcher(reply); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, service]() { + watcher->deleteLater(); + QDBusReply reply = *watcher; + if (!reply.isValid()) { + return; + } + for(const RemoteAction &action: reply.value()) { + auto a = addAction(action.id, QIcon::fromTheme(action.iconName), action.text); + a->setData(action.id); + m_actions[service].append(a); + } + }); + } } void DBusRunner::match(Plasma::RunnerContext &context) { - if (!m_interface) { - return; + QSet services; + { + QMutexLocker lock(&m_mutex); + services = m_matchingServices; } - - auto reply = m_interface->Match(context.query()); - reply.waitForFinished(); //AbstractRunner::match is called in a new thread, may as well block - if (reply.isError()) { - qCDebug(KRUNNER) << "Error calling" << m_interface->service() << " :" << reply.error().name() << reply.error().message(); - return; + //we scope watchers to make sure the lambda that captures context by reference definitely gets disconnected when this function ends + QList> watchers; + + for (const QString service : services) { + auto matchMethod = QDBusMessage::createMethodCall(service, m_path, QStringLiteral(IFACE_NAME), QStringLiteral("Match")); + matchMethod.setArguments(QList({context.query()})); + QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(matchMethod); + + auto watcher = new QDBusPendingCallWatcher(reply); + watchers << QSharedPointer(watcher); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, service, &context, reply]() { + if (reply.isError()) { + qCDebug(KRUNNER) << "Error calling" << service << " :" << reply.error().name() << reply.error().message(); + return; + } + for(const RemoteMatch &match: reply.value()) { + Plasma::QueryMatch m(this); + + m.setText(match.text); + m.setId(match.id); + m.setData(service); + m.setIconName(match.iconName); + m.setType(match.type); + m.setRelevance(match.relevance); + + //split is essential items are as native DBus types, optional extras are in the property map (which is obviously a lot slower to parse) + m.setUrls(QUrl::fromStringList(match.properties.value(QStringLiteral("urls")).toStringList())); + m.setMatchCategory(match.properties.value(QStringLiteral("category")).toString()); + m.setSubtext(match.properties.value(QStringLiteral("subtext")).toString()); + + context.addMatch(m); + }; + }); } - for(const RemoteMatch &match: reply.value()) { - Plasma::QueryMatch m(this); - - m.setText(match.text); - m.setData(match.id); - m.setIconName(match.iconName); - m.setType(match.type); - m.setRelevance(match.relevance); - - //split is essential items are as native DBus types, optional extras are in the property map (which is obviously a lot slower to parse) - m.setUrls(QUrl::fromStringList(match.properties.value(QStringLiteral("urls")).toStringList())); - m.setMatchCategory(match.properties.value(QStringLiteral("category")).toString()); - m.setSubtext(match.properties.value(QStringLiteral("subtext")).toString()); - - context.addMatch(m); + //we're done matching when every service replies + for (auto w : watchers) { + w->waitForFinished(); } } QList DBusRunner::actionsForMatch(const Plasma::QueryMatch &match) { Q_UNUSED(match) - return actions().values(); + const QString service = match.data().toString(); + return m_actions.value(service); } void DBusRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) { Q_UNUSED(context); - if (!m_interface) { - return; - } QString actionId; - QString matchId = match.data().toString(); + QString matchId = match.id().mid(id().length() + 1); //QueryMatch::setId mangles the match ID with runnerID + '_'. This unmangles it + QString service = match.data().toString(); if (match.selectedAction()) { actionId = match.selectedAction()->data().toString(); } - m_interface->Run(matchId, actionId); //don't wait for reply before returning to process + auto runMethod = QDBusMessage::createMethodCall(service, m_path, QStringLiteral(IFACE_NAME), QStringLiteral("Run")); + runMethod.setArguments(QList({matchId, actionId})); + QDBusConnection::sessionBus().call(runMethod, QDBus::NoBlock); } diff --git a/src/dbusrunner_p.h b/src/dbusrunner_p.h index 0f9b805..56e7062 100644 --- a/src/dbusrunner_p.h +++ b/src/dbusrunner_p.h @@ -1,42 +1,49 @@ /* * Copyright (C) 2017 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2 as * published by the Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once #include #include "dbusutils_p.h" class OrgKdeKrunner1Interface; +#include +#include +#include +#include class DBusRunner : public Plasma::AbstractRunner { Q_OBJECT public: explicit DBusRunner(const KService::Ptr service, QObject *parent = nullptr); ~DBusRunner() override; void match(Plasma::RunnerContext &context) override; void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &action) override; QList actionsForMatch(const Plasma::QueryMatch &match) override; private: void requestActions(); void setActions(const RemoteActions &remoteActions); - OrgKdeKrunner1Interface *m_interface = nullptr; + QMutex m_mutex; //needed round any variable also accessed from Match + QString m_path; + QSet m_matchingServices; + QHash > m_actions; };