File indexing completed on 2024-05-05 17:44:56

0001 /*
0002     SPDX-FileCopyrightText: 2016 Eike Hein <hein@kde.org>
0003     SPDX-FileCopyrightText: 2008 Aaron J. Seigo <aseigo@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "xwindowtasksmodel.h"
0009 #include "tasktools.h"
0010 #include "xwindowsystemeventbatcher.h"
0011 
0012 #include <KDesktopFile>
0013 #include <KDirWatch>
0014 #include <KIconLoader>
0015 #include <KService>
0016 #include <KSharedConfig>
0017 #include <KSycoca>
0018 #include <KWindowInfo>
0019 #include <KX11Extras>
0020 
0021 #include <QBuffer>
0022 #include <QDir>
0023 #include <QFile>
0024 #include <QIcon>
0025 #include <QSet>
0026 #include <QTimer>
0027 #include <QUrlQuery>
0028 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0029 #include <private/qtx11extras_p.h>
0030 #else
0031 #include <QX11Info>
0032 #endif
0033 #include <chrono>
0034 
0035 using namespace std::chrono_literals;
0036 
0037 namespace TaskManager
0038 {
0039 static const NET::Properties windowInfoFlags =
0040     NET::WMState | NET::XAWMState | NET::WMDesktop | NET::WMVisibleName | NET::WMGeometry | NET::WMFrameExtents | NET::WMWindowType | NET::WMPid;
0041 static const NET::Properties2 windowInfoFlags2 = NET::WM2DesktopFileName | NET::WM2Activities | NET::WM2WindowClass | NET::WM2AllowedActions
0042     | NET::WM2AppMenuObjectPath | NET::WM2AppMenuServiceName | NET::WM2GTKApplicationId;
0043 
0044 class Q_DECL_HIDDEN XWindowTasksModel::Private
0045 {
0046 public:
0047     Private(XWindowTasksModel *q);
0048     ~Private();
0049 
0050     QVector<WId> windows;
0051 
0052     // key=transient child, value=leader
0053     QHash<WId, WId> transients;
0054     // key=leader, values=transient children
0055     QMultiHash<WId, WId> transientsDemandingAttention;
0056 
0057     QHash<WId, KWindowInfo *> windowInfoCache;
0058     QHash<WId, AppData> appDataCache;
0059     QHash<WId, QRect> delegateGeometries;
0060     QSet<WId> usingFallbackIcon;
0061     QHash<WId, QTime> lastActivated;
0062     QList<WId> cachedStackingOrder;
0063     WId activeWindow = -1;
0064     KSharedConfig::Ptr rulesConfig;
0065     KDirWatch *configWatcher = nullptr;
0066     QTimer sycocaChangeTimer;
0067 
0068     void init();
0069     void addWindow(WId window);
0070     void removeWindow(WId window);
0071     void windowChanged(WId window, NET::Properties properties, NET::Properties2 properties2);
0072     void transientChanged(WId window, NET::Properties properties, NET::Properties2 properties2);
0073     void dataChanged(WId window, const QVector<int> &roles);
0074 
0075     KWindowInfo *windowInfo(WId window);
0076     AppData appData(WId window);
0077     QString appMenuServiceName(WId window);
0078     QString appMenuObjectPath(WId window);
0079 
0080     QIcon icon(WId window);
0081     static QString mimeType();
0082     static QString groupMimeType();
0083     QUrl windowUrl(WId window);
0084     QUrl launcherUrl(WId window, bool encodeFallbackIcon = true);
0085     bool demandsAttention(WId window);
0086 
0087 private:
0088     XWindowTasksModel *q;
0089 };
0090 
0091 XWindowTasksModel::Private::Private(XWindowTasksModel *q)
0092     : q(q)
0093 {
0094 }
0095 
0096 XWindowTasksModel::Private::~Private()
0097 {
0098     qDeleteAll(windowInfoCache);
0099     windowInfoCache.clear();
0100 }
0101 
0102 void XWindowTasksModel::Private::init()
0103 {
0104     auto clearCacheAndRefresh = [this] {
0105         if (!windows.count()) {
0106             return;
0107         }
0108 
0109         appDataCache.clear();
0110 
0111         // Emit changes of all roles satisfied from app data cache.
0112         Q_EMIT q->dataChanged(q->index(0, 0),
0113                               q->index(windows.count() - 1, 0),
0114                               QVector<int>{Qt::DecorationRole,
0115                                            AbstractTasksModel::AppId,
0116                                            AbstractTasksModel::AppName,
0117                                            AbstractTasksModel::GenericName,
0118                                            AbstractTasksModel::LauncherUrl,
0119                                            AbstractTasksModel::LauncherUrlWithoutIcon,
0120                                            AbstractTasksModel::CanLaunchNewInstance,
0121                                            AbstractTasksModel::SkipTaskbar});
0122     };
0123 
0124     cachedStackingOrder = KX11Extras::stackingOrder();
0125 
0126     sycocaChangeTimer.setSingleShot(true);
0127     sycocaChangeTimer.setInterval(100ms);
0128 
0129     QObject::connect(&sycocaChangeTimer, &QTimer::timeout, q, clearCacheAndRefresh);
0130 
0131     QObject::connect(KSycoca::self(), &KSycoca::databaseChanged, q, [this]() {
0132         sycocaChangeTimer.start();
0133     });
0134 
0135     rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc"));
0136     configWatcher = new KDirWatch(q);
0137 
0138     foreach (const QString &location, QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)) {
0139         configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc"));
0140     }
0141 
0142     auto rulesConfigChange = [this, clearCacheAndRefresh] {
0143         rulesConfig->reparseConfiguration();
0144         clearCacheAndRefresh();
0145     };
0146 
0147     QObject::connect(configWatcher, &KDirWatch::dirty, rulesConfigChange);
0148     QObject::connect(configWatcher, &KDirWatch::created, rulesConfigChange);
0149     QObject::connect(configWatcher, &KDirWatch::deleted, rulesConfigChange);
0150 
0151     auto windowSystem = new XWindowSystemEventBatcher(q);
0152 
0153     QObject::connect(windowSystem, &XWindowSystemEventBatcher::windowAdded, q, [this](WId window) {
0154         addWindow(window);
0155     });
0156 
0157     QObject::connect(windowSystem, &XWindowSystemEventBatcher::windowRemoved, q, [this](WId window) {
0158         removeWindow(window);
0159     });
0160 
0161     QObject::connect(windowSystem, &XWindowSystemEventBatcher::windowChanged, q, [this](WId window, NET::Properties properties, NET::Properties2 properties2) {
0162         windowChanged(window, properties, properties2);
0163     });
0164 
0165     // Update IsActive for previously- and newly-active windows.
0166     QObject::connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, q, [this](WId window) {
0167         const WId oldActiveWindow = activeWindow;
0168 
0169         const auto leader = transients.value(window, XCB_WINDOW_NONE);
0170         if (leader != XCB_WINDOW_NONE) {
0171             window = leader;
0172         }
0173 
0174         activeWindow = window;
0175         lastActivated[activeWindow] = QTime::currentTime();
0176 
0177         int row = windows.indexOf(oldActiveWindow);
0178 
0179         if (row != -1) {
0180             dataChanged(oldActiveWindow, QVector<int>{IsActive});
0181         }
0182 
0183         row = windows.indexOf(window);
0184 
0185         if (row != -1) {
0186             dataChanged(window, QVector<int>{IsActive});
0187         }
0188     });
0189 
0190     QObject::connect(KX11Extras::self(), &KX11Extras::stackingOrderChanged, q, [this]() {
0191         // No need to do anything if the model is empty. This avoids calling q->dataChanged with an invalid QModelIndex.
0192         if (q->rowCount() == 0) {
0193             return;
0194         }
0195         cachedStackingOrder = KX11Extras::stackingOrder();
0196         Q_ASSERT(q->hasIndex(0, 0));
0197         Q_ASSERT(q->hasIndex(q->rowCount() - 1, 0));
0198         Q_EMIT q->dataChanged(q->index(0, 0), q->index(q->rowCount() - 1, 0), QVector<int>{StackingOrder});
0199     });
0200 
0201     activeWindow = KX11Extras::activeWindow();
0202 
0203     // Add existing windows.
0204     foreach (const WId window, KX11Extras::windows()) {
0205         addWindow(window);
0206     }
0207 }
0208 
0209 void XWindowTasksModel::Private::addWindow(WId window)
0210 {
0211     // Don't add window twice.
0212     if (windows.contains(window)) {
0213         return;
0214     }
0215 
0216     KWindowInfo info(window, NET::WMWindowType | NET::WMState | NET::WMName | NET::WMVisibleName, NET::WM2TransientFor);
0217 
0218     NET::WindowType wType = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
0219                                             | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask | NET::NotificationMask);
0220 
0221     const WId leader = info.transientFor();
0222 
0223     // Handle transient.
0224     if (leader > 0 && leader != window && leader != QX11Info::appRootWindow() && !transients.contains(window) && windows.contains(leader)) {
0225         transients.insert(window, leader);
0226 
0227         // Update demands attention state for leader.
0228         if (info.hasState(NET::DemandsAttention) && windows.contains(leader)) {
0229             transientsDemandingAttention.insert(leader, window);
0230             dataChanged(leader, QVector<int>{IsDemandingAttention});
0231         }
0232 
0233         return;
0234     }
0235 
0236     // Ignore NET::Tool and other special window types; they are not considered tasks.
0237     if (wType != NET::Normal && wType != NET::Override && wType != NET::Unknown && wType != NET::Dialog && wType != NET::Utility) {
0238         return;
0239     }
0240 
0241     const int count = windows.count();
0242     q->beginInsertRows(QModelIndex(), count, count);
0243     windows.append(window);
0244     q->endInsertRows();
0245 }
0246 
0247 void XWindowTasksModel::Private::removeWindow(WId window)
0248 {
0249     const int row = windows.indexOf(window);
0250 
0251     if (row != -1) {
0252         q->beginRemoveRows(QModelIndex(), row, row);
0253         windows.removeAt(row);
0254         transientsDemandingAttention.remove(window);
0255         delete windowInfoCache.take(window);
0256         appDataCache.remove(window);
0257         delegateGeometries.remove(window);
0258         usingFallbackIcon.remove(window);
0259         lastActivated.remove(window);
0260         q->endRemoveRows();
0261     } else { // Could be a transient.
0262         // Removing a transient might change the demands attention state of the leader.
0263         if (transients.remove(window)) {
0264             const WId leader = transientsDemandingAttention.key(window, XCB_WINDOW_NONE);
0265 
0266             if (leader != XCB_WINDOW_NONE) {
0267                 transientsDemandingAttention.remove(leader, window);
0268                 dataChanged(leader, QVector<int>{IsDemandingAttention});
0269             }
0270         }
0271     }
0272 
0273     if (activeWindow == window) {
0274         activeWindow = -1;
0275     }
0276 }
0277 
0278 void XWindowTasksModel::Private::transientChanged(WId window, NET::Properties properties, NET::Properties2 properties2)
0279 {
0280     // Changes to a transient's state might change demands attention state for leader.
0281     if (properties & (NET::WMState | NET::XAWMState)) {
0282         const KWindowInfo info(window, NET::WMState | NET::XAWMState, NET::WM2TransientFor);
0283         const WId leader = info.transientFor();
0284 
0285         if (!windows.contains(leader)) {
0286             return;
0287         }
0288 
0289         if (info.hasState(NET::DemandsAttention)) {
0290             if (!transientsDemandingAttention.values(leader).contains(window)) {
0291                 transientsDemandingAttention.insert(leader, window);
0292                 dataChanged(leader, QVector<int>{IsDemandingAttention});
0293             }
0294         } else if (transientsDemandingAttention.remove(window)) {
0295             dataChanged(leader, QVector<int>{IsDemandingAttention});
0296         }
0297         // Leader might have changed.
0298     } else if (properties2 & NET::WM2TransientFor) {
0299         const KWindowInfo info(window, NET::WMState | NET::XAWMState, NET::WM2TransientFor);
0300 
0301         if (info.hasState(NET::DemandsAttention)) {
0302             const WId oldLeader = transientsDemandingAttention.key(window, XCB_WINDOW_NONE);
0303 
0304             if (oldLeader != XCB_WINDOW_NONE) {
0305                 const WId leader = info.transientFor();
0306 
0307                 if (leader != oldLeader) {
0308                     transientsDemandingAttention.remove(oldLeader, window);
0309                     transientsDemandingAttention.insert(leader, window);
0310                     dataChanged(oldLeader, QVector<int>{IsDemandingAttention});
0311                     dataChanged(leader, QVector<int>{IsDemandingAttention});
0312                 }
0313             }
0314         }
0315     }
0316 }
0317 
0318 void XWindowTasksModel::Private::windowChanged(WId window, NET::Properties properties, NET::Properties2 properties2)
0319 {
0320     if (transients.contains(window)) {
0321         transientChanged(window, properties, properties2);
0322         return;
0323     }
0324 
0325     bool wipeInfoCache = false;
0326     bool wipeAppDataCache = false;
0327     QVector<int> changedRoles;
0328 
0329     if (properties & (NET::WMPid) || properties2 & (NET::WM2DesktopFileName | NET::WM2WindowClass)) {
0330         wipeInfoCache = true;
0331         wipeAppDataCache = true;
0332         changedRoles << Qt::DecorationRole << AppId << AppName << GenericName << LauncherUrl << AppPid << SkipTaskbar << CanLaunchNewInstance;
0333     }
0334 
0335     if (properties & (NET::WMName | NET::WMVisibleName)) {
0336         changedRoles << Qt::DisplayRole;
0337         wipeInfoCache = true;
0338     }
0339 
0340     if ((properties & NET::WMIcon) && usingFallbackIcon.contains(window)) {
0341         wipeAppDataCache = true;
0342 
0343         if (!changedRoles.contains(Qt::DecorationRole)) {
0344             changedRoles << Qt::DecorationRole;
0345         }
0346     }
0347 
0348     // FIXME TODO: It might be worth keeping track of which windows were demanding
0349     // attention (or not) to avoid emitting this role on every state change, as
0350     // TaskGroupingProxyModel needs to check for group-ability when a change to it
0351     // is announced and the queried state is false.
0352     if (properties & (NET::WMState | NET::XAWMState)) {
0353         wipeInfoCache = true;
0354         changedRoles << IsFullScreen << IsMaximized << IsMinimized << IsKeepAbove << IsKeepBelow;
0355         changedRoles << IsShaded << IsDemandingAttention << SkipTaskbar << SkipPager;
0356     }
0357 
0358     if (properties & NET::WMWindowType) {
0359         wipeInfoCache = true;
0360         changedRoles << SkipTaskbar;
0361     }
0362 
0363     if (properties2 & NET::WM2AllowedActions) {
0364         wipeInfoCache = true;
0365         changedRoles << IsClosable << IsMovable << IsResizable << IsMaximizable << IsMinimizable;
0366         changedRoles << IsFullScreenable << IsShadeable << IsVirtualDesktopsChangeable;
0367     }
0368 
0369     if (properties & NET::WMDesktop) {
0370         wipeInfoCache = true;
0371         changedRoles << VirtualDesktops << IsOnAllVirtualDesktops;
0372     }
0373 
0374     if (properties & NET::WMGeometry) {
0375         wipeInfoCache = true;
0376         changedRoles << Geometry << ScreenGeometry;
0377     }
0378 
0379     if (properties2 & NET::WM2Activities) {
0380         wipeInfoCache = true;
0381         changedRoles << Activities;
0382     }
0383 
0384     if (properties2 & NET::WM2AppMenuServiceName) {
0385         wipeInfoCache = true;
0386         changedRoles << ApplicationMenuServiceName;
0387     }
0388 
0389     if (properties2 & NET::WM2AppMenuObjectPath) {
0390         wipeInfoCache = true;
0391         changedRoles << ApplicationMenuObjectPath;
0392     }
0393 
0394     if (wipeInfoCache) {
0395         delete windowInfoCache.take(window);
0396     }
0397 
0398     if (wipeAppDataCache) {
0399         appDataCache.remove(window);
0400         usingFallbackIcon.remove(window);
0401     }
0402 
0403     if (!changedRoles.isEmpty()) {
0404         dataChanged(window, changedRoles);
0405     }
0406 }
0407 
0408 void XWindowTasksModel::Private::dataChanged(WId window, const QVector<int> &roles)
0409 {
0410     const int i = windows.indexOf(window);
0411 
0412     if (i == -1) {
0413         return;
0414     }
0415 
0416     QModelIndex idx = q->index(i);
0417     Q_EMIT q->dataChanged(idx, idx, roles);
0418 }
0419 
0420 KWindowInfo *XWindowTasksModel::Private::windowInfo(WId window)
0421 {
0422     const auto &it = windowInfoCache.constFind(window);
0423 
0424     if (it != windowInfoCache.constEnd()) {
0425         return *it;
0426     }
0427 
0428     KWindowInfo *info = new KWindowInfo(window, windowInfoFlags, windowInfoFlags2);
0429     windowInfoCache.insert(window, info);
0430 
0431     return info;
0432 }
0433 
0434 AppData XWindowTasksModel::Private::appData(WId window)
0435 {
0436     const auto &it = appDataCache.constFind(window);
0437 
0438     if (it != appDataCache.constEnd()) {
0439         return *it;
0440     }
0441 
0442     const AppData &data = appDataFromUrl(windowUrl(window));
0443 
0444     // If we weren't able to derive a launcher URL from the window meta data,
0445     // fall back to WM_CLASS Class string as app id. This helps with apps we
0446     // can't map to an URL due to existing outside the regular system
0447     // environment, e.g. wine clients.
0448     if (data.id.isEmpty() && data.url.isEmpty()) {
0449         AppData dataCopy = data;
0450 
0451         dataCopy.id = windowInfo(window)->windowClassClass();
0452 
0453         appDataCache.insert(window, dataCopy);
0454 
0455         return dataCopy;
0456     }
0457 
0458     appDataCache.insert(window, data);
0459 
0460     return data;
0461 }
0462 
0463 QString XWindowTasksModel::Private::appMenuServiceName(WId window)
0464 {
0465     const KWindowInfo *info = windowInfo(window);
0466     return QString::fromUtf8(info->applicationMenuServiceName());
0467 }
0468 
0469 QString XWindowTasksModel::Private::appMenuObjectPath(WId window)
0470 {
0471     const KWindowInfo *info = windowInfo(window);
0472     return QString::fromUtf8(info->applicationMenuObjectPath());
0473 }
0474 
0475 QIcon XWindowTasksModel::Private::icon(WId window)
0476 {
0477     const AppData &app = appData(window);
0478 
0479     if (!app.icon.isNull()) {
0480         return app.icon;
0481     }
0482 
0483     QIcon icon;
0484 
0485     icon.addPixmap(KX11Extras::icon(window, KIconLoader::SizeSmall, KIconLoader::SizeSmall, false));
0486     icon.addPixmap(KX11Extras::icon(window, KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium, false));
0487     icon.addPixmap(KX11Extras::icon(window, KIconLoader::SizeMedium, KIconLoader::SizeMedium, false));
0488     icon.addPixmap(KX11Extras::icon(window, KIconLoader::SizeLarge, KIconLoader::SizeLarge, false));
0489 
0490     appDataCache[window].icon = icon;
0491     usingFallbackIcon.insert(window);
0492 
0493     return icon;
0494 }
0495 
0496 QString XWindowTasksModel::Private::mimeType()
0497 {
0498     return QStringLiteral("windowsystem/winid");
0499 }
0500 
0501 QString XWindowTasksModel::Private::groupMimeType()
0502 {
0503     return QStringLiteral("windowsystem/multiple-winids");
0504 }
0505 
0506 QUrl XWindowTasksModel::Private::windowUrl(WId window)
0507 {
0508     const KWindowInfo *info = windowInfo(window);
0509 
0510     QString desktopFile = QString::fromUtf8(info->desktopFileName());
0511 
0512     if (desktopFile.isEmpty()) {
0513         desktopFile = QString::fromUtf8(info->gtkApplicationId());
0514     }
0515 
0516     if (!desktopFile.isEmpty()) {
0517         KService::Ptr service = KService::serviceByStorageId(desktopFile);
0518 
0519         if (service) {
0520             const QString &menuId = service->menuId();
0521 
0522             // applications: URLs are used to refer to applications by their KService::menuId
0523             // (i.e. .desktop file name) rather than the absolute path to a .desktop file.
0524             if (!menuId.isEmpty()) {
0525                 return QUrl(QStringLiteral("applications:") + menuId);
0526             }
0527 
0528             return QUrl::fromLocalFile(service->entryPath());
0529         }
0530 
0531         if (!desktopFile.endsWith(QLatin1String(".desktop"))) {
0532             desktopFile.append(QLatin1String(".desktop"));
0533         }
0534 
0535         if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) {
0536             return QUrl::fromLocalFile(desktopFile);
0537         }
0538     }
0539 
0540     return windowUrlFromMetadata(info->windowClassClass(), info->pid(), rulesConfig, info->windowClassName());
0541 }
0542 
0543 QUrl XWindowTasksModel::Private::launcherUrl(WId window, bool encodeFallbackIcon)
0544 {
0545     const AppData &data = appData(window);
0546 
0547     QUrl url = data.url;
0548     if (!encodeFallbackIcon || !data.icon.name().isEmpty()) {
0549         return url;
0550     }
0551 
0552     // Forego adding the window icon pixmap if the URL is otherwise empty.
0553     if (!url.isValid()) {
0554         return QUrl();
0555     }
0556 
0557     // Only serialize pixmap data if the window pixmap is actually being used.
0558     // QIcon::name() used above only returns a themed icon name but nothing when
0559     // the icon was created using an absolute path, as can be the case with, e.g.
0560     // containerized apps.
0561     if (!usingFallbackIcon.contains(window)) {
0562         return url;
0563     }
0564 
0565     QPixmap pixmap;
0566 
0567     if (!data.icon.isNull()) {
0568         pixmap = data.icon.pixmap(KIconLoader::SizeLarge);
0569     }
0570 
0571     if (pixmap.isNull()) {
0572         pixmap = KX11Extras::icon(window, KIconLoader::SizeLarge, KIconLoader::SizeLarge, false);
0573     }
0574 
0575     if (pixmap.isNull()) {
0576         return data.url;
0577     }
0578     QUrlQuery uQuery(url);
0579     QByteArray bytes;
0580     QBuffer buffer(&bytes);
0581     buffer.open(QIODevice::WriteOnly);
0582     pixmap.save(&buffer, "PNG");
0583     uQuery.addQueryItem(QStringLiteral("iconData"), bytes.toBase64(QByteArray::Base64UrlEncoding));
0584 
0585     url.setQuery(uQuery);
0586 
0587     return url;
0588 }
0589 
0590 bool XWindowTasksModel::Private::demandsAttention(WId window)
0591 {
0592     if (windows.contains(window)) {
0593         return ((windowInfo(window)->hasState(NET::DemandsAttention)) || transientsDemandingAttention.contains(window));
0594     }
0595 
0596     return false;
0597 }
0598 
0599 XWindowTasksModel::XWindowTasksModel(QObject *parent)
0600     : AbstractWindowTasksModel(parent)
0601     , d(new Private(this))
0602 {
0603     d->init();
0604 }
0605 
0606 XWindowTasksModel::~XWindowTasksModel()
0607 {
0608 }
0609 
0610 QVariant XWindowTasksModel::data(const QModelIndex &index, int role) const
0611 {
0612     if (!index.isValid() || index.row() >= d->windows.count()) {
0613         return QVariant();
0614     }
0615 
0616     const WId window = d->windows.at(index.row());
0617 
0618     if (role == Qt::DisplayRole) {
0619         return d->windowInfo(window)->visibleName();
0620     } else if (role == Qt::DecorationRole) {
0621         return d->icon(window);
0622     } else if (role == AppId) {
0623         return d->appData(window).id;
0624     } else if (role == AppName) {
0625         return d->appData(window).name;
0626     } else if (role == GenericName) {
0627         return d->appData(window).genericName;
0628     } else if (role == LauncherUrl) {
0629         return d->launcherUrl(window);
0630     } else if (role == LauncherUrlWithoutIcon) {
0631         return d->launcherUrl(window, false /* encodeFallbackIcon */);
0632     } else if (role == WinIdList) {
0633         return QVariantList() << window;
0634     } else if (role == MimeType) {
0635         return d->mimeType();
0636     } else if (role == MimeData) {
0637         return QByteArray(reinterpret_cast<const char *>(&window), sizeof(window));
0638     } else if (role == IsWindow) {
0639         return true;
0640     } else if (role == IsActive) {
0641         return (window == d->activeWindow);
0642     } else if (role == IsClosable) {
0643         return d->windowInfo(window)->actionSupported(NET::ActionClose);
0644     } else if (role == IsMovable) {
0645         return d->windowInfo(window)->actionSupported(NET::ActionMove);
0646     } else if (role == IsResizable) {
0647         return d->windowInfo(window)->actionSupported(NET::ActionResize);
0648     } else if (role == IsMaximizable) {
0649         return d->windowInfo(window)->actionSupported(NET::ActionMax);
0650     } else if (role == IsMaximized) {
0651         const KWindowInfo *info = d->windowInfo(window);
0652         return info->hasState(NET::MaxHoriz) && info->hasState(NET::MaxVert);
0653     } else if (role == IsMinimizable) {
0654         return d->windowInfo(window)->actionSupported(NET::ActionMinimize);
0655     } else if (role == IsMinimized) {
0656         return d->windowInfo(window)->isMinimized();
0657     } else if (role == IsHidden) {
0658         return d->windowInfo(window)->hasState(NET::Hidden);
0659     } else if (role == IsKeepAbove) {
0660         return d->windowInfo(window)->hasState(NET::KeepAbove);
0661     } else if (role == IsKeepBelow) {
0662         return d->windowInfo(window)->hasState(NET::KeepBelow);
0663     } else if (role == IsFullScreenable) {
0664         return d->windowInfo(window)->actionSupported(NET::ActionFullScreen);
0665     } else if (role == IsFullScreen) {
0666         return d->windowInfo(window)->hasState(NET::FullScreen);
0667     } else if (role == IsShadeable) {
0668         return d->windowInfo(window)->actionSupported(NET::ActionShade);
0669     } else if (role == IsShaded) {
0670         return d->windowInfo(window)->hasState(NET::Shaded);
0671     } else if (role == IsVirtualDesktopsChangeable) {
0672         return d->windowInfo(window)->actionSupported(NET::ActionChangeDesktop);
0673     } else if (role == VirtualDesktops) {
0674         return QVariantList() << d->windowInfo(window)->desktop();
0675     } else if (role == IsOnAllVirtualDesktops) {
0676         return d->windowInfo(window)->onAllDesktops();
0677     } else if (role == Geometry) {
0678         return d->windowInfo(window)->frameGeometry();
0679     } else if (role == ScreenGeometry) {
0680         return screenGeometry(d->windowInfo(window)->frameGeometry().center());
0681     } else if (role == Activities) {
0682         return d->windowInfo(window)->activities();
0683     } else if (role == IsDemandingAttention) {
0684         return d->demandsAttention(window);
0685     } else if (role == SkipTaskbar) {
0686         const KWindowInfo *info = d->windowInfo(window);
0687         // _NET_WM_WINDOW_TYPE_UTILITY type windows should not be on task bars,
0688         // but they should be shown on pagers.
0689         return (info->hasState(NET::SkipTaskbar) || info->windowType(NET::UtilityMask) == NET::Utility || d->appData(window).skipTaskbar);
0690     } else if (role == SkipPager) {
0691         return d->windowInfo(window)->hasState(NET::SkipPager);
0692     } else if (role == AppPid) {
0693         return d->windowInfo(window)->pid();
0694     } else if (role == StackingOrder) {
0695         return d->cachedStackingOrder.indexOf(window);
0696     } else if (role == LastActivated) {
0697         if (d->lastActivated.contains(window)) {
0698             return d->lastActivated.value(window);
0699         }
0700     } else if (role == ApplicationMenuObjectPath) {
0701         return d->appMenuObjectPath(window);
0702     } else if (role == ApplicationMenuServiceName) {
0703         return d->appMenuServiceName(window);
0704     } else if (role == CanLaunchNewInstance) {
0705         return canLauchNewInstance(d->appData(window));
0706     }
0707 
0708     return QVariant();
0709 }
0710 
0711 int XWindowTasksModel::rowCount(const QModelIndex &parent) const
0712 {
0713     return parent.isValid() ? 0 : d->windows.count();
0714 }
0715 
0716 void XWindowTasksModel::requestActivate(const QModelIndex &index)
0717 {
0718     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0719         return;
0720     }
0721 
0722     if (index.row() >= 0 && index.row() < d->windows.count()) {
0723         WId window = d->windows.at(index.row());
0724 
0725         // Pull forward any transient demanding attention.
0726         if (d->transientsDemandingAttention.contains(window)) {
0727             window = d->transientsDemandingAttention.value(window);
0728             // Quote from legacy libtaskmanager:
0729             // "this is a work around for (at least?) kwin where a shaded transient will prevent the main
0730             // window from being brought forward unless the transient is actually pulled forward, most
0731             // easily reproduced by opening a modal file open/save dialog on an app then shading the file
0732             // dialog and trying to bring the window forward by clicking on it in a tasks widget
0733             // TODO: do we need to check all the transients for shaded?"
0734         } else if (!d->transients.isEmpty()) {
0735             const auto transients = d->transients.keys(window);
0736             for (const auto transient : qAsConst(transients)) {
0737                 KWindowInfo info(transient, NET::WMState, NET::WM2TransientFor);
0738                 if (info.valid(true) && info.hasState(NET::Shaded)) {
0739                     window = transient;
0740                     break;
0741                 }
0742             }
0743         }
0744 
0745         KX11Extras::forceActiveWindow(window);
0746     }
0747 }
0748 
0749 void XWindowTasksModel::requestNewInstance(const QModelIndex &index)
0750 {
0751     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0752         return;
0753     }
0754 
0755     runApp(d->appData(d->windows.at(index.row())));
0756 }
0757 
0758 void XWindowTasksModel::requestOpenUrls(const QModelIndex &index, const QList<QUrl> &urls)
0759 {
0760     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count() || urls.isEmpty()) {
0761         return;
0762     }
0763 
0764     runApp(d->appData(d->windows.at(index.row())), urls);
0765 }
0766 
0767 void XWindowTasksModel::requestClose(const QModelIndex &index)
0768 {
0769     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0770         return;
0771     }
0772 
0773     NETRootInfo ri(QX11Info::connection(), NET::CloseWindow);
0774     ri.closeWindowRequest(d->windows.at(index.row()));
0775 }
0776 
0777 void XWindowTasksModel::requestMove(const QModelIndex &index)
0778 {
0779     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0780         return;
0781     }
0782 
0783     const WId window = d->windows.at(index.row());
0784     const KWindowInfo *info = d->windowInfo(window);
0785 
0786     bool onCurrent = info->isOnCurrentDesktop();
0787 
0788     if (!onCurrent) {
0789         KX11Extras::setCurrentDesktop(info->desktop());
0790         KX11Extras::forceActiveWindow(window);
0791     }
0792 
0793     if (info->isMinimized()) {
0794         KX11Extras::unminimizeWindow(window);
0795     }
0796 
0797     const QRect &geom = info->geometry();
0798 
0799     NETRootInfo ri(QX11Info::connection(), NET::WMMoveResize);
0800     ri.moveResizeRequest(window, geom.center().x(), geom.center().y(), NET::Move);
0801 }
0802 
0803 void XWindowTasksModel::requestResize(const QModelIndex &index)
0804 {
0805     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0806         return;
0807     }
0808 
0809     const WId window = d->windows.at(index.row());
0810     const KWindowInfo *info = d->windowInfo(window);
0811 
0812     bool onCurrent = info->isOnCurrentDesktop();
0813 
0814     if (!onCurrent) {
0815         KX11Extras::setCurrentDesktop(info->desktop());
0816         KX11Extras::forceActiveWindow(window);
0817     }
0818 
0819     if (info->isMinimized()) {
0820         KX11Extras::unminimizeWindow(window);
0821     }
0822 
0823     const QRect &geom = info->geometry();
0824 
0825     NETRootInfo ri(QX11Info::connection(), NET::WMMoveResize);
0826     ri.moveResizeRequest(window, geom.bottomRight().x(), geom.bottomRight().y(), NET::BottomRight);
0827 }
0828 
0829 void XWindowTasksModel::requestToggleMinimized(const QModelIndex &index)
0830 {
0831     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0832         return;
0833     }
0834 
0835     const WId window = d->windows.at(index.row());
0836     const KWindowInfo *info = d->windowInfo(window);
0837 
0838     if (index.data(AbstractTasksModel::IsHidden).toBool()) {
0839         bool onCurrent = info->isOnCurrentDesktop();
0840 
0841         // FIXME: Move logic up into proxy? (See also others.)
0842         if (!onCurrent) {
0843             KX11Extras::setCurrentDesktop(info->desktop());
0844         }
0845 
0846         KX11Extras::unminimizeWindow(window);
0847 
0848         if (onCurrent) {
0849             KX11Extras::forceActiveWindow(window);
0850         }
0851     } else {
0852         KX11Extras::minimizeWindow(window);
0853     }
0854 }
0855 
0856 void XWindowTasksModel::requestToggleMaximized(const QModelIndex &index)
0857 {
0858     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0859         return;
0860     }
0861 
0862     const WId window = d->windows.at(index.row());
0863     const KWindowInfo *info = d->windowInfo(window);
0864     bool onCurrent = info->isOnCurrentDesktop();
0865     bool restore = (info->hasState(NET::MaxHoriz) && info->hasState(NET::MaxVert));
0866 
0867     // FIXME: Move logic up into proxy? (See also others.)
0868     if (!onCurrent) {
0869         KX11Extras::setCurrentDesktop(info->desktop());
0870     }
0871 
0872     if (info->isMinimized()) {
0873         KX11Extras::unminimizeWindow(window);
0874     }
0875 
0876     NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
0877 
0878     if (restore) {
0879         ni.setState(NET::States(), NET::Max);
0880     } else {
0881         ni.setState(NET::Max, NET::Max);
0882     }
0883 
0884     if (!onCurrent) {
0885         KX11Extras::forceActiveWindow(window);
0886     }
0887 }
0888 
0889 void XWindowTasksModel::requestToggleKeepAbove(const QModelIndex &index)
0890 {
0891     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0892         return;
0893     }
0894 
0895     const WId window = d->windows.at(index.row());
0896     const KWindowInfo *info = d->windowInfo(window);
0897 
0898     NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
0899 
0900     if (info->hasState(NET::KeepAbove)) {
0901         ni.setState(NET::States(), NET::KeepAbove);
0902     } else {
0903         ni.setState(NET::KeepAbove, NET::KeepAbove);
0904     }
0905 }
0906 
0907 void XWindowTasksModel::requestToggleKeepBelow(const QModelIndex &index)
0908 {
0909     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0910         return;
0911     }
0912 
0913     const WId window = d->windows.at(index.row());
0914     const KWindowInfo *info = d->windowInfo(window);
0915 
0916     NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
0917 
0918     if (info->hasState(NET::KeepBelow)) {
0919         ni.setState(NET::States(), NET::KeepBelow);
0920     } else {
0921         ni.setState(NET::KeepBelow, NET::KeepBelow);
0922     }
0923 }
0924 
0925 void XWindowTasksModel::requestToggleFullScreen(const QModelIndex &index)
0926 {
0927     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0928         return;
0929     }
0930 
0931     const WId window = d->windows.at(index.row());
0932     const KWindowInfo *info = d->windowInfo(window);
0933 
0934     NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
0935 
0936     if (info->hasState(NET::FullScreen)) {
0937         ni.setState(NET::States(), NET::FullScreen);
0938     } else {
0939         ni.setState(NET::FullScreen, NET::FullScreen);
0940     }
0941 }
0942 
0943 void XWindowTasksModel::requestToggleShaded(const QModelIndex &index)
0944 {
0945     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0946         return;
0947     }
0948 
0949     const WId window = d->windows.at(index.row());
0950     const KWindowInfo *info = d->windowInfo(window);
0951 
0952     NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::WMState, NET::Properties2());
0953 
0954     if (info->hasState(NET::Shaded)) {
0955         ni.setState(NET::States(), NET::Shaded);
0956     } else {
0957         ni.setState(NET::Shaded, NET::Shaded);
0958     }
0959 }
0960 
0961 void XWindowTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops)
0962 {
0963     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
0964         return;
0965     }
0966 
0967     int desktop = 0;
0968 
0969     if (!desktops.isEmpty()) {
0970         bool ok = false;
0971 
0972         desktop = desktops.first().toUInt(&ok);
0973 
0974         if (!ok) {
0975             return;
0976         }
0977     }
0978 
0979     if (desktop > KX11Extras::numberOfDesktops()) {
0980         return;
0981     }
0982 
0983     const WId window = d->windows.at(index.row());
0984     const KWindowInfo *info = d->windowInfo(window);
0985 
0986     if (desktop == 0) {
0987         if (info->onAllDesktops()) {
0988             KX11Extras::setOnDesktop(window, KX11Extras::currentDesktop());
0989             KX11Extras::forceActiveWindow(window);
0990         } else {
0991             KX11Extras::setOnAllDesktops(window, true);
0992         }
0993 
0994         return;
0995     }
0996 
0997     KX11Extras::setOnDesktop(window, desktop);
0998 
0999     if (desktop == KX11Extras::currentDesktop()) {
1000         KX11Extras::forceActiveWindow(window);
1001     }
1002 }
1003 
1004 void XWindowTasksModel::requestNewVirtualDesktop(const QModelIndex &index)
1005 {
1006     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
1007         return;
1008     }
1009 
1010     const WId window = d->windows.at(index.row());
1011     const int desktop = KX11Extras::numberOfDesktops() + 1;
1012 
1013     // FIXME Arbitrary limit of 20 copied from old code.
1014     if (desktop > 20) {
1015         return;
1016     }
1017 
1018     NETRootInfo ri(QX11Info::connection(), NET::NumberOfDesktops);
1019     ri.setNumberOfDesktops(desktop);
1020 
1021     KX11Extras::setOnDesktop(window, desktop);
1022 }
1023 
1024 void XWindowTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities)
1025 {
1026     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
1027         return;
1028     }
1029 
1030     const WId window = d->windows.at(index.row());
1031 
1032     KX11Extras::setOnActivities(window, activities);
1033 }
1034 
1035 void XWindowTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate)
1036 {
1037     Q_UNUSED(delegate)
1038 
1039     if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
1040         return;
1041     }
1042 
1043     const WId window = d->windows.at(index.row());
1044 
1045     if (d->delegateGeometries.contains(window) && d->delegateGeometries.value(window) == geometry) {
1046         return;
1047     }
1048 
1049     NETWinInfo ni(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
1050     NETRect rect;
1051 
1052     if (geometry.isValid()) {
1053         rect.pos.x = geometry.x();
1054         rect.pos.y = geometry.y();
1055         rect.size.width = geometry.width();
1056         rect.size.height = geometry.height();
1057 
1058         d->delegateGeometries.insert(window, geometry);
1059     } else {
1060         d->delegateGeometries.remove(window);
1061     }
1062 
1063     ni.setIconGeometry(rect);
1064 }
1065 
1066 WId XWindowTasksModel::winIdFromMimeData(const QMimeData *mimeData, bool *ok)
1067 {
1068     Q_ASSERT(mimeData);
1069 
1070     if (ok) {
1071         *ok = false;
1072     }
1073 
1074     if (!mimeData->hasFormat(Private::mimeType())) {
1075         return 0;
1076     }
1077 
1078     QByteArray data(mimeData->data(Private::mimeType()));
1079     WId id;
1080     if (data.size() != sizeof(WId)) {
1081 #if QT_VERSION < QT_VERSION_CHECK(6, 4, 2)
1082         // Workaround for https://bugreports.qt.io/browse/QTBUG-71922
1083         QString idString = QString::fromUtf8(data);
1084         if (idString.startsWith(QLatin1String("strnum-"))) {
1085             id = QString::fromUtf8(data).mid(7).toUInt();
1086         } else {
1087             return 0;
1088         }
1089 #else
1090         return 0;
1091 #endif
1092     } else {
1093         memcpy(&id, data.data(), sizeof(WId));
1094     }
1095 
1096     if (ok) {
1097         *ok = true;
1098     }
1099 
1100     return id;
1101 }
1102 
1103 QList<WId> XWindowTasksModel::winIdsFromMimeData(const QMimeData *mimeData, bool *ok)
1104 {
1105     Q_ASSERT(mimeData);
1106     QList<WId> ids;
1107 
1108     if (ok) {
1109         *ok = false;
1110     }
1111 
1112     if (!mimeData->hasFormat(Private::groupMimeType())) {
1113         // Try to extract single window id.
1114         bool singularOk;
1115         WId id = winIdFromMimeData(mimeData, &singularOk);
1116 
1117         if (ok) {
1118             *ok = singularOk;
1119         }
1120 
1121         if (singularOk) {
1122             ids << id;
1123         }
1124 
1125         return ids;
1126     }
1127 
1128     QByteArray data(mimeData->data(Private::groupMimeType()));
1129     if ((unsigned int)data.size() < sizeof(int) + sizeof(WId)) {
1130         return ids;
1131     }
1132 
1133     int count = 0;
1134     memcpy(&count, data.data(), sizeof(int));
1135     if (count < 1 || (unsigned int)data.size() < sizeof(int) + sizeof(WId) * count) {
1136         return ids;
1137     }
1138 
1139     WId id;
1140     for (int i = 0; i < count; ++i) {
1141         memcpy(&id, data.data() + sizeof(int) + sizeof(WId) * i, sizeof(WId));
1142         ids << id;
1143     }
1144 
1145     if (ok) {
1146         *ok = true;
1147     }
1148 
1149     return ids;
1150 }
1151 
1152 }