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

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 "libtaskmanager_debug.h"
0009 #include "tasktools.h"
0010 #include "virtualdesktopinfo.h"
0011 
0012 #include <KDirWatch>
0013 #include <KSharedConfig>
0014 #include <KWindowSystem>
0015 
0016 #include <qwayland-plasma-window-management.h>
0017 
0018 #include <QFuture>
0019 #include <QGuiApplication>
0020 #include <QMimeData>
0021 #include <QQuickItem>
0022 #include <QQuickWindow>
0023 #include <QSet>
0024 #include <QUrl>
0025 #include <QUuid>
0026 #include <QWaylandClientExtension>
0027 #include <QWindow>
0028 #include <QtConcurrent>
0029 #include <qpa/qplatformwindow_p.h>
0030 
0031 #include <fcntl.h>
0032 #include <sys/poll.h>
0033 #include <unistd.h>
0034 
0035 namespace TaskManager
0036 {
0037 
0038 class PlasmaWindow : public QObject, public QtWayland::org_kde_plasma_window
0039 {
0040     Q_OBJECT
0041 public:
0042     PlasmaWindow(const QString &uuid, ::org_kde_plasma_window *id)
0043         : org_kde_plasma_window(id)
0044         , uuid(uuid)
0045     {
0046     }
0047     ~PlasmaWindow()
0048     {
0049         destroy();
0050     }
0051     using state = QtWayland::org_kde_plasma_window_management::state;
0052     const QString uuid;
0053     QString title;
0054     QString appId;
0055     QIcon icon;
0056     QFlags<state> windowState;
0057     QList<QString> virtualDesktops;
0058     QRect geometry;
0059     QString applicationMenuService;
0060     QString applicationMenuObjectPath;
0061     QList<QString> activities;
0062     quint32 pid;
0063     QString resourceName;
0064     QPointer<PlasmaWindow> parentWindow;
0065     bool wasUnmapped = false;
0066 
0067 Q_SIGNALS:
0068     void unmapped();
0069     void titleChanged();
0070     void appIdChanged();
0071     void iconChanged();
0072     void activeChanged();
0073     void minimizedChanged();
0074     void maximizedChanged();
0075     void fullscreenChanged();
0076     void keepAboveChanged();
0077     void keepBelowChanged();
0078     void onAllDesktopsChanged();
0079     void demandsAttentionChanged();
0080     void closeableChanged();
0081     void minimizeableChanged();
0082     void maximizeableChanged();
0083     void fullscreenableChanged();
0084     void skiptaskbarChanged();
0085     void shadeableChanged();
0086     void shadedChanged();
0087     void movableChanged();
0088     void resizableChanged();
0089     void virtualDesktopChangeableChanged();
0090     void skipSwitcherChanged();
0091     void virtualDesktopEntered();
0092     void virtualDesktopLeft();
0093     void geometryChanged();
0094     void skipTaskbarChanged();
0095     void applicationMenuChanged();
0096     void activitiesChanged();
0097     void parentWindowChanged();
0098     void initialStateDone();
0099 
0100 protected:
0101     void org_kde_plasma_window_unmapped() override
0102     {
0103         wasUnmapped = true;
0104         Q_EMIT unmapped();
0105     }
0106     void org_kde_plasma_window_title_changed(const QString &title) override
0107     {
0108         this->title = title;
0109         Q_EMIT titleChanged();
0110     }
0111     void org_kde_plasma_window_app_id_changed(const QString &app_id) override
0112     {
0113         appId = app_id;
0114         Q_EMIT appIdChanged();
0115     }
0116     void org_kde_plasma_window_icon_changed() override
0117     {
0118         int pipeFds[2];
0119         if (pipe2(pipeFds, O_CLOEXEC) != 0) {
0120             qCWarning(TASKMANAGER_DEBUG) << "failed creating pipe";
0121             return;
0122         }
0123         get_icon(pipeFds[1]);
0124         ::close(pipeFds[1]);
0125         auto readIcon = [uuid = uuid](int fd) {
0126             auto closeGuard = qScopeGuard([fd]() {
0127                 ::close(fd);
0128             });
0129             pollfd pollFd;
0130             pollFd.fd = fd;
0131             pollFd.events = POLLIN;
0132             QByteArray data;
0133             while (true) {
0134                 int ready = poll(&pollFd, 1, 1000);
0135                 if (ready < 0 && errno != EINTR) {
0136                     qCWarning(TASKMANAGER_DEBUG) << "polling for icon of window" << uuid << "failed";
0137                     return QIcon();
0138                 } else if (ready == 0) {
0139                     qCWarning(TASKMANAGER_DEBUG) << "time out polling for icon of window" << uuid;
0140                     return QIcon();
0141                 } else {
0142                     char buffer[4096];
0143                     int n = read(fd, buffer, sizeof(buffer));
0144                     if (n < 0) {
0145                         qCWarning(TASKMANAGER_DEBUG) << "error reading icon of window" << uuid;
0146                         return QIcon();
0147                     } else if (n > 0) {
0148                         data.append(buffer, n);
0149                     } else {
0150                         QIcon icon;
0151                         QDataStream ds(data);
0152                         ds >> icon;
0153                         return icon;
0154                     }
0155                 }
0156             }
0157         };
0158         QFuture<QIcon> future = QtConcurrent::run(readIcon, pipeFds[0]);
0159         auto watcher = new QFutureWatcher<QIcon>();
0160         watcher->setFuture(future);
0161         connect(watcher, &QFutureWatcher<QIcon>::finished, this, [this, watcher] {
0162             icon = watcher->future().result();
0163             Q_EMIT iconChanged();
0164         });
0165         connect(watcher, &QFutureWatcher<QIcon>::finished, watcher, &QObject::deleteLater);
0166     }
0167     void org_kde_plasma_window_themed_icon_name_changed(const QString &name) override
0168     {
0169         icon = QIcon::fromTheme(name);
0170         Q_EMIT iconChanged();
0171     }
0172     void org_kde_plasma_window_state_changed(uint32_t flags) override
0173     {
0174         auto diff = windowState ^ flags;
0175         if (diff & state::state_active) {
0176             windowState.setFlag(state::state_active, flags & state::state_active);
0177             Q_EMIT activeChanged();
0178         }
0179         if (diff & state::state_minimized) {
0180             windowState.setFlag(state::state_minimized, flags & state::state_minimized);
0181             Q_EMIT minimizedChanged();
0182         }
0183         if (diff & state::state_maximized) {
0184             windowState.setFlag(state::state_maximized, flags & state::state_maximized);
0185             Q_EMIT maximizedChanged();
0186         }
0187         if (diff & state::state_fullscreen) {
0188             windowState.setFlag(state::state_fullscreen, flags & state::state_fullscreen);
0189             Q_EMIT fullscreenChanged();
0190         }
0191         if (diff & state::state_keep_above) {
0192             windowState.setFlag(state::state_keep_above, flags & state::state_keep_above);
0193             Q_EMIT keepAboveChanged();
0194         }
0195         if (diff & state::state_keep_below) {
0196             windowState.setFlag(state::state_keep_below, flags & state::state_keep_below);
0197             Q_EMIT keepBelowChanged();
0198         }
0199         if (diff & state::state_on_all_desktops) {
0200             windowState.setFlag(state::state_on_all_desktops, flags & state::state_on_all_desktops);
0201             Q_EMIT onAllDesktopsChanged();
0202         }
0203         if (diff & state::state_demands_attention) {
0204             windowState.setFlag(state::state_demands_attention, flags & state::state_demands_attention);
0205             Q_EMIT demandsAttentionChanged();
0206         }
0207         if (diff & state::state_closeable) {
0208             windowState.setFlag(state::state_closeable, flags & state::state_closeable);
0209             Q_EMIT closeableChanged();
0210         }
0211         if (diff & state::state_minimizable) {
0212             windowState.setFlag(state::state_minimizable, flags & state::state_minimizable);
0213             Q_EMIT minimizeableChanged();
0214         }
0215         if (diff & state::state_maximizable) {
0216             windowState.setFlag(state::state_maximizable, flags & state::state_maximizable);
0217             Q_EMIT maximizeableChanged();
0218         }
0219         if (diff & state::state_fullscreenable) {
0220             windowState.setFlag(state::state_fullscreenable, flags & state::state_fullscreenable);
0221             Q_EMIT fullscreenableChanged();
0222         }
0223         if (diff & state::state_skiptaskbar) {
0224             windowState.setFlag(state::state_skiptaskbar, flags & state::state_skiptaskbar);
0225             Q_EMIT skipTaskbarChanged();
0226         }
0227         if (diff & state::state_shadeable) {
0228             windowState.setFlag(state::state_shadeable, flags & state::state_shadeable);
0229             Q_EMIT shadeableChanged();
0230         }
0231         if (diff & state::state_shaded) {
0232             windowState.setFlag(state::state_shaded, flags & state::state_shaded);
0233             Q_EMIT shadedChanged();
0234         }
0235         if (diff & state::state_movable) {
0236             windowState.setFlag(state::state_movable, flags & state::state_movable);
0237             Q_EMIT movableChanged();
0238         }
0239         if (diff & state::state_resizable) {
0240             windowState.setFlag(state::state_resizable, flags & state::state_resizable);
0241             Q_EMIT resizableChanged();
0242         }
0243         if (diff & state::state_virtual_desktop_changeable) {
0244             windowState.setFlag(state::state_virtual_desktop_changeable, flags & state::state_virtual_desktop_changeable);
0245             Q_EMIT virtualDesktopChangeableChanged();
0246         }
0247         if (diff & state::state_skipswitcher) {
0248             windowState.setFlag(state::state_skipswitcher, flags & state::state_skipswitcher);
0249             Q_EMIT skipSwitcherChanged();
0250         }
0251     }
0252     void org_kde_plasma_window_virtual_desktop_entered(const QString &id) override
0253     {
0254         virtualDesktops.push_back(id);
0255         Q_EMIT virtualDesktopEntered();
0256     }
0257 
0258     void org_kde_plasma_window_virtual_desktop_left(const QString &id) override
0259     {
0260         virtualDesktops.removeAll(id);
0261         Q_EMIT virtualDesktopLeft();
0262     }
0263     void org_kde_plasma_window_geometry(int32_t x, int32_t y, uint32_t width, uint32_t height) override
0264     {
0265         geometry = QRect(x, y, width, height);
0266         Q_EMIT geometryChanged();
0267     }
0268     void org_kde_plasma_window_application_menu(const QString &service_name, const QString &object_path) override
0269 
0270     {
0271         applicationMenuService = service_name;
0272         applicationMenuObjectPath = object_path;
0273         Q_EMIT applicationMenuChanged();
0274     }
0275     void org_kde_plasma_window_activity_entered(const QString &id) override
0276     {
0277         activities.push_back(id);
0278         Q_EMIT activitiesChanged();
0279     }
0280     void org_kde_plasma_window_activity_left(const QString &id) override
0281     {
0282         activities.removeAll(id);
0283         Q_EMIT activitiesChanged();
0284     }
0285     void org_kde_plasma_window_pid_changed(uint32_t pid) override
0286     {
0287         this->pid = pid;
0288     }
0289     void org_kde_plasma_window_resource_name_changed(const QString &resource_name) override
0290     {
0291         resourceName = resource_name;
0292     }
0293     void org_kde_plasma_window_parent_window(::org_kde_plasma_window *parent) override
0294     {
0295         PlasmaWindow *parentWindow = nullptr;
0296         if (parent) {
0297             parentWindow = dynamic_cast<PlasmaWindow *>(PlasmaWindow::fromObject(parent));
0298         }
0299         setParentWindow(parentWindow);
0300     }
0301     void org_kde_plasma_window_initial_state() override
0302     {
0303         Q_EMIT initialStateDone();
0304     }
0305 
0306 private:
0307     void setParentWindow(PlasmaWindow *parent)
0308     {
0309         const auto old = parentWindow;
0310         QObject::disconnect(parentWindowUnmappedConnection);
0311 
0312         if (parent && !parent->wasUnmapped) {
0313             parentWindow = QPointer<PlasmaWindow>(parent);
0314             parentWindowUnmappedConnection = QObject::connect(parent, &PlasmaWindow::unmapped, this, [this] {
0315                 setParentWindow(nullptr);
0316             });
0317         } else {
0318             parentWindow = QPointer<PlasmaWindow>();
0319             parentWindowUnmappedConnection = QMetaObject::Connection();
0320         }
0321 
0322         if (parentWindow.data() != old.data()) {
0323             Q_EMIT parentWindowChanged();
0324         }
0325     }
0326 
0327     QMetaObject::Connection parentWindowUnmappedConnection;
0328 };
0329 
0330 class PlasmaWindowManagement : public QWaylandClientExtensionTemplate<PlasmaWindowManagement>, public QtWayland::org_kde_plasma_window_management
0331 {
0332     Q_OBJECT
0333 public:
0334     static constexpr int version = 16;
0335     PlasmaWindowManagement()
0336         : QWaylandClientExtensionTemplate(version)
0337     {
0338         connect(this, &QWaylandClientExtension::activeChanged, this, [this] {
0339             if (!isActive()) {
0340                 wl_proxy_destroy(reinterpret_cast<wl_proxy *>(object()));
0341             }
0342         });
0343     }
0344     ~PlasmaWindowManagement()
0345     {
0346         if (isActive()) {
0347             wl_proxy_destroy(reinterpret_cast<wl_proxy *>(object()));
0348         }
0349     }
0350     void org_kde_plasma_window_management_window_with_uuid(uint32_t id, const QString &uuid) override
0351     {
0352         Q_UNUSED(id)
0353         Q_EMIT windowCreated(new PlasmaWindow(uuid, get_window_by_uuid(uuid)));
0354     }
0355     void org_kde_plasma_window_management_stacking_order_uuid_changed(const QString &uuids) override
0356     {
0357         Q_EMIT stackingOrderChanged(uuids);
0358     }
0359 Q_SIGNALS:
0360     void windowCreated(PlasmaWindow *window);
0361     void stackingOrderChanged(const QString &uuids);
0362 };
0363 class Q_DECL_HIDDEN WaylandTasksModel::Private
0364 {
0365 public:
0366     Private(WaylandTasksModel *q);
0367     QHash<PlasmaWindow *, AppData> appDataCache;
0368     QHash<PlasmaWindow *, QTime> lastActivated;
0369     PlasmaWindow *activeWindow = nullptr;
0370     std::vector<std::unique_ptr<PlasmaWindow>> windows;
0371     // key=transient child, value=leader
0372     QHash<PlasmaWindow *, PlasmaWindow *> transients;
0373     // key=leader, values=transient children
0374     QMultiHash<PlasmaWindow *, PlasmaWindow *> transientsDemandingAttention;
0375     std::unique_ptr<PlasmaWindowManagement> windowManagement;
0376     KSharedConfig::Ptr rulesConfig;
0377     KDirWatch *configWatcher = nullptr;
0378     VirtualDesktopInfo *virtualDesktopInfo = nullptr;
0379     static QUuid uuid;
0380     QList<QString> stackingOrder;
0381 
0382     void init();
0383     void initWayland();
0384     auto findWindow(PlasmaWindow *window) const;
0385     void addWindow(PlasmaWindow *window);
0386 
0387     const AppData &appData(PlasmaWindow *window);
0388 
0389     QIcon icon(PlasmaWindow *window);
0390 
0391     static QString mimeType();
0392     static QString groupMimeType();
0393 
0394     void dataChanged(PlasmaWindow *window, int role);
0395     void dataChanged(PlasmaWindow *window, const QList<int> &roles);
0396 
0397 private:
0398     WaylandTasksModel *q;
0399 };
0400 
0401 QUuid WaylandTasksModel::Private::uuid = QUuid::createUuid();
0402 
0403 WaylandTasksModel::Private::Private(WaylandTasksModel *q)
0404     : q(q)
0405 {
0406 }
0407 
0408 void WaylandTasksModel::Private::init()
0409 {
0410     auto clearCacheAndRefresh = [this] {
0411         if (windows.empty()) {
0412             return;
0413         }
0414 
0415         appDataCache.clear();
0416 
0417         // Emit changes of all roles satisfied from app data cache.
0418         Q_EMIT q->dataChanged(q->index(0, 0),
0419                               q->index(windows.size() - 1, 0),
0420                               QList<int>{Qt::DecorationRole,
0421                                          AbstractTasksModel::AppId,
0422                                          AbstractTasksModel::AppName,
0423                                          AbstractTasksModel::GenericName,
0424                                          AbstractTasksModel::LauncherUrl,
0425                                          AbstractTasksModel::LauncherUrlWithoutIcon,
0426                                          AbstractTasksModel::CanLaunchNewInstance,
0427                                          AbstractTasksModel::SkipTaskbar});
0428     };
0429 
0430     rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc"));
0431     configWatcher = new KDirWatch(q);
0432 
0433     for (const QString &location : QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)) {
0434         configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc"));
0435     }
0436 
0437     auto rulesConfigChange = [this, clearCacheAndRefresh] {
0438         rulesConfig->reparseConfiguration();
0439         clearCacheAndRefresh();
0440     };
0441 
0442     QObject::connect(configWatcher, &KDirWatch::dirty, rulesConfigChange);
0443     QObject::connect(configWatcher, &KDirWatch::created, rulesConfigChange);
0444     QObject::connect(configWatcher, &KDirWatch::deleted, rulesConfigChange);
0445 
0446     virtualDesktopInfo = new VirtualDesktopInfo(q);
0447 
0448     initWayland();
0449 }
0450 
0451 void WaylandTasksModel::Private::initWayland()
0452 {
0453     if (!KWindowSystem::isPlatformWayland()) {
0454         return;
0455     }
0456 
0457     windowManagement = std::make_unique<PlasmaWindowManagement>();
0458 
0459     QObject::connect(windowManagement.get(), &PlasmaWindowManagement::activeChanged, q, [this] {
0460         q->beginResetModel();
0461         windows.clear();
0462         q->endResetModel();
0463     });
0464 
0465     QObject::connect(windowManagement.get(), &PlasmaWindowManagement::windowCreated, q, [this](PlasmaWindow *window) {
0466         connect(window, &PlasmaWindow::initialStateDone, q, [this, window] {
0467             addWindow(window);
0468         });
0469     });
0470 
0471     QObject::connect(windowManagement.get(), &PlasmaWindowManagement::stackingOrderChanged, q, [this](const QString &order) {
0472         stackingOrder = order.split(QLatin1Char(';'));
0473         for (const auto &window : std::as_const(windows)) {
0474             this->dataChanged(window.get(), StackingOrder);
0475         }
0476     });
0477 }
0478 
0479 auto WaylandTasksModel::Private::findWindow(PlasmaWindow *window) const
0480 {
0481     return std::find_if(windows.begin(), windows.end(), [window](const std::unique_ptr<PlasmaWindow> &candidate) {
0482         return candidate.get() == window;
0483     });
0484 }
0485 
0486 void WaylandTasksModel::Private::addWindow(PlasmaWindow *window)
0487 {
0488     if (findWindow(window) != windows.end() || transients.contains(window)) {
0489         return;
0490     }
0491 
0492     auto removeWindow = [window, this] {
0493         auto it = findWindow(window);
0494         if (it != windows.end()) {
0495             const int row = it - windows.begin();
0496             q->beginRemoveRows(QModelIndex(), row, row);
0497             windows.erase(it);
0498             transientsDemandingAttention.remove(window);
0499             appDataCache.remove(window);
0500             lastActivated.remove(window);
0501             q->endRemoveRows();
0502         } else { // Could be a transient.
0503             // Removing a transient might change the demands attention state of the leader.
0504             if (transients.remove(window)) {
0505                 if (PlasmaWindow *leader = transientsDemandingAttention.key(window)) {
0506                     transientsDemandingAttention.remove(leader, window);
0507                     dataChanged(leader, QVector<int>{IsDemandingAttention});
0508                 }
0509             }
0510         }
0511 
0512         if (activeWindow == window) {
0513             activeWindow = nullptr;
0514         }
0515     };
0516 
0517     QObject::connect(window, &PlasmaWindow::unmapped, q, removeWindow);
0518 
0519     QObject::connect(window, &PlasmaWindow::titleChanged, q, [window, this] {
0520         this->dataChanged(window, Qt::DisplayRole);
0521     });
0522 
0523     QObject::connect(window, &PlasmaWindow::iconChanged, q, [window, this] {
0524         // The icon in the AppData struct might come from PlasmaWindow if it wasn't
0525         // filled in by windowUrlFromMetadata+appDataFromUrl.
0526         // TODO: Don't evict the cache unnecessarily if this isn't the case. As icons
0527         // are currently very static on Wayland, this eviction is unlikely to happen
0528         // frequently as of now.
0529         appDataCache.remove(window);
0530         this->dataChanged(window, Qt::DecorationRole);
0531     });
0532 
0533     QObject::connect(window, &PlasmaWindow::appIdChanged, q, [window, this] {
0534         // The AppData struct in the cache is derived from this and needs
0535         // to be evicted in favor of a fresh struct based on the changed
0536         // window metadata.
0537         appDataCache.remove(window);
0538 
0539         // Refresh roles satisfied from the app data cache.
0540         this->dataChanged(window,
0541                           QList<int>{Qt::DecorationRole, AppId, AppName, GenericName, LauncherUrl, LauncherUrlWithoutIcon, SkipTaskbar, CanLaunchNewInstance});
0542     });
0543 
0544     if (window->windowState & PlasmaWindow::state::state_active) {
0545         PlasmaWindow *effectiveActive = window;
0546         while (effectiveActive->parentWindow) {
0547             effectiveActive = effectiveActive->parentWindow;
0548         }
0549 
0550         lastActivated[effectiveActive] = QTime::currentTime();
0551         activeWindow = effectiveActive;
0552     }
0553 
0554     QObject::connect(window, &PlasmaWindow::activeChanged, q, [window, this] {
0555         const bool active = window->windowState & PlasmaWindow::state::state_active;
0556 
0557         PlasmaWindow *effectiveWindow = window;
0558 
0559         while (effectiveWindow->parentWindow) {
0560             effectiveWindow = effectiveWindow->parentWindow;
0561         }
0562 
0563         if (active) {
0564             lastActivated[effectiveWindow] = QTime::currentTime();
0565 
0566             if (activeWindow != effectiveWindow) {
0567                 activeWindow = effectiveWindow;
0568                 this->dataChanged(effectiveWindow, IsActive);
0569             }
0570         } else {
0571             if (activeWindow == effectiveWindow) {
0572                 activeWindow = nullptr;
0573                 this->dataChanged(effectiveWindow, IsActive);
0574             }
0575         }
0576     });
0577 
0578     QObject::connect(window, &PlasmaWindow::parentWindowChanged, q, [window, this] {
0579         PlasmaWindow *leader = window->parentWindow.data();
0580 
0581         // Migrate demanding attention to new leader.
0582         if (window->windowState.testFlag(PlasmaWindow::state::state_demands_attention)) {
0583             if (auto *oldLeader = transientsDemandingAttention.key(window)) {
0584                 if (window->parentWindow != oldLeader) {
0585                     transientsDemandingAttention.remove(oldLeader, window);
0586                     transientsDemandingAttention.insert(leader, window);
0587                     dataChanged(oldLeader, QVector<int>{IsDemandingAttention});
0588                     dataChanged(leader, QVector<int>{IsDemandingAttention});
0589                 }
0590             }
0591         }
0592 
0593         if (transients.remove(window)) {
0594             if (leader) { // leader change.
0595                 transients.insert(window, leader);
0596             } else { // lost a leader, add to regular windows list.
0597                 Q_ASSERT(findWindow(window) == windows.end());
0598 
0599                 const int count = windows.size();
0600                 q->beginInsertRows(QModelIndex(), count, count);
0601                 windows.emplace_back(window);
0602                 q->endInsertRows();
0603             }
0604         } else if (leader) { // gained a leader, remove from regular windows list.
0605             auto it = findWindow(window);
0606             Q_ASSERT(it != windows.end());
0607 
0608             const int row = it - windows.begin();
0609             q->beginRemoveRows(QModelIndex(), row, row);
0610             windows.erase(it);
0611             appDataCache.remove(window);
0612             lastActivated.remove(window);
0613             q->endRemoveRows();
0614         }
0615     });
0616 
0617     QObject::connect(window, &PlasmaWindow::closeableChanged, q, [window, this] {
0618         this->dataChanged(window, IsClosable);
0619     });
0620 
0621     QObject::connect(window, &PlasmaWindow::movableChanged, q, [window, this] {
0622         this->dataChanged(window, IsMovable);
0623     });
0624 
0625     QObject::connect(window, &PlasmaWindow::resizableChanged, q, [window, this] {
0626         this->dataChanged(window, IsResizable);
0627     });
0628 
0629     QObject::connect(window, &PlasmaWindow::fullscreenableChanged, q, [window, this] {
0630         this->dataChanged(window, IsFullScreenable);
0631     });
0632 
0633     QObject::connect(window, &PlasmaWindow::fullscreenChanged, q, [window, this] {
0634         this->dataChanged(window, IsFullScreen);
0635     });
0636 
0637     QObject::connect(window, &PlasmaWindow::maximizeableChanged, q, [window, this] {
0638         this->dataChanged(window, IsMaximizable);
0639     });
0640 
0641     QObject::connect(window, &PlasmaWindow::maximizedChanged, q, [window, this] {
0642         this->dataChanged(window, IsMaximized);
0643     });
0644 
0645     QObject::connect(window, &PlasmaWindow::minimizeableChanged, q, [window, this] {
0646         this->dataChanged(window, IsMinimizable);
0647     });
0648 
0649     QObject::connect(window, &PlasmaWindow::minimizedChanged, q, [window, this] {
0650         this->dataChanged(window, IsMinimized);
0651     });
0652 
0653     QObject::connect(window, &PlasmaWindow::keepAboveChanged, q, [window, this] {
0654         this->dataChanged(window, IsKeepAbove);
0655     });
0656 
0657     QObject::connect(window, &PlasmaWindow::keepBelowChanged, q, [window, this] {
0658         this->dataChanged(window, IsKeepBelow);
0659     });
0660 
0661     QObject::connect(window, &PlasmaWindow::shadeableChanged, q, [window, this] {
0662         this->dataChanged(window, IsShadeable);
0663     });
0664 
0665     QObject::connect(window, &PlasmaWindow::virtualDesktopChangeableChanged, q, [window, this] {
0666         this->dataChanged(window, IsVirtualDesktopsChangeable);
0667     });
0668 
0669     QObject::connect(window, &PlasmaWindow::virtualDesktopEntered, q, [window, this] {
0670         this->dataChanged(window, VirtualDesktops);
0671 
0672         // If the count has changed from 0, the window may no longer be on all virtual
0673         // desktops.
0674         if (window->virtualDesktops.count() > 0) {
0675             this->dataChanged(window, IsOnAllVirtualDesktops);
0676         }
0677     });
0678 
0679     QObject::connect(window, &PlasmaWindow::virtualDesktopLeft, q, [window, this] {
0680         this->dataChanged(window, VirtualDesktops);
0681 
0682         // If the count has changed to 0, the window is now on all virtual desktops.
0683         if (window->virtualDesktops.count() == 0) {
0684             this->dataChanged(window, IsOnAllVirtualDesktops);
0685         }
0686     });
0687 
0688     QObject::connect(window, &PlasmaWindow::geometryChanged, q, [window, this] {
0689         this->dataChanged(window, QList<int>{Geometry, ScreenGeometry});
0690     });
0691 
0692     QObject::connect(window, &PlasmaWindow::demandsAttentionChanged, q, [window, this] {
0693         // Changes to a transient's state might change demands attention state for leader.
0694         if (auto *leader = transients.value(window)) {
0695             if (window->windowState.testFlag(PlasmaWindow::state::state_demands_attention)) {
0696                 if (!transientsDemandingAttention.values(leader).contains(window)) {
0697                     transientsDemandingAttention.insert(leader, window);
0698                     this->dataChanged(leader, QVector<int>{IsDemandingAttention});
0699                 }
0700             } else if (transientsDemandingAttention.remove(window)) {
0701                 this->dataChanged(leader, QVector<int>{IsDemandingAttention});
0702             }
0703         } else {
0704             this->dataChanged(window, QVector<int>{IsDemandingAttention});
0705         }
0706     });
0707 
0708     QObject::connect(window, &PlasmaWindow::skipTaskbarChanged, q, [window, this] {
0709         this->dataChanged(window, SkipTaskbar);
0710     });
0711 
0712     QObject::connect(window, &PlasmaWindow::applicationMenuChanged, q, [window, this] {
0713         this->dataChanged(window, QList<int>{ApplicationMenuServiceName, ApplicationMenuObjectPath});
0714     });
0715 
0716     QObject::connect(window, &PlasmaWindow::activitiesChanged, q, [window, this] {
0717         this->dataChanged(window, Activities);
0718     });
0719 
0720     // Handle transient.
0721     if (PlasmaWindow *leader = window->parentWindow.data()) {
0722         transients.insert(window, leader);
0723 
0724         // Update demands attention state for leader.
0725         if (window->windowState.testFlag(PlasmaWindow::state::state_demands_attention)) {
0726             transientsDemandingAttention.insert(leader, window);
0727             dataChanged(leader, QVector<int>{IsDemandingAttention});
0728         }
0729     } else {
0730         const int count = windows.size();
0731 
0732         q->beginInsertRows(QModelIndex(), count, count);
0733 
0734         windows.emplace_back(window);
0735 
0736         q->endInsertRows();
0737     }
0738 }
0739 
0740 const AppData &WaylandTasksModel::Private::appData(PlasmaWindow *window)
0741 {
0742     static_assert(!std::is_trivially_copy_assignable_v<AppData>);
0743     if (auto it = appDataCache.constFind(window); it != appDataCache.constEnd()) {
0744         return *it;
0745     }
0746 
0747     return *appDataCache.emplace(window, appDataFromUrl(windowUrlFromMetadata(window->appId, window->pid, rulesConfig, window->resourceName)));
0748 }
0749 
0750 QIcon WaylandTasksModel::Private::icon(PlasmaWindow *window)
0751 {
0752     const AppData &app = appData(window);
0753 
0754     if (!app.icon.isNull()) {
0755         return app.icon;
0756     }
0757 
0758     appDataCache[window].icon = window->icon;
0759 
0760     return window->icon;
0761 }
0762 
0763 QString WaylandTasksModel::Private::mimeType()
0764 {
0765     // Use a unique format id to make this intentionally useless for
0766     // cross-process DND.
0767     return QStringLiteral("windowsystem/winid+") + uuid.toString();
0768 }
0769 
0770 QString WaylandTasksModel::Private::groupMimeType()
0771 {
0772     // Use a unique format id to make this intentionally useless for
0773     // cross-process DND.
0774     return QStringLiteral("windowsystem/multiple-winids+") + uuid.toString();
0775 }
0776 
0777 void WaylandTasksModel::Private::dataChanged(PlasmaWindow *window, int role)
0778 {
0779     auto it = findWindow(window);
0780     if (it == windows.end()) {
0781         return;
0782     }
0783     QModelIndex idx = q->index(it - windows.begin());
0784     Q_EMIT q->dataChanged(idx, idx, QList<int>{role});
0785 }
0786 
0787 void WaylandTasksModel::Private::dataChanged(PlasmaWindow *window, const QList<int> &roles)
0788 {
0789     auto it = findWindow(window);
0790     if (it == windows.end()) {
0791         return;
0792     }
0793     QModelIndex idx = q->index(it - windows.begin());
0794     Q_EMIT q->dataChanged(idx, idx, roles);
0795 }
0796 
0797 WaylandTasksModel::WaylandTasksModel(QObject *parent)
0798     : AbstractWindowTasksModel(parent)
0799     , d(new Private(this))
0800 {
0801     d->init();
0802 }
0803 
0804 WaylandTasksModel::~WaylandTasksModel() = default;
0805 
0806 QVariant WaylandTasksModel::data(const QModelIndex &index, int role) const
0807 {
0808     // Note: when index is valid, its row >= 0, so casting to unsigned is safe
0809     if (!index.isValid() || static_cast<size_t>(index.row()) >= d->windows.size()) {
0810         return QVariant();
0811     }
0812 
0813     PlasmaWindow *window = d->windows.at(index.row()).get();
0814 
0815     if (role == Qt::DisplayRole) {
0816         return window->title;
0817     } else if (role == Qt::DecorationRole) {
0818         return d->icon(window);
0819     } else if (role == AppId) {
0820         const QString &id = d->appData(window).id;
0821 
0822         if (id.isEmpty()) {
0823             return window->appId;
0824         } else {
0825             return id;
0826         }
0827     } else if (role == AppName) {
0828         return d->appData(window).name;
0829     } else if (role == GenericName) {
0830         return d->appData(window).genericName;
0831     } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) {
0832         return d->appData(window).url;
0833     } else if (role == WinIdList) {
0834         return QVariantList{window->uuid};
0835     } else if (role == MimeType) {
0836         return d->mimeType();
0837     } else if (role == MimeData) {
0838         return window->uuid;
0839     } else if (role == IsWindow) {
0840         return true;
0841     } else if (role == IsActive) {
0842         return (window == d->activeWindow);
0843     } else if (role == IsClosable) {
0844         return window->windowState.testFlag(PlasmaWindow::state::state_closeable);
0845     } else if (role == IsMovable) {
0846         return window->windowState.testFlag(PlasmaWindow::state::state_movable);
0847     } else if (role == IsResizable) {
0848         return window->windowState.testFlag(PlasmaWindow::state::state_resizable);
0849     } else if (role == IsMaximizable) {
0850         return window->windowState.testFlag(PlasmaWindow::state::state_maximizable);
0851     } else if (role == IsMaximized) {
0852         return window->windowState.testFlag(PlasmaWindow::state::state_maximized);
0853     } else if (role == IsMinimizable) {
0854         return window->windowState.testFlag(PlasmaWindow::state::state_minimizable);
0855     } else if (role == IsMinimized || role == IsHidden) {
0856         return window->windowState.testFlag(PlasmaWindow::state::state_minimized);
0857     } else if (role == IsKeepAbove) {
0858         return window->windowState.testFlag(PlasmaWindow::state::state_keep_above);
0859     } else if (role == IsKeepBelow) {
0860         return window->windowState.testFlag(PlasmaWindow::state::state_keep_below);
0861     } else if (role == IsFullScreenable) {
0862         return window->windowState.testFlag(PlasmaWindow::state::state_fullscreenable);
0863     } else if (role == IsFullScreen) {
0864         return window->windowState.testFlag(PlasmaWindow::state::state_fullscreen);
0865     } else if (role == IsShadeable) {
0866         return window->windowState.testFlag(PlasmaWindow::state::state_shadeable);
0867     } else if (role == IsShaded) {
0868         return window->windowState.testFlag(PlasmaWindow::state::state_shaded);
0869     } else if (role == IsVirtualDesktopsChangeable) {
0870         return window->windowState.testFlag(PlasmaWindow::state::state_virtual_desktop_changeable);
0871     } else if (role == VirtualDesktops) {
0872         return window->virtualDesktops;
0873     } else if (role == IsOnAllVirtualDesktops) {
0874         return window->virtualDesktops.isEmpty();
0875     } else if (role == Geometry) {
0876         return window->geometry;
0877     } else if (role == ScreenGeometry) {
0878         return screenGeometry(window->geometry.center());
0879     } else if (role == Activities) {
0880         return window->activities;
0881     } else if (role == IsDemandingAttention) {
0882         return window->windowState.testFlag(PlasmaWindow::state::state_demands_attention) || d->transientsDemandingAttention.contains(window);
0883     } else if (role == SkipTaskbar) {
0884         return window->windowState.testFlag(PlasmaWindow::state::state_skiptaskbar) || d->appData(window).skipTaskbar;
0885     } else if (role == SkipPager) {
0886         // FIXME Implement.
0887     } else if (role == AppPid) {
0888         return window->pid;
0889     } else if (role == StackingOrder) {
0890         return d->stackingOrder.indexOf(window->uuid);
0891     } else if (role == LastActivated) {
0892         if (d->lastActivated.contains(window)) {
0893             return d->lastActivated.value(window);
0894         }
0895     } else if (role == ApplicationMenuObjectPath) {
0896         return window->applicationMenuObjectPath;
0897     } else if (role == ApplicationMenuServiceName) {
0898         return window->applicationMenuService;
0899     } else if (role == CanLaunchNewInstance) {
0900         return canLauchNewInstance(d->appData(window));
0901     }
0902 
0903     return AbstractTasksModel::data(index, role);
0904 }
0905 
0906 int WaylandTasksModel::rowCount(const QModelIndex &parent) const
0907 {
0908     return parent.isValid() ? 0 : d->windows.size();
0909 }
0910 
0911 QModelIndex WaylandTasksModel::index(int row, int column, const QModelIndex &parent) const
0912 {
0913     return hasIndex(row, column, parent) ? createIndex(row, column, d->windows.at(row).get()) : QModelIndex();
0914 }
0915 
0916 void WaylandTasksModel::requestActivate(const QModelIndex &index)
0917 {
0918     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0919         return;
0920     }
0921 
0922     PlasmaWindow *window = d->windows.at(index.row()).get();
0923 
0924     // Pull forward any transient demanding attention.
0925     if (auto *transientDemandingAttention = d->transientsDemandingAttention.value(window)) {
0926         window = transientDemandingAttention;
0927     } else {
0928         // TODO Shouldn't KWin take care of that?
0929         // Bringing a transient to the front usually brings its parent with it
0930         // but focus is not handled properly.
0931         // TODO take into account d->lastActivation instead
0932         // of just taking the first one.
0933         while (d->transients.key(window)) {
0934             window = d->transients.key(window);
0935         }
0936     }
0937 
0938     window->set_state(PlasmaWindow::state::state_active, PlasmaWindow::state::state_active);
0939 }
0940 
0941 void WaylandTasksModel::requestNewInstance(const QModelIndex &index)
0942 {
0943     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0944         return;
0945     }
0946 
0947     runApp(d->appData(d->windows.at(index.row()).get()));
0948 }
0949 
0950 void WaylandTasksModel::requestOpenUrls(const QModelIndex &index, const QList<QUrl> &urls)
0951 {
0952     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent) || urls.isEmpty()) {
0953         return;
0954     }
0955 
0956     runApp(d->appData(d->windows.at(index.row()).get()), urls);
0957 }
0958 
0959 void WaylandTasksModel::requestClose(const QModelIndex &index)
0960 {
0961     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0962         return;
0963     }
0964 
0965     d->windows.at(index.row())->close();
0966 }
0967 
0968 void WaylandTasksModel::requestMove(const QModelIndex &index)
0969 {
0970     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0971         return;
0972     }
0973 
0974     auto &window = d->windows.at(index.row());
0975 
0976     window->set_state(PlasmaWindow::state::state_active, PlasmaWindow::state::state_active);
0977     window->request_move();
0978 }
0979 
0980 void WaylandTasksModel::requestResize(const QModelIndex &index)
0981 {
0982     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0983         return;
0984     }
0985 
0986     auto &window = d->windows.at(index.row());
0987 
0988     window->set_state(PlasmaWindow::state::state_active, PlasmaWindow::state::state_active);
0989     window->request_resize();
0990 }
0991 
0992 void WaylandTasksModel::requestToggleMinimized(const QModelIndex &index)
0993 {
0994     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
0995         return;
0996     }
0997 
0998     auto &window = d->windows.at(index.row());
0999 
1000     if (window->windowState & PlasmaWindow::state::state_minimized) {
1001         window->set_state(PlasmaWindow::state::state_minimized, 0);
1002     } else {
1003         window->set_state(PlasmaWindow::state::state_minimized, PlasmaWindow::state::state_minimized);
1004     }
1005 }
1006 
1007 void WaylandTasksModel::requestToggleMaximized(const QModelIndex &index)
1008 {
1009     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1010         return;
1011     }
1012 
1013     auto &window = d->windows.at(index.row());
1014 
1015     if (window->windowState & PlasmaWindow::state::state_maximized) {
1016         window->set_state(PlasmaWindow::state::state_maximized | PlasmaWindow::state::state_active, PlasmaWindow::state::state_active);
1017     } else {
1018         window->set_state(PlasmaWindow::state::state_maximized | PlasmaWindow::state::state_active,
1019                           PlasmaWindow::state::state_maximized | PlasmaWindow::state::state_active);
1020     }
1021 }
1022 
1023 void WaylandTasksModel::requestToggleKeepAbove(const QModelIndex &index)
1024 {
1025     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1026         return;
1027     }
1028 
1029     auto &window = d->windows.at(index.row());
1030 
1031     if (window->windowState & PlasmaWindow::state::state_keep_above) {
1032         window->set_state(PlasmaWindow::state::state_keep_above, 0);
1033     } else {
1034         window->set_state(PlasmaWindow::state::state_keep_above, PlasmaWindow::state::state_keep_above);
1035     }
1036 }
1037 
1038 void WaylandTasksModel::requestToggleKeepBelow(const QModelIndex &index)
1039 {
1040     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1041         return;
1042     }
1043     auto &window = d->windows.at(index.row());
1044 
1045     if (window->windowState & PlasmaWindow::state::state_keep_below) {
1046         window->set_state(PlasmaWindow::state::state_keep_below, 0);
1047     } else {
1048         window->set_state(PlasmaWindow::state::state_keep_below, PlasmaWindow::state::state_keep_below);
1049     }
1050 }
1051 
1052 void WaylandTasksModel::requestToggleFullScreen(const QModelIndex &index)
1053 {
1054     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1055         return;
1056     }
1057 
1058     auto &window = d->windows.at(index.row());
1059 
1060     if (window->windowState & PlasmaWindow::state::state_fullscreen) {
1061         window->set_state(PlasmaWindow::state::state_fullscreen, 0);
1062     } else {
1063         window->set_state(PlasmaWindow::state::state_fullscreen, PlasmaWindow::state::state_fullscreen);
1064     }
1065 }
1066 
1067 void WaylandTasksModel::requestToggleShaded(const QModelIndex &index)
1068 {
1069     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1070         return;
1071     }
1072 
1073     auto &window = d->windows.at(index.row());
1074 
1075     if (window->windowState & PlasmaWindow::state::state_shaded) {
1076         window->set_state(PlasmaWindow::state::state_shaded, 0);
1077     } else {
1078         window->set_state(PlasmaWindow::state::state_shaded, PlasmaWindow::state::state_shaded);
1079     };
1080 }
1081 
1082 void WaylandTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops)
1083 {
1084     // FIXME TODO: Lacks the "if we've requested the current desktop, force-activate
1085     // the window" logic from X11 version. This behavior should be in KWin rather than
1086     // libtm however.
1087 
1088     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1089         return;
1090     }
1091 
1092     auto &window = d->windows.at(index.row());
1093 
1094     if (desktops.isEmpty()) {
1095         const QStringList virtualDesktops = window->virtualDesktops;
1096         for (const QString &desktop : virtualDesktops) {
1097             window->request_leave_virtual_desktop(desktop);
1098         }
1099     } else {
1100         const QStringList &now = window->virtualDesktops;
1101         QStringList next;
1102 
1103         for (const QVariant &desktop : desktops) {
1104             const QString &desktopId = desktop.toString();
1105 
1106             if (!desktopId.isEmpty()) {
1107                 next << desktopId;
1108 
1109                 if (!now.contains(desktopId)) {
1110                     window->request_enter_virtual_desktop(desktopId);
1111                 }
1112             }
1113         }
1114 
1115         for (const QString &desktop : now) {
1116             if (!next.contains(desktop)) {
1117                 window->request_leave_virtual_desktop(desktop);
1118             }
1119         }
1120     }
1121 }
1122 
1123 void WaylandTasksModel::requestNewVirtualDesktop(const QModelIndex &index)
1124 {
1125     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1126         return;
1127     }
1128 
1129     d->windows.at(index.row())->request_enter_new_virtual_desktop();
1130 }
1131 
1132 void WaylandTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities)
1133 {
1134     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1135         return;
1136     }
1137 
1138     auto &window = d->windows.at(index.row());
1139     const auto newActivities = QSet(activities.begin(), activities.end());
1140     const auto plasmaActivities = window->activities;
1141     const auto oldActivities = QSet(plasmaActivities.begin(), plasmaActivities.end());
1142 
1143     const auto activitiesToAdd = newActivities - oldActivities;
1144     for (const auto &activity : activitiesToAdd) {
1145         window->request_enter_activity(activity);
1146     }
1147 
1148     const auto activitiesToRemove = oldActivities - newActivities;
1149     for (const auto &activity : activitiesToRemove) {
1150         window->request_leave_activity(activity);
1151     }
1152 }
1153 
1154 void WaylandTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate)
1155 {
1156     /*
1157     FIXME: This introduces the dependency on Qt::Quick. I might prefer
1158     reversing this and publishing the window pointer through the model,
1159     then calling PlasmaWindow::setMinimizeGeometry in the applet backend,
1160     rather than hand delegate items into the lib, keeping the lib more UI-
1161     agnostic.
1162     */
1163 
1164     Q_UNUSED(geometry)
1165 
1166     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1167         return;
1168     }
1169 
1170     const QQuickItem *item = qobject_cast<const QQuickItem *>(delegate);
1171 
1172     if (!item || !item->parentItem()) {
1173         return;
1174     }
1175 
1176     QWindow *itemWindow = item->window();
1177 
1178     if (!itemWindow) {
1179         return;
1180     }
1181 
1182     auto waylandWindow = itemWindow->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
1183 
1184     if (!waylandWindow || !waylandWindow->surface()) {
1185         return;
1186     }
1187 
1188     QRect rect(item->x(), item->y(), item->width(), item->height());
1189     rect.moveTopLeft(item->parentItem()->mapToScene(rect.topLeft()).toPoint());
1190 
1191     auto &window = d->windows.at(index.row());
1192 
1193     window->set_minimized_geometry(waylandWindow->surface(), rect.x(), rect.y(), rect.width(), rect.height());
1194 }
1195 
1196 QUuid WaylandTasksModel::winIdFromMimeData(const QMimeData *mimeData, bool *ok)
1197 {
1198     Q_ASSERT(mimeData);
1199 
1200     if (ok) {
1201         *ok = false;
1202     }
1203 
1204     if (!mimeData->hasFormat(Private::mimeType())) {
1205         return {};
1206     }
1207 
1208     QUuid id(mimeData->data(Private::mimeType()));
1209     *ok = !id.isNull();
1210 
1211     return id;
1212 }
1213 
1214 QList<QUuid> WaylandTasksModel::winIdsFromMimeData(const QMimeData *mimeData, bool *ok)
1215 {
1216     Q_ASSERT(mimeData);
1217     QList<QUuid> ids;
1218 
1219     if (ok) {
1220         *ok = false;
1221     }
1222 
1223     if (!mimeData->hasFormat(Private::groupMimeType())) {
1224         // Try to extract single window id.
1225         bool singularOk;
1226         QUuid id = winIdFromMimeData(mimeData, &singularOk);
1227 
1228         if (ok) {
1229             *ok = singularOk;
1230         }
1231 
1232         if (singularOk) {
1233             ids << id;
1234         }
1235 
1236         return ids;
1237     }
1238 
1239     // FIXME: Extracting multiple winids is still unimplemented;
1240     // TaskGroupingProxy::data(..., ::MimeData) can't produce
1241     // a payload with them anyways.
1242 
1243     return ids;
1244 }
1245 
1246 }
1247 
1248 #include "waylandtasksmodel.moc"