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"