File indexing completed on 2024-05-05 05:38:39

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