File indexing completed on 2025-02-16 14:20:31
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 }