1 /*
2 SPDX-FileCopyrightText: 2016 Eike Hein <hein@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6
7 #include "waylandtasksmodel.h"
8 #include "tasktools.h"
9 #include "virtualdesktopinfo.h"
10
11 #include <KDirWatch>
12 #include <KSharedConfig>
13 #include <KWayland/Client/connection_thread.h>
14 #include <KWayland/Client/plasmawindowmanagement.h>
15 #include <KWayland/Client/registry.h>
16 #include <KWayland/Client/surface.h>
17 #include <KWindowSystem>
18
19 #include <QGuiApplication>
20 #include <QMimeData>
21 #include <QQuickItem>
22 #include <QQuickWindow>
23 #include <QSet>
24 #include <QUrl>
25 #include <QUuid>
26 #include <QWindow>
27
28 namespace TaskManager
29 {
30 class Q_DECL_HIDDEN WaylandTasksModel::Private
31 {
32 public:
33 Private(WaylandTasksModel *q);
34 QList<KWayland::Client::PlasmaWindow *> windows;
35 QHash<KWayland::Client::PlasmaWindow *, AppData> appDataCache;
36 QHash<KWayland::Client::PlasmaWindow *, QTime> lastActivated;
37 KWayland::Client::PlasmaWindowManagement *windowManagement = nullptr;
38 KSharedConfig::Ptr rulesConfig;
39 KDirWatch *configWatcher = nullptr;
40 VirtualDesktopInfo *virtualDesktopInfo = nullptr;
41 static QUuid uuid;
42
43 void init();
44 void initWayland();
45 void addWindow(KWayland::Client::PlasmaWindow *window);
46
47 AppData appData(KWayland::Client::PlasmaWindow *window);
48
49 QIcon icon(KWayland::Client::PlasmaWindow *window);
50
51 static QString mimeType();
52 static QString groupMimeType();
53
54 void dataChanged(KWayland::Client::PlasmaWindow *window, int role);
55 void dataChanged(KWayland::Client::PlasmaWindow *window, const QVector<int> &roles);
56
57 private:
58 WaylandTasksModel *q;
59 };
60
61 QUuid WaylandTasksModel::Private::uuid = QUuid::createUuid();
62
Private(WaylandTasksModel * q)63 WaylandTasksModel::Private::Private(WaylandTasksModel *q)
64 : q(q)
65 {
66 }
67
init()68 void WaylandTasksModel::Private::init()
69 {
70 auto clearCacheAndRefresh = [this] {
71 if (!windows.count()) {
72 return;
73 }
74
75 appDataCache.clear();
76
77 // Emit changes of all roles satisfied from app data cache.
78 Q_EMIT q->dataChanged(q->index(0, 0),
79 q->index(windows.count() - 1, 0),
80 QVector<int>{Qt::DecorationRole,
81 AbstractTasksModel::AppId,
82 AbstractTasksModel::AppName,
83 AbstractTasksModel::GenericName,
84 AbstractTasksModel::LauncherUrl,
85 AbstractTasksModel::LauncherUrlWithoutIcon,
86 AbstractTasksModel::SkipTaskbar});
87 };
88
89 rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc"));
90 configWatcher = new KDirWatch(q);
91
92 foreach (const QString &location, QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)) {
93 configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc"));
94 }
95
96 auto rulesConfigChange = [this, clearCacheAndRefresh] {
97 rulesConfig->reparseConfiguration();
98 clearCacheAndRefresh();
99 };
100
101 QObject::connect(configWatcher, &KDirWatch::dirty, rulesConfigChange);
102 QObject::connect(configWatcher, &KDirWatch::created, rulesConfigChange);
103 QObject::connect(configWatcher, &KDirWatch::deleted, rulesConfigChange);
104
105 virtualDesktopInfo = new VirtualDesktopInfo(q);
106
107 initWayland();
108 }
109
initWayland()110 void WaylandTasksModel::Private::initWayland()
111 {
112 if (!KWindowSystem::isPlatformWayland()) {
113 return;
114 }
115
116 KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(q);
117
118 if (!connection) {
119 return;
120 }
121
122 KWayland::Client::Registry *registry = new KWayland::Client::Registry(q);
123 registry->create(connection);
124
125 QObject::connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, q, [this, registry](quint32 name, quint32 version) {
126 windowManagement = registry->createPlasmaWindowManagement(name, version, q);
127
128 QObject::connect(windowManagement, &KWayland::Client::PlasmaWindowManagement::interfaceAboutToBeReleased, q, [this] {
129 q->beginResetModel();
130 windows.clear();
131 q->endResetModel();
132 });
133
134 QObject::connect(windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, q, [this](KWayland::Client::PlasmaWindow *window) {
135 addWindow(window);
136 });
137
138 QObject::connect(windowManagement, &KWayland::Client::PlasmaWindowManagement::stackingOrderUuidsChanged, q, [this]() {
139 for (const auto window : qAsConst(windows)) {
140 this->dataChanged(window, StackingOrder);
141 }
142 });
143
144 const auto windows = windowManagement->windows();
145 for (auto it = windows.constBegin(); it != windows.constEnd(); ++it) {
146 addWindow(*it);
147 }
148 });
149
150 registry->setup();
151 }
152
addWindow(KWayland::Client::PlasmaWindow * window)153 void WaylandTasksModel::Private::addWindow(KWayland::Client::PlasmaWindow *window)
154 {
155 if (windows.indexOf(window) != -1) {
156 return;
157 }
158
159 const int count = windows.count();
160
161 q->beginInsertRows(QModelIndex(), count, count);
162
163 windows.append(window);
164
165 q->endInsertRows();
166
167 auto removeWindow = [window, this] {
168 const int row = windows.indexOf(window);
169 if (row != -1) {
170 q->beginRemoveRows(QModelIndex(), row, row);
171 windows.removeAt(row);
172 appDataCache.remove(window);
173 lastActivated.remove(window);
174 q->endRemoveRows();
175 }
176 };
177
178 QObject::connect(window, &KWayland::Client::PlasmaWindow::unmapped, q, removeWindow);
179 QObject::connect(window, &QObject::destroyed, q, removeWindow);
180
181 QObject::connect(window, &KWayland::Client::PlasmaWindow::titleChanged, q, [window, this] {
182 this->dataChanged(window, Qt::DisplayRole);
183 });
184
185 QObject::connect(window, &KWayland::Client::PlasmaWindow::iconChanged, q, [window, this] {
186 // The icon in the AppData struct might come from PlasmaWindow if it wasn't
187 // filled in by windowUrlFromMetadata+appDataFromUrl.
188 // TODO: Don't evict the cache unnecessarily if this isn't the case. As icons
189 // are currently very static on Wayland, this eviction is unlikely to happen
190 // frequently as of now.
191 appDataCache.remove(window);
192
193 this->dataChanged(window, Qt::DecorationRole);
194 });
195
196 QObject::connect(window, &KWayland::Client::PlasmaWindow::appIdChanged, q, [window, this] {
197 // The AppData struct in the cache is derived from this and needs
198 // to be evicted in favor of a fresh struct based on the changed
199 // window metadata.
200 appDataCache.remove(window);
201
202 // Refresh roles satisfied from the app data cache.
203 this->dataChanged(window, QVector<int>{AppId, AppName, GenericName, LauncherUrl, LauncherUrlWithoutIcon, SkipTaskbar});
204 });
205
206 QObject::connect(window, &KWayland::Client::PlasmaWindow::activeChanged, q, [window, this] {
207 if (window->isActive()) {
208 lastActivated[window] = QTime::currentTime();
209 }
210 this->dataChanged(window, IsActive);
211 });
212
213 QObject::connect(window, &KWayland::Client::PlasmaWindow::closeableChanged, q, [window, this] {
214 this->dataChanged(window, IsClosable);
215 });
216
217 QObject::connect(window, &KWayland::Client::PlasmaWindow::movableChanged, q, [window, this] {
218 this->dataChanged(window, IsMovable);
219 });
220
221 QObject::connect(window, &KWayland::Client::PlasmaWindow::resizableChanged, q, [window, this] {
222 this->dataChanged(window, IsResizable);
223 });
224
225 QObject::connect(window, &KWayland::Client::PlasmaWindow::fullscreenableChanged, q, [window, this] {
226 this->dataChanged(window, IsFullScreenable);
227 });
228
229 QObject::connect(window, &KWayland::Client::PlasmaWindow::fullscreenChanged, q, [window, this] {
230 this->dataChanged(window, IsFullScreen);
231 });
232
233 QObject::connect(window, &KWayland::Client::PlasmaWindow::maximizeableChanged, q, [window, this] {
234 this->dataChanged(window, IsMaximizable);
235 });
236
237 QObject::connect(window, &KWayland::Client::PlasmaWindow::maximizedChanged, q, [window, this] {
238 this->dataChanged(window, IsMaximized);
239 });
240
241 QObject::connect(window, &KWayland::Client::PlasmaWindow::minimizeableChanged, q, [window, this] {
242 this->dataChanged(window, IsMinimizable);
243 });
244
245 QObject::connect(window, &KWayland::Client::PlasmaWindow::minimizedChanged, q, [window, this] {
246 this->dataChanged(window, IsMinimized);
247 });
248
249 QObject::connect(window, &KWayland::Client::PlasmaWindow::keepAboveChanged, q, [window, this] {
250 this->dataChanged(window, IsKeepAbove);
251 });
252
253 QObject::connect(window, &KWayland::Client::PlasmaWindow::keepBelowChanged, q, [window, this] {
254 this->dataChanged(window, IsKeepBelow);
255 });
256
257 QObject::connect(window, &KWayland::Client::PlasmaWindow::shadeableChanged, q, [window, this] {
258 this->dataChanged(window, IsShadeable);
259 });
260
261 // FIXME
262 // QObject::connect(window, &KWayland::Client::PlasmaWindow::virtualDesktopChangeableChanged, q,
263 // // TODO: This is marked deprecated in KWayland, but (IMHO) shouldn't be.
264 // [window, this] { this->dataChanged(window, IsVirtualDesktopsChangeable); }
265 // );
266
267 QObject::connect(window, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopEntered, q, [window, this] {
268 this->dataChanged(window, VirtualDesktops);
269
270 // If the count has changed from 0, the window may no longer be on all virtual
271 // desktops.
272 if (window->plasmaVirtualDesktops().count() > 0) {
273 this->dataChanged(window, IsOnAllVirtualDesktops);
274 }
275 });
276
277 QObject::connect(window, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopLeft, q, [window, this] {
278 this->dataChanged(window, VirtualDesktops);
279
280 // If the count has changed to 0, the window is now on all virtual desktops.
281 if (window->plasmaVirtualDesktops().count() == 0) {
282 this->dataChanged(window, IsOnAllVirtualDesktops);
283 }
284 });
285
286 QObject::connect(window, &KWayland::Client::PlasmaWindow::geometryChanged, q, [window, this] {
287 this->dataChanged(window, QVector<int>{Geometry, ScreenGeometry});
288 });
289
290 QObject::connect(window, &KWayland::Client::PlasmaWindow::demandsAttentionChanged, q, [window, this] {
291 this->dataChanged(window, IsDemandingAttention);
292 });
293
294 QObject::connect(window, &KWayland::Client::PlasmaWindow::skipTaskbarChanged, q, [window, this] {
295 this->dataChanged(window, SkipTaskbar);
296 });
297
298 QObject::connect(window, &KWayland::Client::PlasmaWindow::applicationMenuChanged, q, [window, this] {
299 this->dataChanged(window, QVector<int>{ApplicationMenuServiceName, ApplicationMenuObjectPath});
300 });
301
302 QObject::connect(window, &KWayland::Client::PlasmaWindow::plasmaActivityEntered, q, [window, this] {
303 this->dataChanged(window, Activities);
304 });
305
306 QObject::connect(window, &KWayland::Client::PlasmaWindow::plasmaActivityLeft, q, [window, this] {
307 this->dataChanged(window, Activities);
308 });
309 }
310
appData(KWayland::Client::PlasmaWindow * window)311 AppData WaylandTasksModel::Private::appData(KWayland::Client::PlasmaWindow *window)
312 {
313 const auto &it = appDataCache.constFind(window);
314
315 if (it != appDataCache.constEnd()) {
316 return *it;
317 }
318
319 const AppData &data = appDataFromUrl(windowUrlFromMetadata(window->appId(), window->pid(), rulesConfig));
320
321 appDataCache.insert(window, data);
322
323 return data;
324 }
325
icon(KWayland::Client::PlasmaWindow * window)326 QIcon WaylandTasksModel::Private::icon(KWayland::Client::PlasmaWindow *window)
327 {
328 const AppData &app = appData(window);
329
330 if (!app.icon.isNull()) {
331 return app.icon;
332 }
333
334 appDataCache[window].icon = window->icon();
335
336 return window->icon();
337 }
338
mimeType()339 QString WaylandTasksModel::Private::mimeType()
340 {
341 // Use a unique format id to make this intentionally useless for
342 // cross-process DND.
343 return QStringLiteral("windowsystem/winid+") + uuid.toString();
344 }
345
groupMimeType()346 QString WaylandTasksModel::Private::groupMimeType()
347 {
348 // Use a unique format id to make this intentionally useless for
349 // cross-process DND.
350 return QStringLiteral("windowsystem/multiple-winids+") + uuid.toString();
351 }
352
dataChanged(KWayland::Client::PlasmaWindow * window,int role)353 void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, int role)
354 {
355 QModelIndex idx = q->index(windows.indexOf(window));
356 emit q->dataChanged(idx, idx, QVector<int>{role});
357 }
358
dataChanged(KWayland::Client::PlasmaWindow * window,const QVector<int> & roles)359 void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, const QVector<int> &roles)
360 {
361 QModelIndex idx = q->index(windows.indexOf(window));
362 emit q->dataChanged(idx, idx, roles);
363 }
364
WaylandTasksModel(QObject * parent)365 WaylandTasksModel::WaylandTasksModel(QObject *parent)
366 : AbstractWindowTasksModel(parent)
367 , d(new Private(this))
368 {
369 d->init();
370 }
371
372 WaylandTasksModel::~WaylandTasksModel() = default;
373
data(const QModelIndex & index,int role) const374 QVariant WaylandTasksModel::data(const QModelIndex &index, int role) const
375 {
376 if (!index.isValid() || index.row() >= d->windows.count()) {
377 return QVariant();
378 }
379
380 KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
381
382 if (role == Qt::DisplayRole) {
383 return window->title();
384 } else if (role == Qt::DecorationRole) {
385 return d->icon(window);
386 } else if (role == AppId) {
387 const QString &id = d->appData(window).id;
388
389 if (id.isEmpty()) {
390 return window->appId();
391 } else {
392 return id;
393 }
394 } else if (role == AppName) {
395 return d->appData(window).name;
396 } else if (role == GenericName) {
397 return d->appData(window).genericName;
398 } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) {
399 return d->appData(window).url;
400 } else if (role == WinIdList) {
401 return QVariantList() << window->uuid();
402 } else if (role == MimeType) {
403 return d->mimeType();
404 } else if (role == MimeData) {
405 return window->uuid();
406 } else if (role == IsWindow) {
407 return true;
408 } else if (role == IsActive) {
409 return window->isActive();
410 } else if (role == IsClosable) {
411 return window->isCloseable();
412 } else if (role == IsMovable) {
413 return window->isMovable();
414 } else if (role == IsResizable) {
415 return window->isResizable();
416 } else if (role == IsMaximizable) {
417 return window->isMaximizeable();
418 } else if (role == IsMaximized) {
419 return window->isMaximized();
420 } else if (role == IsMinimizable) {
421 return window->isMinimizeable();
422 } else if (role == IsMinimized || role == IsHidden) {
423 return window->isMinimized();
424 } else if (role == IsKeepAbove) {
425 return window->isKeepAbove();
426 } else if (role == IsKeepBelow) {
427 return window->isKeepBelow();
428 } else if (role == IsFullScreenable) {
429 return window->isFullscreenable();
430 } else if (role == IsFullScreen) {
431 return window->isFullscreen();
432 } else if (role == IsShadeable) {
433 return window->isShadeable();
434 } else if (role == IsShaded) {
435 return window->isShaded();
436 } else if (role == IsVirtualDesktopsChangeable) {
437 // FIXME Currently not implemented in KWayland.
438 return true;
439 } else if (role == VirtualDesktops) {
440 return window->plasmaVirtualDesktops();
441 } else if (role == IsOnAllVirtualDesktops) {
442 return window->plasmaVirtualDesktops().isEmpty();
443 } else if (role == Geometry) {
444 return window->geometry();
445 } else if (role == ScreenGeometry) {
446 return screenGeometry(window->geometry().center());
447 } else if (role == Activities) {
448 return window->plasmaActivities();
449 } else if (role == IsDemandingAttention) {
450 return window->isDemandingAttention();
451 } else if (role == SkipTaskbar) {
452 return window->skipTaskbar() || d->appData(window).skipTaskbar;
453 } else if (role == SkipPager) {
454 // FIXME Implement.
455 } else if (role == AppPid) {
456 return window->pid();
457 } else if (role == StackingOrder) {
458 return d->windowManagement->stackingOrderUuids().indexOf(window->uuid());
459 } else if (role == LastActivated) {
460 if (d->lastActivated.contains(window)) {
461 return d->lastActivated.value(window);
462 }
463 } else if (role == ApplicationMenuObjectPath) {
464 return window->applicationMenuObjectPath();
465 } else if (role == ApplicationMenuServiceName) {
466 return window->applicationMenuServiceName();
467 }
468
469 return QVariant();
470 }
471
rowCount(const QModelIndex & parent) const472 int WaylandTasksModel::rowCount(const QModelIndex &parent) const
473 {
474 return parent.isValid() ? 0 : d->windows.count();
475 }
476
index(int row,int column,const QModelIndex & parent) const477 QModelIndex WaylandTasksModel::index(int row, int column, const QModelIndex &parent) const
478 {
479 return hasIndex(row, column, parent) ? createIndex(row, column, d->windows.at(row)) : QModelIndex();
480 }
481
requestActivate(const QModelIndex & index)482 void WaylandTasksModel::requestActivate(const QModelIndex &index)
483 {
484 // FIXME Lacks transient handling of the XWindows version.
485
486 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
487 return;
488 }
489
490 d->windows.at(index.row())->requestActivate();
491 }
492
requestNewInstance(const QModelIndex & index)493 void WaylandTasksModel::requestNewInstance(const QModelIndex &index)
494 {
495 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
496 return;
497 }
498
499 runApp(d->appData(d->windows.at(index.row())));
500 }
501
requestOpenUrls(const QModelIndex & index,const QList<QUrl> & urls)502 void WaylandTasksModel::requestOpenUrls(const QModelIndex &index, const QList<QUrl> &urls)
503 {
504 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count() || urls.isEmpty()) {
505 return;
506 }
507
508 runApp(d->appData(d->windows.at(index.row())), urls);
509 }
510
requestClose(const QModelIndex & index)511 void WaylandTasksModel::requestClose(const QModelIndex &index)
512 {
513 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
514 return;
515 }
516
517 d->windows.at(index.row())->requestClose();
518 }
519
requestMove(const QModelIndex & index)520 void WaylandTasksModel::requestMove(const QModelIndex &index)
521 {
522 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
523 return;
524 }
525
526 KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
527
528 const QString ¤tDesktop = d->virtualDesktopInfo->currentDesktop().toString();
529
530 if (!currentDesktop.isEmpty()) {
531 window->requestEnterVirtualDesktop(currentDesktop);
532 }
533
534 window->requestMove();
535 }
536
requestResize(const QModelIndex & index)537 void WaylandTasksModel::requestResize(const QModelIndex &index)
538 {
539 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
540 return;
541 }
542
543 KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
544
545 const QString ¤tDesktop = d->virtualDesktopInfo->currentDesktop().toString();
546
547 if (!currentDesktop.isEmpty()) {
548 window->requestEnterVirtualDesktop(currentDesktop);
549 }
550
551 window->requestResize();
552 }
553
requestToggleMinimized(const QModelIndex & index)554 void WaylandTasksModel::requestToggleMinimized(const QModelIndex &index)
555 {
556 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
557 return;
558 }
559
560 KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
561
562 const QString ¤tDesktop = d->virtualDesktopInfo->currentDesktop().toString();
563
564 if (!currentDesktop.isEmpty()) {
565 window->requestEnterVirtualDesktop(currentDesktop);
566 }
567
568 window->requestToggleMinimized();
569 }
570
requestToggleMaximized(const QModelIndex & index)571 void WaylandTasksModel::requestToggleMaximized(const QModelIndex &index)
572 {
573 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
574 return;
575 }
576
577 KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
578
579 const QString ¤tDesktop = d->virtualDesktopInfo->currentDesktop().toString();
580
581 if (!currentDesktop.isEmpty()) {
582 window->requestEnterVirtualDesktop(currentDesktop);
583 }
584
585 window->requestToggleMaximized();
586 }
587
requestToggleKeepAbove(const QModelIndex & index)588 void WaylandTasksModel::requestToggleKeepAbove(const QModelIndex &index)
589 {
590 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
591 return;
592 }
593
594 d->windows.at(index.row())->requestToggleKeepAbove();
595 }
596
requestToggleKeepBelow(const QModelIndex & index)597 void WaylandTasksModel::requestToggleKeepBelow(const QModelIndex &index)
598 {
599 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
600 return;
601 }
602
603 d->windows.at(index.row())->requestToggleKeepBelow();
604 }
605
requestToggleFullScreen(const QModelIndex & index)606 void WaylandTasksModel::requestToggleFullScreen(const QModelIndex &index)
607 {
608 Q_UNUSED(index)
609
610 // FIXME Implement.
611 }
612
requestToggleShaded(const QModelIndex & index)613 void WaylandTasksModel::requestToggleShaded(const QModelIndex &index)
614 {
615 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
616 return;
617 }
618
619 d->windows.at(index.row())->requestToggleShaded();
620 }
621
requestVirtualDesktops(const QModelIndex & index,const QVariantList & desktops)622 void WaylandTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops)
623 {
624 // FIXME TODO: Lacks the "if we've requested the current desktop, force-activate
625 // the window" logic from X11 version. This behavior should be in KWin rather than
626 // libtm however.
627
628 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
629 return;
630 }
631
632 KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
633
634 if (desktops.isEmpty()) {
635 const QStringList virtualDesktops = window->plasmaVirtualDesktops();
636 for (const QString &desktop : virtualDesktops) {
637 window->requestLeaveVirtualDesktop(desktop);
638 }
639 } else {
640 const QStringList &now = window->plasmaVirtualDesktops();
641 QStringList next;
642
643 for (const QVariant &desktop : desktops) {
644 const QString &desktopId = desktop.toString();
645
646 if (!desktopId.isEmpty()) {
647 next << desktopId;
648
649 if (!now.contains(desktopId)) {
650 window->requestEnterVirtualDesktop(desktopId);
651 }
652 }
653 }
654
655 for (const QString &desktop : now) {
656 if (!next.contains(desktop)) {
657 window->requestLeaveVirtualDesktop(desktop);
658 }
659 }
660 }
661 }
662
requestNewVirtualDesktop(const QModelIndex & index)663 void WaylandTasksModel::requestNewVirtualDesktop(const QModelIndex &index)
664 {
665 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
666 return;
667 }
668
669 d->windows.at(index.row())->requestEnterNewVirtualDesktop();
670 }
671
requestActivities(const QModelIndex & index,const QStringList & activities)672 void WaylandTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities)
673 {
674 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
675 return;
676 }
677
678 auto *const window = d->windows.at(index.row());
679 const auto newActivities = QSet(activities.begin(), activities.end());
680 const auto plasmaActivities = window->plasmaActivities();
681 const auto oldActivities = QSet(plasmaActivities.begin(), plasmaActivities.end());
682
683 const auto activitiesToAdd = newActivities - oldActivities;
684 for (const auto &activity : activitiesToAdd) {
685 window->requestEnterActivity(activity);
686 }
687
688 const auto activitiesToRemove = oldActivities - newActivities;
689 for (const auto &activity : activitiesToRemove) {
690 window->requestLeaveActivity(activity);
691 }
692 }
693
requestPublishDelegateGeometry(const QModelIndex & index,const QRect & geometry,QObject * delegate)694 void WaylandTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate)
695 {
696 /*
697 FIXME: This introduces the dependency on Qt::Quick. I might prefer
698 reversing this and publishing the window pointer through the model,
699 then calling PlasmaWindow::setMinimizeGeometry in the applet backend,
700 rather than hand delegate items into the lib, keeping the lib more UI-
701 agnostic.
702 */
703
704 Q_UNUSED(geometry)
705
706 if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
707 return;
708 }
709
710 const QQuickItem *item = qobject_cast<const QQuickItem *>(delegate);
711
712 if (!item || !item->parentItem() || !item->window()) {
713 return;
714 }
715
716 QWindow *itemWindow = item->window();
717
718 if (!itemWindow) {
719 return;
720 }
721
722 using namespace KWayland::Client;
723 Surface *surface = Surface::fromWindow(itemWindow);
724
725 if (!surface) {
726 return;
727 }
728
729 QRect rect(item->x(), item->y(), item->width(), item->height());
730 rect.moveTopLeft(item->parentItem()->mapToScene(rect.topLeft()).toPoint());
731
732 KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
733
734 window->setMinimizedGeometry(surface, rect);
735 }
736
winIdFromMimeData(const QMimeData * mimeData,bool * ok)737 QUuid WaylandTasksModel::winIdFromMimeData(const QMimeData *mimeData, bool *ok)
738 {
739 Q_ASSERT(mimeData);
740
741 if (ok) {
742 *ok = false;
743 }
744
745 if (!mimeData->hasFormat(Private::mimeType())) {
746 return 0;
747 }
748
749 QUuid id(mimeData->data(Private::mimeType()));
750 *ok = !id.isNull();
751
752 return id;
753 }
754
winIdsFromMimeData(const QMimeData * mimeData,bool * ok)755 QList<QUuid> WaylandTasksModel::winIdsFromMimeData(const QMimeData *mimeData, bool *ok)
756 {
757 Q_ASSERT(mimeData);
758 QList<QUuid> ids;
759
760 if (ok) {
761 *ok = false;
762 }
763
764 if (!mimeData->hasFormat(Private::groupMimeType())) {
765 // Try to extract single window id.
766 bool singularOk;
767 QUuid id = winIdFromMimeData(mimeData, &singularOk);
768
769 if (ok) {
770 *ok = singularOk;
771 }
772
773 if (singularOk) {
774 ids << id;
775 }
776
777 return ids;
778 }
779
780 // FIXME: Extracting multiple winids is still unimplemented;
781 // TaskGroupingProxy::data(..., ::MimeData) can't produce
782 // a payload with them anyways.
783
784 return ids;
785 }
786
787 }
788