Overhaul app associativity heuristic to give precendence to StartupWMClass.

Summary:
WM_CLASS instance and general classes are checked against StartupWMClass
prior to most rewrite and mapping rules, and prior to the DesktopEntryName
and Name checks.

This matches the fd.o spec more closely and makes Chrome Apps and
LibreOffice work without special mapping rules, which is also a speed
win.

Reviewers: #plasma, davidedmundson, broulik

Subscribers: plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D4706
wilder-5.14
Eike Hein 9 years ago
parent ce3b4cb6f5
commit f17a395a40
  1. 35
      libtaskmanager/taskmanagerrulesrc
  2. 115
      libtaskmanager/xwindowtasksmodel.cpp

@ -3,41 +3,8 @@ Gimp-2.8=GIMP
Google-chrome=Google Chrome
Google-chrome-stable=Google Chrome
Systemsettings=System Settings
libreoffice-base=LibreOffice Base
libreoffice-calc=LibreOffice Calc
libreoffice-draw=LibreOffice Draw
libreoffice-writer=LibreOffice Writer
libreoffice-math=LibreOffice Math
libreoffice-impress=LibreOffice Impress
libreoffice-startcenter=LibreOffice
oracle-ide-boot-Launcher=Oracle SQL Developer
Dragon=dragonplayer
[Settings]
ManualOnly=Wine
[Rewrite Rules][google-chrome][1]
Property=ClassName
Identifier=StartupWMClass
Match=(?<=crx_)(?'match'[a-z]+)
# Google changed the class to be "Google-chrome" around version 54
[Rewrite Rules][Google-chrome][1]
Property=ClassName
Identifier=StartupWMClass
Match=(?<=crx_)(?'match'[a-z]+)
[Rewrite Rules][chromium][1]
Property=ClassName
Identifier=StartupWMClass
Match=(?<=crx_)(?'match'[a-z]+)
[Rewrite Rules][chromium-browser][1]
Property=ClassName
Identifier=StartupWMClass
Match=(?<=crx_)(?'match'[a-z]+)
# Chromium changed the class too
[Rewrite Rules][Chromium][1]
Property=ClassName
Identifier=StartupWMClass
Match=(?<=crx_)(?'match'[a-z]+)

@ -517,6 +517,7 @@ QUrl XWindowTasksModel::Private::windowUrl(WId window)
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());
@ -531,15 +532,15 @@ QUrl XWindowTasksModel::Private::windowUrl(WId window)
services = servicesFromPid(pid);
}
// If the user has manually set a mapping, respect this first...
QString mapped(grp.readEntry(classClass + "::" + className, QString()));
if (!classClass.isEmpty()) {
// Evaluate any mapping rules that map to a specific .desktop file.
QString mapped(grp.readEntry(classClass + "::" + className, QString()));
if (mapped.endsWith(QLatin1String(".desktop"))) {
url = QUrl(mapped);
return url;
}
if (mapped.endsWith(QLatin1String(".desktop"))) {
url = QUrl(mapped);
return url;
}
if (!classClass.isEmpty()) {
if (mapped.isEmpty()) {
mapped = grp.readEntry(classClass, QString());
@ -557,75 +558,95 @@ QUrl XWindowTasksModel::Private::windowUrl(WId window)
return url;
}
KConfigGroup rewriteRulesGroup(rulesConfig, QStringLiteral("Rewrite Rules"));
if (rewriteRulesGroup.hasGroup(classClass)) {
KConfigGroup rewriteGroup(&rewriteRulesGroup, classClass);
const QStringList &rules = rewriteGroup.groupList();
for (const QString &rule : rules) {
KConfigGroup ruleGroup(&rewriteGroup, rule);
// Try matching both WM_CLASS instance and general class 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.empty()) {
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(classClass));
}
const QString propertyConfig = ruleGroup.readEntry(QStringLiteral("Property"), QString());
if (services.empty()) {
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(className));
}
QString matchProperty;
if (propertyConfig == QLatin1String("ClassClass")) {
matchProperty = classClass;
} else if (propertyConfig == QLatin1String("ClassName")) {
matchProperty = className;
}
// Evaluate rewrite rules from config.
if (services.empty()) {
KConfigGroup rewriteRulesGroup(rulesConfig, QStringLiteral("Rewrite Rules"));
if (rewriteRulesGroup.hasGroup(classClass)) {
KConfigGroup rewriteGroup(&rewriteRulesGroup, classClass);
if (matchProperty.isEmpty()) {
continue;
}
const QStringList &rules = rewriteGroup.groupList();
for (const QString &rule : rules) {
KConfigGroup ruleGroup(&rewriteGroup, rule);
const QString serviceSearchIdentifier = ruleGroup.readEntry(QStringLiteral("Identifier"), QString());
if (serviceSearchIdentifier.isEmpty()) {
continue;
}
const QString propertyConfig = ruleGroup.readEntry(QStringLiteral("Property"), QString());
QRegularExpression regExp(ruleGroup.readEntry(QStringLiteral("Match")));
const auto match = regExp.match(matchProperty);
QString matchProperty;
if (propertyConfig == QLatin1String("ClassClass")) {
matchProperty = classClass;
} else if (propertyConfig == QLatin1String("ClassName")) {
matchProperty = className;
}
if (match.hasMatch()) {
const QString actualMatch = match.captured(QStringLiteral("match"));
if (actualMatch.isEmpty()) {
if (matchProperty.isEmpty()) {
continue;
}
QString rewrittenString = ruleGroup.readEntry(QStringLiteral("Target")).arg(actualMatch);
// If no "Target" is provided, instead assume the matched property (ClassClass/ClassName).
if (rewrittenString.isEmpty()) {
rewrittenString = matchProperty;
const QString serviceSearchIdentifier = ruleGroup.readEntry(QStringLiteral("Identifier"), QString());
if (serviceSearchIdentifier.isEmpty()) {
continue;
}
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ %2)").arg(rewrittenString, serviceSearchIdentifier));
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;
}
if (!services.isEmpty()) {
break;
QString rewrittenString = ruleGroup.readEntry(QStringLiteral("Target")).arg(actualMatch);
// If no "Target" is provided, instead assume the matched property (ClassClass/ClassName).
if (rewrittenString.isEmpty()) {
rewrittenString = matchProperty;
}
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ %2)").arg(rewrittenString, serviceSearchIdentifier));
if (!services.isEmpty()) {
break;
}
}
}
}
}
// Try matching mapped name against DesktopEntryName.
if (!mapped.isEmpty() && services.empty()) {
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName)").arg(mapped));
}
// Try matching mapped name against 'Name'.
if (!mapped.isEmpty() && services.empty()) {
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name)").arg(mapped));
}
// To match other docks (docky, unity, etc.) attempt to match on DesktopEntryName first ...
// Try matching WM_CLASS general class against DesktopEntryName.
if (services.empty()) {
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName)").arg(classClass));
}
// Try StartupWMClass.
if (services.empty()) {
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(classClass));
}
// Try 'Name' - unfortunately this can be translated, so has a good chance of failing! (As it does for KDE's own "System Settings" (even in English!!))
// Try matching WM_CLASS general class against 'Name'.
// This has a shaky chance of success as WM_CLASS is untranslated, but 'Name' may be localized.
if (services.empty()) {
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name)").arg(classClass));
}

Loading…
Cancel
Save