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
155 for (const auto locations = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation); const QString &location : locations) {
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 for (const auto ids = KX11Extras::windows(); const WId window : ids) {
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 bool wipeAppDataIcon = false;
345 QList<int> changedRoles;
346
347 if (properties & (NET::WMPid) || properties2 & (NET::WM2DesktopFileName | NET::WM2WindowClass)) {
348 wipeInfoCache = true;
349 wipeAppDataCache = true;
350 changedRoles << Qt::DecorationRole << AppId << AppName << GenericName << LauncherUrl << AppPid << SkipTaskbar << CanLaunchNewInstance;
351 }
352
353 if (properties & (NET::WMName | NET::WMVisibleName)) {
354 changedRoles << Qt::DisplayRole;
355 wipeInfoCache = true;
356 }
357
358 if ((properties & NET::WMIcon) && usingFallbackIcon.contains(window)) {
359 wipeAppDataIcon = true;
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 } else if (wipeAppDataIcon) {
419 // Only the icon changes
420 appDataCache[window].icon = QIcon();
421 }
422
423 if (!changedRoles.isEmpty()) {
424 dataChanged(window, changedRoles);
425 }
426}
427
428void XWindowTasksModel::Private::dataChanged(WId window, const QList<int> &roles)
429{
430 const int i = windows.indexOf(window);
431
432 if (i == -1) {
433 return;
434 }
435
436 QModelIndex idx = q->index(i);
437 Q_EMIT q->dataChanged(idx, idx, roles);
438}
439
440KWindowInfo *XWindowTasksModel::Private::windowInfo(WId window)
441{
442 const auto &it = windowInfoCache.constFind(window);
443
444 if (it != windowInfoCache.constEnd()) {
445 return *it;
446 }
447
448 KWindowInfo *info = new KWindowInfo(window, windowInfoFlags, windowInfoFlags2);
449 windowInfoCache.insert(window, info);
450
451 return info;
452}
453
454const AppData &XWindowTasksModel::Private::appData(WId window)
455{
456 static_assert(!std::is_trivially_copy_assignable_v<AppData>);
457 if (auto it = appDataCache.constFind(window); it != appDataCache.constEnd()) {
458 return *it;
459 }
460
461 AppData data = appDataFromUrl(windowUrl(window));
462
463 // If we weren't able to derive a launcher URL from the window meta data,
464 // fall back to WM_CLASS Class string as app id. This helps with apps we
465 // can't map to an URL due to existing outside the regular system
466 // environment, e.g. wine clients.
467 if (data.id.isEmpty() && data.url.isEmpty()) {
468 data.id = QString::fromLocal8Bit(windowInfo(window)->windowClassClass());
469 return *appDataCache.emplace(window, std::move(data));
470 }
471
472 return *appDataCache.emplace(window, std::move(data));
473}
474
475QString XWindowTasksModel::Private::appMenuServiceName(WId window)
476{
477 const KWindowInfo *info = windowInfo(window);
479}
480
481QString XWindowTasksModel::Private::appMenuObjectPath(WId window)
482{
483 const KWindowInfo *info = windowInfo(window);
485}
486
487QIcon XWindowTasksModel::Private::icon(WId window)
488{
489 const AppData &app = appData(window);
490
491 if (!app.icon.isNull()) {
492 return app.icon;
493 }
494
495 QIcon icon;
496
501
502 appDataCache[window].icon = icon;
503 usingFallbackIcon.insert(window);
504
505 return icon;
506}
507
508QString XWindowTasksModel::Private::mimeType()
509{
510 return QStringLiteral("windowsystem/winid");
511}
512
513QString XWindowTasksModel::Private::groupMimeType()
514{
515 return QStringLiteral("windowsystem/multiple-winids");
516}
517
518QUrl XWindowTasksModel::Private::windowUrl(WId window)
519{
520 const KWindowInfo *info = windowInfo(window);
521
522 QString desktopFile = QString::fromUtf8(info->desktopFileName());
523
524 if (desktopFile.isEmpty()) {
525 desktopFile = QString::fromUtf8(info->gtkApplicationId());
526 }
527
528 if (!desktopFile.isEmpty()) {
529 KService::Ptr service = KService::serviceByStorageId(desktopFile);
530
531 if (service) {
532 const QString &menuId = service->menuId();
533
534 // applications: URLs are used to refer to applications by their KService::menuId
535 // (i.e. .desktop file name) rather than the absolute path to a .desktop file.
536 if (!menuId.isEmpty()) {
537 return QUrl(QString(u"applications:" + menuId));
538 }
539
540 return QUrl::fromLocalFile(service->entryPath());
541 }
542
543 if (!desktopFile.endsWith(QLatin1String(".desktop"))) {
544 desktopFile.append(QLatin1String(".desktop"));
545 }
546
547 if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) {
548 return QUrl::fromLocalFile(desktopFile);
549 }
550 }
551
552 return windowUrlFromMetadata(QString::fromLocal8Bit(info->windowClassClass()), info->pid(), rulesConfig, QString::fromLocal8Bit(info->windowClassName()));
553}
554
555QUrl XWindowTasksModel::Private::launcherUrl(WId window, bool encodeFallbackIcon)
556{
557 const AppData &data = appData(window);
558
559 QUrl url = data.url;
560 if (!encodeFallbackIcon || !data.icon.name().isEmpty()) {
561 return url;
562 }
563
564 // Forego adding the window icon pixmap if the URL is otherwise empty.
565 if (!url.isValid()) {
566 return QUrl();
567 }
568
569 // Only serialize pixmap data if the window pixmap is actually being used.
570 // QIcon::name() used above only returns a themed icon name but nothing when
571 // the icon was created using an absolute path, as can be the case with, e.g.
572 // containerized apps.
573 if (!usingFallbackIcon.contains(window)) {
574 return url;
575 }
576
577 QPixmap pixmap;
578
579 if (!data.icon.isNull()) {
580 pixmap = data.icon.pixmap(KIconLoader::SizeLarge);
581 }
582
583 if (pixmap.isNull()) {
585 }
586
587 if (pixmap.isNull()) {
588 return data.url;
589 }
590 QUrlQuery uQuery(url);
591 QByteArray bytes;
592 QBuffer buffer(&bytes);
593 buffer.open(QIODevice::WriteOnly);
594 pixmap.save(&buffer, "PNG");
595 uQuery.addQueryItem(QStringLiteral("iconData"), QString::fromLatin1(bytes.toBase64(QByteArray::Base64UrlEncoding)));
596
597 url.setQuery(uQuery);
598
599 return url;
600}
601
602bool XWindowTasksModel::Private::demandsAttention(WId window)
603{
604 if (windows.contains(window)) {
605 return ((windowInfo(window)->hasState(NET::DemandsAttention)) || transientsDemandingAttention.contains(window));
606 }
607
608 return false;
609}
610
611XWindowTasksModel::XWindowTasksModel(QObject *parent)
612 : AbstractWindowTasksModel(parent)
613 , d(new Private(this))
614{
615 d->init();
616}
617
618XWindowTasksModel::~XWindowTasksModel()
619{
620}
621
622QVariant XWindowTasksModel::data(const QModelIndex &index, int role) const
623{
624 if (!index.isValid() || index.row() >= d->windows.count()) {
625 return QVariant();
626 }
627
628 const WId window = d->windows.at(index.row());
629
630 if (role == Qt::DisplayRole) {
631 return d->windowInfo(window)->visibleName();
632 } else if (role == Qt::DecorationRole) {
633 return d->icon(window);
634 } else if (role == AppId) {
635 return d->appData(window).id;
636 } else if (role == AppName) {
637 return d->appData(window).name;
638 } else if (role == GenericName) {
639 return d->appData(window).genericName;
640 } else if (role == LauncherUrl) {
641 return d->launcherUrl(window);
642 } else if (role == LauncherUrlWithoutIcon) {
643 return d->launcherUrl(window, false /* encodeFallbackIcon */);
644 } else if (role == WinIdList) {
645 return QVariantList() << window;
646 } else if (role == MimeType) {
647 return d->mimeType();
648 } else if (role == MimeData) {
649 return QByteArray(reinterpret_cast<const char *>(&window), sizeof(window));
650 } else if (role == IsWindow) {
651 return true;
652 } else if (role == IsActive) {
653 return (window == d->activeWindow);
654 } else if (role == IsClosable) {
655 return d->windowInfo(window)->actionSupported(NET::ActionClose);
656 } else if (role == IsMovable) {
657 return d->windowInfo(window)->actionSupported(NET::ActionMove);
658 } else if (role == IsResizable) {
659 return d->windowInfo(window)->actionSupported(NET::ActionResize);
660 } else if (role == IsMaximizable) {
661 return d->windowInfo(window)->actionSupported(NET::ActionMax);
662 } else if (role == IsMaximized) {
663 const KWindowInfo *info = d->windowInfo(window);
664 return info->hasState(NET::MaxHoriz) && info->hasState(NET::MaxVert);
665 } else if (role == IsMinimizable) {
666 return d->windowInfo(window)->actionSupported(NET::ActionMinimize);
667 } else if (role == IsMinimized) {
668 return d->windowInfo(window)->isMinimized();
669 } else if (role == IsHidden) {
670 return d->windowInfo(window)->hasState(NET::Hidden);
671 } else if (role == IsKeepAbove) {
672 return d->windowInfo(window)->hasState(NET::KeepAbove);
673 } else if (role == IsKeepBelow) {
674 return d->windowInfo(window)->hasState(NET::KeepBelow);
675 } else if (role == IsFullScreenable) {
676 return d->windowInfo(window)->actionSupported(NET::ActionFullScreen);
677 } else if (role == IsFullScreen) {
678 return d->windowInfo(window)->hasState(NET::FullScreen);
679 } else if (role == IsShadeable) {
680 return d->windowInfo(window)->actionSupported(NET::ActionShade);
681 } else if (role == IsShaded) {
682 return d->windowInfo(window)->hasState(NET::Shaded);
683 } else if (role == IsVirtualDesktopsChangeable) {
684 return d->windowInfo(window)->actionSupported(NET::ActionChangeDesktop);
685 } else if (role == VirtualDesktops) {
686 return QVariantList() << d->windowInfo(window)->desktop();
687 } else if (role == IsOnAllVirtualDesktops) {
688 return d->windowInfo(window)->onAllDesktops();
689 } else if (role == Geometry) {
690 // Both the topLeft position and the size belong to the non-scaled coordinate system
691 return d->windowInfo(window)->frameGeometry();
692 } else if (role == ScreenGeometry) {
693 // The topLeft position belongs to the non-scaled coordinate system
694 // The size belongs to the scaled coordinate system, which means the size is already divided by DPR
695 return screenGeometry(d->windowInfo(window)->frameGeometry().center());
696 } else if (role == Activities) {
697 return d->windowInfo(window)->activities();
698 } else if (role == IsDemandingAttention) {
699 return d->demandsAttention(window);
700 } else if (role == SkipTaskbar) {
701 const KWindowInfo *info = d->windowInfo(window);
702 // _NET_WM_WINDOW_TYPE_UTILITY type windows should not be on task bars,
703 // but they should be shown on pagers.
704 return (info->hasState(NET::SkipTaskbar) || info->windowType(NET::UtilityMask) == NET::Utility || d->appData(window).skipTaskbar);
705 } else if (role == SkipPager) {
706 return d->windowInfo(window)->hasState(NET::SkipPager);
707 } else if (role == AppPid) {
708 return d->windowInfo(window)->pid();
709 } else if (role == StackingOrder) {
710 return d->cachedStackingOrder.indexOf(window);
711 } else if (role == LastActivated) {
712 if (d->lastActivated.contains(window)) {
713 return d->lastActivated.value(window);
714 }
715 } else if (role == ApplicationMenuObjectPath) {
716 return d->appMenuObjectPath(window);
717 } else if (role == ApplicationMenuServiceName) {
718 return d->appMenuServiceName(window);
719 } else if (role == CanLaunchNewInstance) {
720 return canLauchNewInstance(d->appData(window));
721 }
722
723 return AbstractTasksModel::data(index, role);
724}
725
726int XWindowTasksModel::rowCount(const QModelIndex &parent) const
727{
728 return parent.isValid() ? 0 : d->windows.count();
729}
730
731void XWindowTasksModel::requestActivate(const QModelIndex &index)
732{
733 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
734 return;
735 }
736
737 if (index.row() >= 0 && index.row() < d->windows.count()) {
738 WId window = d->windows.at(index.row());
739
740 // Pull forward any transient demanding attention.
741 if (d->transientsDemandingAttention.contains(window)) {
742 window = d->transientsDemandingAttention.value(window);
743 // Quote from legacy libtaskmanager:
744 // "this is a work around for (at least?) kwin where a shaded transient will prevent the main
745 // window from being brought forward unless the transient is actually pulled forward, most
746 // easily reproduced by opening a modal file open/save dialog on an app then shading the file
747 // dialog and trying to bring the window forward by clicking on it in a tasks widget
748 // TODO: do we need to check all the transients for shaded?"
749 } else if (!d->transients.isEmpty()) {
750 const auto transients = d->transients.keys(window);
751 for (const auto transient : std::as_const(transients)) {
752 KWindowInfo info(transient, NET::WMState, NET::WM2TransientFor);
753 if (info.valid(true) && info.hasState(NET::Shaded)) {
754 window = transient;
755 break;
756 }
757 }
758 }
759
761 }
762}
763
764void XWindowTasksModel::requestNewInstance(const QModelIndex &index)
765{
766 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
767 return;
768 }
769
770 runApp(d->appData(d->windows.at(index.row())));
771}
772
773void XWindowTasksModel::requestOpenUrls(const QModelIndex &index, const QList<QUrl> &urls)
774{
775 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count() || urls.isEmpty()) {
776 return;
777 }
778
779 runApp(d->appData(d->windows.at(index.row())), urls);
780}
781
782void XWindowTasksModel::requestClose(const QModelIndex &index)
783{
784 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
785 return;
786 }
787
788 NETRootInfo ri(X11Info::connection(), NET::CloseWindow);
789 ri.closeWindowRequest(d->windows.at(index.row()));
790}
791
792void XWindowTasksModel::requestMove(const QModelIndex &index)
793{
794 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
795 return;
796 }
797
798 const WId window = d->windows.at(index.row());
799 const KWindowInfo *info = d->windowInfo(window);
800
801 bool onCurrent = info->isOnCurrentDesktop();
802
803 if (!onCurrent) {
806 }
807
808 if (info->isMinimized()) {
810 }
811
812 const QRect &geom = info->geometry();
813
814 NETRootInfo ri(X11Info::connection(), NET::WMMoveResize);
815 ri.moveResizeRequest(window, geom.center().x(), geom.center().y(), NET::Move, XCB_BUTTON_INDEX_ANY);
816}
817
818void XWindowTasksModel::requestResize(const QModelIndex &index)
819{
820 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
821 return;
822 }
823
824 const WId window = d->windows.at(index.row());
825 const KWindowInfo *info = d->windowInfo(window);
826
827 bool onCurrent = info->isOnCurrentDesktop();
828
829 if (!onCurrent) {
832 }
833
834 if (info->isMinimized()) {
836 }
837
838 const QRect &geom = info->geometry();
839
840 NETRootInfo ri(X11Info::connection(), NET::WMMoveResize);
841 ri.moveResizeRequest(window, geom.bottomRight().x(), geom.bottomRight().y(), NET::BottomRight, XCB_BUTTON_INDEX_ANY);
842}
843
844void XWindowTasksModel::requestToggleMinimized(const QModelIndex &index)
845{
846 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
847 return;
848 }
849
850 const WId window = d->windows.at(index.row());
851 const KWindowInfo *info = d->windowInfo(window);
852
853 if (index.data(AbstractTasksModel::IsHidden).toBool()) {
854 bool onCurrent = info->isOnCurrentDesktop();
855
856 // FIXME: Move logic up into proxy? (See also others.)
857 if (!onCurrent) {
859 }
860
862
863 if (onCurrent) {
865 }
866 } else {
868 }
869}
870
871void XWindowTasksModel::requestToggleMaximized(const QModelIndex &index)
872{
873 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
874 return;
875 }
876
877 const WId window = d->windows.at(index.row());
878 const KWindowInfo *info = d->windowInfo(window);
879 bool onCurrent = info->isOnCurrentDesktop();
880 bool restore = (info->hasState(NET::MaxHoriz) && info->hasState(NET::MaxVert));
881
882 // FIXME: Move logic up into proxy? (See also others.)
883 if (!onCurrent) {
885 }
886
887 if (info->isMinimized()) {
889 }
890
891 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::WMState, NET::Properties2());
892
893 if (restore) {
895 } else {
897 }
898
899 if (!onCurrent) {
901 }
902}
903
904void XWindowTasksModel::requestToggleKeepAbove(const QModelIndex &index)
905{
906 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
907 return;
908 }
909
910 const WId window = d->windows.at(index.row());
911 const KWindowInfo *info = d->windowInfo(window);
912
913 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::WMState, NET::Properties2());
914
915 if (info->hasState(NET::KeepAbove)) {
917 } else {
919 }
920}
921
922void XWindowTasksModel::requestToggleKeepBelow(const QModelIndex &index)
923{
924 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
925 return;
926 }
927
928 const WId window = d->windows.at(index.row());
929 const KWindowInfo *info = d->windowInfo(window);
930
931 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::WMState, NET::Properties2());
932
933 if (info->hasState(NET::KeepBelow)) {
935 } else {
937 }
938}
939
940void XWindowTasksModel::requestToggleFullScreen(const QModelIndex &index)
941{
942 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
943 return;
944 }
945
946 const WId window = d->windows.at(index.row());
947 const KWindowInfo *info = d->windowInfo(window);
948
949 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::WMState, NET::Properties2());
950
951 if (info->hasState(NET::FullScreen)) {
953 } else {
955 }
956}
957
958void XWindowTasksModel::requestToggleShaded(const QModelIndex &index)
959{
960 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
961 return;
962 }
963
964 const WId window = d->windows.at(index.row());
965 const KWindowInfo *info = d->windowInfo(window);
966
967 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::WMState, NET::Properties2());
968
969 if (info->hasState(NET::Shaded)) {
971 } else {
973 }
974}
975
976void XWindowTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops)
977{
978 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
979 return;
980 }
981
982 int desktop = 0;
983
984 if (!desktops.isEmpty()) {
985 bool ok = false;
986
987 desktop = desktops.first().toUInt(&ok);
988
989 if (!ok) {
990 return;
991 }
992 }
993
994 if (desktop > KX11Extras::numberOfDesktops()) {
995 return;
996 }
997
998 const WId window = d->windows.at(index.row());
999 const KWindowInfo *info = d->windowInfo(window);
1000
1001 if (desktop == 0) {
1002 if (info->onAllDesktops()) {
1005 } else {
1006 KX11Extras::setOnAllDesktops(window, true);
1007 }
1008
1009 return;
1010 }
1011
1012 KX11Extras::setOnDesktop(window, desktop);
1013
1014 if (desktop == KX11Extras::currentDesktop()) {
1016 }
1017}
1018
1019void XWindowTasksModel::requestNewVirtualDesktop(const QModelIndex &index)
1020{
1021 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
1022 return;
1023 }
1024
1025 const WId window = d->windows.at(index.row());
1026 const int desktop = KX11Extras::numberOfDesktops() + 1;
1027
1028 // FIXME Arbitrary limit of 20 copied from old code.
1029 if (desktop > 20) {
1030 return;
1031 }
1032
1033 NETRootInfo ri(X11Info::connection(), NET::NumberOfDesktops);
1034 ri.setNumberOfDesktops(desktop);
1035
1036 KX11Extras::setOnDesktop(window, desktop);
1037}
1038
1039void XWindowTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities)
1040{
1041 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
1042 return;
1043 }
1044
1045 const WId window = d->windows.at(index.row());
1046
1047 KX11Extras::setOnActivities(window, activities);
1048}
1049
1050void XWindowTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate)
1051{
1052 Q_UNUSED(delegate)
1053
1054 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
1055 return;
1056 }
1057
1058 const WId window = d->windows.at(index.row());
1059
1060 if (d->delegateGeometries.contains(window) && d->delegateGeometries.value(window) == geometry) {
1061 return;
1062 }
1063
1064 NETWinInfo ni(X11Info::connection(), window, X11Info::appRootWindow(), NET::Properties(), NET::Properties2());
1065 NETRect rect;
1066
1067 if (geometry.isValid()) {
1068 rect.pos.x = geometry.x();
1069 rect.pos.y = geometry.y();
1070 rect.size.width = geometry.width();
1071 rect.size.height = geometry.height();
1072
1073 d->delegateGeometries.insert(window, geometry);
1074 } else {
1075 d->delegateGeometries.remove(window);
1076 }
1077
1078 ni.setIconGeometry(rect);
1079}
1080
1081WId XWindowTasksModel::winIdFromMimeData(const QMimeData *mimeData, bool *ok)
1082{
1083 Q_ASSERT(mimeData);
1084
1085 if (ok) {
1086 *ok = false;
1087 }
1088
1089 if (!mimeData->hasFormat(Private::mimeType())) {
1090 return 0;
1091 }
1092
1093 QByteArray data(mimeData->data(Private::mimeType()));
1094 WId id;
1095 if (data.size() != sizeof(WId)) {
1096 return 0;
1097 } else {
1098 memcpy(&id, data.data(), sizeof(WId));
1099 }
1100
1101 if (ok) {
1102 *ok = true;
1103 }
1104
1105 return id;
1106}
1107
1108QList<WId> XWindowTasksModel::winIdsFromMimeData(const QMimeData *mimeData, bool *ok)
1109{
1110 Q_ASSERT(mimeData);
1111 QList<WId> ids;
1112
1113 if (ok) {
1114 *ok = false;
1115 }
1116
1117 if (!mimeData->hasFormat(Private::groupMimeType())) {
1118 // Try to extract single window id.
1119 bool singularOk;
1120 WId id = winIdFromMimeData(mimeData, &singularOk);
1121
1122 if (ok) {
1123 *ok = singularOk;
1124 }
1125
1126 if (singularOk) {
1127 ids << id;
1128 }
1129
1130 return ids;
1131 }
1132
1133 QByteArray data(mimeData->data(Private::groupMimeType()));
1134 if ((unsigned int)data.size() < sizeof(int) + sizeof(WId)) {
1135 return ids;
1136 }
1137
1138 int count = 0;
1139 memcpy(&count, data.data(), sizeof(int));
1140 if (count < 1 || (unsigned int)data.size() < sizeof(int) + sizeof(WId) * count) {
1141 return ids;
1142 }
1143
1144 WId id;
1145 for (int i = 0; i < count; ++i) {
1146 memcpy(&id, data.data() + sizeof(int) + sizeof(WId) * i, sizeof(WId));
1147 ids << id;
1148 }
1149
1150 if (ok) {
1151 *ok = true;
1152 }
1153
1154 return ids;
1155}
1156
1157}
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()
QVariant location(const QVariant &res)
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
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
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 Mon Nov 18 2024 12:14:59 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.