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

KDE's Doxygen guidelines are available online.