File indexing completed on 2025-04-20 08:03:32
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 }