Summary:
This factors the app identification heuristic out of XWindowTasksModel
and turns it into generic code in TaskTools, producing a URL from a set
of window metadata bits. The key metadata is the 'appId', which is the
classClass part of WM_CLASS on X11 and PlasmaWindow::appId on Wayland -
which KWin sets to the former for XWayland clients. The result is much
improved support for XWayland clients in the Wayland session, with most
X clients now identified correctly.
As a side effect, the Wayland model gains access to the X model's much
superior code for grabbing a suitable icon, with PlasmaWindow::icon now
serving only as a fallback, similar to KWindowSystem::icon in the X
model.
Moving the code to TaskTools also means it now sports nice API docs.
The heuristic has seen some work as well, namely adding two passes that
try to parse the appId as a path to a desktop file, which we've never
seen on X11 but is common on Wayland - this heuristic was previously in
appDataFromAppId, which has been removed here since the shared heuristic
now satisfies this case.
Further, an old codepath handling kcmshell in a special way has been
removed. This is no longer necessary as we have better ways to tell
libtaskmanager about the KCM KService now, such as the .desktop file
window hint on X11 and a reliable appId on Wayland.
This patch also fixes some bugs around app data cache eviction and
telling model clients about data changes when cache eviction happens.
The X model didn't use to evict the cache and refresh when the
taskmanagerrulesrc file was changed at runtime, and the refresh for
sycoca changes didn't refresh the LauncherUrlWithoutIcon role. It does
now, and the Wayland model - which has gained taskmanagerrulesrc support
by way of the shared heuristic - now behaves in the same way.
The combined changes achieve near behavior parity between the X
and Wayland models when it comes to identifying apps by window meta
data, with the only exception being XWayland clients that need to be
identified by the (incorrectly used by the client developer) instance
name part of the WM_CLASS window property, which we can't access (this
case is so rare it's not worth handling at this time).
Depends on D5747, D5755.
Reviewers: #plasma, davidedmundson
Subscribers: plasma-devel
Tags: #plasma
Differential Revision: https://phabricator.kde.org/D5818
// 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) and (not exist NoDisplay or not NoDisplay)").arg(mapped));
}
// Try matching appId against DesktopEntryName.
if(services.empty()){
services=KServiceTypeTrader::self()->query(QStringLiteral("Application"),QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName)").arg(appId));
}
// Try matching appId against 'Name'.
// This has a shaky chance of success as appId is untranslated, but 'Name' may be localized.
if(services.empty()){
services=KServiceTypeTrader::self()->query(QStringLiteral("Application"),QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(appId));
}
}
// Ok, absolute *last* chance, try matching via pid (but only if we have not already tried this!) ...
if(services.empty()&&!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 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) and (not exist NoDisplay or not NoDisplay)").arg(mapped));
}
// 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 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) and (not exist NoDisplay or not NoDisplay)").arg(classClass));
}
}
// Ok, absolute *last* chance, try matching via pid (but only if we have not already tried this!) ...
if(services.empty()&&!triedPid){
services=servicesFromPid(pid);
}
}
// 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 '*.classClass.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 classClass is 'dragonplayer'
// - classClass cannot directly match the desktop file because of RDN
// - classClass also cannot match the binary because of name mismatch
// - in the following code *.classClass can match org.kde.dragonplayer though