File indexing completed on 2024-04-21 05:31:13

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