diff --git a/libtaskmanager/tasktools.cpp b/libtaskmanager/tasktools.cpp --- a/libtaskmanager/tasktools.cpp +++ b/libtaskmanager/tasktools.cpp @@ -42,6 +42,7 @@ #include #include +#include #include #include #include @@ -215,6 +216,40 @@ KService::List services; bool triedPid = false; + // The code below goes on a hunt for services based on the metadata that has been + // passed in. Occasionally, it will find more than one matching service. In some + // scenarios (e.g. multiple identically-named .desktop files) there's a need to + // pick the most useful one. The function below promises to "sort" a list of + // services by how closely their KService::menuId() relates to the key that has + // been passed in. The current naive implementation simply looks for a menuId that + // starts with the key, prepends it to the list and returns it. In practice, that + // means a KService with a menuId matching the appId will win over one with a menuId + // that encodes a subfolder hierarchy. + // A concrete example: Valve's Steam client is sometimes installed two times, once + // natively as a Linux application, once via Wine. Both have .desktop files named + // (S|)steam.desktop. The Linux native version is located in the menu by means of + // categorization ("Games") and just has a menuId() matching the .desktop file name, + // but the Wine version is placed in a folder hierarchy by Wine and gets a menuId() + // of wine-Programs-Steam-Steam.desktop. The weighing done by this function makes + // sure the Linux native version gets mapped to the former, while other heuristics + // map the Wine version reliably to the latter. + // In lieu of this weighing we just used whatever KServiceTypeTrader returned first, + // so what we do here can be no worse. + auto sortServicesByMenuId = [](KService::List &services, const QString &key) { + if (services.count() == 1) { + return services; + } + + for (const auto service : services) { + if (service->menuId().startsWith(key, Qt::CaseInsensitive)) { + services.prepend(service); + return services; + } + } + + return services; + }; + if (!(appId.isEmpty() && xWindowsWMClassName.isEmpty())) { // Check to see if this wmClass matched a saved one ... KConfigGroup grp(rulesConfig, "Mapping"); @@ -274,10 +309,12 @@ // Source: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-0.1.txt if (services.empty()) { services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(appId)); + services = sortServicesByMenuId(services, appId); } if (services.empty()) { services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(xWindowsWMClassName)); + services = sortServicesByMenuId(services, xWindowsWMClassName); } // Evaluate rewrite rules from config. @@ -324,6 +361,7 @@ } services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ %2)").arg(rewrittenString, serviceSearchIdentifier)); + services = sortServicesByMenuId(services, serviceSearchIdentifier); if (!services.isEmpty()) { break; @@ -334,7 +372,7 @@ } // The appId looks like a path. - if (appId.startsWith(QStringLiteral("/"))) { + if (services.empty() && appId.startsWith(QStringLiteral("/"))) { // Check if it's a path to a .desktop file. if (KDesktopFile::isDesktopFile(appId) && QFile::exists(appId)) { return QUrl::fromLocalFile(appId); @@ -351,22 +389,26 @@ // Try matching mapped name against DesktopEntryName. if (!mapped.isEmpty() && services.empty()) { services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName) and (not exist NoDisplay or not NoDisplay)").arg(mapped)); + services = sortServicesByMenuId(services, mapped); } // Try matching mapped name against 'Name'. if (!mapped.isEmpty() && services.empty()) { services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(mapped)); + services = sortServicesByMenuId(services, mapped); } // Try matching appId against DesktopEntryName. if (services.empty()) { services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName) and (not exist NoDisplay or not NoDisplay)").arg(appId)); + services = sortServicesByMenuId(services, appId); } // Try matching appId against 'Name'. // This has a shaky chance of success as appId is untranslated, but 'Name' may be localized. if (services.empty()) { services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(appId)); + services = sortServicesByMenuId(services, appId); } }