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

0001 /*
0002     SPDX-FileCopyrightText: 2016 Eike Hein <hein@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "waylandtasksmodel.h"
0008 #include "tasktools.h"
0009 #include "virtualdesktopinfo.h"
0010 
0011 #include <KDirWatch>
0012 #include <KSharedConfig>
0013 #include <KWayland/Client/connection_thread.h>
0014 #include <KWayland/Client/plasmawindowmanagement.h>
0015 #include <KWayland/Client/registry.h>
0016 #include <KWayland/Client/surface.h>
0017 #include <KWindowSystem>
0018 
0019 #include <QGuiApplication>
0020 #include <QMimeData>
0021 #include <QQuickItem>
0022 #include <QQuickWindow>
0023 #include <QSet>
0024 #include <QUrl>
0025 #include <QUuid>
0026 #include <QWindow>
0027 
0028 namespace TaskManager
0029 {
0030 class Q_DECL_HIDDEN WaylandTasksModel::Private
0031 {
0032 public:
0033     Private(WaylandTasksModel *q);
0034     QList<KWayland::Client::PlasmaWindow *> windows;
0035     QHash<KWayland::Client::PlasmaWindow *, AppData> appDataCache;
0036     QHash<KWayland::Client::PlasmaWindow *, QTime> lastActivated;
0037     KWayland::Client::PlasmaWindowManagement *windowManagement = nullptr;
0038     KSharedConfig::Ptr rulesConfig;
0039     KDirWatch *configWatcher = nullptr;
0040     VirtualDesktopInfo *virtualDesktopInfo = nullptr;
0041     static QUuid uuid;
0042 
0043     void init();
0044     void initWayland();
0045     void addWindow(KWayland::Client::PlasmaWindow *window);
0046 
0047     AppData appData(KWayland::Client::PlasmaWindow *window);
0048 
0049     QIcon icon(KWayland::Client::PlasmaWindow *window);
0050 
0051     static QString mimeType();
0052     static QString groupMimeType();
0053 
0054     void dataChanged(KWayland::Client::PlasmaWindow *window, int role);
0055     void dataChanged(KWayland::Client::PlasmaWindow *window, const QVector<int> &roles);
0056 
0057 private:
0058     WaylandTasksModel *q;
0059 };
0060 
0061 QUuid WaylandTasksModel::Private::uuid = QUuid::createUuid();
0062 
0063 WaylandTasksModel::Private::Private(WaylandTasksModel *q)
0064     : q(q)
0065 {
0066 }
0067 
0068 void WaylandTasksModel::Private::init()
0069 {
0070     auto clearCacheAndRefresh = [this] {
0071         if (!windows.count()) {
0072             return;
0073         }
0074 
0075         appDataCache.clear();
0076 
0077         // Emit changes of all roles satisfied from app data cache.
0078         Q_EMIT q->dataChanged(q->index(0, 0),
0079                               q->index(windows.count() - 1, 0),
0080                               QVector<int>{Qt::DecorationRole,
0081                                            AbstractTasksModel::AppId,
0082                                            AbstractTasksModel::AppName,
0083                                            AbstractTasksModel::GenericName,
0084                                            AbstractTasksModel::LauncherUrl,
0085                                            AbstractTasksModel::LauncherUrlWithoutIcon,
0086                                            AbstractTasksModel::CanLaunchNewInstance,
0087                                            AbstractTasksModel::SkipTaskbar});
0088     };
0089 
0090     rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc"));
0091     configWatcher = new KDirWatch(q);
0092 
0093     for (const QString &location : QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)) {
0094         configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc"));
0095     }
0096 
0097     auto rulesConfigChange = [this, clearCacheAndRefresh] {
0098         rulesConfig->reparseConfiguration();
0099         clearCacheAndRefresh();
0100     };
0101 
0102     QObject::connect(configWatcher, &KDirWatch::dirty, rulesConfigChange);
0103     QObject::connect(configWatcher, &KDirWatch::created, rulesConfigChange);
0104     QObject::connect(configWatcher, &KDirWatch::deleted, rulesConfigChange);
0105 
0106     virtualDesktopInfo = new VirtualDesktopInfo(q);
0107 
0108     initWayland();
0109 }
0110 
0111 void WaylandTasksModel::Private::initWayland()
0112 {
0113     if (!KWindowSystem::isPlatformWayland()) {
0114         return;
0115     }
0116 
0117     KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(q);
0118 
0119     if (!connection) {
0120         return;
0121     }
0122 
0123     KWayland::Client::Registry *registry = new KWayland::Client::Registry(q);
0124     registry->create(connection);
0125 
0126     QObject::connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, q, [this, registry](quint32 name, quint32 version) {
0127         windowManagement = registry->createPlasmaWindowManagement(name, version, q);
0128 
0129         QObject::connect(windowManagement, &KWayland::Client::PlasmaWindowManagement::interfaceAboutToBeReleased, q, [this] {
0130             q->beginResetModel();
0131             windows.clear();
0132             q->endResetModel();
0133         });
0134 
0135         QObject::connect(windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, q, [this](KWayland::Client::PlasmaWindow *window) {
0136             addWindow(window);
0137         });
0138 
0139         QObject::connect(windowManagement, &KWayland::Client::PlasmaWindowManagement::stackingOrderUuidsChanged, q, [this]() {
0140             for (const auto window : qAsConst(windows)) {
0141                 this->dataChanged(window, StackingOrder);
0142             }
0143         });
0144 
0145         const auto windows = windowManagement->windows();
0146         for (auto it = windows.constBegin(); it != windows.constEnd(); ++it) {
0147             addWindow(*it);
0148         }
0149     });
0150 
0151     registry->setup();
0152 }
0153 
0154 void WaylandTasksModel::Private::addWindow(KWayland::Client::PlasmaWindow *window)
0155 {
0156     if (windows.indexOf(window) != -1) {
0157         return;
0158     }
0159 
0160     const int count = windows.count();
0161 
0162     q->beginInsertRows(QModelIndex(), count, count);
0163 
0164     windows.append(window);
0165 
0166     q->endInsertRows();
0167 
0168     auto removeWindow = [window, this] {
0169         const int row = windows.indexOf(window);
0170         if (row != -1) {
0171             q->beginRemoveRows(QModelIndex(), row, row);
0172             windows.removeAt(row);
0173             appDataCache.remove(window);
0174             lastActivated.remove(window);
0175             q->endRemoveRows();
0176         }
0177     };
0178 
0179     QObject::connect(window, &KWayland::Client::PlasmaWindow::unmapped, q, removeWindow);
0180     QObject::connect(window, &QObject::destroyed, q, removeWindow);
0181 
0182     QObject::connect(window, &KWayland::Client::PlasmaWindow::titleChanged, q, [window, this] {
0183         this->dataChanged(window, Qt::DisplayRole);
0184     });
0185 
0186     QObject::connect(window, &KWayland::Client::PlasmaWindow::iconChanged, q, [window, this] {
0187         // The icon in the AppData struct might come from PlasmaWindow if it wasn't
0188         // filled in by windowUrlFromMetadata+appDataFromUrl.
0189         // TODO: Don't evict the cache unnecessarily if this isn't the case. As icons
0190         // are currently very static on Wayland, this eviction is unlikely to happen
0191         // frequently as of now.
0192         appDataCache.remove(window);
0193 
0194         this->dataChanged(window, Qt::DecorationRole);
0195     });
0196 
0197     QObject::connect(window, &KWayland::Client::PlasmaWindow::appIdChanged, q, [window, this] {
0198         // The AppData struct in the cache is derived from this and needs
0199         // to be evicted in favor of a fresh struct based on the changed
0200         // window metadata.
0201         appDataCache.remove(window);
0202 
0203         // Refresh roles satisfied from the app data cache.
0204         this->dataChanged(
0205             window,
0206             QVector<int>{Qt::DecorationRole, AppId, AppName, GenericName, LauncherUrl, LauncherUrlWithoutIcon, SkipTaskbar, CanLaunchNewInstance});
0207     });
0208 
0209     QObject::connect(window, &KWayland::Client::PlasmaWindow::activeChanged, q, [window, this] {
0210         if (window->isActive()) {
0211             lastActivated[window] = QTime::currentTime();
0212         }
0213         this->dataChanged(window, IsActive);
0214     });
0215 
0216     QObject::connect(window, &KWayland::Client::PlasmaWindow::closeableChanged, q, [window, this] {
0217         this->dataChanged(window, IsClosable);
0218     });
0219 
0220     QObject::connect(window, &KWayland::Client::PlasmaWindow::movableChanged, q, [window, this] {
0221         this->dataChanged(window, IsMovable);
0222     });
0223 
0224     QObject::connect(window, &KWayland::Client::PlasmaWindow::resizableChanged, q, [window, this] {
0225         this->dataChanged(window, IsResizable);
0226     });
0227 
0228     QObject::connect(window, &KWayland::Client::PlasmaWindow::fullscreenableChanged, q, [window, this] {
0229         this->dataChanged(window, IsFullScreenable);
0230     });
0231 
0232     QObject::connect(window, &KWayland::Client::PlasmaWindow::fullscreenChanged, q, [window, this] {
0233         this->dataChanged(window, IsFullScreen);
0234     });
0235 
0236     QObject::connect(window, &KWayland::Client::PlasmaWindow::maximizeableChanged, q, [window, this] {
0237         this->dataChanged(window, IsMaximizable);
0238     });
0239 
0240     QObject::connect(window, &KWayland::Client::PlasmaWindow::maximizedChanged, q, [window, this] {
0241         this->dataChanged(window, IsMaximized);
0242     });
0243 
0244     QObject::connect(window, &KWayland::Client::PlasmaWindow::minimizeableChanged, q, [window, this] {
0245         this->dataChanged(window, IsMinimizable);
0246     });
0247 
0248     QObject::connect(window, &KWayland::Client::PlasmaWindow::minimizedChanged, q, [window, this] {
0249         this->dataChanged(window, IsMinimized);
0250     });
0251 
0252     QObject::connect(window, &KWayland::Client::PlasmaWindow::keepAboveChanged, q, [window, this] {
0253         this->dataChanged(window, IsKeepAbove);
0254     });
0255 
0256     QObject::connect(window, &KWayland::Client::PlasmaWindow::keepBelowChanged, q, [window, this] {
0257         this->dataChanged(window, IsKeepBelow);
0258     });
0259 
0260     QObject::connect(window, &KWayland::Client::PlasmaWindow::shadeableChanged, q, [window, this] {
0261         this->dataChanged(window, IsShadeable);
0262     });
0263 
0264     // FIXME
0265     //     QObject::connect(window, &KWayland::Client::PlasmaWindow::virtualDesktopChangeableChanged, q,
0266     //         // TODO: This is marked deprecated in KWayland, but (IMHO) shouldn't be.
0267     //         [window, this] { this->dataChanged(window, IsVirtualDesktopsChangeable); }
0268     //     );
0269 
0270     QObject::connect(window, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopEntered, q, [window, this] {
0271         this->dataChanged(window, VirtualDesktops);
0272 
0273         // If the count has changed from 0, the window may no longer be on all virtual
0274         // desktops.
0275         if (window->plasmaVirtualDesktops().count() > 0) {
0276             this->dataChanged(window, IsOnAllVirtualDesktops);
0277         }
0278     });
0279 
0280     QObject::connect(window, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopLeft, q, [window, this] {
0281         this->dataChanged(window, VirtualDesktops);
0282 
0283         // If the count has changed to 0, the window is now on all virtual desktops.
0284         if (window->plasmaVirtualDesktops().count() == 0) {
0285             this->dataChanged(window, IsOnAllVirtualDesktops);
0286         }
0287     });
0288 
0289     QObject::connect(window, &KWayland::Client::PlasmaWindow::geometryChanged, q, [window, this] {
0290         this->dataChanged(window, QVector<int>{Geometry, ScreenGeometry});
0291     });
0292 
0293     QObject::connect(window, &KWayland::Client::PlasmaWindow::demandsAttentionChanged, q, [window, this] {
0294         this->dataChanged(window, IsDemandingAttention);
0295     });
0296 
0297     QObject::connect(window, &KWayland::Client::PlasmaWindow::skipTaskbarChanged, q, [window, this] {
0298         this->dataChanged(window, SkipTaskbar);
0299     });
0300 
0301     QObject::connect(window, &KWayland::Client::PlasmaWindow::applicationMenuChanged, q, [window, this] {
0302         this->dataChanged(window, QVector<int>{ApplicationMenuServiceName, ApplicationMenuObjectPath});
0303     });
0304 
0305     QObject::connect(window, &KWayland::Client::PlasmaWindow::plasmaActivityEntered, q, [window, this] {
0306         this->dataChanged(window, Activities);
0307     });
0308 
0309     QObject::connect(window, &KWayland::Client::PlasmaWindow::plasmaActivityLeft, q, [window, this] {
0310         this->dataChanged(window, Activities);
0311     });
0312 }
0313 
0314 AppData WaylandTasksModel::Private::appData(KWayland::Client::PlasmaWindow *window)
0315 {
0316     const auto &it = appDataCache.constFind(window);
0317 
0318     if (it != appDataCache.constEnd()) {
0319         return *it;
0320     }
0321 
0322     const AppData &data = appDataFromUrl(windowUrlFromMetadata(window->appId(), window->pid(), rulesConfig, window->resourceName()));
0323 
0324     appDataCache.insert(window, data);
0325 
0326     return data;
0327 }
0328 
0329 QIcon WaylandTasksModel::Private::icon(KWayland::Client::PlasmaWindow *window)
0330 {
0331     const AppData &app = appData(window);
0332 
0333     if (!app.icon.isNull()) {
0334         return app.icon;
0335     }
0336 
0337     appDataCache[window].icon = window->icon();
0338 
0339     return window->icon();
0340 }
0341 
0342 QString WaylandTasksModel::Private::mimeType()
0343 {
0344     // Use a unique format id to make this intentionally useless for
0345     // cross-process DND.
0346     return QStringLiteral("windowsystem/winid+") + uuid.toString();
0347 }
0348 
0349 QString WaylandTasksModel::Private::groupMimeType()
0350 {
0351     // Use a unique format id to make this intentionally useless for
0352     // cross-process DND.
0353     return QStringLiteral("windowsystem/multiple-winids+") + uuid.toString();
0354 }
0355 
0356 void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, int role)
0357 {
0358     QModelIndex idx = q->index(windows.indexOf(window));
0359     Q_EMIT q->dataChanged(idx, idx, QVector<int>{role});
0360 }
0361 
0362 void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, const QVector<int> &roles)
0363 {
0364     QModelIndex idx = q->index(windows.indexOf(window));
0365     Q_EMIT q->dataChanged(idx, idx, roles);
0366 }
0367 
0368 WaylandTasksModel::WaylandTasksModel(QObject *parent)
0369     : AbstractWindowTasksModel(parent)
0370     , d(new Private(this))
0371 {
0372     d->init();
0373 }
0374 
0375 WaylandTasksModel::~WaylandTasksModel() = default;
0376 
0377 QVariant WaylandTasksModel::data(const QModelIndex &index, int role) const
0378 {
0379     if (!index.isValid() || index.row() >= d->windows.count()) {
0380         return QVariant();
0381     }
0382 
0383     KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
0384 
0385     if (role == Qt::DisplayRole) {
0386         return window->title();
0387     } else if (role == Qt::DecorationRole) {
0388         return d->icon(window);
0389     } else if (role == AppId) {
0390         const QString &id = d->appData(window).id;
0391 
0392         if (id.isEmpty()) {
0393             return window->appId();
0394         } else {
0395             return id;
0396         }
0397     } else if (role == AppName) {
0398         return d->appData(window).name;
0399     } else if (role == GenericName) {
0400         return d->appData(window).genericName;
0401     } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) {
0402         return d->appData(window).url;
0403     } else if (role == WinIdList) {
0404         return QVariantList{window->uuid()};
0405     } else if (role == MimeType) {
0406         return d->mimeType();
0407     } else if (role == MimeData) {
0408         return window->uuid();
0409     } else if (role == IsWindow) {
0410         return true;
0411     } else if (role == IsActive) {
0412         return window->isActive();
0413     } else if (role == IsClosable) {
0414         return window->isCloseable();
0415     } else if (role == IsMovable) {
0416         return window->isMovable();
0417     } else if (role == IsResizable) {
0418         return window->isResizable();
0419     } else if (role == IsMaximizable) {
0420         return window->isMaximizeable();
0421     } else if (role == IsMaximized) {
0422         return window->isMaximized();
0423     } else if (role == IsMinimizable) {
0424         return window->isMinimizeable();
0425     } else if (role == IsMinimized || role == IsHidden) {
0426         return window->isMinimized();
0427     } else if (role == IsKeepAbove) {
0428         return window->isKeepAbove();
0429     } else if (role == IsKeepBelow) {
0430         return window->isKeepBelow();
0431     } else if (role == IsFullScreenable) {
0432         return window->isFullscreenable();
0433     } else if (role == IsFullScreen) {
0434         return window->isFullscreen();
0435     } else if (role == IsShadeable) {
0436         return window->isShadeable();
0437     } else if (role == IsShaded) {
0438         return window->isShaded();
0439     } else if (role == IsVirtualDesktopsChangeable) {
0440         // FIXME Currently not implemented in KWayland.
0441         return true;
0442     } else if (role == VirtualDesktops) {
0443         return window->plasmaVirtualDesktops();
0444     } else if (role == IsOnAllVirtualDesktops) {
0445         return window->plasmaVirtualDesktops().isEmpty();
0446     } else if (role == Geometry) {
0447         return window->geometry();
0448     } else if (role == ScreenGeometry) {
0449         return screenGeometry(window->geometry().center());
0450     } else if (role == Activities) {
0451         return window->plasmaActivities();
0452     } else if (role == IsDemandingAttention) {
0453         return window->isDemandingAttention();
0454     } else if (role == SkipTaskbar) {
0455         return window->skipTaskbar() || d->appData(window).skipTaskbar;
0456     } else if (role == SkipPager) {
0457         // FIXME Implement.
0458     } else if (role == AppPid) {
0459         return window->pid();
0460     } else if (role == StackingOrder) {
0461         return d->windowManagement->stackingOrderUuids().indexOf(window->uuid());
0462     } else if (role == LastActivated) {
0463         if (d->lastActivated.contains(window)) {
0464             return d->lastActivated.value(window);
0465         }
0466     } else if (role == ApplicationMenuObjectPath) {
0467         return window->applicationMenuObjectPath();
0468     } else if (role == ApplicationMenuServiceName) {
0469         return window->applicationMenuServiceName();
0470     } else if (role == CanLaunchNewInstance) {
0471         return canLauchNewInstance(d->appData(window));
0472     }
0473 
0474     return {};
0475 }
0476 
0477 int WaylandTasksModel::rowCount(const QModelIndex &parent) const
0478 {
0479     return parent.isValid() ? 0 : d->windows.count();
0480 }
0481 
0482 QModelIndex WaylandTasksModel::index(int row, int column, const QModelIndex &parent) const
0483 {
0484     return hasIndex(row, column, parent) ? createIndex(row, column, d->windows.at(row)) : QModelIndex();
0485 }
0486 
0487 void WaylandTasksModel::requestActivate(const QModelIndex &index)
0488 {
0489     // FIXME Lacks transient handling of the XWindows version.
0490 
0491     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0492         return;
0493     }
0494 
0495     d->windows.at(index.row())->requestActivate();
0496 }
0497 
0498 void WaylandTasksModel::requestNewInstance(const QModelIndex &index)
0499 {
0500     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0501         return;
0502     }
0503 
0504     runApp(d->appData(d->windows.at(index.row())));
0505 }
0506 
0507 void WaylandTasksModel::requestOpenUrls(const QModelIndex &index, const QList<QUrl> &urls)
0508 {
0509     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent) || urls.isEmpty()) {
0510         return;
0511     }
0512 
0513     runApp(d->appData(d->windows.at(index.row())), urls);
0514 }
0515 
0516 void WaylandTasksModel::requestClose(const QModelIndex &index)
0517 {
0518     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0519         return;
0520     }
0521 
0522     d->windows.at(index.row())->requestClose();
0523 }
0524 
0525 void WaylandTasksModel::requestMove(const QModelIndex &index)
0526 {
0527     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0528         return;
0529     }
0530 
0531     KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
0532 
0533     window->requestActivate();
0534     window->requestMove();
0535 }
0536 
0537 void WaylandTasksModel::requestResize(const QModelIndex &index)
0538 {
0539     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0540         return;
0541     }
0542 
0543     KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
0544 
0545     window->requestActivate();
0546     window->requestResize();
0547 }
0548 
0549 void WaylandTasksModel::requestToggleMinimized(const QModelIndex &index)
0550 {
0551     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0552         return;
0553     }
0554 
0555     d->windows.at(index.row())->requestToggleMinimized();
0556 }
0557 
0558 void WaylandTasksModel::requestToggleMaximized(const QModelIndex &index)
0559 {
0560     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0561         return;
0562     }
0563 
0564     KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
0565 
0566     window->requestActivate();
0567     window->requestToggleMaximized();
0568 }
0569 
0570 void WaylandTasksModel::requestToggleKeepAbove(const QModelIndex &index)
0571 {
0572     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0573         return;
0574     }
0575 
0576     d->windows.at(index.row())->requestToggleKeepAbove();
0577 }
0578 
0579 void WaylandTasksModel::requestToggleKeepBelow(const QModelIndex &index)
0580 {
0581     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0582         return;
0583     }
0584 
0585     d->windows.at(index.row())->requestToggleKeepBelow();
0586 }
0587 
0588 void WaylandTasksModel::requestToggleFullScreen(const QModelIndex &index)
0589 {
0590     Q_UNUSED(index)
0591 
0592     // FIXME Implement.
0593 }
0594 
0595 void WaylandTasksModel::requestToggleShaded(const QModelIndex &index)
0596 {
0597     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0598         return;
0599     }
0600 
0601     d->windows.at(index.row())->requestToggleShaded();
0602 }
0603 
0604 void WaylandTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops)
0605 {
0606     // FIXME TODO: Lacks the "if we've requested the current desktop, force-activate
0607     // the window" logic from X11 version. This behavior should be in KWin rather than
0608     // libtm however.
0609 
0610     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0611         return;
0612     }
0613 
0614     KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
0615 
0616     if (desktops.isEmpty()) {
0617         const QStringList virtualDesktops = window->plasmaVirtualDesktops();
0618         for (const QString &desktop : virtualDesktops) {
0619             window->requestLeaveVirtualDesktop(desktop);
0620         }
0621     } else {
0622         const QStringList &now = window->plasmaVirtualDesktops();
0623         QStringList next;
0624 
0625         for (const QVariant &desktop : desktops) {
0626             const QString &desktopId = desktop.toString();
0627 
0628             if (!desktopId.isEmpty()) {
0629                 next << desktopId;
0630 
0631                 if (!now.contains(desktopId)) {
0632                     window->requestEnterVirtualDesktop(desktopId);
0633                 }
0634             }
0635         }
0636 
0637         for (const QString &desktop : now) {
0638             if (!next.contains(desktop)) {
0639                 window->requestLeaveVirtualDesktop(desktop);
0640             }
0641         }
0642     }
0643 }
0644 
0645 void WaylandTasksModel::requestNewVirtualDesktop(const QModelIndex &index)
0646 {
0647     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0648         return;
0649     }
0650 
0651     d->windows.at(index.row())->requestEnterNewVirtualDesktop();
0652 }
0653 
0654 void WaylandTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities)
0655 {
0656     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0657         return;
0658     }
0659 
0660     auto *const window = d->windows.at(index.row());
0661     const auto newActivities = QSet(activities.begin(), activities.end());
0662     const auto plasmaActivities = window->plasmaActivities();
0663     const auto oldActivities = QSet(plasmaActivities.begin(), plasmaActivities.end());
0664 
0665     const auto activitiesToAdd = newActivities - oldActivities;
0666     for (const auto &activity : activitiesToAdd) {
0667         window->requestEnterActivity(activity);
0668     }
0669 
0670     const auto activitiesToRemove = oldActivities - newActivities;
0671     for (const auto &activity : activitiesToRemove) {
0672         window->requestLeaveActivity(activity);
0673     }
0674 }
0675 
0676 void WaylandTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate)
0677 {
0678     /*
0679     FIXME: This introduces the dependency on Qt::Quick. I might prefer
0680     reversing this and publishing the window pointer through the model,
0681     then calling PlasmaWindow::setMinimizeGeometry in the applet backend,
0682     rather than hand delegate items into the lib, keeping the lib more UI-
0683     agnostic.
0684     */
0685 
0686     Q_UNUSED(geometry)
0687 
0688     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0689         return;
0690     }
0691 
0692     const QQuickItem *item = qobject_cast<const QQuickItem *>(delegate);
0693 
0694     if (!item || !item->parentItem()) {
0695         return;
0696     }
0697 
0698     QWindow *itemWindow = item->window();
0699 
0700     if (!itemWindow) {
0701         return;
0702     }
0703 
0704     using namespace KWayland::Client;
0705     Surface *surface = Surface::fromWindow(itemWindow);
0706 
0707     if (!surface) {
0708         return;
0709     }
0710 
0711     QRect rect(item->x(), item->y(), item->width(), item->height());
0712     rect.moveTopLeft(item->parentItem()->mapToScene(rect.topLeft()).toPoint());
0713 
0714     KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
0715 
0716     window->setMinimizedGeometry(surface, rect);
0717 }
0718 
0719 QUuid WaylandTasksModel::winIdFromMimeData(const QMimeData *mimeData, bool *ok)
0720 {
0721     Q_ASSERT(mimeData);
0722 
0723     if (ok) {
0724         *ok = false;
0725     }
0726 
0727     if (!mimeData->hasFormat(Private::mimeType())) {
0728         return {};
0729     }
0730 
0731     QUuid id(mimeData->data(Private::mimeType()));
0732     *ok = !id.isNull();
0733 
0734     return id;
0735 }
0736 
0737 QList<QUuid> WaylandTasksModel::winIdsFromMimeData(const QMimeData *mimeData, bool *ok)
0738 {
0739     Q_ASSERT(mimeData);
0740     QList<QUuid> ids;
0741 
0742     if (ok) {
0743         *ok = false;
0744     }
0745 
0746     if (!mimeData->hasFormat(Private::groupMimeType())) {
0747         // Try to extract single window id.
0748         bool singularOk;
0749         QUuid id = winIdFromMimeData(mimeData, &singularOk);
0750 
0751         if (ok) {
0752             *ok = singularOk;
0753         }
0754 
0755         if (singularOk) {
0756             ids << id;
0757         }
0758 
0759         return ids;
0760     }
0761 
0762     // FIXME: Extracting multiple winids is still unimplemented;
0763     // TaskGroupingProxy::data(..., ::MimeData) can't produce
0764     // a payload with them anyways.
0765 
0766     return ids;
0767 }
0768 
0769 }