Plasma-workspace

waylandtasksmodel.cpp
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 "libtaskmanager_debug.h"
9#include "tasktools.h"
10#include "virtualdesktopinfo.h"
11
12#include <KDirWatch>
13#include <KSharedConfig>
14#include <KWindowSystem>
15
16#include <qwayland-plasma-window-management.h>
17
18#include <QFuture>
19#include <QFutureWatcher>
20#include <QGuiApplication>
21#include <QMimeData>
22#include <QQuickItem>
23#include <QQuickWindow>
24#include <QSet>
25#include <QUrl>
26#include <QUuid>
27#include <QWaylandClientExtension>
28#include <QWindow>
29#include <QtConcurrentRun>
30#include <qpa/qplatformwindow_p.h>
31
32#include <fcntl.h>
33#include <sys/poll.h>
34#include <unistd.h>
35
36namespace TaskManager
37{
38
39class PlasmaWindow : public QObject, public QtWayland::org_kde_plasma_window
40{
42public:
43 PlasmaWindow(const QString &uuid, ::org_kde_plasma_window *id)
44 : org_kde_plasma_window(id)
45 , uuid(uuid)
46 {
47 }
48 ~PlasmaWindow()
49 {
50 destroy();
51 }
52 using state = QtWayland::org_kde_plasma_window_management::state;
53 const QString uuid;
54 QString title;
55 QString appId;
56 QIcon icon;
57 QFlags<state> windowState;
58 QList<QString> virtualDesktops;
59 QRect geometry;
60 QString applicationMenuService;
61 QString applicationMenuObjectPath;
62 QList<QString> activities;
63 quint32 pid;
64 QString resourceName;
65 QPointer<PlasmaWindow> parentWindow;
66 bool wasUnmapped = false;
67
69 void unmapped();
70 void titleChanged();
71 void appIdChanged();
72 void iconChanged();
73 void activeChanged();
74 void minimizedChanged();
75 void maximizedChanged();
76 void fullscreenChanged();
77 void keepAboveChanged();
78 void keepBelowChanged();
79 void onAllDesktopsChanged();
80 void demandsAttentionChanged();
81 void closeableChanged();
82 void minimizeableChanged();
83 void maximizeableChanged();
84 void fullscreenableChanged();
85 void skiptaskbarChanged();
86 void shadeableChanged();
87 void shadedChanged();
88 void movableChanged();
89 void resizableChanged();
90 void virtualDesktopChangeableChanged();
91 void skipSwitcherChanged();
92 void virtualDesktopEntered();
93 void virtualDesktopLeft();
94 void geometryChanged();
95 void skipTaskbarChanged();
96 void applicationMenuChanged();
97 void activitiesChanged();
98 void parentWindowChanged();
99 void initialStateDone();
100
101protected:
102 void org_kde_plasma_window_unmapped() override
103 {
104 wasUnmapped = true;
105 Q_EMIT unmapped();
106 }
107 void org_kde_plasma_window_title_changed(const QString &title) override
108 {
109 this->title = title;
110 Q_EMIT titleChanged();
111 }
112 void org_kde_plasma_window_app_id_changed(const QString &app_id) override
113 {
114 appId = app_id;
115 Q_EMIT appIdChanged();
116 }
117 void org_kde_plasma_window_icon_changed() override
118 {
119 int pipeFds[2];
120 if (pipe2(pipeFds, O_CLOEXEC) != 0) {
121 qCWarning(TASKMANAGER_DEBUG) << "failed creating pipe";
122 return;
123 }
124 get_icon(pipeFds[1]);
125 ::close(pipeFds[1]);
126 auto readIcon = [uuid = uuid](int fd) {
127 auto closeGuard = qScopeGuard([fd]() {
128 ::close(fd);
129 });
130 pollfd pollFd;
131 pollFd.fd = fd;
132 pollFd.events = POLLIN;
133 QByteArray data;
134 while (true) {
135 int ready = poll(&pollFd, 1, 1000);
136 if (ready < 0 && errno != EINTR) {
137 qCWarning(TASKMANAGER_DEBUG) << "polling for icon of window" << uuid << "failed";
138 return QIcon();
139 } else if (ready == 0) {
140 qCWarning(TASKMANAGER_DEBUG) << "time out polling for icon of window" << uuid;
141 return QIcon();
142 } else {
143 char buffer[4096];
144 int n = read(fd, buffer, sizeof(buffer));
145 if (n < 0) {
146 qCWarning(TASKMANAGER_DEBUG) << "error reading icon of window" << uuid;
147 return QIcon();
148 } else if (n > 0) {
149 data.append(buffer, n);
150 } else {
151 QIcon icon;
152 QDataStream ds(data);
153 ds >> icon;
154 return icon;
155 }
156 }
157 }
158 };
159 QFuture<QIcon> future = QtConcurrent::run(readIcon, pipeFds[0]);
160 auto watcher = new QFutureWatcher<QIcon>();
161 watcher->setFuture(future);
162 connect(watcher, &QFutureWatcher<QIcon>::finished, this, [this, watcher] {
163 icon = watcher->future().result();
164 Q_EMIT iconChanged();
165 });
167 }
168 void org_kde_plasma_window_themed_icon_name_changed(const QString &name) override
169 {
170 icon = QIcon::fromTheme(name);
171 Q_EMIT iconChanged();
172 }
173 void org_kde_plasma_window_state_changed(uint32_t flags) override
174 {
175 auto diff = windowState ^ flags;
176 if (diff & state::state_active) {
177 windowState.setFlag(state::state_active, flags & state::state_active);
178 Q_EMIT activeChanged();
179 }
180 if (diff & state::state_minimized) {
181 windowState.setFlag(state::state_minimized, flags & state::state_minimized);
182 Q_EMIT minimizedChanged();
183 }
184 if (diff & state::state_maximized) {
185 windowState.setFlag(state::state_maximized, flags & state::state_maximized);
186 Q_EMIT maximizedChanged();
187 }
188 if (diff & state::state_fullscreen) {
189 windowState.setFlag(state::state_fullscreen, flags & state::state_fullscreen);
190 Q_EMIT fullscreenChanged();
191 }
192 if (diff & state::state_keep_above) {
193 windowState.setFlag(state::state_keep_above, flags & state::state_keep_above);
194 Q_EMIT keepAboveChanged();
195 }
196 if (diff & state::state_keep_below) {
197 windowState.setFlag(state::state_keep_below, flags & state::state_keep_below);
198 Q_EMIT keepBelowChanged();
199 }
200 if (diff & state::state_on_all_desktops) {
201 windowState.setFlag(state::state_on_all_desktops, flags & state::state_on_all_desktops);
202 Q_EMIT onAllDesktopsChanged();
203 }
204 if (diff & state::state_demands_attention) {
205 windowState.setFlag(state::state_demands_attention, flags & state::state_demands_attention);
206 Q_EMIT demandsAttentionChanged();
207 }
208 if (diff & state::state_closeable) {
209 windowState.setFlag(state::state_closeable, flags & state::state_closeable);
210 Q_EMIT closeableChanged();
211 }
212 if (diff & state::state_minimizable) {
213 windowState.setFlag(state::state_minimizable, flags & state::state_minimizable);
214 Q_EMIT minimizeableChanged();
215 }
216 if (diff & state::state_maximizable) {
217 windowState.setFlag(state::state_maximizable, flags & state::state_maximizable);
218 Q_EMIT maximizeableChanged();
219 }
220 if (diff & state::state_fullscreenable) {
221 windowState.setFlag(state::state_fullscreenable, flags & state::state_fullscreenable);
222 Q_EMIT fullscreenableChanged();
223 }
224 if (diff & state::state_skiptaskbar) {
225 windowState.setFlag(state::state_skiptaskbar, flags & state::state_skiptaskbar);
226 Q_EMIT skipTaskbarChanged();
227 }
228 if (diff & state::state_shadeable) {
229 windowState.setFlag(state::state_shadeable, flags & state::state_shadeable);
230 Q_EMIT shadeableChanged();
231 }
232 if (diff & state::state_shaded) {
233 windowState.setFlag(state::state_shaded, flags & state::state_shaded);
234 Q_EMIT shadedChanged();
235 }
236 if (diff & state::state_movable) {
237 windowState.setFlag(state::state_movable, flags & state::state_movable);
238 Q_EMIT movableChanged();
239 }
240 if (diff & state::state_resizable) {
241 windowState.setFlag(state::state_resizable, flags & state::state_resizable);
242 Q_EMIT resizableChanged();
243 }
244 if (diff & state::state_virtual_desktop_changeable) {
245 windowState.setFlag(state::state_virtual_desktop_changeable, flags & state::state_virtual_desktop_changeable);
246 Q_EMIT virtualDesktopChangeableChanged();
247 }
248 if (diff & state::state_skipswitcher) {
249 windowState.setFlag(state::state_skipswitcher, flags & state::state_skipswitcher);
250 Q_EMIT skipSwitcherChanged();
251 }
252 }
253 void org_kde_plasma_window_virtual_desktop_entered(const QString &id) override
254 {
255 virtualDesktops.push_back(id);
256 Q_EMIT virtualDesktopEntered();
257 }
258
259 void org_kde_plasma_window_virtual_desktop_left(const QString &id) override
260 {
261 virtualDesktops.removeAll(id);
262 Q_EMIT virtualDesktopLeft();
263 }
264 void org_kde_plasma_window_geometry(int32_t x, int32_t y, uint32_t width, uint32_t height) override
265 {
266 geometry = QRect(x, y, width, height);
267 Q_EMIT geometryChanged();
268 }
269 void org_kde_plasma_window_application_menu(const QString &service_name, const QString &object_path) override
270
271 {
272 applicationMenuService = service_name;
273 applicationMenuObjectPath = object_path;
274 Q_EMIT applicationMenuChanged();
275 }
276 void org_kde_plasma_window_activity_entered(const QString &id) override
277 {
278 activities.push_back(id);
279 Q_EMIT activitiesChanged();
280 }
281 void org_kde_plasma_window_activity_left(const QString &id) override
282 {
283 activities.removeAll(id);
284 Q_EMIT activitiesChanged();
285 }
286 void org_kde_plasma_window_pid_changed(uint32_t pid) override
287 {
288 this->pid = pid;
289 }
290 void org_kde_plasma_window_resource_name_changed(const QString &resource_name) override
291 {
292 resourceName = resource_name;
293 }
294 void org_kde_plasma_window_parent_window(::org_kde_plasma_window *parent) override
295 {
296 PlasmaWindow *parentWindow = nullptr;
297 if (parent) {
298 parentWindow = dynamic_cast<PlasmaWindow *>(PlasmaWindow::fromObject(parent));
299 }
300 setParentWindow(parentWindow);
301 }
302 void org_kde_plasma_window_initial_state() override
303 {
304 Q_EMIT initialStateDone();
305 }
306
307private:
308 void setParentWindow(PlasmaWindow *parent)
309 {
310 const auto old = parentWindow;
311 QObject::disconnect(parentWindowUnmappedConnection);
312 QObject::disconnect(parentWindowDestroyedConnection);
313
314 if (parent && !parent->wasUnmapped) {
315 parentWindow = QPointer<PlasmaWindow>(parent);
316 parentWindowUnmappedConnection = QObject::connect(parent, &PlasmaWindow::unmapped, this, [this] {
317 setParentWindow(nullptr);
318 });
319 // QPointer nulling itself wouldn't cause the change signal to be emitted.
320 parentWindowDestroyedConnection = QObject::connect(parent, &QObject::destroyed, this, &PlasmaWindow::parentWindowChanged);
321 } else {
322 parentWindow = QPointer<PlasmaWindow>();
323 parentWindowUnmappedConnection = QMetaObject::Connection();
324 parentWindowDestroyedConnection = QMetaObject::Connection();
325 }
326
327 if (parentWindow.data() != old.data()) {
328 Q_EMIT parentWindowChanged();
329 }
330 }
331
332 QMetaObject::Connection parentWindowUnmappedConnection;
333 QMetaObject::Connection parentWindowDestroyedConnection;
334};
335
336class PlasmaWindowManagement : public QWaylandClientExtensionTemplate<PlasmaWindowManagement>, public QtWayland::org_kde_plasma_window_management
337{
338 Q_OBJECT
339public:
340 static constexpr int version = 16;
341 PlasmaWindowManagement()
342 : QWaylandClientExtensionTemplate(version)
343 {
344 connect(this, &QWaylandClientExtension::activeChanged, this, [this] {
345 if (!isActive()) {
346 wl_proxy_destroy(reinterpret_cast<wl_proxy *>(object()));
347 }
348 });
349 initialize();
350 }
351 ~PlasmaWindowManagement()
352 {
353 if (isActive()) {
354 wl_proxy_destroy(reinterpret_cast<wl_proxy *>(object()));
355 }
356 }
357 void org_kde_plasma_window_management_window_with_uuid(uint32_t id, const QString &uuid) override
358 {
359 Q_UNUSED(id)
360 Q_EMIT windowCreated(new PlasmaWindow(uuid, get_window_by_uuid(uuid)));
361 }
362 void org_kde_plasma_window_management_stacking_order_uuid_changed(const QString &uuids) override
363 {
364 Q_EMIT stackingOrderChanged(uuids);
365 }
366Q_SIGNALS:
367 void windowCreated(PlasmaWindow *window);
368 void stackingOrderChanged(const QString &uuids);
369};
370class Q_DECL_HIDDEN WaylandTasksModel::Private
371{
372public:
373 Private(WaylandTasksModel *q);
375 QHash<PlasmaWindow *, QTime> lastActivated;
376 PlasmaWindow *activeWindow = nullptr;
377 std::vector<std::unique_ptr<PlasmaWindow>> windows;
378 // key=transient child, value=leader
380 // key=leader, values=transient children
381 QMultiHash<PlasmaWindow *, PlasmaWindow *> transientsDemandingAttention;
382 std::unique_ptr<PlasmaWindowManagement> windowManagement;
383 KSharedConfig::Ptr rulesConfig;
384 KDirWatch *configWatcher = nullptr;
385 VirtualDesktopInfo *virtualDesktopInfo = nullptr;
386 static QUuid uuid;
387 QList<QString> stackingOrder;
388
389 void init();
390 void initWayland();
391 auto findWindow(PlasmaWindow *window) const;
392 void addWindow(PlasmaWindow *window);
393
394 const AppData &appData(PlasmaWindow *window);
395
396 QIcon icon(PlasmaWindow *window);
397
398 static QString mimeType();
399 static QString groupMimeType();
400
401 void dataChanged(PlasmaWindow *window, int role);
402 void dataChanged(PlasmaWindow *window, const QList<int> &roles);
403
404private:
405 WaylandTasksModel *q;
406};
407
408QUuid WaylandTasksModel::Private::uuid = QUuid::createUuid();
409
410WaylandTasksModel::Private::Private(WaylandTasksModel *q)
411 : q(q)
412{
413}
414
415void WaylandTasksModel::Private::init()
416{
417 auto clearCacheAndRefresh = [this] {
418 if (windows.empty()) {
419 return;
420 }
421
422 appDataCache.clear();
423
424 // Emit changes of all roles satisfied from app data cache.
425 Q_EMIT q->dataChanged(q->index(0, 0),
426 q->index(windows.size() - 1, 0),
427 QList<int>{Qt::DecorationRole,
428 AbstractTasksModel::AppId,
429 AbstractTasksModel::AppName,
430 AbstractTasksModel::GenericName,
431 AbstractTasksModel::LauncherUrl,
432 AbstractTasksModel::LauncherUrlWithoutIcon,
433 AbstractTasksModel::CanLaunchNewInstance,
434 AbstractTasksModel::SkipTaskbar});
435 };
436
437 rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc"));
438 configWatcher = new KDirWatch(q);
439
440 for (const QString &location : QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)) {
441 configWatcher->addFile(location + QLatin1String("/taskmanagerrulesrc"));
442 }
443
444 auto rulesConfigChange = [this, clearCacheAndRefresh] {
445 rulesConfig->reparseConfiguration();
446 clearCacheAndRefresh();
447 };
448
449 QObject::connect(configWatcher, &KDirWatch::dirty, rulesConfigChange);
450 QObject::connect(configWatcher, &KDirWatch::created, rulesConfigChange);
451 QObject::connect(configWatcher, &KDirWatch::deleted, rulesConfigChange);
452
453 virtualDesktopInfo = new VirtualDesktopInfo(q);
454
455 initWayland();
456}
457
458void WaylandTasksModel::Private::initWayland()
459{
461 return;
462 }
463
464 windowManagement = std::make_unique<PlasmaWindowManagement>();
465
466 QObject::connect(windowManagement.get(), &PlasmaWindowManagement::activeChanged, q, [this] {
467 q->beginResetModel();
468 windows.clear();
469 q->endResetModel();
470 });
471
472 QObject::connect(windowManagement.get(), &PlasmaWindowManagement::windowCreated, q, [this](PlasmaWindow *window) {
473 connect(window, &PlasmaWindow::initialStateDone, q, [this, window] {
474 addWindow(window);
475 });
476 });
477
478 QObject::connect(windowManagement.get(), &PlasmaWindowManagement::stackingOrderChanged, q, [this](const QString &order) {
479 stackingOrder = order.split(QLatin1Char(';'));
480 for (const auto &window : std::as_const(windows)) {
481 this->dataChanged(window.get(), StackingOrder);
482 }
483 });
484}
485
486auto WaylandTasksModel::Private::findWindow(PlasmaWindow *window) const
487{
488 return std::find_if(windows.begin(), windows.end(), [window](const std::unique_ptr<PlasmaWindow> &candidate) {
489 return candidate.get() == window;
490 });
491}
492
493void WaylandTasksModel::Private::addWindow(PlasmaWindow *window)
494{
495 if (findWindow(window) != windows.end() || transients.contains(window)) {
496 return;
497 }
498
499 auto removeWindow = [window, this] {
500 // findWindow() is const, we need a non-const iterator for the "take" below.
501 auto it = std::find_if(windows.begin(), windows.end(), [window](const std::unique_ptr<PlasmaWindow> &candidate) {
502 return candidate.get() == window;
503 });
504
505 if (it != windows.end()) {
506 const int row = it - windows.begin();
507 q->beginRemoveRows(QModelIndex(), row, row);
508
509 // "take"... don't use just erase() here as it will destroy the unique_ptr and thus
510 // the window, which will trigger QObject::destroyed handlers which might
511 // add/remove items from the model whilst we're removing rows ourselves.
512 const std::unique_ptr<PlasmaWindow> removedWindow = std::move(*it);
513 windows.erase(it);
514
515 transientsDemandingAttention.remove(window);
516 appDataCache.remove(window);
517 lastActivated.remove(window);
518 q->endRemoveRows();
519 } else { // Could be a transient.
520 // Removing a transient might change the demands attention state of the leader.
521 if (transients.remove(window)) {
522 if (PlasmaWindow *leader = transientsDemandingAttention.key(window)) {
523 transientsDemandingAttention.remove(leader, window);
524 dataChanged(leader, QVector<int>{IsDemandingAttention});
525 }
526 }
527 }
528
529 if (activeWindow == window) {
530 activeWindow = nullptr;
531 }
532 };
533
534 QObject::connect(window, &PlasmaWindow::unmapped, q, removeWindow);
535
536 QObject::connect(window, &PlasmaWindow::titleChanged, q, [window, this] {
537 this->dataChanged(window, Qt::DisplayRole);
538 });
539
540 QObject::connect(window, &PlasmaWindow::iconChanged, q, [window, this] {
541 // The icon in the AppData struct might come from PlasmaWindow if it wasn't
542 // filled in by windowUrlFromMetadata+appDataFromUrl.
543 // TODO: Don't evict the cache unnecessarily if this isn't the case. As icons
544 // are currently very static on Wayland, this eviction is unlikely to happen
545 // frequently as of now.
546 appDataCache.remove(window);
547 this->dataChanged(window, Qt::DecorationRole);
548 });
549
550 QObject::connect(window, &PlasmaWindow::appIdChanged, q, [window, this] {
551 // The AppData struct in the cache is derived from this and needs
552 // to be evicted in favor of a fresh struct based on the changed
553 // window metadata.
554 appDataCache.remove(window);
555
556 // Refresh roles satisfied from the app data cache.
557 this->dataChanged(window,
558 QList<int>{Qt::DecorationRole, AppId, AppName, GenericName, LauncherUrl, LauncherUrlWithoutIcon, SkipTaskbar, CanLaunchNewInstance});
559 });
560
561 if (window->windowState & PlasmaWindow::state::state_active) {
562 PlasmaWindow *effectiveActive = window;
563 while (effectiveActive->parentWindow) {
564 effectiveActive = effectiveActive->parentWindow;
565 }
566
567 lastActivated[effectiveActive] = QTime::currentTime();
568 activeWindow = effectiveActive;
569 }
570
571 QObject::connect(window, &PlasmaWindow::activeChanged, q, [window, this] {
572 const bool active = window->windowState & PlasmaWindow::state::state_active;
573
574 PlasmaWindow *effectiveWindow = window;
575
576 while (effectiveWindow->parentWindow) {
577 effectiveWindow = effectiveWindow->parentWindow;
578 }
579
580 if (active) {
581 lastActivated[effectiveWindow] = QTime::currentTime();
582
583 if (activeWindow != effectiveWindow) {
584 activeWindow = effectiveWindow;
585 this->dataChanged(effectiveWindow, IsActive);
586 }
587 } else {
588 if (activeWindow == effectiveWindow) {
589 activeWindow = nullptr;
590 this->dataChanged(effectiveWindow, IsActive);
591 }
592 }
593 });
594
595 QObject::connect(window, &PlasmaWindow::parentWindowChanged, q, [window, this] {
596 PlasmaWindow *leader = window->parentWindow.data();
597
598 // Migrate demanding attention to new leader.
599 if (window->windowState.testFlag(PlasmaWindow::state::state_demands_attention)) {
600 if (auto *oldLeader = transientsDemandingAttention.key(window)) {
601 if (window->parentWindow != oldLeader) {
602 transientsDemandingAttention.remove(oldLeader, window);
603 transientsDemandingAttention.insert(leader, window);
604 dataChanged(oldLeader, QVector<int>{IsDemandingAttention});
605 dataChanged(leader, QVector<int>{IsDemandingAttention});
606 }
607 }
608 }
609
610 if (transients.remove(window)) {
611 if (leader) { // leader change.
612 transients.insert(window, leader);
613 } else { // lost a leader, add to regular windows list.
614 Q_ASSERT(findWindow(window) == windows.end());
615
616 const int count = windows.size();
617 q->beginInsertRows(QModelIndex(), count, count);
618 windows.emplace_back(window);
619 q->endInsertRows();
620 }
621 } else if (leader) { // gained a leader, remove from regular windows list.
622 auto it = findWindow(window);
623 Q_ASSERT(it != windows.end());
624
625 const int row = it - windows.begin();
626 q->beginRemoveRows(QModelIndex(), row, row);
627 windows.erase(it);
628 appDataCache.remove(window);
629 lastActivated.remove(window);
630 q->endRemoveRows();
631 }
632 });
633
634 QObject::connect(window, &PlasmaWindow::closeableChanged, q, [window, this] {
635 this->dataChanged(window, IsClosable);
636 });
637
638 QObject::connect(window, &PlasmaWindow::movableChanged, q, [window, this] {
639 this->dataChanged(window, IsMovable);
640 });
641
642 QObject::connect(window, &PlasmaWindow::resizableChanged, q, [window, this] {
643 this->dataChanged(window, IsResizable);
644 });
645
646 QObject::connect(window, &PlasmaWindow::fullscreenableChanged, q, [window, this] {
647 this->dataChanged(window, IsFullScreenable);
648 });
649
650 QObject::connect(window, &PlasmaWindow::fullscreenChanged, q, [window, this] {
651 this->dataChanged(window, IsFullScreen);
652 });
653
654 QObject::connect(window, &PlasmaWindow::maximizeableChanged, q, [window, this] {
655 this->dataChanged(window, IsMaximizable);
656 });
657
658 QObject::connect(window, &PlasmaWindow::maximizedChanged, q, [window, this] {
659 this->dataChanged(window, IsMaximized);
660 });
661
662 QObject::connect(window, &PlasmaWindow::minimizeableChanged, q, [window, this] {
663 this->dataChanged(window, IsMinimizable);
664 });
665
666 QObject::connect(window, &PlasmaWindow::minimizedChanged, q, [window, this] {
667 this->dataChanged(window, IsMinimized);
668 });
669
670 QObject::connect(window, &PlasmaWindow::keepAboveChanged, q, [window, this] {
671 this->dataChanged(window, IsKeepAbove);
672 });
673
674 QObject::connect(window, &PlasmaWindow::keepBelowChanged, q, [window, this] {
675 this->dataChanged(window, IsKeepBelow);
676 });
677
678 QObject::connect(window, &PlasmaWindow::shadeableChanged, q, [window, this] {
679 this->dataChanged(window, IsShadeable);
680 });
681
682 QObject::connect(window, &PlasmaWindow::virtualDesktopChangeableChanged, q, [window, this] {
683 this->dataChanged(window, IsVirtualDesktopsChangeable);
684 });
685
686 QObject::connect(window, &PlasmaWindow::virtualDesktopEntered, q, [window, this] {
687 this->dataChanged(window, VirtualDesktops);
688
689 // If the count has changed from 0, the window may no longer be on all virtual
690 // desktops.
691 if (window->virtualDesktops.count() > 0) {
692 this->dataChanged(window, IsOnAllVirtualDesktops);
693 }
694 });
695
696 QObject::connect(window, &PlasmaWindow::virtualDesktopLeft, q, [window, this] {
697 this->dataChanged(window, VirtualDesktops);
698
699 // If the count has changed to 0, the window is now on all virtual desktops.
700 if (window->virtualDesktops.count() == 0) {
701 this->dataChanged(window, IsOnAllVirtualDesktops);
702 }
703 });
704
705 QObject::connect(window, &PlasmaWindow::geometryChanged, q, [window, this] {
706 this->dataChanged(window, QList<int>{Geometry, ScreenGeometry});
707 });
708
709 QObject::connect(window, &PlasmaWindow::demandsAttentionChanged, q, [window, this] {
710 // Changes to a transient's state might change demands attention state for leader.
711 if (auto *leader = transients.value(window)) {
712 if (window->windowState.testFlag(PlasmaWindow::state::state_demands_attention)) {
713 if (!transientsDemandingAttention.values(leader).contains(window)) {
714 transientsDemandingAttention.insert(leader, window);
715 this->dataChanged(leader, QVector<int>{IsDemandingAttention});
716 }
717 } else if (transientsDemandingAttention.remove(window)) {
718 this->dataChanged(leader, QVector<int>{IsDemandingAttention});
719 }
720 } else {
721 this->dataChanged(window, QVector<int>{IsDemandingAttention});
722 }
723 });
724
725 QObject::connect(window, &PlasmaWindow::skipTaskbarChanged, q, [window, this] {
726 this->dataChanged(window, SkipTaskbar);
727 });
728
729 QObject::connect(window, &PlasmaWindow::applicationMenuChanged, q, [window, this] {
730 this->dataChanged(window, QList<int>{ApplicationMenuServiceName, ApplicationMenuObjectPath});
731 });
732
733 QObject::connect(window, &PlasmaWindow::activitiesChanged, q, [window, this] {
734 this->dataChanged(window, Activities);
735 });
736
737 // Handle transient.
738 if (PlasmaWindow *leader = window->parentWindow.data()) {
739 transients.insert(window, leader);
740
741 // Update demands attention state for leader.
742 if (window->windowState.testFlag(PlasmaWindow::state::state_demands_attention)) {
743 transientsDemandingAttention.insert(leader, window);
744 dataChanged(leader, QVector<int>{IsDemandingAttention});
745 }
746 } else {
747 const int count = windows.size();
748
749 q->beginInsertRows(QModelIndex(), count, count);
750
751 windows.emplace_back(window);
752
753 q->endInsertRows();
754 }
755}
756
757const AppData &WaylandTasksModel::Private::appData(PlasmaWindow *window)
758{
759 static_assert(!std::is_trivially_copy_assignable_v<AppData>);
760 if (auto it = appDataCache.constFind(window); it != appDataCache.constEnd()) {
761 return *it;
762 }
763
764 return *appDataCache.emplace(window, appDataFromUrl(windowUrlFromMetadata(window->appId, window->pid, rulesConfig, window->resourceName)));
765}
766
767QIcon WaylandTasksModel::Private::icon(PlasmaWindow *window)
768{
769 const AppData &app = appData(window);
770
771 if (!app.icon.isNull()) {
772 return app.icon;
773 }
774
775 appDataCache[window].icon = window->icon;
776
777 return window->icon;
778}
779
780QString WaylandTasksModel::Private::mimeType()
781{
782 // Use a unique format id to make this intentionally useless for
783 // cross-process DND.
784 return QStringLiteral("windowsystem/winid+") + uuid.toString();
785}
786
787QString WaylandTasksModel::Private::groupMimeType()
788{
789 // Use a unique format id to make this intentionally useless for
790 // cross-process DND.
791 return QStringLiteral("windowsystem/multiple-winids+") + uuid.toString();
792}
793
794void WaylandTasksModel::Private::dataChanged(PlasmaWindow *window, int role)
795{
796 auto it = findWindow(window);
797 if (it == windows.end()) {
798 return;
799 }
800 QModelIndex idx = q->index(it - windows.begin());
801 Q_EMIT q->dataChanged(idx, idx, QList<int>{role});
802}
803
804void WaylandTasksModel::Private::dataChanged(PlasmaWindow *window, const QList<int> &roles)
805{
806 auto it = findWindow(window);
807 if (it == windows.end()) {
808 return;
809 }
810 QModelIndex idx = q->index(it - windows.begin());
811 Q_EMIT q->dataChanged(idx, idx, roles);
812}
813
814WaylandTasksModel::WaylandTasksModel(QObject *parent)
815 : AbstractWindowTasksModel(parent)
816 , d(new Private(this))
817{
818 d->init();
819}
820
821WaylandTasksModel::~WaylandTasksModel() = default;
822
823QVariant WaylandTasksModel::data(const QModelIndex &index, int role) const
824{
825 // Note: when index is valid, its row >= 0, so casting to unsigned is safe
826 if (!index.isValid() || static_cast<size_t>(index.row()) >= d->windows.size()) {
827 return QVariant();
828 }
829
830 PlasmaWindow *window = d->windows.at(index.row()).get();
831
832 if (role == Qt::DisplayRole) {
833 return window->title;
834 } else if (role == Qt::DecorationRole) {
835 return d->icon(window);
836 } else if (role == AppId) {
837 const QString &id = d->appData(window).id;
838
839 if (id.isEmpty()) {
840 return window->appId;
841 } else {
842 return id;
843 }
844 } else if (role == AppName) {
845 return d->appData(window).name;
846 } else if (role == GenericName) {
847 return d->appData(window).genericName;
848 } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) {
849 return d->appData(window).url;
850 } else if (role == WinIdList) {
851 return QVariantList{window->uuid};
852 } else if (role == MimeType) {
853 return d->mimeType();
854 } else if (role == MimeData) {
855 return window->uuid;
856 } else if (role == IsWindow) {
857 return true;
858 } else if (role == IsActive) {
859 return (window == d->activeWindow);
860 } else if (role == IsClosable) {
861 return window->windowState.testFlag(PlasmaWindow::state::state_closeable);
862 } else if (role == IsMovable) {
863 return window->windowState.testFlag(PlasmaWindow::state::state_movable);
864 } else if (role == IsResizable) {
865 return window->windowState.testFlag(PlasmaWindow::state::state_resizable);
866 } else if (role == IsMaximizable) {
867 return window->windowState.testFlag(PlasmaWindow::state::state_maximizable);
868 } else if (role == IsMaximized) {
869 return window->windowState.testFlag(PlasmaWindow::state::state_maximized);
870 } else if (role == IsMinimizable) {
871 return window->windowState.testFlag(PlasmaWindow::state::state_minimizable);
872 } else if (role == IsMinimized || role == IsHidden) {
873 return window->windowState.testFlag(PlasmaWindow::state::state_minimized);
874 } else if (role == IsKeepAbove) {
875 return window->windowState.testFlag(PlasmaWindow::state::state_keep_above);
876 } else if (role == IsKeepBelow) {
877 return window->windowState.testFlag(PlasmaWindow::state::state_keep_below);
878 } else if (role == IsFullScreenable) {
879 return window->windowState.testFlag(PlasmaWindow::state::state_fullscreenable);
880 } else if (role == IsFullScreen) {
881 return window->windowState.testFlag(PlasmaWindow::state::state_fullscreen);
882 } else if (role == IsShadeable) {
883 return window->windowState.testFlag(PlasmaWindow::state::state_shadeable);
884 } else if (role == IsShaded) {
885 return window->windowState.testFlag(PlasmaWindow::state::state_shaded);
886 } else if (role == IsVirtualDesktopsChangeable) {
887 return window->windowState.testFlag(PlasmaWindow::state::state_virtual_desktop_changeable);
888 } else if (role == VirtualDesktops) {
889 return window->virtualDesktops;
890 } else if (role == IsOnAllVirtualDesktops) {
891 return window->virtualDesktops.isEmpty();
892 } else if (role == Geometry) {
893 return window->geometry;
894 } else if (role == ScreenGeometry) {
895 return screenGeometry(window->geometry.center());
896 } else if (role == Activities) {
897 return window->activities;
898 } else if (role == IsDemandingAttention) {
899 return window->windowState.testFlag(PlasmaWindow::state::state_demands_attention) || d->transientsDemandingAttention.contains(window);
900 } else if (role == SkipTaskbar) {
901 return window->windowState.testFlag(PlasmaWindow::state::state_skiptaskbar) || d->appData(window).skipTaskbar;
902 } else if (role == SkipPager) {
903 // FIXME Implement.
904 } else if (role == AppPid) {
905 return window->pid;
906 } else if (role == StackingOrder) {
907 return d->stackingOrder.indexOf(window->uuid);
908 } else if (role == LastActivated) {
909 if (d->lastActivated.contains(window)) {
910 return d->lastActivated.value(window);
911 }
912 } else if (role == ApplicationMenuObjectPath) {
913 return window->applicationMenuObjectPath;
914 } else if (role == ApplicationMenuServiceName) {
915 return window->applicationMenuService;
916 } else if (role == CanLaunchNewInstance) {
917 return canLauchNewInstance(d->appData(window));
918 }
919
920 return AbstractTasksModel::data(index, role);
921}
922
923int WaylandTasksModel::rowCount(const QModelIndex &parent) const
924{
925 return parent.isValid() ? 0 : d->windows.size();
926}
927
928QModelIndex WaylandTasksModel::index(int row, int column, const QModelIndex &parent) const
929{
930 return hasIndex(row, column, parent) ? createIndex(row, column, d->windows.at(row).get()) : QModelIndex();
931}
932
933void WaylandTasksModel::requestActivate(const QModelIndex &index)
934{
935 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
936 return;
937 }
938
939 PlasmaWindow *window = d->windows.at(index.row()).get();
940
941 // Pull forward any transient demanding attention.
942 if (auto *transientDemandingAttention = d->transientsDemandingAttention.value(window)) {
943 window = transientDemandingAttention;
944 } else {
945 // TODO Shouldn't KWin take care of that?
946 // Bringing a transient to the front usually brings its parent with it
947 // but focus is not handled properly.
948 // TODO take into account d->lastActivation instead
949 // of just taking the first one.
950 while (d->transients.key(window)) {
951 window = d->transients.key(window);
952 }
953 }
954
955 window->set_state(PlasmaWindow::state::state_active, PlasmaWindow::state::state_active);
956}
957
958void WaylandTasksModel::requestNewInstance(const QModelIndex &index)
959{
960 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
961 return;
962 }
963
964 runApp(d->appData(d->windows.at(index.row()).get()));
965}
966
967void WaylandTasksModel::requestOpenUrls(const QModelIndex &index, const QList<QUrl> &urls)
968{
969 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent) || urls.isEmpty()) {
970 return;
971 }
972
973 runApp(d->appData(d->windows.at(index.row()).get()), urls);
974}
975
976void WaylandTasksModel::requestClose(const QModelIndex &index)
977{
978 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
979 return;
980 }
981
982 d->windows.at(index.row())->close();
983}
984
985void WaylandTasksModel::requestMove(const QModelIndex &index)
986{
987 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
988 return;
989 }
990
991 auto &window = d->windows.at(index.row());
992
993 window->set_state(PlasmaWindow::state::state_active, PlasmaWindow::state::state_active);
994 window->request_move();
995}
996
997void WaylandTasksModel::requestResize(const QModelIndex &index)
998{
999 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1000 return;
1001 }
1002
1003 auto &window = d->windows.at(index.row());
1004
1005 window->set_state(PlasmaWindow::state::state_active, PlasmaWindow::state::state_active);
1006 window->request_resize();
1007}
1008
1009void WaylandTasksModel::requestToggleMinimized(const QModelIndex &index)
1010{
1011 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1012 return;
1013 }
1014
1015 auto &window = d->windows.at(index.row());
1016
1017 if (window->windowState & PlasmaWindow::state::state_minimized) {
1018 window->set_state(PlasmaWindow::state::state_minimized, 0);
1019 } else {
1020 window->set_state(PlasmaWindow::state::state_minimized, PlasmaWindow::state::state_minimized);
1021 }
1022}
1023
1024void WaylandTasksModel::requestToggleMaximized(const QModelIndex &index)
1025{
1026 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1027 return;
1028 }
1029
1030 auto &window = d->windows.at(index.row());
1031
1032 if (window->windowState & PlasmaWindow::state::state_maximized) {
1033 window->set_state(PlasmaWindow::state::state_maximized | PlasmaWindow::state::state_active, PlasmaWindow::state::state_active);
1034 } else {
1035 window->set_state(PlasmaWindow::state::state_maximized | PlasmaWindow::state::state_active,
1036 PlasmaWindow::state::state_maximized | PlasmaWindow::state::state_active);
1037 }
1038}
1039
1040void WaylandTasksModel::requestToggleKeepAbove(const QModelIndex &index)
1041{
1042 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1043 return;
1044 }
1045
1046 auto &window = d->windows.at(index.row());
1047
1048 if (window->windowState & PlasmaWindow::state::state_keep_above) {
1049 window->set_state(PlasmaWindow::state::state_keep_above, 0);
1050 } else {
1051 window->set_state(PlasmaWindow::state::state_keep_above, PlasmaWindow::state::state_keep_above);
1052 }
1053}
1054
1055void WaylandTasksModel::requestToggleKeepBelow(const QModelIndex &index)
1056{
1057 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1058 return;
1059 }
1060 auto &window = d->windows.at(index.row());
1061
1062 if (window->windowState & PlasmaWindow::state::state_keep_below) {
1063 window->set_state(PlasmaWindow::state::state_keep_below, 0);
1064 } else {
1065 window->set_state(PlasmaWindow::state::state_keep_below, PlasmaWindow::state::state_keep_below);
1066 }
1067}
1068
1069void WaylandTasksModel::requestToggleFullScreen(const QModelIndex &index)
1070{
1071 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1072 return;
1073 }
1074
1075 auto &window = d->windows.at(index.row());
1076
1077 if (window->windowState & PlasmaWindow::state::state_fullscreen) {
1078 window->set_state(PlasmaWindow::state::state_fullscreen, 0);
1079 } else {
1080 window->set_state(PlasmaWindow::state::state_fullscreen, PlasmaWindow::state::state_fullscreen);
1081 }
1082}
1083
1084void WaylandTasksModel::requestToggleShaded(const QModelIndex &index)
1085{
1086 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1087 return;
1088 }
1089
1090 auto &window = d->windows.at(index.row());
1091
1092 if (window->windowState & PlasmaWindow::state::state_shaded) {
1093 window->set_state(PlasmaWindow::state::state_shaded, 0);
1094 } else {
1095 window->set_state(PlasmaWindow::state::state_shaded, PlasmaWindow::state::state_shaded);
1096 };
1097}
1098
1099void WaylandTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops)
1100{
1101 // FIXME TODO: Lacks the "if we've requested the current desktop, force-activate
1102 // the window" logic from X11 version. This behavior should be in KWin rather than
1103 // libtm however.
1104
1105 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1106 return;
1107 }
1108
1109 auto &window = d->windows.at(index.row());
1110
1111 if (desktops.isEmpty()) {
1112 const QStringList virtualDesktops = window->virtualDesktops;
1113 for (const QString &desktop : virtualDesktops) {
1114 window->request_leave_virtual_desktop(desktop);
1115 }
1116 } else {
1117 const QStringList &now = window->virtualDesktops;
1118 QStringList next;
1119
1120 for (const QVariant &desktop : desktops) {
1121 const QString &desktopId = desktop.toString();
1122
1123 if (!desktopId.isEmpty()) {
1124 next << desktopId;
1125
1126 if (!now.contains(desktopId)) {
1127 window->request_enter_virtual_desktop(desktopId);
1128 }
1129 }
1130 }
1131
1132 for (const QString &desktop : now) {
1133 if (!next.contains(desktop)) {
1134 window->request_leave_virtual_desktop(desktop);
1135 }
1136 }
1137 }
1138}
1139
1140void WaylandTasksModel::requestNewVirtualDesktop(const QModelIndex &index)
1141{
1142 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1143 return;
1144 }
1145
1146 d->windows.at(index.row())->request_enter_new_virtual_desktop();
1147}
1148
1149void WaylandTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities)
1150{
1151 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1152 return;
1153 }
1154
1155 auto &window = d->windows.at(index.row());
1156 const auto newActivities = QSet(activities.begin(), activities.end());
1157 const auto plasmaActivities = window->activities;
1158 const auto oldActivities = QSet(plasmaActivities.begin(), plasmaActivities.end());
1159
1160 const auto activitiesToAdd = newActivities - oldActivities;
1161 for (const auto &activity : activitiesToAdd) {
1162 window->request_enter_activity(activity);
1163 }
1164
1165 const auto activitiesToRemove = oldActivities - newActivities;
1166 for (const auto &activity : activitiesToRemove) {
1167 window->request_leave_activity(activity);
1168 }
1169}
1170
1171void WaylandTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate)
1172{
1173 /*
1174 FIXME: This introduces the dependency on Qt::Quick. I might prefer
1175 reversing this and publishing the window pointer through the model,
1176 then calling PlasmaWindow::setMinimizeGeometry in the applet backend,
1177 rather than hand delegate items into the lib, keeping the lib more UI-
1178 agnostic.
1179 */
1180
1181 Q_UNUSED(geometry)
1182
1183 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::DoNotUseParent)) {
1184 return;
1185 }
1186
1187 const QQuickItem *item = qobject_cast<const QQuickItem *>(delegate);
1188
1189 if (!item || !item->parentItem()) {
1190 return;
1191 }
1192
1193 QWindow *itemWindow = item->window();
1194
1195 if (!itemWindow) {
1196 return;
1197 }
1198
1199 auto waylandWindow = itemWindow->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
1200
1201 if (!waylandWindow || !waylandWindow->surface()) {
1202 return;
1203 }
1204
1205 QRect rect(item->x(), item->y(), item->width(), item->height());
1206 rect.moveTopLeft(item->parentItem()->mapToScene(rect.topLeft()).toPoint());
1207
1208 auto &window = d->windows.at(index.row());
1209
1210 window->set_minimized_geometry(waylandWindow->surface(), rect.x(), rect.y(), rect.width(), rect.height());
1211}
1212
1213QUuid WaylandTasksModel::winIdFromMimeData(const QMimeData *mimeData, bool *ok)
1214{
1215 Q_ASSERT(mimeData);
1216
1217 if (ok) {
1218 *ok = false;
1219 }
1220
1221 if (!mimeData->hasFormat(Private::mimeType())) {
1222 return {};
1223 }
1224
1225 QUuid id(mimeData->data(Private::mimeType()));
1226 *ok = !id.isNull();
1227
1228 return id;
1229}
1230
1231QList<QUuid> WaylandTasksModel::winIdsFromMimeData(const QMimeData *mimeData, bool *ok)
1232{
1233 Q_ASSERT(mimeData);
1234 QList<QUuid> ids;
1235
1236 if (ok) {
1237 *ok = false;
1238 }
1239
1240 if (!mimeData->hasFormat(Private::groupMimeType())) {
1241 // Try to extract single window id.
1242 bool singularOk;
1243 QUuid id = winIdFromMimeData(mimeData, &singularOk);
1244
1245 if (ok) {
1246 *ok = singularOk;
1247 }
1248
1249 if (singularOk) {
1250 ids << id;
1251 }
1252
1253 return ids;
1254 }
1255
1256 // FIXME: Extracting multiple winids is still unimplemented;
1257 // TaskGroupingProxy::data(..., ::MimeData) can't produce
1258 // a payload with them anyways.
1259
1260 return ids;
1261}
1262
1263}
1264
1265#include "waylandtasksmodel.moc"
void deleted(const QString &path)
void dirty(const QString &path)
void created(const QString &path)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
static bool isPlatformWayland()
KCALUTILS_EXPORT QString mimeType()
KDB_EXPORT KDbVersionInfo version()
QVariant read(const QByteArray &data, int versionOverride=0)
QWidget * window(QObject *job)
void initialize(StandardShortcut id)
QCA_EXPORT void init()
QByteArray & append(QByteArrayView data)
QFlags< T > & setFlag(Enum flag, bool on)
QIcon fromTheme(const QString &name)
iterator begin()
iterator end()
bool isEmpty() const const
void push_back(parameter_type value)
qsizetype removeAll(const AT &t)
QByteArray data(const QString &mimeType) const const
virtual bool hasFormat(const QString &mimeType) const const
bool isValid() const const
int row() const const
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
Q_SIGNALSQ_SIGNALS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void destroyed(QObject *obj)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
QPoint toPoint() const const
QPointF mapToScene(const QPointF &point) const const
QQuickItem * parentItem() const const
QQuickWindow * window() const const
int height() const const
void moveTopLeft(const QPoint &position)
QPoint topLeft() const const
int width() const const
int x() const const
int y() const const
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
DisplayRole
QFuture< T > run(Function function,...)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QTime currentTime()
QUuid createUuid()
Qt::WindowStates windowState() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:42 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.