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