mirror of
https://github.com/KDE/latte-dock.git
synced 2024-12-24 17:33:50 +03:00
36f7834cff
BUG:413735
696 lines
27 KiB
C++
696 lines
27 KiB
C++
/********************************************************************
|
|
Copyright 2016 Eike Hein <hein.org>
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) version 3, or any
|
|
later version accepted by the membership of KDE e.V. (or its
|
|
successor approved by the membership of KDE e.V.), which shall
|
|
act as a proxy defined in Section 6 of version 3 of the license.
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*********************************************************************/
|
|
|
|
#include "tasktools.h"
|
|
#include <config-latte.h>
|
|
|
|
#include <KActivities/ResourceInstance>
|
|
#include <KConfigGroup>
|
|
#include <KDesktopFile>
|
|
#include <kemailsettings.h>
|
|
#include <KMimeTypeTrader>
|
|
#include <KServiceTypeTrader>
|
|
#include <KSharedConfig>
|
|
#include <KStartupInfo>
|
|
#include <KWindowSystem>
|
|
|
|
#if KF5_VERSION_MINOR >= 62
|
|
#include <KProcessList>
|
|
#else
|
|
#include <processcore/processes.h>
|
|
#include <processcore/process.h>
|
|
#endif
|
|
|
|
#include <QDir>
|
|
#include <QGuiApplication>
|
|
#include <QRegularExpression>
|
|
#include <QScreen>
|
|
#include <QUrlQuery>
|
|
#if HAVE_X11
|
|
#include <QX11Info>
|
|
#endif
|
|
|
|
namespace Latte
|
|
{
|
|
namespace WindowSystem
|
|
{
|
|
|
|
AppData appDataFromUrl(const QUrl &url, const QIcon &fallbackIcon)
|
|
{
|
|
AppData data;
|
|
data.url = url;
|
|
|
|
if (url.hasQuery()) {
|
|
QUrlQuery uQuery(url);
|
|
|
|
if (uQuery.hasQueryItem(QLatin1String("iconData"))) {
|
|
QString iconData(uQuery.queryItemValue(QLatin1String("iconData")));
|
|
QPixmap pixmap;
|
|
QByteArray bytes = QByteArray::fromBase64(iconData.toLocal8Bit(), QByteArray::Base64UrlEncoding);
|
|
pixmap.loadFromData(bytes);
|
|
data.icon.addPixmap(pixmap);
|
|
}
|
|
|
|
if (uQuery.hasQueryItem(QLatin1String("skipTaskbar"))) {
|
|
QString skipTaskbar(uQuery.queryItemValue(QLatin1String("skipTaskbar")));
|
|
data.skipTaskbar = (skipTaskbar == QStringLiteral("true"));
|
|
}
|
|
}
|
|
|
|
// applications: URLs are used to refer to applications by their KService::menuId
|
|
// (i.e. .desktop file name) rather than the absolute path to a .desktop file.
|
|
if (url.scheme() == QStringLiteral("applications")) {
|
|
const KService::Ptr service = KService::serviceByMenuId(url.path());
|
|
|
|
if (service && url.path() == service->menuId()) {
|
|
data.name = service->name();
|
|
data.genericName = service->genericName();
|
|
data.id = service->storageId();
|
|
|
|
if (data.icon.isNull()) {
|
|
data.icon = QIcon::fromTheme(service->icon());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (url.isLocalFile() && KDesktopFile::isDesktopFile(url.toLocalFile())) {
|
|
const KService::Ptr service = KService::serviceByStorageId(url.fileName());
|
|
|
|
// Resolve to non-absolute menuId-based URL if possible.
|
|
if (service) {
|
|
const QString &menuId = service->menuId();
|
|
|
|
if (!menuId.isEmpty()) {
|
|
data.url = QUrl(QStringLiteral("applications:") + menuId);
|
|
}
|
|
}
|
|
|
|
if (service && QUrl::fromLocalFile(service->entryPath()) == url) {
|
|
data.name = service->name();
|
|
data.genericName = service->genericName();
|
|
data.id = service->storageId();
|
|
|
|
if (data.icon.isNull()) {
|
|
data.icon = QIcon::fromTheme(service->icon());
|
|
}
|
|
} else {
|
|
KDesktopFile f(url.toLocalFile());
|
|
if (f.tryExec()) {
|
|
data.name = f.readName();
|
|
data.genericName = f.readGenericName();
|
|
data.id = QUrl::fromLocalFile(f.fileName()).fileName();
|
|
|
|
if (data.icon.isNull()) {
|
|
data.icon = QIcon::fromTheme(f.readIcon());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data.id.endsWith(".desktop")) {
|
|
data.id = data.id.left(data.id.length() - 8);
|
|
}
|
|
} else if (url.scheme() == QLatin1String("preferred")) {
|
|
data.id = defaultApplication(url);
|
|
|
|
const KService::Ptr service = KService::serviceByStorageId(data.id);
|
|
|
|
if (service) {
|
|
const QString &menuId = service->menuId();
|
|
const QString &desktopFile = service->entryPath();
|
|
|
|
data.name = service->name();
|
|
data.genericName = service->genericName();
|
|
data.id = service->storageId();
|
|
|
|
if (data.icon.isNull()) {
|
|
data.icon = QIcon::fromTheme(service->icon());
|
|
}
|
|
|
|
// Update with resolved URL.
|
|
if (!menuId.isEmpty()) {
|
|
data.url = QUrl(QStringLiteral("applications:") + menuId);
|
|
} else {
|
|
data.url = QUrl::fromLocalFile(desktopFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data.name.isEmpty()) {
|
|
data.name = url.fileName();
|
|
}
|
|
|
|
if (data.icon.isNull()) {
|
|
data.icon = fallbackIcon;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
AppData appDataFromAppId(const QString &appId)
|
|
{
|
|
AppData data;
|
|
|
|
KService::Ptr service = KService::serviceByStorageId(appId);
|
|
|
|
if (service) {
|
|
data.id = service->storageId();
|
|
data.name = service->name();
|
|
data.genericName = service->genericName();
|
|
|
|
const QString &menuId = service->menuId();
|
|
|
|
// applications: URLs are used to refer to applications by their KService::menuId
|
|
// (i.e. .desktop file name) rather than the absolute path to a .desktop file.
|
|
if (!menuId.isEmpty()) {
|
|
data.url = QUrl(QStringLiteral("applications:") + menuId);
|
|
} else {
|
|
data.url = QUrl::fromLocalFile(service->entryPath());
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
QString desktopFile = appId;
|
|
|
|
if (!desktopFile.endsWith(QLatin1String(".desktop"))) {
|
|
desktopFile.append(QLatin1String(".desktop"));
|
|
}
|
|
|
|
if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) {
|
|
KDesktopFile f(desktopFile);
|
|
|
|
data.id = QUrl::fromLocalFile(f.fileName()).fileName();
|
|
|
|
if (data.id.endsWith(QLatin1String(".desktop"))) {
|
|
data.id = data.id.left(data.id.length() - 8);
|
|
}
|
|
|
|
data.name = f.readName();
|
|
data.genericName = f.readGenericName();
|
|
data.url = QUrl::fromLocalFile(desktopFile);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
QUrl windowUrlFromMetadata(const QString &appId, quint32 pid,
|
|
KSharedConfig::Ptr rulesConfig, const QString &xWindowsWMClassName)
|
|
{
|
|
if (!rulesConfig) {
|
|
return QUrl();
|
|
}
|
|
|
|
QUrl url;
|
|
KService::List services;
|
|
bool triedPid = false;
|
|
|
|
// The code below this function goes on a hunt for services based on the metadata
|
|
// that has been passed in. Occasionally, it will find more than one matching
|
|
// service. In some scenarios (e.g. multiple identically-named .desktop files)
|
|
// there's a need to pick the most useful one. The function below promises to "sort"
|
|
// a list of services by how closely their KService::menuId() relates to the key that
|
|
// has been passed in. The current naive implementation simply looks for a menuId
|
|
// that starts with the key, prepends it to the list and returns it. In practice,
|
|
// that means a KService with a menuId matching the appId will win over one with a
|
|
// menuId that encodes a subfolder hierarchy.
|
|
// A concrete example: Valve's Steam client is sometimes installed two times, once
|
|
// natively as a Linux application, once via Wine. Both have .desktop files named
|
|
// (S|)steam.desktop. The Linux native version is located in the menu by means of
|
|
// categorization ("Games") and just has a menuId() matching the .desktop file name,
|
|
// but the Wine version is placed in a folder hierarchy by Wine and gets a menuId()
|
|
// of wine-Programs-Steam-Steam.desktop. The weighing done by this function makes
|
|
// sure the Linux native version gets mapped to the former, while other heuristics
|
|
// map the Wine version reliably to the latter.
|
|
// In lieu of this weighing we just used whatever KServiceTypeTrader returned first,
|
|
// so what we do here can be no worse.
|
|
auto sortServicesByMenuId = [](KService::List &services, const QString &key) {
|
|
if (services.count() == 1) {
|
|
return;
|
|
}
|
|
|
|
for (const auto service : services) {
|
|
if (service->menuId().startsWith(key, Qt::CaseInsensitive)) {
|
|
services.prepend(service);
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
if (!(appId.isEmpty() && xWindowsWMClassName.isEmpty())) {
|
|
// Check to see if this wmClass matched a saved one ...
|
|
KConfigGroup grp(rulesConfig, "Mapping");
|
|
KConfigGroup set(rulesConfig, "Settings");
|
|
|
|
// Evaluate MatchCommandLineFirst directives from config first.
|
|
// Some apps have different launchers depending upon command line ...
|
|
QStringList matchCommandLineFirst = set.readEntry("MatchCommandLineFirst", QStringList());
|
|
|
|
if (!appId.isEmpty() && matchCommandLineFirst.contains(appId)) {
|
|
triedPid = true;
|
|
services = servicesFromPid(pid, rulesConfig);
|
|
}
|
|
|
|
// Try to match using xWindowsWMClassName also.
|
|
if (!xWindowsWMClassName.isEmpty() && matchCommandLineFirst.contains("::"+xWindowsWMClassName)) {
|
|
triedPid = true;
|
|
services = servicesFromPid(pid, rulesConfig);
|
|
}
|
|
|
|
if (!appId.isEmpty()) {
|
|
// Evaluate any mapping rules that map to a specific .desktop file.
|
|
QString mapped(grp.readEntry(appId + "::" + xWindowsWMClassName, QString()));
|
|
|
|
if (mapped.endsWith(QLatin1String(".desktop"))) {
|
|
url = QUrl(mapped);
|
|
return url;
|
|
}
|
|
|
|
if (mapped.isEmpty()) {
|
|
mapped = grp.readEntry(appId, QString());
|
|
|
|
if (mapped.endsWith(QLatin1String(".desktop"))) {
|
|
url = QUrl(mapped);
|
|
return url;
|
|
}
|
|
}
|
|
|
|
// Some apps, such as Wine, cannot use xWindowsWMClassName to map to launcher name - as Wine itself is not a GUI app
|
|
// So, Settings/ManualOnly lists window classes where the user will always have to manualy set the launcher ...
|
|
QStringList manualOnly = set.readEntry("ManualOnly", QStringList());
|
|
|
|
if (!appId.isEmpty() && manualOnly.contains(appId)) {
|
|
return url;
|
|
}
|
|
|
|
// Try matching both appId and xWindowsWMClassName against StartupWMClass.
|
|
// We do this before evaluating the mapping rules further, because StartupWMClass
|
|
// is essentially a mapping rule, and we expect it to be set deliberately and
|
|
// sensibly to instruct us what to do. Also, mapping rules
|
|
//
|
|
// StartupWMClass=STRING
|
|
//
|
|
// If true, it is KNOWN that the application will map at least one
|
|
// window with the given string as its WM class or WM name hint.
|
|
//
|
|
// Source: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-0.1.txt
|
|
if (services.isEmpty()) {
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(appId));
|
|
sortServicesByMenuId(services, appId);
|
|
}
|
|
|
|
if (services.isEmpty() && !xWindowsWMClassName.isEmpty()) {
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(xWindowsWMClassName));
|
|
sortServicesByMenuId(services, xWindowsWMClassName);
|
|
}
|
|
|
|
// Evaluate rewrite rules from config.
|
|
if (services.isEmpty()) {
|
|
KConfigGroup rewriteRulesGroup(rulesConfig, QStringLiteral("Rewrite Rules"));
|
|
if (rewriteRulesGroup.hasGroup(appId)) {
|
|
KConfigGroup rewriteGroup(&rewriteRulesGroup, appId);
|
|
|
|
const QStringList &rules = rewriteGroup.groupList();
|
|
for (const QString &rule : rules) {
|
|
KConfigGroup ruleGroup(&rewriteGroup, rule);
|
|
|
|
const QString propertyConfig = ruleGroup.readEntry(QStringLiteral("Property"), QString());
|
|
|
|
QString matchProperty;
|
|
if (propertyConfig == QLatin1String("ClassClass")) {
|
|
matchProperty = appId;
|
|
} else if (propertyConfig == QLatin1String("ClassName")) {
|
|
matchProperty = xWindowsWMClassName;
|
|
}
|
|
|
|
if (matchProperty.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
const QString serviceSearchIdentifier = ruleGroup.readEntry(QStringLiteral("Identifier"), QString());
|
|
if (serviceSearchIdentifier.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
QRegularExpression regExp(ruleGroup.readEntry(QStringLiteral("Match")));
|
|
const auto match = regExp.match(matchProperty);
|
|
|
|
if (match.hasMatch()) {
|
|
const QString actualMatch = match.captured(QStringLiteral("match"));
|
|
if (actualMatch.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
QString rewrittenString = ruleGroup.readEntry(QStringLiteral("Target")).arg(actualMatch);
|
|
// If no "Target" is provided, instead assume the matched property (appId/xWindowsWMClassName).
|
|
if (rewrittenString.isEmpty()) {
|
|
rewrittenString = matchProperty;
|
|
}
|
|
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ %2)").arg(rewrittenString, serviceSearchIdentifier));
|
|
sortServicesByMenuId(services, serviceSearchIdentifier);
|
|
|
|
if (!services.isEmpty()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The appId looks like a path.
|
|
if (services.isEmpty() && appId.startsWith(QStringLiteral("/"))) {
|
|
// Check if it's a path to a .desktop file.
|
|
if (KDesktopFile::isDesktopFile(appId) && QFile::exists(appId)) {
|
|
return QUrl::fromLocalFile(appId);
|
|
}
|
|
|
|
// Check if the appId passes as a .desktop file path if we add the extension.
|
|
const QString appIdPlusExtension(appId + QStringLiteral(".desktop"));
|
|
|
|
if (KDesktopFile::isDesktopFile(appIdPlusExtension) && QFile::exists(appIdPlusExtension)) {
|
|
return QUrl::fromLocalFile(appIdPlusExtension);
|
|
}
|
|
}
|
|
|
|
// Try matching mapped name against DesktopEntryName.
|
|
if (!mapped.isEmpty() && services.isEmpty()) {
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName) and (not exist NoDisplay or not NoDisplay)").arg(mapped));
|
|
sortServicesByMenuId(services, mapped);
|
|
}
|
|
|
|
// Try matching mapped name against 'Name'.
|
|
if (!mapped.isEmpty() && services.isEmpty()) {
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(mapped));
|
|
sortServicesByMenuId(services, mapped);
|
|
}
|
|
|
|
// Try matching appId against DesktopEntryName.
|
|
if (services.isEmpty()) {
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName) and (not exist NoDisplay or not NoDisplay)").arg(appId));
|
|
sortServicesByMenuId(services, appId);
|
|
}
|
|
|
|
// Try matching appId against 'Name'.
|
|
// This has a shaky chance of success as appId is untranslated, but 'Name' may be localized.
|
|
if (services.isEmpty()) {
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(appId));
|
|
sortServicesByMenuId(services, appId);
|
|
}
|
|
|
|
// Check rules configuration for whether we want to hide this task.
|
|
// Some window tasks update from bogus to useful metadata early during startup.
|
|
// This config key allows listing the bogus metadata, and the matching window
|
|
// tasks are hidden until they perform a metadate update that stops them from
|
|
// matching.
|
|
QStringList skipTaskbar = set.readEntry("SkipTaskbar", QStringList());
|
|
|
|
if (skipTaskbar.contains(appId)) {
|
|
QUrlQuery query(url);
|
|
query.addQueryItem(QStringLiteral("skipTaskbar"), QStringLiteral("true"));
|
|
url.setQuery(query);
|
|
} else if (skipTaskbar.contains(mapped)) {
|
|
QUrlQuery query(url);
|
|
query.addQueryItem(QStringLiteral("skipTaskbar"), QStringLiteral("true"));
|
|
url.setQuery(query);
|
|
}
|
|
}
|
|
|
|
// Ok, absolute *last* chance, try matching via pid (but only if we have not already tried this!) ...
|
|
if (services.isEmpty() && !triedPid) {
|
|
services = servicesFromPid(pid, rulesConfig);
|
|
}
|
|
}
|
|
|
|
// Try to improve on a possible from-binary fallback.
|
|
// If no services were found or we got a fake-service back from getServicesViaPid()
|
|
// we attempt to improve on this by adding a loosely matched reverse-domain-name
|
|
// DesktopEntryName. Namely anything that is '*.appId.desktop' would qualify here.
|
|
//
|
|
// Illustrative example of a case where the above heuristics would fail to produce
|
|
// a reasonable result:
|
|
// - org.kde.dragonplayer.desktop
|
|
// - binary is 'dragon'
|
|
// - qapp appname and thus appId is 'dragonplayer'
|
|
// - appId cannot directly match the desktop file because of RDN
|
|
// - appId also cannot match the binary because of name mismatch
|
|
// - in the following code *.appId can match org.kde.dragonplayer though
|
|
if (services.isEmpty() || services.at(0)->desktopEntryName().isEmpty()) {
|
|
auto matchingServices = KServiceTypeTrader::self()->query(QStringLiteral("Application"),
|
|
QStringLiteral("exist Exec and ('%1' ~~ DesktopEntryName)").arg(appId));
|
|
QMutableListIterator<KService::Ptr> it(matchingServices);
|
|
while (it.hasNext()) {
|
|
auto service = it.next();
|
|
if (!service->desktopEntryName().endsWith("." + appId)) {
|
|
it.remove();
|
|
}
|
|
}
|
|
// Exactly one match is expected, otherwise we discard the results as to reduce
|
|
// the likelihood of false-positive mappings. Since we essentially eliminate the
|
|
// uniqueness that RDN is meant to bring to the table we could potentially end
|
|
// up with more than one match here.
|
|
if (matchingServices.length() == 1) {
|
|
services = matchingServices;
|
|
}
|
|
}
|
|
|
|
if (!services.isEmpty()) {
|
|
const QString &menuId = services.at(0)->menuId();
|
|
|
|
// applications: URLs are used to refer to applications by their KService::menuId
|
|
// (i.e. .desktop file name) rather than the absolute path to a .desktop file.
|
|
if (!menuId.isEmpty()) {
|
|
url.setUrl(QStringLiteral("applications:") + menuId);
|
|
return url;
|
|
}
|
|
|
|
QString path = services.at(0)->entryPath();
|
|
|
|
if (path.isEmpty()) {
|
|
path = services.at(0)->exec();
|
|
}
|
|
|
|
if (!path.isEmpty()) {
|
|
QString query = url.query();
|
|
url = QUrl::fromLocalFile(path);
|
|
url.setQuery(query);
|
|
return url;
|
|
}
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
KService::List servicesFromPid(quint32 pid, KSharedConfig::Ptr rulesConfig)
|
|
{
|
|
if (pid == 0) {
|
|
return KService::List();
|
|
}
|
|
|
|
if (!rulesConfig) {
|
|
return KService::List();
|
|
}
|
|
|
|
#if KF5_VERSION_MINOR >= 62
|
|
auto proc = KProcessList::processInfo(pid);
|
|
if (!proc.isValid()) {
|
|
return KService::List();
|
|
}
|
|
|
|
const QString cmdLine = proc.command();
|
|
#else
|
|
KSysGuard::Processes procs;
|
|
procs.updateOrAddProcess(pid);
|
|
|
|
KSysGuard::Process *proc = procs.getProcess(pid);
|
|
const QString &cmdLine = proc ? proc->command().simplified() : QString(); // proc->command has a trailing space???
|
|
#endif
|
|
|
|
if (cmdLine.isEmpty()) {
|
|
return KService::List();
|
|
}
|
|
|
|
#if KF5_VERSION_MINOR >= 62
|
|
return servicesFromCmdLine(cmdLine, proc.name(), rulesConfig);
|
|
#else
|
|
return servicesFromCmdLine(cmdLine, proc->name(), rulesConfig);
|
|
#endif
|
|
}
|
|
|
|
KService::List servicesFromCmdLine(const QString &_cmdLine, const QString &processName,
|
|
KSharedConfig::Ptr rulesConfig)
|
|
{
|
|
QString cmdLine = _cmdLine;
|
|
KService::List services;
|
|
|
|
if (!rulesConfig) {
|
|
return services;
|
|
}
|
|
|
|
const int firstSpace = cmdLine.indexOf(' ');
|
|
int slash = 0;
|
|
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine));
|
|
|
|
if (services.isEmpty()) {
|
|
// Could not find with complete command line, so strip out the path part ...
|
|
slash = cmdLine.lastIndexOf('/', firstSpace);
|
|
|
|
if (slash > 0) {
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine.mid(slash + 1)));
|
|
}
|
|
}
|
|
|
|
if (services.isEmpty() && firstSpace > 0) {
|
|
// Could not find with arguments, so try without ...
|
|
cmdLine = cmdLine.left(firstSpace);
|
|
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine));
|
|
|
|
if (services.isEmpty()) {
|
|
slash = cmdLine.lastIndexOf('/');
|
|
|
|
if (slash > 0) {
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine.mid(slash + 1)));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (services.isEmpty()) {
|
|
KConfigGroup set(rulesConfig, "Settings");
|
|
const QStringList &runtimes = set.readEntry("TryIgnoreRuntimes", QStringList());
|
|
|
|
bool ignore = runtimes.contains(cmdLine);
|
|
|
|
if (!ignore && slash > 0) {
|
|
ignore = runtimes.contains(cmdLine.mid(slash + 1));
|
|
}
|
|
|
|
if (ignore) {
|
|
return servicesFromCmdLine(_cmdLine.mid(firstSpace + 1), processName, rulesConfig);
|
|
}
|
|
}
|
|
|
|
if (services.isEmpty() && !processName.isEmpty() && !QStandardPaths::findExecutable(cmdLine).isEmpty()) {
|
|
// cmdLine now exists without arguments if there were any.
|
|
services << QExplicitlySharedDataPointer<KService>(new KService(processName, cmdLine, QString()));
|
|
}
|
|
|
|
return services;
|
|
}
|
|
|
|
QString defaultApplication(const QUrl &url)
|
|
{
|
|
if (url.scheme() != QLatin1String("preferred")) {
|
|
return QString();
|
|
}
|
|
|
|
const QString &application = url.host();
|
|
|
|
if (application.isEmpty()) {
|
|
return QString();
|
|
}
|
|
|
|
if (application.compare(QLatin1String("mailer"), Qt::CaseInsensitive) == 0) {
|
|
KEMailSettings settings;
|
|
|
|
// In KToolInvocation, the default is kmail; but let's be friendlier.
|
|
QString command = settings.getSetting(KEMailSettings::ClientProgram);
|
|
|
|
if (command.isEmpty()) {
|
|
if (KService::Ptr kontact = KService::serviceByStorageId(QStringLiteral("kontact"))) {
|
|
return kontact->storageId();
|
|
} else if (KService::Ptr kmail = KService::serviceByStorageId(QStringLiteral("kmail"))) {
|
|
return kmail->storageId();
|
|
}
|
|
}
|
|
|
|
if (!command.isEmpty()) {
|
|
if (settings.getSetting(KEMailSettings::ClientTerminal) == QLatin1String("true")) {
|
|
KConfigGroup confGroup(KSharedConfig::openConfig(), "General");
|
|
const QString preferredTerminal = confGroup.readPathEntry("TerminalApplication",
|
|
QStringLiteral("konsole"));
|
|
command = preferredTerminal + QLatin1String(" -e ") + command;
|
|
}
|
|
|
|
return command;
|
|
}
|
|
} else if (application.compare(QLatin1String("browser"), Qt::CaseInsensitive) == 0) {
|
|
KConfigGroup config(KSharedConfig::openConfig(), "General");
|
|
QString browserApp = config.readPathEntry("BrowserApplication", QString());
|
|
|
|
if (browserApp.isEmpty()) {
|
|
const KService::Ptr htmlApp = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/html"));
|
|
|
|
if (htmlApp) {
|
|
browserApp = htmlApp->storageId();
|
|
}
|
|
} else if (browserApp.startsWith('!')) {
|
|
browserApp = browserApp.mid(1);
|
|
}
|
|
|
|
return browserApp;
|
|
} else if (application.compare(QLatin1String("terminal"), Qt::CaseInsensitive) == 0) {
|
|
KConfigGroup confGroup(KSharedConfig::openConfig(), "General");
|
|
|
|
return confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole"));
|
|
} else if (application.compare(QLatin1String("filemanager"), Qt::CaseInsensitive) == 0) {
|
|
KService::Ptr service = KMimeTypeTrader::self()->preferredService(QStringLiteral("inode/directory"));
|
|
|
|
if (service) {
|
|
return service->storageId();
|
|
}
|
|
} else if (KService::Ptr service = KMimeTypeTrader::self()->preferredService(application)) {
|
|
return service->storageId();
|
|
} else {
|
|
// Try the files in share/apps/kcm_componentchooser/*.desktop.
|
|
QStringList directories = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kcm_componentchooser"), QStandardPaths::LocateDirectory);
|
|
QStringList services;
|
|
|
|
foreach(const QString& directory, directories) {
|
|
QDir dir(directory);
|
|
foreach(const QString& f, dir.entryList(QStringList("*.desktop")))
|
|
services += dir.absoluteFilePath(f);
|
|
}
|
|
|
|
foreach (const QString & service, services) {
|
|
KConfig config(service, KConfig::SimpleConfig);
|
|
KConfigGroup cg = config.group(QByteArray());
|
|
const QString type = cg.readEntry("valueName", QString());
|
|
|
|
if (type.compare(application, Qt::CaseInsensitive) == 0) {
|
|
KConfig store(cg.readPathEntry("storeInFile", QStringLiteral("null")));
|
|
KConfigGroup storeCg(&store, cg.readEntry("valueSection", QString()));
|
|
const QString exec = storeCg.readPathEntry(cg.readEntry("valueName", "kcm_componenchooser_null"),
|
|
cg.readEntry("defaultImplementation", QString()));
|
|
|
|
if (!exec.isEmpty()) {
|
|
return exec;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return QString("");
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|