Plasma-workspace

xwindowtasksmodel.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Eike Hein <hein@kde.org>
3 SPDX-FileCopyrightText: 2008 Aaron J. Seigo <aseigo@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "xwindowtasksmodel.h"
9#include "tasktools.h"
10#include "xwindowsystemeventbatcher.h"
11
12#include <KDesktopFile>
13#include <KDirWatch>
14#include <KIconLoader>
15#include <KService>
16#include <KSharedConfig>
17#include <KSycoca>
18#include <KWindowInfo>
19#include <KX11Extras>
20
21#include <QBuffer>
22#include <QDateTime>
23#include <QDir>
24#include <QFile>
25#include <QGuiApplication>
26#include <QIcon>
27#include <QSet>
28#include <QTimer>
29#include <QUrlQuery>
30
31#include <chrono>
32
33#include <X11/Xlib.h>
34
35using namespace std::chrono_literals;
36
37namespace X11Info
38{
39[[nodiscard]] inline auto display()
40{
41 return qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display();
42}
43
44[[nodiscard]] inline auto connection()
45{
46 return qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection();
47}
48
49[[nodiscard]] inline Window appRootWindow()
50{
51 return DefaultRootWindow(display());
52}
53}
54
55namespace TaskManager
56{
57static const NET::Properties windowInfoFlags =
58 NET::WMState | NET::XAWMState | NET::WMDesktop | NET::WMVisibleName | NET::WMGeometry | NET::WMFrameExtents | NET::WMWindowType | NET::WMPid;
59static const NET::Properties2 windowInfoFlags2 = NET::WM2DesktopFileName | NET::WM2Activities | NET::WM2WindowClass | NET::WM2AllowedActions
60 | NET::WM2AppMenuObjectPath | NET::WM2AppMenuServiceName | NET::WM2GTKApplicationId;
61
62class Q_DECL_HIDDEN XWindowTasksModel::Private
63{
64public:
65 Private(XWindowTasksModel *q);
66 ~Private();
67
68 QList<WId> windows;
69
70 // key=transient child, value=leader
71 QHash<WId, WId> transients;
72 // key=leader, values=transient children
73 QMultiHash<WId, WId> transientsDemandingAttention;
74
75 QHash<WId, KWindowInfo *> windowInfoCache;
76 QHash<WId, AppData> appDataCache;
77 QHash<WId, QRect> delegateGeometries;
78 QSet<WId> usingFallbackIcon;
79 QHash<WId, QDateTime> lastActivated;
80 QList<WId> cachedStackingOrder;
81 WId activeWindow = -1;
82 KSharedConfig::Ptr rulesConfig;
83 KDirWatch *configWatcher = nullptr;
84 QTimer sycocaChangeTimer;
85
86 void init();
87 void addWindow(WId window);
88 void removeWindow(WId window);
89 void windowChanged(WId window, NET::Properties properties, NET::Properties2 properties2);
90 void transientChanged(WId window, NET::Properties properties, NET::Properties2 properties2);
91 void dataChanged(WId window, const QList<int> &roles);
92
93 KWindowInfo *windowInfo(WId window);
94 const AppData &appData(WId window);
95 QString appMenuServiceName(WId window);
96 QString appMenuObjectPath(WId window);
97
98 QIcon icon(WId window);
99 static QString mimeType();
100 static QString groupMimeType();
101 QUrl windowUrl(WId window);
102 QUrl launcherUrl(WId window, bool encodeFallbackIcon = true);
103 bool demandsAttention(WId window);
104
105private:
106 XWindowTasksModel *const q;
107};
108
109XWindowTasksModel::Private::Private(XWindowTasksModel *q)
110 : q(q)
111{
112}
113
114XWindowTasksModel::Private::~Private()
115{
116 qDeleteAll(windowInfoCache);
117 windowInfoCache.clear();
118}
119
120void XWindowTasksModel::Private::init()
121{
122 auto clearCacheAndRefresh = [this] {
123 if (!windows.count()) {
124 return;
125 }
126
127 appDataCache.clear();
128
129 // Emit changes of all roles satisfied from app data cache.
130 Q_EMIT q->dataChanged(q->index(0, 0),
131 q->index(windows.count() - 1, 0),
132 QList<int>{Qt::DecorationRole,
133 AbstractTasksModel::AppId,
134 AbstractTasksModel::AppName,
135 AbstractTasksModel::GenericName,
136 AbstractTasksModel::LauncherUrl,
137 AbstractTasksModel::LauncherUrlWithoutIcon,
138 AbstractTasksModel::CanLaunchNewInstance,
139 AbstractTasksModel::SkipTaskbar});
140 };
141
142 cachedStackingOrder = KX11Extras::stackingOrder();
143
144 sycocaChangeTimer.setSingleShot(true);
145 sycocaChangeTimer.setInterval(100ms);
146
147 QObject::connect(&sycocaChangeTimer, &QTimer::timeout, q, clearCacheAndRefresh);
148
150 sycocaChangeTimer.start();
151 });
152
153 rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc"));
154 configWatcher = new KDirWatch(q);
155
156 for (const auto locations = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation); const QString &location : locations) {
157 configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc"));
158 }
159
160 auto rulesConfigChange = [this, clearCacheAndRefresh] {
161 rulesConfig->reparseConfiguration();
162 clearCacheAndRefresh();
163 };
164
165 QObject::connect(configWatcher, &KDirWatch::dirty, rulesConfigChange);
166 QObject::connect(configWatcher, &KDirWatch::created, rulesConfigChange);
167 QObject::connect(configWatcher, &KDirWatch::deleted, rulesConfigChange);
168
169 auto windowSystem = new XWindowSystemEventBatcher(q);
170
171 QObject::connect(windowSystem, &XWindowSystemEventBatcher::windowAdded, q, [this](WId window) {
172 addWindow(window);
173 });
174
175 QObject::connect(windowSystem, &XWindowSystemEventBatcher::windowRemoved, q, [this](WId window) {
176 removeWindow(window);
177 });
178
179 QObject::connect(windowSystem, &XWindowSystemEventBatcher::windowChanged, q, [this](WId window, NET::Properties properties, NET::Properties2 properties2) {
180 windowChanged(window, properties, properties2);
181 });
182
183 // Update IsActive for previously- and newly-active windows.
184 QObject::connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, q, [this](WId window) {
185 const WId oldActiveWindow = activeWindow;
186
187 const auto leader = transients.value(window, XCB_WINDOW_NONE);
188 if (leader != XCB_WINDOW_NONE) {
189 window = leader;
190 }
191
192 activeWindow = window;
193 lastActivated[activeWindow] = QDateTime::currentDateTime();
194
195 int row = windows.indexOf(oldActiveWindow);
196
197 if (row != -1) {
198 dataChanged(oldActiveWindow, QList<int>{IsActive});
199 }
200
201 row = windows.indexOf(window);
202
203 if (row != -1) {
204 dataChanged(window, QList<int>{IsActive});
205 }
206 });
207
208 QObject::connect(KX11Extras::self(), &KX11Extras::stackingOrderChanged, q, [this]() {
209 // No need to do anything if the model is empty. This avoids calling q->dataChanged with an invalid QModelIndex.
210 if (q->rowCount() == 0) {
211 return;
212 }
213 cachedStackingOrder = KX11Extras::stackingOrder();
214 Q_ASSERT(q->hasIndex(0, 0));
215 Q_ASSERT(q->hasIndex(q->rowCount() - 1, 0));
216 Q_EMIT q->dataChanged(q->index(0, 0), q->index(q->rowCount() - 1, 0), QList<int>{StackingOrder});
217 });
218
219 activeWindow = KX11Extras::activeWindow();
220
221 // Add existing windows.
222 for (const auto ids = KX11Extras::windows(); const WId window : ids) {
223 addWindow(window);
224 }
225}
226
227void XWindowTasksModel::Private::addWindow(WId window)
228{
229 // Don't add window twice.
230 if (windows.contains(window)) {
231 return;
232 }
233
234 KWindowInfo info(window, NET::WMWindowType | NET::WMState | NET::WMName | NET::WMVisibleName, NET::WM2TransientFor);
235
238
239 const WId leader = info.transientFor();
240
241 // Handle transient.
242 if (leader > 0 && leader != window && leader != X11Info::appRootWindow() && !transients.contains(window) && windows.contains(leader)) {
243 transients.insert(window, leader);
244
245 // Update demands attention state for leader.
246 if (info.hasState(NET::DemandsAttention) && windows.contains(leader)) {
247 transientsDemandingAttention.insert(leader, window);
248 dataChanged(leader, QList<int>{IsDemandingAttention});
249 }
250
251 return;
252 }
253
254 // Ignore NET::Tool and other special window types; they are not considered tasks.
255 if (wType != NET::Normal && wType != NET::Override && wType != NET::Unknown && wType != NET::Dialog && wType != NET::Utility) {
256 return;
257 }
258
259 const int count = windows.count();
260 q->beginInsertRows(QModelIndex(), count, count);
261 windows.append(window);
262 q->endInsertRows();
263}
264
265void XWindowTasksModel::Private::removeWindow(WId window)
266{
267 const int row = windows.indexOf(window);
268
269 if (row != -1) {
270 q->beginRemoveRows(QModelIndex(), row, row);
271 windows.removeAt(row);
272 transientsDemandingAttention.remove(window);
273 delete windowInfoCache.take(window);
274 appDataCache.remove(window);
275 delegateGeometries.remove(window);
276 usingFallbackIcon.remove(window);
277 lastActivated.remove(window);
278 q->endRemoveRows();
279 } else { // Could be a transient.
280 // Removing a transient might change the demands attention state of the leader.
281 if (transients.remove(window)) {
282 const WId leader = transientsDemandingAttention.key(window, XCB_WINDOW_NONE);
283
284 if (leader != XCB_WINDOW_NONE) {
285 transientsDemandingAttention.remove(leader, window);
286 dataChanged(leader, QList<int>{IsDemandingAttention});
287 }
288 }
289 }
290
291 if (activeWindow == window) {
292 activeWindow = -1;
293 }
294}
295
296void XWindowTasksModel::Private::transientChanged(WId window, NET::Properties properties, NET::Properties2 properties2)
297{
298 // Changes to a transient's state might change demands attention state for leader.
299 if (properties & (NET::WMState | NET::XAWMState)) {
300 const KWindowInfo info(window, NET::WMState | NET::XAWMState, NET::WM2TransientFor);
301 const WId leader = info.transientFor();
302
303 if (!windows.contains(leader)) {
304 return;
305 }
306
307 if (info.hasState(NET::DemandsAttention)) {
308 if (!transientsDemandingAttention.values(leader).contains(window)) {
309 transientsDemandingAttention.insert(leader, window);
310 dataChanged(leader, QList<int>{IsDemandingAttention});
311 }
312 } else if (transientsDemandingAttention.remove(leader, window)) {
313 dataChanged(leader, QList<int>{IsDemandingAttention});
314 }
315 // Leader might have changed.
316 } else if (properties2 & NET::WM2TransientFor) {
317 const KWindowInfo info(window, NET::WMState | NET::XAWMState, NET::WM2TransientFor);
318
319 if (info.hasState(NET::DemandsAttention)) {
320 const WId oldLeader = transientsDemandingAttention.key(window, XCB_WINDOW_NONE);
321
322 if (oldLeader != XCB_WINDOW_NONE) {
323 const WId leader = info.transientFor();
324
325 if (leader != oldLeader) {
326 transientsDemandingAttention.remove(oldLeader, window);
327 transientsDemandingAttention.insert(leader, window);
328 dataChanged(oldLeader, QList<int>{IsDemandingAttention});
329 dataChanged(leader, QList<int>{IsDemandingAttention});
330 }
331 }
332 }
333 }
334}
335
336void XWindowTasksModel::Private::windowChanged(WId window, NET::Properties properties, NET::Properties2 properties2)
337{
338 if (transients.contains(window)) {
339 transientChanged(window, properties, properties2);
340 return;
341 }
342
343 bool wipeInfoCache = false;
344 bool wipeAppDataCache = false;
345 bool wipeAppDataIcon = false;
346 QList<int> changedRoles;
347
348 if (properties & (NET::WMPid) || properties2 & (NET::WM2DesktopFileName | NET::WM2WindowClass)) {
349 wipeInfoCache = true;
350 wipeAppDataCache = true;
351 changedRoles << Qt::DecorationRole << AppId << AppName << GenericName << LauncherUrl << AppPid << SkipTaskbar << CanLaunchNewInstance;
352 }
353
354 if (properties & (NET::WMName | NET::WMVisibleName)) {
355 changedRoles << Qt::DisplayRole;
356 wipeInfoCache = true;
357 }
358
359 if ((properties & NET::WMIcon) && usingFallbackIcon.contains(window)) {
360 wipeAppDataIcon = true;
361 if (!changedRoles.contains(Qt::DecorationRole)) {
362 changedRoles << Qt::DecorationRole;
363 }
364 }
365
366 // FIXME TODO: It might be worth keeping track of which windows were demanding
367 // attention (or not) to avoid emitting this role on every state change, as
368 // TaskGroupingProxyModel needs to check for group-ability when a change to it
369 // is announced and the queried state is false.
370 if (properties & (NET::WMState | NET::XAWMState)) {
371 wipeInfoCache = true;
372 changedRoles << IsFullScreen << IsMaximized << IsMinimized << IsKeepAbove << IsKeepBelow;
373 changedRoles << IsShaded << IsDemandingAttention << SkipTaskbar << SkipPager;
374 }
375
376 if (properties & NET::WMWindowType) {
377 wipeInfoCache = true;
378 changedRoles << SkipTaskbar;
379 }
380
381 if (properties2 & NET::WM2AllowedActions) {
382 wipeInfoCache = true;
383 changedRoles << IsClosable << IsMovable << IsResizable << IsMaximizable << IsMinimizable;
384 changedRoles << IsFullScreenable << IsShadeable << IsVirtualDesktopsChangeable;
385 }
386
387 if (properties & NET::WMDesktop) {
388 wipeInfoCache = true;
389 changedRoles << VirtualDesktops << IsOnAllVirtualDesktops;
390 }
391
392 if (properties & NET::WMGeometry) {
393 wipeInfoCache = true;
394 changedRoles << Geometry << ScreenGeometry;
395 }
396
397 if (properties2 & NET::WM2Activities) {
398 wipeInfoCache = true;
399 changedRoles << Activities;
400 }
401
402 if (properties2 & NET::WM2AppMenuServiceName) {
403 wipeInfoCache = true;
404 changedRoles << ApplicationMenuServiceName;
405 }
406
407 if (properties2 & NET::WM2AppMenuObjectPath) {
408 wipeInfoCache = true;
409 changedRoles << ApplicationMenuObjectPath;
410 }
411
412 if (wipeInfoCache) {
413 delete windowInfoCache.take(window);
414 }
415
416 if (wipeAppDataCache) {
417 appDataCache.remove(window);
418 usingFallbackIcon.remove(window);
419 } else if (wipeAppDataIcon) {
420 // Only the icon changes
421 appDataCache[window].icon = QIcon();
422 }
423
424 if (!changedRoles.isEmpty()) {
425 dataChanged(window, changedRoles);
426 }
427}
428
429void XWindowTasksModel::Private::dataChanged(WId window, const QList<int> &roles)
430{
431 const int i = windows.indexOf(window);
432
433 if (i == -1) {
434 return;
435 }
436
437 QModelIndex idx = q->index(i);
438 Q_EMIT q->dataChanged(idx, idx, roles);
439}
440
441KWindowInfo *XWindowTasksModel::Private::windowInfo(WId window)
442{
443 const auto &it = windowInfoCache.constFind(window);
444
445 if (it != windowInfoCache.constEnd()) {
446 return *it;
447 }
448
449 KWindowInfo *info = new KWindowInfo(window, windowInfoFlags, windowInfoFlags2);
450 windowInfoCache.insert(window, info);
451
452 return info;
453}
454
455const AppData &XWindowTasksModel::Private::appData(WId window)
456{
457 static_assert(!std::is_trivially_copy_assignable_v<AppData>);
458 if (auto it = appDataCache.constFind(window); it != appDataCache.constEnd()) {
459 return *it;
460 }
461
462 AppData data = appDataFromUrl(windowUrl(window));
463
464 // If we weren't able to derive a launcher URL from the window meta data,
465 // fall back to WM_CLASS Class string as app id. This helps with apps we
466 // can't map to an URL due to existing outside the regular system
467 // environment, e.g. wine clients.
468 if (data.id.isEmpty() && data.url.isEmpty()) {
469 data.id = QString::fromLocal8Bit(windowInfo(window)->windowClassClass());
470 return *appDataCache.emplace(window, std::move(data));
471 }
472
473 return *appDataCache.emplace(window, std::move(data));
474}
475
476QString XWindowTasksModel::Private::appMenuServiceName(WId window)
477{
478 const KWindowInfo *info = windowInfo(window);
480}
481
482QString XWindowTasksModel::Private::appMenuObjectPath(WId window)
483{
484 const KWindowInfo *info = windowInfo(window);
486}
487
488QIcon XWindowTasksModel::Private::icon(WId window)
489{
490 const AppData &app = appData(window);
491
492 if (!app.icon.isNull()) {
493 return app.icon;
494 }
495
496 QIcon icon;
497
502
503 appDataCache[window].icon = icon;
504 usingFallbackIcon.insert(window);
505
506 return icon;
507}
508
509QString XWindowTasksModel::Private::mimeType()
510{
511 return QStringLiteral("windowsystem/winid");
512}
513
514QString XWindowTasksModel::Private::groupMimeType()
515{
516 return QStringLiteral("windowsystem/multiple-winids");
517}
518
519QUrl XWindowTasksModel::Private::windowUrl(WId window)
520{
521 const KWindowInfo *info = windowInfo(window);
522
523 QString desktopFile = QString::fromUtf8(info->desktopFileName());
524
525 if (desktopFile.isEmpty()) {
526 desktopFile = QString::fromUtf8(info->gtkApplicationId());
527 }
528
529 if (!desktopFile.isEmpty()) {
530 KService::Ptr service = KService::serviceByStorageId(desktopFile);
531
532 if (service) {
533 const QString &menuId = service->menuId();
534
535 // applications: URLs are used to refer to applications by their KService::menuId
536 // (i.e. .desktop file name) rather than the absolute path to a .desktop file.
537 if (!menuId.isEmpty()) {
538 return QUrl(QString(u"applications:" + menuId));
539 }
540
541 return QUrl::fromLocalFile(service->entryPath());
542 }
543
544 if (!desktopFile.endsWith(QLatin1String(".desktop"))) {
545 desktopFile.append(QLatin1String(".desktop"));
546 }
547
548 if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) {
549 return QUrl::fromLocalFile(desktopFile);
550 }
551 }
552
553 return windowUrlFromMetadata(QString::fromLocal8Bit(info->windowClassClass()), info->pid(), rulesConfig, QString::fromLocal8Bit(info->windowClassName()));
554}
555
556QUrl XWindowTasksModel::Private::launcherUrl(WId window, bool encodeFallbackIcon)
557{
558 const AppData &data = appData(window);
559
560 QUrl url = data.url;
561 if (!encodeFallbackIcon || !data.icon.name().isEmpty()) {
562 return url;
563 }
564
565 // Forego adding the window icon pixmap if the URL is otherwise empty.
566 if (!url.isValid()) {
567 return QUrl();
568 }
569
570 // Only serialize pixmap data if the window pixmap is actually being used.
571 // QIcon::name() used above only returns a themed icon name but nothing when
572 // the icon was created using an absolute path, as can be the case with, e.g.
573 // containerized apps.
574 if (!usingFallbackIcon.contains(window)) {
575 return url;
576 }
577
578 QPixmap pixmap;
579
580 if (!data.icon.isNull()) {
581 pixmap = data.icon.pixmap(KIconLoader::SizeLarge);
582 }
583
584 if (pixmap.isNull()) {
586 }
587
588 if (pixmap.isNull()) {
589 return data.url;
590 }
591 QUrlQuery uQuery(url);
592 QByteArray bytes;
593 QBuffer buffer(&bytes);
594 buffer.open(QIODevice::WriteOnly);
595 pixmap.save(&buffer, "PNG");
596 uQuery.addQueryItem(QStringLiteral("iconData"), QString::fromLatin1(bytes.toBase64(QByteArray::Base64UrlEncoding)));
597
598 url.setQuery(uQuery);
599
600 return url;
601}
602
603bool XWindowTasksModel::Private::demandsAttention(WId window)
604{
605 if (windows.contains(window)) {
606 return ((windowInfo(window)->hasState(NET::DemandsAttention)) || transientsDemandingAttention.contains(window));
607 }
608
609 return false;
610}
611
612XWindowTasksModel::XWindowTasksModel(QObject *parent)
613 : AbstractWindowTasksModel(parent)
614 , d(new Private(this))
615{
616 d->init();
617}
618
619XWindowTasksModel::~XWindowTasksModel()
620{
621}
622
623QVariant XWindowTasksModel::data(const QModelIndex &index, int role) const
624{
625 if (!index.isValid() || index.row() >= d->windows.count()) {
626 return QVariant();
627 }
628
629 const WId window = d->windows.at(index.row());
630
631 if (role == Qt::DisplayRole) {
632 return d->windowInfo(window)->visibleName();
633 } else if (role == Qt::DecorationRole) {
634 return d->icon(window);
635 } else if (role == AppId) {
636 return d->appData(window).id;
637 } else if (role == AppName) {
638 return d->appData(window).name;
639 } else if (role == GenericName) {
640 return d->appData(window).genericName;
641 } else if (role == LauncherUrl) {
642 return d->launcherUrl(window);
643 } else if (role == LauncherUrlWithoutIcon) {
644 return d->launcherUrl(window, false /* encodeFallbackIcon */);
645 } else if (role == WinIdList) {
646 return QVariantList() << window;
647 } else if (role == MimeType) {
648 return d->mimeType();
649 } else if (role == MimeData) {
650 return QByteArray(reinterpret_cast<const char *>(&window), sizeof(window));
651 } else if (role == IsWindow) {
652 return true;
653 } else if (role == IsActive) {
654 return (window == d->activeWindow);
655 } else if (role == IsClosable) {
656 return d->windowInfo(window)->actionSupported(NET::ActionClose);
657 } else if (role == IsMovable) {
658 return d->windowInfo(window)->actionSupported(NET::ActionMove);
659 } else if (role == IsResizable) {
660 return d->windowInfo(window)->actionSupported(NET::ActionResize);
661 } else if (role == IsMaximizable) {
662 return d->windowInfo(window)->actionSupported(NET::ActionMax);
663 } else if (role == IsMaximized) {
664 const KWindowInfo *info = d->windowInfo(window);
665 return info->hasState(NET::MaxHoriz) && info->hasState(NET::MaxVert);
666 } else if (role == IsMinimizable) {
667 return d->windowInfo(window)->actionSupported(NET::ActionMinimize);
668 } else if (role == IsMinimized) {
669 return d->windowInfo(window)->isMinimized();
670 } else if (role == IsHidden) {
671 return d->windowInfo(window)->hasState(NET::Hidden);
672 } else if (role == IsKeepAbove) {
673 return d->windowInfo(window)->hasState(NET::KeepAbove);
674 } else if (role == IsKeepBelow) {
675 return d->windowInfo(window)->hasState(NET::KeepBelow);
676 } else if (role == IsFullScreenable) {
677 return d->windowInfo(window)->actionSupported(NET::ActionFullScreen);
678 } else if (role == IsFullScreen) {
679 return d->windowInfo(window)->hasState(NET::FullScreen);
680 } else if (role == IsShadeable) {
681 return d->windowInfo(window)->actionSupported(NET::ActionShade);
682 } else if (role == IsShaded) {
683 return d->windowInfo(window)->hasState(NET::Shaded);
684 } else if (role == IsVirtualDesktopsChangeable) {
685 return d->windowInfo(window)->actionSupported(NET::ActionChangeDesktop);
686 } else if (role == VirtualDesktops) {
687 return QVariantList() << d->windowInfo(window)->desktop();
688 } else if (role == IsOnAllVirtualDesktops) {
689 return d->windowInfo(window)->onAllDesktops();
690 } else if (role == Geometry) {
691 // Both the topLeft position and the size belong to the non-scaled coordinate system
692 return d->windowInfo(window)->frameGeometry();
693 } else if (role == ScreenGeometry) {
694 // The topLeft position belongs to the non-scaled coordinate system
695 // The size belongs to the scaled coordinate system, which means the size is already divided by DPR
696 return screenGeometry(d->windowInfo(window)->frameGeometry().center());
697 } else if (role == Activities) {
698 return d->windowInfo(window)->activities();
699 } else if (role == IsDemandingAttention) {
700 return d->demandsAttention(window);
701 } else if (role == SkipTaskbar) {
702 const KWindowInfo *info = d->windowInfo(window);
703 // _NET_WM_WINDOW_TYPE_UTILITY type windows should not be on task bars,
704 // but they should be shown on pagers.
705 return (info->hasState(NET::SkipTaskbar) || info->windowType(NET::UtilityMask) == NET::Utility || d->appData(window).skipTaskbar);
706 } else if (role == SkipPager) {
707 return d->windowInfo(window)->hasState(NET::SkipPager);
708 } else if (role == AppPid) {
709 return d->windowInfo(window)->pid();
710 } else if (role == StackingOrder) {
711 return d->cachedStackingOrder.indexOf(window);
712 } else if (role == LastActivated) {
713 if (d->lastActivated.contains(window)) {
714 return d->lastActivated.value(window);
715 }
716 } else if (role == ApplicationMenuObjectPath) {
717 return d->appMenuObjectPath(window);
718 } else if (role == ApplicationMenuServiceName) {
719 return d->appMenuServiceName(window);
720 } else if (role == CanLaunchNewInstance) {
721 return canLauchNewInstance(d->appData(window));
722 }
723
724 return AbstractTasksModel::data(index, role);
725}
726
727int XWindowTasksModel::rowCount(const QModelIndex &parent) const
728{
729 return parent.isValid() ? 0 : d->windows.count();
730}
731
732void XWindowTasksModel::requestActivate(const QModelIndex &index)
733{
734 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
735 return;
736 }
737
738 if (index.row() >= 0 && index.row() < d->windows.count()) {
739 WId window = d->windows.at(index.row());
740
741 // Pull forward any transient demanding attention.
742 if (d->transientsDemandingAttention.contains(window)) {
743 window = d->transientsDemandingAttention.value(window);
744 // Quote from legacy libtaskmanager:
745 // "this is a work around for (at least?) kwin where a shaded transient will prevent the main
746 // window from being brought forward unless the transient is actually pulled forward, most
747 // easily reproduced by opening a modal file open/save dialog on an app then shading the file
748 // dialog and trying to bring the window forward by clicking on it in a tasks widget
749 // TODO: do we need to check all the transients for shaded?"
750 } else if (!d->transients.isEmpty()) {
751 const auto transients = d->transients.keys(window);
752 for (const auto transient : std::as_const(transients)) {
753 KWindowInfo info(transient, NET::WMState, NET::WM2TransientFor);
754 if (info.valid(true) && info.hasState(NET::Shaded)) {
755 window = transient;
756 break;
757 }
758 }
759 }
760
762 }
763}
764
765void XWindowTasksModel::requestNewInstance(const QModelIndex &index)
766{
767 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
768 return;
769 }
770
771 runApp(d->appData(d->windows.at(index.row())));
772}
773
774void XWindowTasksModel::requestOpenUrls(const QModelIndex &index, const QList<QUrl> &urls)
775{
776 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count() || urls.isEmpty()) {
777 return;
778 }
779
780 runApp(d->appData(d->windows.at(index.row())), urls);
781}
782
783void XWindowTasksModel::requestClose(const QModelIndex &index)
784{
785 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
786 return;
787 }
788
789 NETRootInfo ri(X11Info::connection(), NET::CloseWindow);
790 ri.closeWindowRequest(d->windows.at(index.row()));
791}
792
793void XWindowTasksModel::requestMove(const QModelIndex &index)
794{
795 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
796 return;
797 }
798
799 const WId window = d->windows.at(index.row());
800 const KWindowInfo *info = d->windowInfo(window);
801
802 bool onCurrent = info->isOnCurrentDesktop();
803
804 if (!onCurrent) {
807 }
808
809 if (info->isMinimized()) {
811 }
812
813 const QRect &geom = info->geometry();
814
815 NETRootInfo ri(X11Info::connection(), NET::WMMoveResize);
816 ri.moveResizeRequest(window, geom.center().x(), geom.center().y(), NET::Move, XCB_BUTTON_INDEX_ANY);
817}
818
819void XWindowTasksModel::requestResize(const QModelIndex &index)
820{
821 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
822 return;
823 }
824
825 const WId window = d->windows.at(index.row());
826 const KWindowInfo *info = d->windowInfo(window);
827
828 bool onCurrent = info->isOnCurrentDesktop();
829
830 if (!onCurrent) {
833 }
834
835 if (info->isMinimized()) {
837 }
838
839 const QRect &geom = info->geometry();
840
841 NETRootInfo ri(X11Info::connection(), NET::WMMoveResize);
842 ri.moveResizeRequest(window, geom.bottomRight().x(), geom.bottomRight().y(), NET::BottomRight, XCB_BUTTON_INDEX_ANY);
843}
844
845void XWindowTasksModel::requestToggleMinimized(const QModelIndex &index)
846{
847 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
848 return;
849 }
850
851 const WId window = d->windows.at(index.row());
852 const KWindowInfo *info = d->windowInfo(window);
853
854 if (index.data(AbstractTasksModel::IsHidden).toBool()) {
855 bool onCurrent = info->isOnCurrentDesktop();
856
857 // FIXME: Move logic up into proxy? (See also others.)
858 if (!onCurrent) {
860 }
861
863
864 if (onCurrent) {
866 }
867 } else {
869 }
870}
871
872void XWindowTasksModel::requestToggleMaximized(const QModelIndex &index)
873{
874 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
875 return;
876 }
877
878 const WId window = d->windows.at(index.row());
879 const KWindowInfo *info = d->windowInfo(window);
880 bool onCurrent = info->isOnCurrentDesktop();
881 bool restore = (info->hasState(NET::MaxHoriz) && info->hasState(NET::MaxVert));
882
883 // FIXME: Move logic up into proxy? (See also others.)
884 if (!onCurrent) {
886 }
887
888 if (info->isMinimized()) {
890 }
891
892 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::WMState, NET::Properties2());
893
894 if (restore) {
896 } else {
898 }
899
900 if (!onCurrent) {
902 }
903}
904
905void XWindowTasksModel::requestToggleKeepAbove(const QModelIndex &index)
906{
907 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
908 return;
909 }
910
911 const WId window = d->windows.at(index.row());
912 const KWindowInfo *info = d->windowInfo(window);
913
914 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::WMState, NET::Properties2());
915
916 if (info->hasState(NET::KeepAbove)) {
918 } else {
920 }
921}
922
923void XWindowTasksModel::requestToggleKeepBelow(const QModelIndex &index)
924{
925 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
926 return;
927 }
928
929 const WId window = d->windows.at(index.row());
930 const KWindowInfo *info = d->windowInfo(window);
931
932 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::WMState, NET::Properties2());
933
934 if (info->hasState(NET::KeepBelow)) {
936 } else {
938 }
939}
940
941void XWindowTasksModel::requestToggleFullScreen(const QModelIndex &index)
942{
943 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
944 return;
945 }
946
947 const WId window = d->windows.at(index.row());
948 const KWindowInfo *info = d->windowInfo(window);
949
950 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::WMState, NET::Properties2());
951
952 if (info->hasState(NET::FullScreen)) {
954 } else {
956 }
957}
958
959void XWindowTasksModel::requestToggleShaded(const QModelIndex &index)
960{
961 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
962 return;
963 }
964
965 const WId window = d->windows.at(index.row());
966 const KWindowInfo *info = d->windowInfo(window);
967
968 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::WMState, NET::Properties2());
969
970 if (info->hasState(NET::Shaded)) {
972 } else {
974 }
975}
976
977void XWindowTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops)
978{
979 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
980 return;
981 }
982
983 int desktop = 0;
984
985 if (!desktops.isEmpty()) {
986 bool ok = false;
987
988 desktop = desktops.first().toUInt(&ok);
989
990 if (!ok) {
991 return;
992 }
993 }
994
995 if (desktop > KX11Extras::numberOfDesktops()) {
996 return;
997 }
998
999 const WId window = d->windows.at(index.row());
1000 const KWindowInfo *info = d->windowInfo(window);
1001
1002 if (desktop == 0) {
1003 if (info->onAllDesktops()) {
1006 } else {
1007 KX11Extras::setOnAllDesktops(window, true);
1008 }
1009
1010 return;
1011 }
1012
1013 KX11Extras::setOnDesktop(window, desktop);
1014
1015 if (desktop == KX11Extras::currentDesktop()) {
1017 }
1018}
1019
1020void XWindowTasksModel::requestNewVirtualDesktop(const QModelIndex &index)
1021{
1022 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
1023 return;
1024 }
1025
1026 const WId window = d->windows.at(index.row());
1027 const int desktop = KX11Extras::numberOfDesktops() + 1;
1028
1029 // FIXME Arbitrary limit of 20 copied from old code.
1030 if (desktop > 20) {
1031 return;
1032 }
1033
1034 NETRootInfo ri(X11Info::connection(), NET::NumberOfDesktops);
1035 ri.setNumberOfDesktops(desktop);
1036
1037 KX11Extras::setOnDesktop(window, desktop);
1038}
1039
1040void XWindowTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities)
1041{
1042 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
1043 return;
1044 }
1045
1046 const WId window = d->windows.at(index.row());
1047
1048 KX11Extras::setOnActivities(window, activities);
1049}
1050
1051void XWindowTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate)
1052{
1053 Q_UNUSED(delegate)
1054
1055 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
1056 return;
1057 }
1058
1059 const WId window = d->windows.at(index.row());
1060
1061 if (d->delegateGeometries.contains(window) && d->delegateGeometries.value(window) == geometry) {
1062 return;
1063 }
1064
1065 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::Properties(), NET::Properties2());
1066 NETRect rect;
1067
1068 if (geometry.isValid()) {
1069 rect.pos.x = geometry.x();
1070 rect.pos.y = geometry.y();
1071 rect.size.width = geometry.width();
1072 rect.size.height = geometry.height();
1073
1074 d->delegateGeometries.insert(window, geometry);
1075 } else {
1076 d->delegateGeometries.remove(window);
1077 }
1078
1079 ni.setIconGeometry(rect);
1080}
1081
1082WId XWindowTasksModel::winIdFromMimeData(const QMimeData *mimeData, bool *ok)
1083{
1084 Q_ASSERT(mimeData);
1085
1086 if (ok) {
1087 *ok = false;
1088 }
1089
1090 if (!mimeData->hasFormat(Private::mimeType())) {
1091 return 0;
1092 }
1093
1094 QByteArray data(mimeData->data(Private::mimeType()));
1095 WId id;
1096 if (data.size() != sizeof(WId)) {
1097 return 0;
1098 } else {
1099 memcpy(&id, data.data(), sizeof(WId));
1100 }
1101
1102 if (ok) {
1103 *ok = true;
1104 }
1105
1106 return id;
1107}
1108
1109QList<WId> XWindowTasksModel::winIdsFromMimeData(const QMimeData *mimeData, bool *ok)
1110{
1111 Q_ASSERT(mimeData);
1112 QList<WId> ids;
1113
1114 if (ok) {
1115 *ok = false;
1116 }
1117
1118 if (!mimeData->hasFormat(Private::groupMimeType())) {
1119 // Try to extract single window id.
1120 bool singularOk;
1121 WId id = winIdFromMimeData(mimeData, &singularOk);
1122
1123 if (ok) {
1124 *ok = singularOk;
1125 }
1126
1127 if (singularOk) {
1128 ids << id;
1129 }
1130
1131 return ids;
1132 }
1133
1134 QByteArray data(mimeData->data(Private::groupMimeType()));
1135 if ((unsigned int)data.size() < sizeof(int) + sizeof(WId)) {
1136 return ids;
1137 }
1138
1139 int count = 0;
1140 memcpy(&count, data.data(), sizeof(int));
1141 if (count < 1 || (unsigned int)data.size() < sizeof(int) + sizeof(WId) * count) {
1142 return ids;
1143 }
1144
1145 WId id;
1146 for (int i = 0; i < count; ++i) {
1147 memcpy(&id, data.data() + sizeof(int) + sizeof(WId) * i, sizeof(WId));
1148 ids << id;
1149 }
1150
1151 if (ok) {
1152 *ok = true;
1153 }
1154
1155 return ids;
1156}
1157
1158}
1159
1160#include "moc_xwindowtasksmodel.cpp"
static bool isDesktopFile(const QString &path)
void deleted(const QString &path)
void dirty(const QString &path)
void created(const QString &path)
static Ptr serviceByStorageId(const QString &_storageId)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
static KSycoca * self()
Q_SIGNAL void databaseChanged()
bool isMinimized() const
int pid() const
bool valid(bool withdrawn_is_valid=false) const
QByteArray gtkApplicationId() const
QByteArray windowClassName() const
bool isOnCurrentDesktop() const
bool hasState(NET::States s) const
QByteArray applicationMenuServiceName() const
bool onAllDesktops() const
QRect geometry() const
NET::WindowType windowType(NET::WindowTypes supported_types) const
QByteArray desktopFileName() const
int desktop() const
QByteArray windowClassClass() const
QByteArray applicationMenuObjectPath() const
static QPixmap icon(WId win, int width, int height, bool scale, int flags)
static void setOnAllDesktops(WId win, bool b)
void activeWindowChanged(WId id)
static void minimizeWindow(WId win)
static int currentDesktop()
static int numberOfDesktops()
static void setCurrentDesktop(int desktop)
static void unminimizeWindow(WId win)
static QList< WId > stackingOrder()
static WId activeWindow()
static Q_INVOKABLE void forceActiveWindow(QWindow *window, long time=0)
static QList< WId > windows()
static void setOnActivities(WId win, const QStringList &activities)
void stackingOrderChanged()
static void setOnDesktop(WId win, int desktop)
void moveResizeRequest(xcb_window_t window, int x_root, int y_root, Direction direction, xcb_button_t button=XCB_BUTTON_INDEX_ANY, RequestSource source=RequestSource::FromUnknown)
void closeWindowRequest(xcb_window_t window)
void setNumberOfDesktops(int numberOfDesktops)
void setState(NET::States state, NET::States mask)
void setIconGeometry(NETRect geometry)
DemandsAttention
SkipTaskbar
FullScreen
DialogMask
SplashMask
UtilityMask
OverrideMask
ToolbarMask
NormalMask
DesktopMask
TopMenuMask
NotificationMask
WindowType
KCALUTILS_EXPORT QString mimeType()
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
QVariant location(const QVariant &res)
QWidget * window(QObject *job)
char * data()
qsizetype size() const const
QByteArray toBase64(Base64Options options) const const
QDateTime currentDateTime()
bool exists() const const
void addPixmap(const QPixmap &pixmap, Mode mode, State state)
bool contains(const AT &value) const const
qsizetype indexOf(const AT &value, qsizetype from) const const
bool isEmpty() const const
QByteArray data(const QString &mimeType) const const
virtual bool hasFormat(const QString &mimeType) const const
QVariant data(int role) const const
bool isValid() const const
const QAbstractItemModel * model() const const
int row() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool isNull() const const
bool save(QIODevice *device, const char *format, int quality) const const
int x() const const
int y() const const
QPoint bottomRight() const const
QPoint center() const const
int height() const const
bool isValid() const const
int width() const const
int x() const const
int y() const const
QStringList standardLocations(StandardLocation type)
QString & append(QChar ch)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QString fromLocal8Bit(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
DecorationRole
void timeout()
QUrl fromLocalFile(const QString &localFile)
bool isValid() const const
void setQuery(const QString &query, ParsingMode mode)
QString url(FormattingOptions options) const const
bool toBool() const const
NETPoint pos
NETSize size
int height
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 20 2024 12:00:00 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.