Changeset View
Standalone View
libtaskmanager/tasktools.cpp
Show All 36 Lines | |||||
37 | #include <KWindowSystem> | 37 | #include <KWindowSystem> | ||
38 | 38 | | |||
39 | #include <processcore/processes.h> | 39 | #include <processcore/processes.h> | ||
40 | #include <processcore/process.h> | 40 | #include <processcore/process.h> | ||
41 | 41 | | |||
42 | #include <config-X11.h> | 42 | #include <config-X11.h> | ||
43 | 43 | | |||
44 | #include <QDir> | 44 | #include <QDir> | ||
45 | #include <QDebug> | ||||
cfeck: Can be removed? If not, sort correctly. | |||||
45 | #include <QGuiApplication> | 46 | #include <QGuiApplication> | ||
46 | #include <QRegularExpression> | 47 | #include <QRegularExpression> | ||
47 | #include <QScreen> | 48 | #include <QScreen> | ||
48 | #if HAVE_X11 | 49 | #if HAVE_X11 | ||
49 | #include <QX11Info> | 50 | #include <QX11Info> | ||
50 | #endif | 51 | #endif | ||
51 | 52 | | |||
52 | namespace TaskManager | 53 | namespace TaskManager | ||
▲ Show 20 Lines • Show All 157 Lines • ▼ Show 20 Line(s) | 210 | { | |||
210 | if (!rulesConfig) { | 211 | if (!rulesConfig) { | ||
211 | return QUrl(); | 212 | return QUrl(); | ||
212 | } | 213 | } | ||
213 | 214 | | |||
214 | QUrl url; | 215 | QUrl url; | ||
215 | KService::List services; | 216 | KService::List services; | ||
216 | bool triedPid = false; | 217 | bool triedPid = false; | ||
217 | 218 | | |||
219 | // The code below goes on a hunt for services based on the metadata that has been | ||||
220 | // passed in. Occasionally, it will find more than one matching service. In some | ||||
221 | // scenarios (e.g. multiple identically-named .desktop files) there's a need to | ||||
222 | // pick the most useful one. The function below promises to "sort" a list of | ||||
223 | // services by how closely their KService::menuId() relates to the key that has | ||||
224 | // been passed in. The current naive implementation simply looks for a menuId that | ||||
225 | // starts with the key, prepends it to the list and returns it. In practice, that | ||||
226 | // means a KService with a menuId matching the appId will win over one with a menuId | ||||
227 | // that encodes a subfolder hierarchy. | ||||
228 | // A concrete example: Valve's Steam client is sometimes installed two times, once | ||||
This comment makes it sound like this pretends to be a generic solution to a particular workaround. But you told me there's also Telegram and others being affected (fixed) by this, right? broulik: This comment makes it sound like this pretends to be a generic solution to a particular… | |||||
I started doing this for the Wine thing based on a user bug report. But for some reason I did wind up with two .desktop files for Telegram on my system, I think one from the Fedora package and one from ... a Flatpak? Their tarball? Who knows. In any case, this algo helps pick the right one there, too. The general idea of "if we find more than one service, the one with a menuId that's more closely related to the search term we found them with" seems to be better than "just use whatever KSTT returns first", considering KSTT does no sorting whatsoever. hein: I started doing this for the Wine thing based on a user bug report. But for some reason I did… | |||||
229 | // natively as a Linux application, once via Wine. Both have .desktop files named | ||||
230 | // (S|)steam.desktop. The Linux native version is located in the menu by means of | ||||
231 | // categorization ("Games") and just has a menuId() matching the .desktop file name, | ||||
232 | // but the Wine version is placed in a folder hierarchy by Wine and gets a menuId() | ||||
233 | // of wine-Programs-Steam-Steam.desktop. The weighing done by this function makes | ||||
234 | // sure the Linux native version gets mapped to the former, while other heuristics | ||||
235 | // map the Wine version reliably to the latter. | ||||
236 | // In lieu of this weighing we just used whatever KServiceTypeTrader returned first, | ||||
237 | // so what we do here can be no worse. | ||||
238 | auto sortServicesByMenuId = [](KService::List &services, const QString &key) { | ||||
239 | if (services.count() == 1) { | ||||
240 | return services; | ||||
241 | } | ||||
242 | | ||||
243 | for (const auto service : services) { | ||||
broulik: Is there some nicer algo in place of this loop? | |||||
hein: You tell me? :) | |||||
244 | if (service->menuId().startsWith(key, Qt::CaseInsensitive)) { | ||||
245 | services.prepend(service); | ||||
You're mutating the list (argument services is a non-const reference) and also returning it. Is this intentional? broulik: You're mutating the list (argument `services` is a non-`const` reference) and also returning it. | |||||
Yes. It's just faster and less costly than actually sorting the list. This is a bit ugly, but it takes the stance of "this is a private anonymous function so it can't hurt any lib user, and if this sorting algo ever needs to be more sophisticated we'll fill it in then". hein: Yes. It's just faster and less costly than actually sorting the list. This is a bit ugly, but… | |||||
246 | return services; | ||||
247 | } | ||||
248 | } | ||||
249 | | ||||
250 | return services; | ||||
251 | }; | ||||
252 | | ||||
218 | if (!(appId.isEmpty() && xWindowsWMClassName.isEmpty())) { | 253 | if (!(appId.isEmpty() && xWindowsWMClassName.isEmpty())) { | ||
219 | // Check to see if this wmClass matched a saved one ... | 254 | // Check to see if this wmClass matched a saved one ... | ||
220 | KConfigGroup grp(rulesConfig, "Mapping"); | 255 | KConfigGroup grp(rulesConfig, "Mapping"); | ||
221 | KConfigGroup set(rulesConfig, "Settings"); | 256 | KConfigGroup set(rulesConfig, "Settings"); | ||
222 | 257 | | |||
223 | // Evaluate MatchCommandLineFirst directives from config first. | 258 | // Evaluate MatchCommandLineFirst directives from config first. | ||
224 | // Some apps have different launchers depending upon command line ... | 259 | // Some apps have different launchers depending upon command line ... | ||
225 | QStringList matchCommandLineFirst = set.readEntry("MatchCommandLineFirst", QStringList()); | 260 | QStringList matchCommandLineFirst = set.readEntry("MatchCommandLineFirst", QStringList()); | ||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Line(s) | 273 | if (!appId.isEmpty()) { | |||
269 | // StartupWMClass=STRING | 304 | // StartupWMClass=STRING | ||
270 | // | 305 | // | ||
271 | // If true, it is KNOWN that the application will map at least one | 306 | // If true, it is KNOWN that the application will map at least one | ||
272 | // window with the given string as its WM class or WM name hint. | 307 | // window with the given string as its WM class or WM name hint. | ||
273 | // | 308 | // | ||
274 | // Source: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-0.1.txt | 309 | // Source: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-0.1.txt | ||
275 | if (services.empty()) { | 310 | if (services.empty()) { | ||
276 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(appId)); | 311 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(appId)); | ||
312 | services = sortServicesByMenuId(services, appId); | ||||
277 | } | 313 | } | ||
278 | 314 | | |||
279 | if (services.empty()) { | 315 | if (services.empty()) { | ||
280 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(xWindowsWMClassName)); | 316 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(xWindowsWMClassName)); | ||
317 | services = sortServicesByMenuId(services, xWindowsWMClassName); | ||||
281 | } | 318 | } | ||
282 | 319 | | |||
283 | // Evaluate rewrite rules from config. | 320 | // Evaluate rewrite rules from config. | ||
284 | if (services.empty()) { | 321 | if (services.empty()) { | ||
285 | KConfigGroup rewriteRulesGroup(rulesConfig, QStringLiteral("Rewrite Rules")); | 322 | KConfigGroup rewriteRulesGroup(rulesConfig, QStringLiteral("Rewrite Rules")); | ||
286 | if (rewriteRulesGroup.hasGroup(appId)) { | 323 | if (rewriteRulesGroup.hasGroup(appId)) { | ||
287 | KConfigGroup rewriteGroup(&rewriteRulesGroup, appId); | 324 | KConfigGroup rewriteGroup(&rewriteRulesGroup, appId); | ||
288 | 325 | | |||
Show All 30 Lines | 351 | if (match.hasMatch()) { | |||
319 | 356 | | |||
320 | QString rewrittenString = ruleGroup.readEntry(QStringLiteral("Target")).arg(actualMatch); | 357 | QString rewrittenString = ruleGroup.readEntry(QStringLiteral("Target")).arg(actualMatch); | ||
321 | // If no "Target" is provided, instead assume the matched property (appId/xWindowsWMClassName). | 358 | // If no "Target" is provided, instead assume the matched property (appId/xWindowsWMClassName). | ||
322 | if (rewrittenString.isEmpty()) { | 359 | if (rewrittenString.isEmpty()) { | ||
323 | rewrittenString = matchProperty; | 360 | rewrittenString = matchProperty; | ||
324 | } | 361 | } | ||
325 | 362 | | |||
326 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ %2)").arg(rewrittenString, serviceSearchIdentifier)); | 363 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ %2)").arg(rewrittenString, serviceSearchIdentifier)); | ||
364 | services = sortServicesByMenuId(services, serviceSearchIdentifier); | ||||
327 | 365 | | |||
328 | if (!services.isEmpty()) { | 366 | if (!services.isEmpty()) { | ||
329 | break; | 367 | break; | ||
330 | } | 368 | } | ||
331 | } | 369 | } | ||
332 | } | 370 | } | ||
333 | } | 371 | } | ||
334 | } | 372 | } | ||
335 | 373 | | |||
336 | // The appId looks like a path. | 374 | // The appId looks like a path. | ||
337 | if (appId.startsWith(QStringLiteral("/"))) { | 375 | if (services.empty() && appId.startsWith(QStringLiteral("/"))) { | ||
broulik: `isEmpty` (unless consistent with the rest but it seems to be mixed already) | |||||
hein: Will change. | |||||
338 | // Check if it's a path to a .desktop file. | 376 | // Check if it's a path to a .desktop file. | ||
339 | if (KDesktopFile::isDesktopFile(appId) && QFile::exists(appId)) { | 377 | if (KDesktopFile::isDesktopFile(appId) && QFile::exists(appId)) { | ||
340 | return QUrl::fromLocalFile(appId); | 378 | return QUrl::fromLocalFile(appId); | ||
341 | } | 379 | } | ||
342 | 380 | | |||
343 | // Check if the appId passes as a .desktop file path if we add the extension. | 381 | // Check if the appId passes as a .desktop file path if we add the extension. | ||
344 | const QString appIdPlusExtension(appId + QStringLiteral(".desktop")); | 382 | const QString appIdPlusExtension(appId + QStringLiteral(".desktop")); | ||
345 | 383 | | |||
346 | if (KDesktopFile::isDesktopFile(appIdPlusExtension) && QFile::exists(appIdPlusExtension)) { | 384 | if (KDesktopFile::isDesktopFile(appIdPlusExtension) && QFile::exists(appIdPlusExtension)) { | ||
347 | return QUrl::fromLocalFile(appIdPlusExtension); | 385 | return QUrl::fromLocalFile(appIdPlusExtension); | ||
348 | } | 386 | } | ||
349 | } | 387 | } | ||
350 | 388 | | |||
351 | // Try matching mapped name against DesktopEntryName. | 389 | // Try matching mapped name against DesktopEntryName. | ||
352 | if (!mapped.isEmpty() && services.empty()) { | 390 | if (!mapped.isEmpty() && services.empty()) { | ||
353 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName) and (not exist NoDisplay or not NoDisplay)").arg(mapped)); | 391 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName) and (not exist NoDisplay or not NoDisplay)").arg(mapped)); | ||
392 | services = sortServicesByMenuId(services, mapped); | ||||
354 | } | 393 | } | ||
355 | 394 | | |||
356 | // Try matching mapped name against 'Name'. | 395 | // Try matching mapped name against 'Name'. | ||
357 | if (!mapped.isEmpty() && services.empty()) { | 396 | if (!mapped.isEmpty() && services.empty()) { | ||
358 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(mapped)); | 397 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(mapped)); | ||
398 | services = sortServicesByMenuId(services, mapped); | ||||
359 | } | 399 | } | ||
360 | 400 | | |||
361 | // Try matching appId against DesktopEntryName. | 401 | // Try matching appId against DesktopEntryName. | ||
362 | if (services.empty()) { | 402 | if (services.empty()) { | ||
363 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName) and (not exist NoDisplay or not NoDisplay)").arg(appId)); | 403 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName) and (not exist NoDisplay or not NoDisplay)").arg(appId)); | ||
404 | services = sortServicesByMenuId(services, appId); | ||||
364 | } | 405 | } | ||
365 | 406 | | |||
366 | // Try matching appId against 'Name'. | 407 | // Try matching appId against 'Name'. | ||
367 | // This has a shaky chance of success as appId is untranslated, but 'Name' may be localized. | 408 | // This has a shaky chance of success as appId is untranslated, but 'Name' may be localized. | ||
368 | if (services.empty()) { | 409 | if (services.empty()) { | ||
369 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(appId)); | 410 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(appId)); | ||
411 | services = sortServicesByMenuId(services, appId); | ||||
370 | } | 412 | } | ||
371 | } | 413 | } | ||
372 | 414 | | |||
373 | // Ok, absolute *last* chance, try matching via pid (but only if we have not already tried this!) ... | 415 | // Ok, absolute *last* chance, try matching via pid (but only if we have not already tried this!) ... | ||
374 | if (services.empty() && !triedPid) { | 416 | if (services.empty() && !triedPid) { | ||
375 | services = servicesFromPid(pid, rulesConfig); | 417 | services = servicesFromPid(pid, rulesConfig); | ||
376 | } | 418 | } | ||
377 | } | 419 | } | ||
▲ Show 20 Lines • Show All 367 Lines • Show Last 20 Lines |
Can be removed? If not, sort correctly.