File indexing completed on 2024-03-24 17:07:29

0001 /********************************************************************
0002 Copyright 2016  Eike Hein <hein.org>
0003 This library is free software; you can redistribute it and/or
0004 modify it under the terms of the GNU Lesser General Public
0005 License as published by the Free Software Foundation; either
0006 version 2.1 of the License, or (at your option) version 3, or any
0007 later version accepted by the membership of KDE e.V. (or its
0008 successor approved by the membership of KDE e.V.), which shall
0009 act as a proxy defined in Section 6 of version 3 of the license.
0010 This library is distributed in the hope that it will be useful,
0011 but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013 Lesser General Public License for more details.
0014 You should have received a copy of the GNU Lesser General Public
0015 License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0016 *********************************************************************/
0017 
0018 #include "tasktools.h"
0019 #include <config-latte.h>
0020 
0021 #include <KActivities/ResourceInstance>
0022 #include <KConfigGroup>
0023 #include <KDesktopFile>
0024 #include <kemailsettings.h>
0025 #include <KMimeTypeTrader>
0026 #include <KServiceTypeTrader>
0027 #include <KSharedConfig>
0028 #include <KStartupInfo>
0029 #include <KWindowSystem>
0030 
0031 #if KF5_VERSION_MINOR >= 62
0032     #include <KProcessList>
0033 #else
0034     #include <processcore/processes.h>
0035     #include <processcore/process.h>
0036 #endif
0037 
0038 #include <QDir>
0039 #include <QGuiApplication>
0040 #include <QRegularExpression>
0041 #include <QScreen>
0042 #include <QUrlQuery>
0043 #if HAVE_X11
0044 #include <QX11Info>
0045 #endif
0046 
0047 namespace Latte
0048 {
0049 namespace WindowSystem
0050 {
0051 
0052 AppData appDataFromUrl(const QUrl &url, const QIcon &fallbackIcon)
0053 {
0054     AppData data;
0055     data.url = url;
0056 
0057     if (url.hasQuery()) {
0058         QUrlQuery uQuery(url);
0059 
0060         if (uQuery.hasQueryItem(QLatin1String("iconData"))) {
0061             QString iconData(uQuery.queryItemValue(QLatin1String("iconData")));
0062             QPixmap pixmap;
0063             QByteArray bytes = QByteArray::fromBase64(iconData.toLocal8Bit(), QByteArray::Base64UrlEncoding);
0064             pixmap.loadFromData(bytes);
0065             data.icon.addPixmap(pixmap);
0066         }
0067 
0068         if (uQuery.hasQueryItem(QLatin1String("skipTaskbar"))) {
0069             QString skipTaskbar(uQuery.queryItemValue(QLatin1String("skipTaskbar")));
0070             data.skipTaskbar = (skipTaskbar == QStringLiteral("true"));
0071         }
0072     }
0073 
0074     // applications: URLs are used to refer to applications by their KService::menuId
0075     // (i.e. .desktop file name) rather than the absolute path to a .desktop file.
0076     if (url.scheme() == QStringLiteral("applications")) {
0077         const KService::Ptr service = KService::serviceByMenuId(url.path());
0078 
0079         if (service && url.path() == service->menuId()) {
0080             data.name = service->name();
0081             data.genericName = service->genericName();
0082             data.id = service->storageId();
0083 
0084             if (data.icon.isNull()) {
0085                 data.icon = QIcon::fromTheme(service->icon());
0086             }
0087         }
0088     }
0089 
0090     if (url.isLocalFile() && KDesktopFile::isDesktopFile(url.toLocalFile())) {
0091         const KService::Ptr service = KService::serviceByStorageId(url.fileName());
0092 
0093         // Resolve to non-absolute menuId-based URL if possible.
0094         if (service) {
0095             const QString &menuId = service->menuId();
0096 
0097             if (!menuId.isEmpty()) {
0098                 data.url = QUrl(QStringLiteral("applications:") + menuId);
0099             }
0100         }
0101 
0102         if (service && QUrl::fromLocalFile(service->entryPath()) == url) {
0103             data.name = service->name();
0104             data.genericName = service->genericName();
0105             data.id = service->storageId();
0106 
0107             if (data.icon.isNull()) {
0108                 data.icon = QIcon::fromTheme(service->icon());
0109             }
0110         } else {
0111             KDesktopFile f(url.toLocalFile());
0112             if (f.tryExec()) {
0113                 data.name = f.readName();
0114                 data.genericName = f.readGenericName();
0115                 data.id = QUrl::fromLocalFile(f.fileName()).fileName();
0116 
0117                 if (data.icon.isNull()) {
0118                     data.icon = QIcon::fromTheme(f.readIcon());
0119                 }
0120             }
0121         }
0122 
0123         if (data.id.endsWith(".desktop")) {
0124             data.id = data.id.left(data.id.length() - 8);
0125         }
0126     } else if (url.scheme() == QLatin1String("preferred")) {
0127         data.id = defaultApplication(url);
0128 
0129         const KService::Ptr service = KService::serviceByStorageId(data.id);
0130 
0131         if (service) {
0132             const QString &menuId = service->menuId();
0133             const QString &desktopFile = service->entryPath();
0134 
0135             data.name = service->name();
0136             data.genericName = service->genericName();
0137             data.id = service->storageId();
0138 
0139             if (data.icon.isNull()) {
0140                 data.icon = QIcon::fromTheme(service->icon());
0141             }
0142 
0143             // Update with resolved URL.
0144             if (!menuId.isEmpty()) {
0145                 data.url = QUrl(QStringLiteral("applications:") + menuId);
0146             } else {
0147                 data.url = QUrl::fromLocalFile(desktopFile);
0148             }
0149         }
0150     }
0151 
0152     if (data.name.isEmpty()) {
0153         data.name = url.fileName();
0154     }
0155 
0156     if (data.icon.isNull()) {
0157         data.icon = fallbackIcon;
0158     }
0159 
0160     return data;
0161 }
0162 
0163 AppData appDataFromAppId(const QString &appId)
0164 {
0165     AppData data;
0166 
0167     KService::Ptr service = KService::serviceByStorageId(appId);
0168 
0169     if (service) {
0170         data.id = service->storageId();
0171         data.name = service->name();
0172         data.genericName = service->genericName();
0173 
0174         const QString &menuId = service->menuId();
0175 
0176         // applications: URLs are used to refer to applications by their KService::menuId
0177         // (i.e. .desktop file name) rather than the absolute path to a .desktop file.
0178         if (!menuId.isEmpty()) {
0179             data.url = QUrl(QStringLiteral("applications:") + menuId);
0180         } else {
0181             data.url = QUrl::fromLocalFile(service->entryPath());
0182         }
0183 
0184         return data;
0185     }
0186 
0187     QString desktopFile = appId;
0188 
0189     if (!desktopFile.endsWith(QLatin1String(".desktop"))) {
0190         desktopFile.append(QLatin1String(".desktop"));
0191     }
0192 
0193     if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) {
0194         KDesktopFile f(desktopFile);
0195 
0196         data.id = QUrl::fromLocalFile(f.fileName()).fileName();
0197 
0198         if (data.id.endsWith(QLatin1String(".desktop"))) {
0199             data.id = data.id.left(data.id.length() - 8);
0200         }
0201 
0202         data.name = f.readName();
0203         data.genericName = f.readGenericName();
0204         data.url = QUrl::fromLocalFile(desktopFile);
0205     }
0206 
0207     return data;
0208 }
0209 
0210 QUrl windowUrlFromMetadata(const QString &appId, quint32 pid,
0211     KSharedConfig::Ptr rulesConfig, const QString &xWindowsWMClassName)
0212 {
0213     if (!rulesConfig) {
0214         return QUrl();
0215     }
0216 
0217     QUrl url;
0218     KService::List services;
0219     bool triedPid = false;
0220 
0221     // The code below this function goes on a hunt for services based on the metadata
0222     // that has been passed in. Occasionally, it will find more than one matching
0223     // service. In some scenarios (e.g. multiple identically-named .desktop files)
0224     // there's a need to pick the most useful one. The function below promises to "sort"
0225     // a list of services by how closely their KService::menuId() relates to the key that
0226     // has been passed in. The current naive implementation simply looks for a menuId
0227     // that starts with the key, prepends it to the list and returns it. In practice,
0228     // that means a KService with a menuId matching the appId will win over one with a
0229     // menuId that encodes a subfolder hierarchy.
0230     // A concrete example: Valve's Steam client is sometimes installed two times, once
0231     // natively as a Linux application, once via Wine. Both have .desktop files named
0232     // (S|)steam.desktop. The Linux native version is located in the menu by means of
0233     // categorization ("Games") and just has a menuId() matching the .desktop file name,
0234     // but the Wine version is placed in a folder hierarchy by Wine and gets a menuId()
0235     // of wine-Programs-Steam-Steam.desktop. The weighing done by this function makes
0236     // sure the Linux native version gets mapped to the former, while other heuristics
0237     // map the Wine version reliably to the latter.
0238     // In lieu of this weighing we just used whatever KServiceTypeTrader returned first,
0239     // so what we do here can be no worse.
0240     auto sortServicesByMenuId = [](KService::List &services, const QString &key) {
0241         if (services.count() == 1) {
0242             return;
0243         }
0244 
0245         for (const auto service : services) {
0246             if (service->menuId().startsWith(key, Qt::CaseInsensitive)) {
0247                 services.prepend(service);
0248                 return;
0249             }
0250         }
0251     };
0252 
0253     if (!(appId.isEmpty() && xWindowsWMClassName.isEmpty())) {
0254         // Check to see if this wmClass matched a saved one ...
0255         KConfigGroup grp(rulesConfig, "Mapping");
0256         KConfigGroup set(rulesConfig, "Settings");
0257 
0258         // Evaluate MatchCommandLineFirst directives from config first.
0259         // Some apps have different launchers depending upon command line ...
0260         QStringList matchCommandLineFirst = set.readEntry("MatchCommandLineFirst", QStringList());
0261 
0262         if (!appId.isEmpty() && matchCommandLineFirst.contains(appId)) {
0263             triedPid = true;
0264             services = servicesFromPid(pid, rulesConfig);
0265         }
0266 
0267         // Try to match using xWindowsWMClassName also.
0268         if (!xWindowsWMClassName.isEmpty() && matchCommandLineFirst.contains("::"+xWindowsWMClassName)) {
0269             triedPid = true;
0270             services = servicesFromPid(pid, rulesConfig);
0271         }
0272 
0273         if (!appId.isEmpty()) {
0274             // Evaluate any mapping rules that map to a specific .desktop file.
0275             QString mapped(grp.readEntry(appId + "::" + xWindowsWMClassName, QString()));
0276 
0277             if (mapped.endsWith(QLatin1String(".desktop"))) {
0278                 url = QUrl(mapped);
0279                 return url;
0280             }
0281 
0282             if (mapped.isEmpty()) {
0283                 mapped = grp.readEntry(appId, QString());
0284 
0285                 if (mapped.endsWith(QLatin1String(".desktop"))) {
0286                     url = QUrl(mapped);
0287                     return url;
0288                 }
0289             }
0290 
0291             // Some apps, such as Wine, cannot use xWindowsWMClassName to map to launcher name - as Wine itself is not a GUI app
0292             // So, Settings/ManualOnly lists window classes where the user will always have to manualy set the launcher ...
0293             QStringList manualOnly = set.readEntry("ManualOnly", QStringList());
0294 
0295             if (!appId.isEmpty() && manualOnly.contains(appId)) {
0296                 return url;
0297             }
0298 
0299             // Try matching both appId and xWindowsWMClassName against StartupWMClass.
0300             // We do this before evaluating the mapping rules further, because StartupWMClass
0301             // is essentially a mapping rule, and we expect it to be set deliberately and
0302             // sensibly to instruct us what to do. Also, mapping rules
0303             //
0304             // StartupWMClass=STRING
0305             //
0306             //   If true, it is KNOWN that the application will map at least one
0307             //   window with the given string as its WM class or WM name hint.
0308             //
0309             // Source: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-0.1.txt
0310             if (services.isEmpty()) {
0311                 services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(appId));
0312                 sortServicesByMenuId(services, appId);
0313             }
0314 
0315             if (services.isEmpty() && !xWindowsWMClassName.isEmpty()) {
0316                 services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(xWindowsWMClassName));
0317                 sortServicesByMenuId(services, xWindowsWMClassName);
0318             }
0319 
0320             // Evaluate rewrite rules from config.
0321             if (services.isEmpty()) {
0322                 KConfigGroup rewriteRulesGroup(rulesConfig, QStringLiteral("Rewrite Rules"));
0323                 if (rewriteRulesGroup.hasGroup(appId)) {
0324                     KConfigGroup rewriteGroup(&rewriteRulesGroup, appId);
0325 
0326                     const QStringList &rules = rewriteGroup.groupList();
0327                     for (const QString &rule : rules) {
0328                         KConfigGroup ruleGroup(&rewriteGroup, rule);
0329 
0330                         const QString propertyConfig = ruleGroup.readEntry(QStringLiteral("Property"), QString());
0331 
0332                         QString matchProperty;
0333                         if (propertyConfig == QLatin1String("ClassClass")) {
0334                             matchProperty = appId;
0335                         } else if (propertyConfig == QLatin1String("ClassName")) {
0336                             matchProperty = xWindowsWMClassName;
0337                         }
0338 
0339                         if (matchProperty.isEmpty()) {
0340                             continue;
0341                         }
0342 
0343                         const QString serviceSearchIdentifier = ruleGroup.readEntry(QStringLiteral("Identifier"), QString());
0344                         if (serviceSearchIdentifier.isEmpty()) {
0345                             continue;
0346                         }
0347 
0348                         QRegularExpression regExp(ruleGroup.readEntry(QStringLiteral("Match")));
0349                         const auto match = regExp.match(matchProperty);
0350 
0351                         if (match.hasMatch()) {
0352                             const QString actualMatch = match.captured(QStringLiteral("match"));
0353                             if (actualMatch.isEmpty()) {
0354                                 continue;
0355                             }
0356 
0357                             QString rewrittenString = ruleGroup.readEntry(QStringLiteral("Target")).arg(actualMatch);
0358                             // If no "Target" is provided, instead assume the matched property (appId/xWindowsWMClassName).
0359                             if (rewrittenString.isEmpty()) {
0360                                 rewrittenString = matchProperty;
0361                             }
0362 
0363                             services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ %2)").arg(rewrittenString, serviceSearchIdentifier));
0364                             sortServicesByMenuId(services, serviceSearchIdentifier);
0365 
0366                             if (!services.isEmpty()) {
0367                                 break;
0368                             }
0369                         }
0370                     }
0371                 }
0372             }
0373 
0374             // The appId looks like a path.
0375             if (services.isEmpty() && appId.startsWith(QStringLiteral("/"))) {
0376                 // Check if it's a path to a .desktop file.
0377                 if (KDesktopFile::isDesktopFile(appId) && QFile::exists(appId)) {
0378                     return QUrl::fromLocalFile(appId);
0379                 }
0380 
0381                 // Check if the appId passes as a .desktop file path if we add the extension.
0382                 const QString appIdPlusExtension(appId + QStringLiteral(".desktop"));
0383 
0384                 if (KDesktopFile::isDesktopFile(appIdPlusExtension) && QFile::exists(appIdPlusExtension)) {
0385                     return QUrl::fromLocalFile(appIdPlusExtension);
0386                 }
0387             }
0388 
0389             // Try matching mapped name against DesktopEntryName.
0390             if (!mapped.isEmpty() && services.isEmpty()) {
0391                 services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName) and (not exist NoDisplay or not NoDisplay)").arg(mapped));
0392                 sortServicesByMenuId(services, mapped);
0393             }
0394 
0395             // Try matching mapped name against 'Name'.
0396             if (!mapped.isEmpty() && services.isEmpty()) {
0397                 services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(mapped));
0398                 sortServicesByMenuId(services, mapped);
0399             }
0400 
0401             // Try matching appId against DesktopEntryName.
0402             if (services.isEmpty()) {
0403                 services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName) and (not exist NoDisplay or not NoDisplay)").arg(appId));
0404                 sortServicesByMenuId(services, appId);
0405             }
0406 
0407             // Try matching appId against 'Name'.
0408             // This has a shaky chance of success as appId is untranslated, but 'Name' may be localized.
0409             if (services.isEmpty()) {
0410                 services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(appId));
0411                 sortServicesByMenuId(services, appId);
0412             }
0413 
0414             // Check rules configuration for whether we want to hide this task.
0415             // Some window tasks update from bogus to useful metadata early during startup.
0416             // This config key allows listing the bogus metadata, and the matching window
0417             // tasks are hidden until they perform a metadate update that stops them from
0418             // matching.
0419             QStringList skipTaskbar = set.readEntry("SkipTaskbar", QStringList());
0420 
0421             if (skipTaskbar.contains(appId)) {
0422                 QUrlQuery query(url);
0423                 query.addQueryItem(QStringLiteral("skipTaskbar"), QStringLiteral("true"));
0424                 url.setQuery(query);
0425             } else if (skipTaskbar.contains(mapped)) {
0426                 QUrlQuery query(url);
0427                 query.addQueryItem(QStringLiteral("skipTaskbar"), QStringLiteral("true"));
0428                 url.setQuery(query);
0429             }
0430         }
0431 
0432         // Ok, absolute *last* chance, try matching via pid (but only if we have not already tried this!) ...
0433         if (services.isEmpty() && !triedPid) {
0434             services = servicesFromPid(pid, rulesConfig);
0435         }
0436     }
0437 
0438     // Try to improve on a possible from-binary fallback.
0439     // If no services were found or we got a fake-service back from getServicesViaPid()
0440     // we attempt to improve on this by adding a loosely matched reverse-domain-name
0441     // DesktopEntryName. Namely anything that is '*.appId.desktop' would qualify here.
0442     //
0443     // Illustrative example of a case where the above heuristics would fail to produce
0444     // a reasonable result:
0445     // - org.kde.dragonplayer.desktop
0446     // - binary is 'dragon'
0447     // - qapp appname and thus appId is 'dragonplayer'
0448     // - appId cannot directly match the desktop file because of RDN
0449     // - appId also cannot match the binary because of name mismatch
0450     // - in the following code *.appId can match org.kde.dragonplayer though
0451     if (services.isEmpty() || services.at(0)->desktopEntryName().isEmpty()) {
0452         auto matchingServices = KServiceTypeTrader::self()->query(QStringLiteral("Application"),
0453             QStringLiteral("exist Exec and ('%1' ~~ DesktopEntryName)").arg(appId));
0454         QMutableListIterator<KService::Ptr> it(matchingServices);
0455         while (it.hasNext()) {
0456             auto service = it.next();
0457             if (!service->desktopEntryName().endsWith("." + appId)) {
0458                 it.remove();
0459             }
0460         }
0461         // Exactly one match is expected, otherwise we discard the results as to reduce
0462         // the likelihood of false-positive mappings. Since we essentially eliminate the
0463         // uniqueness that RDN is meant to bring to the table we could potentially end
0464         // up with more than one match here.
0465         if (matchingServices.length() == 1) {
0466             services = matchingServices;
0467         }
0468     }
0469 
0470     if (!services.isEmpty()) {
0471         const QString &menuId = services.at(0)->menuId();
0472 
0473         // applications: URLs are used to refer to applications by their KService::menuId
0474         // (i.e. .desktop file name) rather than the absolute path to a .desktop file.
0475         if (!menuId.isEmpty()) {
0476             url.setUrl(QStringLiteral("applications:") + menuId);
0477             return url;
0478         }
0479 
0480         QString path = services.at(0)->entryPath();
0481 
0482         if (path.isEmpty()) {
0483             path = services.at(0)->exec();
0484         }
0485 
0486         if (!path.isEmpty()) {
0487             QString query = url.query();
0488             url = QUrl::fromLocalFile(path);
0489             url.setQuery(query);
0490             return url;
0491         }
0492     }
0493 
0494     return url;
0495 }
0496 
0497 KService::List servicesFromPid(quint32 pid, KSharedConfig::Ptr rulesConfig)
0498 {
0499     if (pid == 0) {
0500         return KService::List();
0501     }
0502 
0503     if (!rulesConfig) {
0504         return KService::List();
0505     }
0506 
0507 #if KF5_VERSION_MINOR >= 62
0508     auto proc = KProcessList::processInfo(pid);
0509     if (!proc.isValid()) {
0510         return KService::List();
0511     }
0512 
0513     const QString cmdLine = proc.command();
0514 #else
0515     KSysGuard::Processes procs;
0516     procs.updateOrAddProcess(pid);
0517 
0518     KSysGuard::Process *proc = procs.getProcess(pid);
0519     const QString &cmdLine = proc ? proc->command().simplified() : QString(); // proc->command has a trailing space???
0520 #endif
0521 
0522     if (cmdLine.isEmpty()) {
0523         return KService::List();
0524     }
0525 
0526 #if KF5_VERSION_MINOR >= 62
0527     return servicesFromCmdLine(cmdLine, proc.name(), rulesConfig);
0528 #else
0529     return servicesFromCmdLine(cmdLine, proc->name(), rulesConfig);
0530 #endif
0531 }
0532 
0533 KService::List servicesFromCmdLine(const QString &_cmdLine, const QString &processName,
0534     KSharedConfig::Ptr rulesConfig)
0535 {
0536     QString cmdLine = _cmdLine;
0537     KService::List services;
0538 
0539     if (!rulesConfig) {
0540         return services;
0541     }
0542 
0543     const int firstSpace = cmdLine.indexOf(' ');
0544     int slash = 0;
0545 
0546     services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine));
0547 
0548     if (services.isEmpty()) {
0549         // Could not find with complete command line, so strip out the path part ...
0550         slash = cmdLine.lastIndexOf('/', firstSpace);
0551 
0552         if (slash > 0) {
0553             services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine.mid(slash + 1)));
0554         }
0555     }
0556 
0557     if (services.isEmpty() && firstSpace > 0) {
0558         // Could not find with arguments, so try without ...
0559         cmdLine = cmdLine.left(firstSpace);
0560 
0561         services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine));
0562 
0563         if (services.isEmpty()) {
0564             slash = cmdLine.lastIndexOf('/');
0565 
0566             if (slash > 0) {
0567                 services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine.mid(slash + 1)));
0568             }
0569         }
0570     }
0571 
0572     if (services.isEmpty()) {
0573         KConfigGroup set(rulesConfig, "Settings");
0574         const QStringList &runtimes = set.readEntry("TryIgnoreRuntimes", QStringList());
0575 
0576         bool ignore = runtimes.contains(cmdLine);
0577 
0578         if (!ignore && slash > 0) {
0579             ignore = runtimes.contains(cmdLine.mid(slash + 1));
0580         }
0581 
0582         if (ignore) {
0583             return servicesFromCmdLine(_cmdLine.mid(firstSpace + 1), processName, rulesConfig);
0584         }
0585     }
0586 
0587     if (services.isEmpty() && !processName.isEmpty() && !QStandardPaths::findExecutable(cmdLine).isEmpty()) {
0588         // cmdLine now exists without arguments if there were any.
0589         services << QExplicitlySharedDataPointer<KService>(new KService(processName, cmdLine, QString()));
0590     }
0591 
0592     return services;
0593 }
0594 
0595 QString defaultApplication(const QUrl &url)
0596 {
0597     if (url.scheme() != QLatin1String("preferred")) {
0598         return QString();
0599     }
0600 
0601     const QString &application = url.host();
0602 
0603     if (application.isEmpty()) {
0604         return QString();
0605     }
0606 
0607     if (application.compare(QLatin1String("mailer"), Qt::CaseInsensitive) == 0) {
0608         KEMailSettings settings;
0609 
0610         // In KToolInvocation, the default is kmail; but let's be friendlier.
0611         QString command = settings.getSetting(KEMailSettings::ClientProgram);
0612 
0613         if (command.isEmpty()) {
0614             if (KService::Ptr kontact = KService::serviceByStorageId(QStringLiteral("kontact"))) {
0615                 return kontact->storageId();
0616             } else if (KService::Ptr kmail = KService::serviceByStorageId(QStringLiteral("kmail"))) {
0617                 return kmail->storageId();
0618             }
0619         }
0620 
0621         if (!command.isEmpty()) {
0622             if (settings.getSetting(KEMailSettings::ClientTerminal) == QLatin1String("true")) {
0623                 KConfigGroup confGroup(KSharedConfig::openConfig(), "General");
0624                 const QString preferredTerminal = confGroup.readPathEntry("TerminalApplication",
0625                                                   QStringLiteral("konsole"));
0626                 command = preferredTerminal + QLatin1String(" -e ") + command;
0627             }
0628 
0629             return command;
0630         }
0631     } else if (application.compare(QLatin1String("browser"), Qt::CaseInsensitive) == 0) {
0632         KConfigGroup config(KSharedConfig::openConfig(), "General");
0633         QString browserApp = config.readPathEntry("BrowserApplication", QString());
0634 
0635         if (browserApp.isEmpty()) {
0636             const KService::Ptr htmlApp = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/html"));
0637 
0638             if (htmlApp) {
0639                 browserApp = htmlApp->storageId();
0640             }
0641         } else if (browserApp.startsWith('!')) {
0642             browserApp = browserApp.mid(1);
0643         }
0644 
0645         return browserApp;
0646     } else if (application.compare(QLatin1String("terminal"), Qt::CaseInsensitive) == 0) {
0647         KConfigGroup confGroup(KSharedConfig::openConfig(), "General");
0648 
0649         return confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole"));
0650     } else if (application.compare(QLatin1String("filemanager"), Qt::CaseInsensitive) == 0) {
0651         KService::Ptr service = KMimeTypeTrader::self()->preferredService(QStringLiteral("inode/directory"));
0652 
0653         if (service) {
0654             return service->storageId();
0655         }
0656     } else if (KService::Ptr service = KMimeTypeTrader::self()->preferredService(application)) {
0657         return service->storageId();
0658     } else {
0659         // Try the files in share/apps/kcm_componentchooser/*.desktop.
0660         QStringList directories = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kcm_componentchooser"), QStandardPaths::LocateDirectory);
0661         QStringList services;
0662 
0663         foreach(const QString& directory, directories) {
0664             QDir dir(directory);
0665             foreach(const QString& f, dir.entryList(QStringList("*.desktop")))
0666                 services += dir.absoluteFilePath(f);
0667         }
0668 
0669         foreach (const QString & service, services) {
0670             KConfig config(service, KConfig::SimpleConfig);
0671             KConfigGroup cg = config.group(QByteArray());
0672             const QString type = cg.readEntry("valueName", QString());
0673 
0674             if (type.compare(application, Qt::CaseInsensitive) == 0) {
0675                 KConfig store(cg.readPathEntry("storeInFile", QStringLiteral("null")));
0676                 KConfigGroup storeCg(&store, cg.readEntry("valueSection", QString()));
0677                 const QString exec = storeCg.readPathEntry(cg.readEntry("valueName", "kcm_componenchooser_null"),
0678                                      cg.readEntry("defaultImplementation", QString()));
0679 
0680                 if (!exec.isEmpty()) {
0681                     return exec;
0682                 }
0683 
0684                 break;
0685             }
0686         }
0687     }
0688 
0689     return QString("");
0690 }
0691 
0692 
0693 
0694 }
0695 }