diff --git a/runners/services/autotests/fixtures/ding.desktop b/runners/services/autotests/fixtures/ding.desktop new file mode 100644 --- /dev/null +++ b/runners/services/autotests/fixtures/ding.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Ding: Dictionary English-German +GenericName=ding +Exec=/usr/bin/ding +Icon=ding +Terminal=false +Type=Application +Categories=Office;Dictionary; +Keywords=dictionary;Wörterbuch;translation;Übersetzung; diff --git a/runners/services/autotests/fixtures/flatpak_org.kde.konversation.desktop b/runners/services/autotests/fixtures/flatpak_org.kde.konversation.desktop new file mode 100755 --- /dev/null +++ b/runners/services/autotests/fixtures/flatpak_org.kde.konversation.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Type=Application +Exec=/usr/bin/flatpak run --branch=stable --arch=x86_64 --command=konversation --file-forwarding org.kde.konversation -qwindowtitle %c @@u %u @@ +Icon=org.kde.konversation +X-DocPath=konversation/index.html +MimeType=x-scheme-handler/irc;x-scheme-handler/ircs; +GenericName=IRC Client +Terminal=false +Name=Konversation Flatpak +Categories=Qt;KDE;Network;IRCClient; +X-KDE-ServiceTypes=DBUS/InstantMessenger +X-DBUS-StartupType=Unique +X-DBUS-ServiceName=org.kde.konversation +StartupNotify=true +X-Flatpak=org.kde.konversation diff --git a/runners/services/autotests/servicerunnertest.cpp b/runners/services/autotests/servicerunnertest.cpp --- a/runners/services/autotests/servicerunnertest.cpp +++ b/runners/services/autotests/servicerunnertest.cpp @@ -41,6 +41,7 @@ void testChromeAppsRelevance(); void testKonsoleVsYakuakeComment(); void testSystemSettings(); + void testFlatpakConflict(); }; void ServiceRunnerTest::initTestCase() @@ -159,6 +160,35 @@ QVERIFY(!foreignSystemSettingsFound); } +void ServiceRunnerTest::testFlatpakConflict() +{ + // flatpaks have additional noise in their Exec lines that can mess with + // finding results. + // When looking for 'ding' we'll have the actual ding application as well + // as konversation flatpak matching. Konversation can match because its + // Exec contains an argument --forwarDING. + ServiceRunner runner(this, QVariantList()); + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("ding")); + + runner.match(context); + + bool dingFound = false; + bool konversationFound = false; + for (auto match : context.matches()) { + qDebug() << "matched" << match.text(); + if (match.text() == QLatin1String("Ding: Dictionary English-German")) { + dingFound = true; + } + if (match.text() == QLatin1String("Konversation Flatpak")) { + konversationFound = true; + } + } + QVERIFY(dingFound); + QVERIFY(!konversationFound); +} + + QTEST_MAIN(ServiceRunnerTest) #include "servicerunnertest.moc" diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp --- a/runners/services/servicerunner.cpp +++ b/runners/services/servicerunner.cpp @@ -39,6 +39,7 @@ #include #include +#include #include "debug.h" @@ -160,8 +161,8 @@ commentTemplate += QStringLiteral(" and '%1' ~~ Comment").arg(str.toString()); } - QString finalQuery = QStringLiteral("exist Exec and ( (%1) or (%2) or (%3) or ('%4' ~~ Exec) or (%5) )") - .arg(keywordTemplate, genericNameTemplate, nameTemplate, strList[0].toString(), commentTemplate); + QString finalQuery = QStringLiteral("exist Exec and ( (%1) or (%2) or (%3) or (%4) )") + .arg(keywordTemplate, genericNameTemplate, nameTemplate, commentTemplate); qCDebug(RUNNER_SERVICES) << "Final query : " << finalQuery; return finalQuery; @@ -217,21 +218,54 @@ } } + KService::List execServicesFor(const QStringRef &primaryQuery, const KService::List &services) + { + // Potential Exec matches. + // Exec lines can contain a lot of garbage so we'll want to apply additional constraints. + // Notably any service that exclusively matches on its Exec content MUST have the match + // on the actual exectuableName not any env var or other arguments. + // All services that are also matched through the general purpose query do not count into this + // as they are matches in their own right. + const QString execQuery = QStringLiteral("exist Exec and ('%1' ~~ Exec)").arg(primaryQuery); + KService::List execServices = KServiceTypeTrader::self()->query(QStringLiteral("Application"), execQuery); + execServices += KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), execQuery); + + QHash execMap; + for (const KService::Ptr &service : qAsConst(execServices)) { + execMap.insert(service->storageId(), service); + } + + for (const KService::Ptr &service : qAsConst(services)) { + execMap.remove(service->storageId()); + } + for (auto it = execMap.begin(); it != execMap.end(); ++it) { + if (!KIO::DesktopExecParser::executableName(it.value()->exec()).contains(primaryQuery)) { + it = execMap.erase(it); + } + if (it == execMap.end()) { + break; + } + } + + return execMap.values(); + } + void matchNameKeywordAndGenericName() { //Splitting the query term to match using subsequences QVector queryList = term.splitRef(QLatin1Char(' ')); // If the term length is < 3, no real point searching the Keywords and GenericName if (weightedTermLength < 3) { - query = QStringLiteral("exist Exec and ( (exist Name and '%1' ~~ Name) or ('%1' ~~ Exec) )").arg(term); + query = QStringLiteral("exist Exec and (exist Name and '%1' ~~ Name)").arg(term); } else { //Match using subsequences (Bug: 262837) query = generateQuery(queryList); } KService::List services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), query); services += KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), query); + services += execServicesFor(queryList[0], services); qCDebug(RUNNER_SERVICES) << "got " << services.count() << " services from " << query; for (const KService::Ptr &service : qAsConst(services)) { @@ -241,7 +275,7 @@ const QString id = service->storageId(); const QString name = service->desktopEntryName(); - const QString exec = service->exec(); + const QString exec = KIO::DesktopExecParser::executableName(service->exec()); Plasma::QueryMatch match(m_runner); match.setType(Plasma::QueryMatch::PossibleMatch);