Changeset View
Changeset View
Standalone View
Standalone View
libtaskmanager/xwindowtasksmodel.cpp
Show All 17 Lines | |||||
18 | You should have received a copy of the GNU Lesser General Public | 18 | You should have received a copy of the GNU Lesser General Public | ||
19 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | 19 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||
20 | *********************************************************************/ | 20 | *********************************************************************/ | ||
21 | 21 | | |||
22 | #include "xwindowtasksmodel.h" | 22 | #include "xwindowtasksmodel.h" | ||
23 | #include "tasktools.h" | 23 | #include "tasktools.h" | ||
24 | 24 | | |||
25 | #include <KActivities/ResourceInstance> | 25 | #include <KActivities/ResourceInstance> | ||
26 | #include <KConfigGroup> | | |||
27 | #include <KDesktopFile> | 26 | #include <KDesktopFile> | ||
28 | #include <KDirWatch> | 27 | #include <KDirWatch> | ||
29 | #include <KIconLoader> | 28 | #include <KIconLoader> | ||
30 | #include <KRun> | 29 | #include <KRun> | ||
31 | #include <KService> | 30 | #include <KService> | ||
32 | #include <KServiceTypeTrader> | | |||
33 | #include <KSharedConfig> | 31 | #include <KSharedConfig> | ||
34 | #include <KStartupInfo> | 32 | #include <KStartupInfo> | ||
35 | #include <KSycoca> | 33 | #include <KSycoca> | ||
36 | #include <KWindowInfo> | 34 | #include <KWindowInfo> | ||
37 | #include <KWindowSystem> | 35 | #include <KWindowSystem> | ||
38 | #include <processcore/processes.h> | | |||
39 | #include <processcore/process.h> | | |||
40 | 36 | | |||
41 | #include <QBuffer> | 37 | #include <QBuffer> | ||
42 | #include <QDir> | 38 | #include <QDir> | ||
43 | #include <QIcon> | 39 | #include <QIcon> | ||
44 | #include <QFile> | 40 | #include <QFile> | ||
45 | #include <QRegularExpression> | | |||
46 | #include <QSet> | 41 | #include <QSet> | ||
47 | #include <QStandardPaths> | | |||
48 | #include <QTimer> | 42 | #include <QTimer> | ||
49 | #include <QX11Info> | 43 | #include <QX11Info> | ||
50 | 44 | | |||
51 | namespace TaskManager | 45 | namespace TaskManager | ||
52 | { | 46 | { | ||
53 | 47 | | |||
54 | static const NET::Properties windowInfoFlags = NET::WMState | NET::XAWMState | NET::WMDesktop | | 48 | static const NET::Properties windowInfoFlags = NET::WMState | NET::XAWMState | NET::WMDesktop | | ||
55 | NET::WMVisibleName | NET::WMGeometry | NET::WMFrameExtents | NET::WMWindowType | NET::WMPid; | 49 | NET::WMVisibleName | NET::WMGeometry | NET::WMFrameExtents | NET::WMWindowType | NET::WMPid; | ||
Show All 27 Lines | 55 | public: | |||
83 | KWindowInfo* windowInfo(WId window); | 77 | KWindowInfo* windowInfo(WId window); | ||
84 | AppData appData(WId window); | 78 | AppData appData(WId window); | ||
85 | 79 | | |||
86 | QIcon icon(WId window); | 80 | QIcon icon(WId window); | ||
87 | static QString mimeType(); | 81 | static QString mimeType(); | ||
88 | static QString groupMimeType(); | 82 | static QString groupMimeType(); | ||
89 | QUrl windowUrl(WId window); | 83 | QUrl windowUrl(WId window); | ||
90 | QUrl launcherUrl(WId window, bool encodeFallbackIcon = true); | 84 | QUrl launcherUrl(WId window, bool encodeFallbackIcon = true); | ||
91 | QUrl serviceUrl(int pid, const QString &type, const QStringList &cmdRemovals); | | |||
92 | KService::List servicesFromPid(int pid); | | |||
93 | KService::List servicesFromCmdLine(const QString &cmdLine, const QString &processName); | | |||
94 | bool demandsAttention(WId window); | 85 | bool demandsAttention(WId window); | ||
95 | 86 | | |||
96 | private: | 87 | private: | ||
97 | XWindowTasksModel *q; | 88 | XWindowTasksModel *q; | ||
98 | }; | 89 | }; | ||
99 | 90 | | |||
100 | XWindowTasksModel::Private::Private(XWindowTasksModel *q) | 91 | XWindowTasksModel::Private::Private(XWindowTasksModel *q) | ||
101 | : q(q) | 92 | : q(q) | ||
102 | { | 93 | { | ||
103 | } | 94 | } | ||
104 | 95 | | |||
105 | XWindowTasksModel::Private::~Private() | 96 | XWindowTasksModel::Private::~Private() | ||
106 | { | 97 | { | ||
107 | qDeleteAll(windowInfoCache); | 98 | qDeleteAll(windowInfoCache); | ||
108 | windowInfoCache.clear(); | 99 | windowInfoCache.clear(); | ||
109 | } | 100 | } | ||
110 | 101 | | |||
111 | void XWindowTasksModel::Private::init() | 102 | void XWindowTasksModel::Private::init() | ||
112 | { | 103 | { | ||
113 | rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")); | 104 | auto clearCacheAndRefresh = [this] { | ||
114 | configWatcher = new KDirWatch(q); | | |||
115 | | ||||
116 | foreach (const QString &location, QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)) { | | |||
117 | configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc")); | | |||
118 | } | | |||
119 | | ||||
120 | QObject::connect(configWatcher, &KDirWatch::dirty, [this] { rulesConfig->reparseConfiguration(); }); | | |||
121 | QObject::connect(configWatcher, &KDirWatch::created, [this] { rulesConfig->reparseConfiguration(); }); | | |||
122 | QObject::connect(configWatcher, &KDirWatch::deleted, [this] { rulesConfig->reparseConfiguration(); }); | | |||
123 | | ||||
124 | sycocaChangeTimer.setSingleShot(true); | | |||
125 | sycocaChangeTimer.setInterval(100); | | |||
126 | | ||||
127 | QObject::connect(&sycocaChangeTimer, &QTimer::timeout, q, | | |||
128 | [this]() { | | |||
129 | if (!windows.count()) { | 105 | if (!windows.count()) { | ||
130 | return; | 106 | return; | ||
131 | } | 107 | } | ||
132 | 108 | | |||
133 | appDataCache.clear(); | 109 | appDataCache.clear(); | ||
134 | 110 | | |||
135 | // Emit changes of all roles satisfied from app data cache. | 111 | // Emit changes of all roles satisfied from app data cache. | ||
136 | q->dataChanged(q->index(0, 0), q->index(windows.count() - 1, 0), | 112 | q->dataChanged(q->index(0, 0), q->index(windows.count() - 1, 0), | ||
137 | QVector<int>{Qt::DecorationRole, AbstractTasksModel::AppId, | 113 | QVector<int>{Qt::DecorationRole, AbstractTasksModel::AppId, | ||
138 | AbstractTasksModel::AppName, AbstractTasksModel::GenericName, | 114 | AbstractTasksModel::AppName, AbstractTasksModel::GenericName, | ||
139 | AbstractTasksModel::LauncherUrl}); | 115 | AbstractTasksModel::LauncherUrl, | ||
140 | } | 116 | AbstractTasksModel::LauncherUrlWithoutIcon}); | ||
141 | ); | 117 | }; | ||
118 | | ||||
119 | sycocaChangeTimer.setSingleShot(true); | ||||
120 | sycocaChangeTimer.setInterval(100); | ||||
121 | | ||||
122 | QObject::connect(&sycocaChangeTimer, &QTimer::timeout, q, clearCacheAndRefresh); | ||||
142 | 123 | | |||
143 | void (KSycoca::*myDatabaseChangeSignal)(const QStringList &) = &KSycoca::databaseChanged; | 124 | void (KSycoca::*myDatabaseChangeSignal)(const QStringList &) = &KSycoca::databaseChanged; | ||
144 | QObject::connect(KSycoca::self(), myDatabaseChangeSignal, q, | 125 | QObject::connect(KSycoca::self(), myDatabaseChangeSignal, q, | ||
145 | [this](const QStringList &changedResources) { | 126 | [this](const QStringList &changedResources) { | ||
146 | if (changedResources.contains(QLatin1String("services")) | 127 | if (changedResources.contains(QLatin1String("services")) | ||
147 | || changedResources.contains(QLatin1String("apps")) | 128 | || changedResources.contains(QLatin1String("apps")) | ||
148 | || changedResources.contains(QLatin1String("xdgdata-apps"))) { | 129 | || changedResources.contains(QLatin1String("xdgdata-apps"))) { | ||
149 | sycocaChangeTimer.start(); | 130 | sycocaChangeTimer.start(); | ||
150 | } | 131 | } | ||
151 | } | 132 | } | ||
152 | ); | 133 | ); | ||
153 | 134 | | |||
135 | rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")); | ||||
136 | configWatcher = new KDirWatch(q); | ||||
137 | | ||||
138 | foreach (const QString &location, QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)) { | ||||
139 | configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc")); | ||||
140 | } | ||||
141 | | ||||
142 | auto rulesConfigChange = [this, &clearCacheAndRefresh] { | ||||
143 | rulesConfig->reparseConfiguration(); | ||||
144 | clearCacheAndRefresh(); | ||||
145 | }; | ||||
146 | | ||||
147 | QObject::connect(configWatcher, &KDirWatch::dirty, rulesConfigChange); | ||||
148 | QObject::connect(configWatcher, &KDirWatch::created, rulesConfigChange); | ||||
149 | QObject::connect(configWatcher, &KDirWatch::deleted, rulesConfigChange); | ||||
150 | | ||||
154 | QObject::connect(KWindowSystem::self(), &KWindowSystem::windowAdded, q, | 151 | QObject::connect(KWindowSystem::self(), &KWindowSystem::windowAdded, q, | ||
155 | [this](WId window) { | 152 | [this](WId window) { | ||
156 | addWindow(window); | 153 | addWindow(window); | ||
157 | } | 154 | } | ||
158 | ); | 155 | ); | ||
159 | 156 | | |||
160 | QObject::connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, q, | 157 | QObject::connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, q, | ||
161 | [this](WId window) { | 158 | [this](WId window) { | ||
▲ Show 20 Lines • Show All 304 Lines • ▼ Show 20 Line(s) | |||||
466 | 463 | | |||
467 | QString XWindowTasksModel::Private::groupMimeType() | 464 | QString XWindowTasksModel::Private::groupMimeType() | ||
468 | { | 465 | { | ||
469 | return QStringLiteral("windowsystem/multiple-winids"); | 466 | return QStringLiteral("windowsystem/multiple-winids"); | ||
470 | } | 467 | } | ||
471 | 468 | | |||
472 | QUrl XWindowTasksModel::Private::windowUrl(WId window) | 469 | QUrl XWindowTasksModel::Private::windowUrl(WId window) | ||
473 | { | 470 | { | ||
474 | QUrl url; | | |||
475 | | ||||
476 | const KWindowInfo *info = windowInfo(window); | 471 | const KWindowInfo *info = windowInfo(window); | ||
477 | 472 | | |||
478 | QString desktopFile = QString::fromUtf8(info->desktopFileName()); | 473 | QString desktopFile = QString::fromUtf8(info->desktopFileName()); | ||
479 | 474 | | |||
480 | if (!desktopFile.isEmpty()) { | 475 | if (!desktopFile.isEmpty()) { | ||
481 | KService::Ptr service = KService::serviceByStorageId(desktopFile); | 476 | KService::Ptr service = KService::serviceByStorageId(desktopFile); | ||
482 | 477 | | |||
483 | if (service) { | 478 | if (service) { | ||
484 | return QUrl::fromLocalFile(service->entryPath()); | 479 | return QUrl::fromLocalFile(service->entryPath()); | ||
485 | } | 480 | } | ||
486 | 481 | | |||
487 | if (!desktopFile.endsWith(QLatin1String(".desktop"))) { | 482 | if (!desktopFile.endsWith(QLatin1String(".desktop"))) { | ||
488 | desktopFile.append(QLatin1String(".desktop")); | 483 | desktopFile.append(QLatin1String(".desktop")); | ||
489 | } | 484 | } | ||
490 | 485 | | |||
491 | if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) { | 486 | if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) { | ||
492 | return QUrl::fromLocalFile(desktopFile); | 487 | return QUrl::fromLocalFile(desktopFile); | ||
493 | } | 488 | } | ||
494 | } | 489 | } | ||
495 | 490 | | |||
496 | const QString &classClass = info->windowClassClass(); | 491 | return windowUrlFromMetadata(info->windowClassClass(), | ||
497 | const QString &className = info->windowClassName(); | 492 | NETWinInfo(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMPid, 0).pid(), | ||
498 | 493 | rulesConfig, info->windowClassName()); | |||
499 | KService::List services; | | |||
500 | bool triedPid = false; | | |||
501 | | ||||
502 | if (!(classClass.isEmpty() && className.isEmpty())) { | | |||
503 | int pid = NETWinInfo(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMPid, 0).pid(); | | |||
504 | | ||||
505 | // For KCModules, if we matched on window class, etc, we would end up matching | | |||
506 | // to kcmshell5 itself - but we are more than likely interested in the actual | | |||
507 | // control module. Therefore we obtain this via the commandline. This commandline | | |||
508 | // may contain "kdeinit4:" or "[kdeinit]", so we remove these first. | | |||
509 | if (classClass == "kcmshell5") { | | |||
510 | url = serviceUrl(pid, QStringLiteral("KCModule"), QStringList() << QStringLiteral("kdeinit5:") << QStringLiteral("[kdeinit]")); | | |||
511 | | ||||
512 | if (!url.isEmpty()) { | | |||
513 | return url; | | |||
514 | } | | |||
515 | } | | |||
516 | | ||||
517 | // Check to see if this wmClass matched a saved one ... | | |||
518 | KConfigGroup grp(rulesConfig, "Mapping"); | | |||
519 | KConfigGroup set(rulesConfig, "Settings"); | | |||
520 | | ||||
521 | // Evaluate MatchCommandLineFirst directives from config first. | | |||
522 | // Some apps have different launchers depending upon command line ... | | |||
523 | QStringList matchCommandLineFirst = set.readEntry("MatchCommandLineFirst", QStringList()); | | |||
524 | | ||||
525 | if (!classClass.isEmpty() && matchCommandLineFirst.contains(classClass)) { | | |||
526 | triedPid = true; | | |||
527 | services = servicesFromPid(pid); | | |||
528 | } | | |||
529 | | ||||
530 | // Try to match using className also. | | |||
531 | if (!className.isEmpty() && matchCommandLineFirst.contains("::"+className)) { | | |||
532 | triedPid = true; | | |||
533 | services = servicesFromPid(pid); | | |||
534 | } | | |||
535 | | ||||
536 | if (!classClass.isEmpty()) { | | |||
537 | // Evaluate any mapping rules that map to a specific .desktop file. | | |||
538 | QString mapped(grp.readEntry(classClass + "::" + className, QString())); | | |||
539 | | ||||
540 | if (mapped.endsWith(QLatin1String(".desktop"))) { | | |||
541 | url = QUrl(mapped); | | |||
542 | return url; | | |||
543 | } | | |||
544 | | ||||
545 | if (mapped.isEmpty()) { | | |||
546 | mapped = grp.readEntry(classClass, QString()); | | |||
547 | | ||||
548 | if (mapped.endsWith(QLatin1String(".desktop"))) { | | |||
549 | url = QUrl(mapped); | | |||
550 | return url; | | |||
551 | } | | |||
552 | } | | |||
553 | | ||||
554 | // Some apps, such as Wine, cannot use className to map to launcher name - as Wine itself is not a GUI app | | |||
555 | // So, Settings/ManualOnly lists window classes where the user will always have to manualy set the launcher ... | | |||
556 | QStringList manualOnly = set.readEntry("ManualOnly", QStringList()); | | |||
557 | | ||||
558 | if (!classClass.isEmpty() && manualOnly.contains(classClass)) { | | |||
559 | return url; | | |||
560 | } | | |||
561 | | ||||
562 | // Try matching both WM_CLASS instance and general class against StartupWMClass. | | |||
563 | // We do this before evaluating the mapping rules further, because StartupWMClass | | |||
564 | // is essentially a mapping rule, and we expect it to be set deliberately and | | |||
565 | // sensibly to instruct us what to do. Also, mapping rules | | |||
566 | // | | |||
567 | // StartupWMClass=STRING | | |||
568 | // | | |||
569 | // If true, it is KNOWN that the application will map at least one | | |||
570 | // window with the given string as its WM class or WM name hint. | | |||
571 | // | | |||
572 | // Source: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-0.1.txt | | |||
573 | if (services.empty()) { | | |||
574 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(classClass)); | | |||
575 | } | | |||
576 | | ||||
577 | if (services.empty()) { | | |||
578 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(className)); | | |||
579 | } | | |||
580 | | ||||
581 | // Evaluate rewrite rules from config. | | |||
582 | if (services.empty()) { | | |||
583 | KConfigGroup rewriteRulesGroup(rulesConfig, QStringLiteral("Rewrite Rules")); | | |||
584 | if (rewriteRulesGroup.hasGroup(classClass)) { | | |||
585 | KConfigGroup rewriteGroup(&rewriteRulesGroup, classClass); | | |||
586 | | ||||
587 | const QStringList &rules = rewriteGroup.groupList(); | | |||
588 | for (const QString &rule : rules) { | | |||
589 | KConfigGroup ruleGroup(&rewriteGroup, rule); | | |||
590 | | ||||
591 | const QString propertyConfig = ruleGroup.readEntry(QStringLiteral("Property"), QString()); | | |||
592 | | ||||
593 | QString matchProperty; | | |||
594 | if (propertyConfig == QLatin1String("ClassClass")) { | | |||
595 | matchProperty = classClass; | | |||
596 | } else if (propertyConfig == QLatin1String("ClassName")) { | | |||
597 | matchProperty = className; | | |||
598 | } | | |||
599 | | ||||
600 | if (matchProperty.isEmpty()) { | | |||
601 | continue; | | |||
602 | } | | |||
603 | | ||||
604 | const QString serviceSearchIdentifier = ruleGroup.readEntry(QStringLiteral("Identifier"), QString()); | | |||
605 | if (serviceSearchIdentifier.isEmpty()) { | | |||
606 | continue; | | |||
607 | } | | |||
608 | | ||||
609 | QRegularExpression regExp(ruleGroup.readEntry(QStringLiteral("Match"))); | | |||
610 | const auto match = regExp.match(matchProperty); | | |||
611 | | ||||
612 | if (match.hasMatch()) { | | |||
613 | const QString actualMatch = match.captured(QStringLiteral("match")); | | |||
614 | if (actualMatch.isEmpty()) { | | |||
615 | continue; | | |||
616 | } | | |||
617 | | ||||
618 | QString rewrittenString = ruleGroup.readEntry(QStringLiteral("Target")).arg(actualMatch); | | |||
619 | // If no "Target" is provided, instead assume the matched property (ClassClass/ClassName). | | |||
620 | if (rewrittenString.isEmpty()) { | | |||
621 | rewrittenString = matchProperty; | | |||
622 | } | | |||
623 | | ||||
624 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ %2)").arg(rewrittenString, serviceSearchIdentifier)); | | |||
625 | | ||||
626 | if (!services.isEmpty()) { | | |||
627 | break; | | |||
628 | } | | |||
629 | } | | |||
630 | } | | |||
631 | } | | |||
632 | } | | |||
633 | | ||||
634 | // Try matching mapped name against DesktopEntryName. | | |||
635 | if (!mapped.isEmpty() && services.empty()) { | | |||
636 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName)").arg(mapped)); | | |||
637 | } | | |||
638 | | ||||
639 | // Try matching mapped name against 'Name'. | | |||
640 | if (!mapped.isEmpty() && services.empty()) { | | |||
641 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(mapped)); | | |||
642 | } | | |||
643 | | ||||
644 | // Try matching WM_CLASS general class against DesktopEntryName. | | |||
645 | if (services.empty()) { | | |||
646 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName)").arg(classClass)); | | |||
647 | } | | |||
648 | | ||||
649 | // Try matching WM_CLASS general class against 'Name'. | | |||
650 | // This has a shaky chance of success as WM_CLASS is untranslated, but 'Name' may be localized. | | |||
651 | if (services.empty()) { | | |||
652 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Name) and (not exist NoDisplay or not NoDisplay)").arg(classClass)); | | |||
653 | } | | |||
654 | } | | |||
655 | | ||||
656 | // Ok, absolute *last* chance, try matching via pid (but only if we have not already tried this!) ... | | |||
657 | if (services.empty() && !triedPid) { | | |||
658 | services = servicesFromPid(pid); | | |||
659 | } | | |||
660 | } | | |||
661 | | ||||
662 | // Try to improve on a possible from-binary fallback. | | |||
663 | // If no services were found or we got a fake-service back from getServicesViaPid() | | |||
664 | // we attempt to improve on this by adding a loosely matched reverse-domain-name | | |||
665 | // DesktopEntryName. Namely anything that is '*.classClass.desktop' would qualify here. | | |||
666 | // | | |||
667 | // Illustrative example of a case where the above heuristics would fail to produce | | |||
668 | // a reasonable result: | | |||
669 | // - org.kde.dragonplayer.desktop | | |||
670 | // - binary is 'dragon' | | |||
671 | // - qapp appname and thus classClass is 'dragonplayer' | | |||
672 | // - classClass cannot directly match the desktop file because of RDN | | |||
673 | // - classClass also cannot match the binary because of name mismatch | | |||
674 | // - in the following code *.classClass can match org.kde.dragonplayer though | | |||
675 | if (services.empty() || services.at(0)->desktopEntryName().isEmpty()) { | | |||
676 | auto matchingServices = KServiceTypeTrader::self()->query(QStringLiteral("Application"), | | |||
677 | QStringLiteral("exist Exec and ('%1' ~~ DesktopEntryName)").arg(classClass)); | | |||
678 | QMutableListIterator<KService::Ptr> it(matchingServices); | | |||
679 | while (it.hasNext()) { | | |||
680 | auto service = it.next(); | | |||
681 | if (!service->desktopEntryName().endsWith("." + classClass)) { | | |||
682 | it.remove(); | | |||
683 | } | | |||
684 | } | | |||
685 | // Exactly one match is expected, otherwise we discard the results as to reduce | | |||
686 | // the likelihood of false-positive mappings. Since we essentially eliminate the | | |||
687 | // uniqueness that RDN is meant to bring to the table we could potentially end | | |||
688 | // up with more than one match here. | | |||
689 | if (matchingServices.length() == 1) { | | |||
690 | services = matchingServices; | | |||
691 | } | | |||
692 | } | | |||
693 | | ||||
694 | if (!services.empty()) { | | |||
695 | QString path = services[0]->entryPath(); | | |||
696 | if (path.isEmpty()) { | | |||
697 | path = services[0]->exec(); | | |||
698 | } | | |||
699 | | ||||
700 | if (!path.isEmpty()) { | | |||
701 | url = QUrl::fromLocalFile(path); | | |||
702 | } | | |||
703 | } | | |||
704 | | ||||
705 | return url; | | |||
706 | } | 494 | } | ||
707 | 495 | | |||
708 | QUrl XWindowTasksModel::Private::launcherUrl(WId window, bool encodeFallbackIcon) | 496 | QUrl XWindowTasksModel::Private::launcherUrl(WId window, bool encodeFallbackIcon) | ||
709 | { | 497 | { | ||
710 | const AppData &data = appData(window); | 498 | const AppData &data = appData(window); | ||
711 | 499 | | |||
712 | if (!encodeFallbackIcon || !data.icon.name().isEmpty()) { | 500 | if (!encodeFallbackIcon || !data.icon.name().isEmpty()) { | ||
713 | return data.url; | 501 | return data.url; | ||
Show All 17 Lines | |||||
731 | pixmap.save(&buffer, "PNG"); | 519 | pixmap.save(&buffer, "PNG"); | ||
732 | uQuery.addQueryItem(QStringLiteral("iconData"), bytes.toBase64(QByteArray::Base64UrlEncoding)); | 520 | uQuery.addQueryItem(QStringLiteral("iconData"), bytes.toBase64(QByteArray::Base64UrlEncoding)); | ||
733 | 521 | | |||
734 | url.setQuery(uQuery); | 522 | url.setQuery(uQuery); | ||
735 | 523 | | |||
736 | return url; | 524 | return url; | ||
737 | } | 525 | } | ||
738 | 526 | | |||
739 | QUrl XWindowTasksModel::Private::serviceUrl(int pid, const QString &type, const QStringList &cmdRemovals = QStringList()) | | |||
740 | { | | |||
741 | if (pid == 0) { | | |||
742 | return QUrl(); | | |||
743 | } | | |||
744 | | ||||
745 | KSysGuard::Processes procs; | | |||
746 | procs.updateOrAddProcess(pid); | | |||
747 | | ||||
748 | KSysGuard::Process *proc = procs.getProcess(pid); | | |||
749 | QString cmdline = proc ? proc->command().simplified() : QString(); // proc->command has a trailing space??? | | |||
750 | | ||||
751 | if (cmdline.isEmpty()) { | | |||
752 | return QUrl(); | | |||
753 | } | | |||
754 | | ||||
755 | foreach (const QString & r, cmdRemovals) { | | |||
756 | cmdline.replace(r, QLatin1String("")); | | |||
757 | } | | |||
758 | | ||||
759 | KService::List services = KServiceTypeTrader::self()->query(type, QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdline)); | | |||
760 | | ||||
761 | if (services.empty()) { | | |||
762 | // Could not find with complete command line, so strip out path part ... | | |||
763 | int slash = cmdline.lastIndexOf('/', cmdline.indexOf(' ')); | | |||
764 | if (slash > 0) { | | |||
765 | services = KServiceTypeTrader::self()->query(type, QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdline.mid(slash + 1))); | | |||
766 | } | | |||
767 | | ||||
768 | if (services.empty()) { | | |||
769 | return QUrl(); | | |||
770 | } | | |||
771 | } | | |||
772 | | ||||
773 | if (!services.isEmpty()) { | | |||
774 | QString path = services[0]->entryPath(); | | |||
775 | | ||||
776 | if (!QDir::isAbsolutePath(path)) { | | |||
777 | QString absolutePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kservices5/"+path); | | |||
778 | if (!absolutePath.isEmpty()) | | |||
779 | path = absolutePath; | | |||
780 | } | | |||
781 | | ||||
782 | if (QFile::exists(path)) { | | |||
783 | return QUrl::fromLocalFile(path); | | |||
784 | } | | |||
785 | } | | |||
786 | | ||||
787 | return QUrl(); | | |||
788 | } | | |||
789 | | ||||
790 | KService::List XWindowTasksModel::Private::servicesFromPid(int pid) | | |||
791 | { | | |||
792 | if (pid == 0) { | | |||
793 | return KService::List(); | | |||
794 | } | | |||
795 | | ||||
796 | KSysGuard::Processes procs; | | |||
797 | procs.updateOrAddProcess(pid); | | |||
798 | | ||||
799 | KSysGuard::Process *proc = procs.getProcess(pid); | | |||
800 | const QString &cmdLine = proc ? proc->command().simplified() : QString(); // proc->command has a trailing space??? | | |||
801 | | ||||
802 | if (cmdLine.isEmpty()) { | | |||
803 | return KService::List(); | | |||
804 | } | | |||
805 | | ||||
806 | return servicesFromCmdLine(cmdLine, proc->name()); | | |||
807 | } | | |||
808 | | ||||
809 | KService::List XWindowTasksModel::Private::servicesFromCmdLine(const QString &_cmdLine, const QString &processName) | | |||
810 | { | | |||
811 | QString cmdLine = _cmdLine; | | |||
812 | KService::List services; | | |||
813 | | ||||
814 | const int firstSpace = cmdLine.indexOf(' '); | | |||
815 | int slash = 0; | | |||
816 | | ||||
817 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine)); | | |||
818 | | ||||
819 | if (services.empty()) { | | |||
820 | // Could not find with complete command line, so strip out the path part ... | | |||
821 | slash = cmdLine.lastIndexOf('/', firstSpace); | | |||
822 | | ||||
823 | if (slash > 0) { | | |||
824 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine.mid(slash + 1))); | | |||
825 | } | | |||
826 | } | | |||
827 | | ||||
828 | if (services.empty() && firstSpace > 0) { | | |||
829 | // Could not find with arguments, so try without ... | | |||
830 | cmdLine = cmdLine.left(firstSpace); | | |||
831 | | ||||
832 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine)); | | |||
833 | | ||||
834 | if (services.empty()) { | | |||
835 | slash = cmdLine.lastIndexOf('/'); | | |||
836 | | ||||
837 | if (slash > 0) { | | |||
838 | services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and ('%1' =~ Exec)").arg(cmdLine.mid(slash + 1))); | | |||
839 | } | | |||
840 | } | | |||
841 | } | | |||
842 | | ||||
843 | if (services.empty()) { | | |||
844 | KConfigGroup set(rulesConfig, "Settings"); | | |||
845 | const QStringList &runtimes = set.readEntry("TryIgnoreRuntimes", QStringList()); | | |||
846 | | ||||
847 | bool ignore = runtimes.contains(cmdLine); | | |||
848 | | ||||
849 | if (!ignore && slash > 0) { | | |||
850 | ignore = runtimes.contains(cmdLine.mid(slash + 1)); | | |||
851 | } | | |||
852 | | ||||
853 | if (ignore) { | | |||
854 | return servicesFromCmdLine(_cmdLine.mid(firstSpace + 1), processName); | | |||
855 | } | | |||
856 | } | | |||
857 | | ||||
858 | if (services.empty() && !processName.isEmpty() && !QStandardPaths::findExecutable(cmdLine).isEmpty()) { | | |||
859 | // cmdLine now exists without arguments if there were any. | | |||
860 | services << QExplicitlySharedDataPointer<KService>(new KService(processName, cmdLine, QString())); | | |||
861 | } | | |||
862 | | ||||
863 | return services; | | |||
864 | } | | |||
865 | | ||||
866 | bool XWindowTasksModel::Private::demandsAttention(WId window) | 527 | bool XWindowTasksModel::Private::demandsAttention(WId window) | ||
867 | { | 528 | { | ||
868 | if (windows.contains(window)) { | 529 | if (windows.contains(window)) { | ||
869 | return ((windowInfo(window)->hasState(NET::DemandsAttention)) | 530 | return ((windowInfo(window)->hasState(NET::DemandsAttention)) | ||
870 | || transientsDemandingAttention.contains(window)); | 531 | || transientsDemandingAttention.contains(window)); | ||
871 | } | 532 | } | ||
872 | 533 | | |||
873 | return false; | 534 | return false; | ||
▲ Show 20 Lines • Show All 528 Lines • Show Last 20 Lines |