Plasma-workspace

virtualdesktopinfo.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 "virtualdesktopinfo.h"
8#include "libtaskmanager_debug.h"
9
10#include <KLocalizedString>
11#include <KWindowSystem>
12#include <KX11Extras>
13
14#include <qwayland-org-kde-plasma-virtual-desktop.h>
15
16#include <QDBusConnection>
17#include <QDBusMessage>
18#include <QDBusPendingCallWatcher>
19#include <QDBusPendingReply>
20#include <QGuiApplication>
21#include <QWaylandClientExtension>
22
23#include <config-X11.h>
24
25#if HAVE_X11
26#include <netwm.h>
27#endif // HAVE_X11
28
29namespace X11Info
30{
31[[nodiscard]] inline auto connection()
32{
33 return qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection();
34}
35}
36
37namespace TaskManager
38{
39class Q_DECL_HIDDEN VirtualDesktopInfo::Private : public QObject
40{
41 Q_OBJECT
42
43public:
44 Private();
45 virtual ~Private()
46 {
47 }
48
49 uint refCount = 1;
50 // Fall back to true if we get an invalid DBus response when asking for the
51 // user's preference since that's what it was for years and years while
52 // 425787 was broken.
53 bool navigationWrappingAround = true;
54
55 virtual void init() = 0;
56 virtual QVariant currentDesktop() const = 0;
57 virtual int numberOfDesktops() const = 0;
58 virtual QVariantList desktopIds() const = 0;
59 virtual QStringList desktopNames() const = 0;
60 virtual quint32 position(const QVariant &desktop) const = 0;
61 virtual int desktopLayoutRows() const = 0;
62 virtual void requestActivate(const QVariant &desktop) = 0;
63 virtual void requestCreateDesktop(quint32 position) = 0;
64 virtual void requestRemoveDesktop(quint32 position) = 0;
65
66Q_SIGNALS:
67 void currentDesktopChanged() const;
68 void numberOfDesktopsChanged() const;
69 void desktopIdsChanged() const;
70 void desktopNamesChanged() const;
71 void desktopLayoutRowsChanged() const;
72 void navigationWrappingAroundChanged() const;
73
74protected Q_SLOTS:
75 void navigationWrappingAroundChanged(bool newVal);
76};
77
78VirtualDesktopInfo::Private::Private()
79{
80 // Connect to navigationWrappingAroundChanged signal
81 const bool connection = QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.KWin"),
82 QStringLiteral("/VirtualDesktopManager"),
83 QStringLiteral("org.kde.KWin.VirtualDesktopManager"),
84 QStringLiteral("navigationWrappingAroundChanged"),
85 this,
86 SLOT(navigationWrappingAroundChanged(bool)));
87 if (!connection) {
88 qCWarning(TASKMANAGER_DEBUG) << "Could not connect to org.kde.KWin.VirtualDesktopManager.navigationWrappingAroundChanged signal";
89 }
90
91 // ...Then get the property's current value
92 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
93 QStringLiteral("/VirtualDesktopManager"),
94 QStringLiteral("org.freedesktop.DBus.Properties"),
95 QStringLiteral("Get"));
96 msg.setArguments({QStringLiteral("org.kde.KWin.VirtualDesktopManager"), QStringLiteral("navigationWrappingAround")});
97 auto *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(msg), this);
99 QDBusPendingReply<QVariant> reply = *watcher;
100 watcher->deleteLater();
101
102 if (reply.isError()) {
103 qCWarning(TASKMANAGER_DEBUG) << "Failed to determine whether virtual desktop navigation wrapping is enabled: " << reply.error().message();
104 return;
105 }
106
107 navigationWrappingAroundChanged(reply.value().toBool());
108 });
109}
110
111void VirtualDesktopInfo::Private::navigationWrappingAroundChanged(bool newVal)
112{
113 if (navigationWrappingAround == newVal) {
114 return;
115 }
116 navigationWrappingAround = newVal;
117 Q_EMIT navigationWrappingAroundChanged();
118}
119
120#if HAVE_X11
121class Q_DECL_HIDDEN VirtualDesktopInfo::XWindowPrivate : public VirtualDesktopInfo::Private
122{
123 Q_OBJECT
124public:
125 XWindowPrivate();
126
127 void init() override;
128 QVariant currentDesktop() const override;
129 int numberOfDesktops() const override;
130 QVariantList desktopIds() const override;
131 QStringList desktopNames() const override;
132 quint32 position(const QVariant &desktop) const override;
133 int desktopLayoutRows() const override;
134 void requestActivate(const QVariant &desktop) override;
135 void requestCreateDesktop(quint32 position) override;
136 void requestRemoveDesktop(quint32 position) override;
137};
138
139VirtualDesktopInfo::XWindowPrivate::XWindowPrivate()
140 : VirtualDesktopInfo::Private()
141{
142 init();
143}
144
145void VirtualDesktopInfo::XWindowPrivate::init()
146{
147 connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &VirtualDesktopInfo::XWindowPrivate::currentDesktopChanged);
148
149 connect(KX11Extras::self(), &KX11Extras::numberOfDesktopsChanged, this, &VirtualDesktopInfo::XWindowPrivate::numberOfDesktopsChanged);
150
151 connect(KX11Extras::self(), &KX11Extras::desktopNamesChanged, this, &VirtualDesktopInfo::XWindowPrivate::desktopNamesChanged);
152
154 dbus.connect(QString(),
155 QStringLiteral("/VirtualDesktopManager"),
156 QStringLiteral("org.kde.KWin.VirtualDesktopManager"),
157 QStringLiteral("rowsChanged"),
158 this,
159 SIGNAL(desktopLayoutRowsChanged()));
160}
161
162QVariant VirtualDesktopInfo::XWindowPrivate::currentDesktop() const
163{
165}
166
167int VirtualDesktopInfo::XWindowPrivate::numberOfDesktops() const
168{
170}
171
172QVariantList VirtualDesktopInfo::XWindowPrivate::desktopIds() const
173{
174 QVariantList ids;
175
176 for (int i = 1; i <= KX11Extras::numberOfDesktops(); ++i) {
177 ids << i;
178 }
179
180 return ids;
181}
182
183QStringList VirtualDesktopInfo::XWindowPrivate::desktopNames() const
184{
185 QStringList names;
186
187 // Virtual desktop numbers start at 1.
188 for (int i = 1; i <= KX11Extras::numberOfDesktops(); ++i) {
189 names << KX11Extras::desktopName(i);
190 }
191
192 return names;
193}
194
195quint32 VirtualDesktopInfo::XWindowPrivate::position(const QVariant &desktop) const
196{
197 bool ok = false;
198
199 const quint32 desktopNumber = desktop.toUInt(&ok);
200
201 if (!ok) {
202 return -1;
203 }
204
205 return desktopNumber;
206}
207
208int VirtualDesktopInfo::XWindowPrivate::desktopLayoutRows() const
209{
210 const NETRootInfo info(X11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout);
211 return info.desktopLayoutColumnsRows().height();
212}
213
214void VirtualDesktopInfo::XWindowPrivate::requestActivate(const QVariant &desktop)
215{
216 bool ok = false;
217 const int desktopNumber = desktop.toInt(&ok);
218
219 // Virtual desktop numbers start at 1.
220 if (ok && desktopNumber > 0 && desktopNumber <= KX11Extras::numberOfDesktops()) {
221 KX11Extras::setCurrentDesktop(desktopNumber);
222 }
223}
224
225void VirtualDesktopInfo::XWindowPrivate::requestCreateDesktop(quint32 position)
226{
227 Q_UNUSED(position)
228
229 NETRootInfo info(X11Info::connection(), NET::NumberOfDesktops);
230 info.setNumberOfDesktops(info.numberOfDesktops() + 1);
231}
232
233void VirtualDesktopInfo::XWindowPrivate::requestRemoveDesktop(quint32 position)
234{
235 Q_UNUSED(position)
236
237 NETRootInfo info(X11Info::connection(), NET::NumberOfDesktops);
238
239 if (info.numberOfDesktops() > 1) {
240 info.setNumberOfDesktops(info.numberOfDesktops() - 1);
241 }
242}
243#endif // HAVE_X11
244
245class PlasmaVirtualDesktop : public QObject, public QtWayland::org_kde_plasma_virtual_desktop
246{
247 Q_OBJECT
248public:
249 PlasmaVirtualDesktop(::org_kde_plasma_virtual_desktop *object, const QString &id)
250 : org_kde_plasma_virtual_desktop(object)
251 , id(id)
252 {
253 }
254 ~PlasmaVirtualDesktop()
255 {
256 wl_proxy_destroy(reinterpret_cast<wl_proxy *>(object()));
257 }
258 const QString id;
261 void done();
262 void activated();
263
264protected:
265 void org_kde_plasma_virtual_desktop_name(const QString &name) override
266 {
267 this->name = name;
268 }
269 void org_kde_plasma_virtual_desktop_done() override
270 {
271 Q_EMIT done();
272 }
273 void org_kde_plasma_virtual_desktop_activated() override
274 {
275 Q_EMIT activated();
276 }
277};
278
279class PlasmaVirtualDesktopManagement : public QWaylandClientExtensionTemplate<PlasmaVirtualDesktopManagement>,
280 public QtWayland::org_kde_plasma_virtual_desktop_management
281{
282 Q_OBJECT
283public:
284 PlasmaVirtualDesktopManagement()
285 : QWaylandClientExtensionTemplate(2)
286 {
287 connect(this, &QWaylandClientExtension::activeChanged, this, [this] {
288 if (!isActive()) {
289 wl_proxy_destroy(reinterpret_cast<wl_proxy *>(object()));
290 }
291 });
292 }
293 ~PlasmaVirtualDesktopManagement()
294 {
295 if (isActive()) {
296 wl_proxy_destroy(reinterpret_cast<wl_proxy *>(object()));
297 }
298 }
299Q_SIGNALS:
300 void desktopCreated(const QString &id, quint32 position);
301 void desktopRemoved(const QString &id);
302 void rowsChanged(const quint32 rows);
303
304protected:
305 void org_kde_plasma_virtual_desktop_management_desktop_created(const QString &desktop_id, uint32_t position) override
306 {
307 Q_EMIT desktopCreated(desktop_id, position);
308 }
309 void org_kde_plasma_virtual_desktop_management_desktop_removed(const QString &desktop_id) override
310 {
311 Q_EMIT desktopRemoved(desktop_id);
312 }
313 void org_kde_plasma_virtual_desktop_management_rows(uint32_t rows) override
314 {
315 Q_EMIT rowsChanged(rows);
316 }
317};
318
319class Q_DECL_HIDDEN VirtualDesktopInfo::WaylandPrivate : public VirtualDesktopInfo::Private
320{
321 Q_OBJECT
322public:
323 WaylandPrivate();
324
325 QVariant currentVirtualDesktop;
326 std::vector<std::unique_ptr<PlasmaVirtualDesktop>> virtualDesktops;
327 std::unique_ptr<PlasmaVirtualDesktopManagement> virtualDesktopManagement;
328 quint32 rows;
329
330 auto findDesktop(const QString &id) const;
331
332 void init() override;
333 void addDesktop(const QString &id, quint32 position);
334 QVariant currentDesktop() const override;
335 int numberOfDesktops() const override;
336 QVariantList desktopIds() const override;
337 QStringList desktopNames() const override;
338 quint32 position(const QVariant &desktop) const override;
339 int desktopLayoutRows() const override;
340 void requestActivate(const QVariant &desktop) override;
341 void requestCreateDesktop(quint32 position) override;
342 void requestRemoveDesktop(quint32 position) override;
343};
344
345VirtualDesktopInfo::WaylandPrivate::WaylandPrivate()
346 : VirtualDesktopInfo::Private()
347{
348 init();
349}
350
351auto VirtualDesktopInfo::WaylandPrivate::findDesktop(const QString &id) const
352{
353 return std::find_if(virtualDesktops.begin(), virtualDesktops.end(), [&id](const std::unique_ptr<PlasmaVirtualDesktop> &desktop) {
354 return desktop->id == id;
355 });
356}
357
358void VirtualDesktopInfo::WaylandPrivate::init()
359{
361 return;
362 }
363
364 virtualDesktopManagement = std::make_unique<PlasmaVirtualDesktopManagement>();
365
366 connect(virtualDesktopManagement.get(), &PlasmaVirtualDesktopManagement::activeChanged, this, [this] {
367 if (!virtualDesktopManagement->isActive()) {
368 rows = 0;
369 virtualDesktops.clear();
370 currentVirtualDesktop.clear();
371 Q_EMIT currentDesktopChanged();
372 Q_EMIT numberOfDesktopsChanged();
373 Q_EMIT navigationWrappingAroundChanged();
374 Q_EMIT desktopIdsChanged();
375 Q_EMIT desktopNamesChanged();
376 Q_EMIT desktopLayoutRowsChanged();
377 }
378 });
379
380 connect(virtualDesktopManagement.get(), &PlasmaVirtualDesktopManagement::desktopCreated, this, &WaylandPrivate::addDesktop);
381
382 connect(virtualDesktopManagement.get(), &PlasmaVirtualDesktopManagement::desktopRemoved, this, [this](const QString &id) {
383 std::erase_if(virtualDesktops, [id](const std::unique_ptr<PlasmaVirtualDesktop> &desktop) {
384 return desktop->id == id;
385 });
386
387 Q_EMIT numberOfDesktopsChanged();
388 Q_EMIT desktopIdsChanged();
389 Q_EMIT desktopNamesChanged();
390
391 if (currentVirtualDesktop == id) {
392 currentVirtualDesktop.clear();
393 Q_EMIT currentDesktopChanged();
394 }
395 });
396
397 connect(virtualDesktopManagement.get(), &PlasmaVirtualDesktopManagement::rowsChanged, this, [this](quint32 rows) {
398 this->rows = rows;
399 Q_EMIT desktopLayoutRowsChanged();
400 });
401}
402
403void VirtualDesktopInfo::WaylandPrivate::addDesktop(const QString &id, quint32 position)
404{
405 if (findDesktop(id) != virtualDesktops.end()) {
406 return;
407 }
408
409 auto desktop = std::make_unique<PlasmaVirtualDesktop>(virtualDesktopManagement->get_virtual_desktop(id), id);
410
411 connect(desktop.get(), &PlasmaVirtualDesktop::activated, this, [id, this]() {
412 currentVirtualDesktop = id;
413 Q_EMIT currentDesktopChanged();
414 });
415
416 connect(desktop.get(), &PlasmaVirtualDesktop::done, this, [this]() {
417 Q_EMIT desktopNamesChanged();
418 });
419
420 virtualDesktops.insert(std::next(virtualDesktops.begin(), position), std::move(desktop));
421
422 Q_EMIT numberOfDesktopsChanged();
423 Q_EMIT desktopIdsChanged();
424 Q_EMIT desktopNamesChanged();
425}
426
427QVariant VirtualDesktopInfo::WaylandPrivate::currentDesktop() const
428{
429 return currentVirtualDesktop;
430}
431
432int VirtualDesktopInfo::WaylandPrivate::numberOfDesktops() const
433{
434 return virtualDesktops.size();
435}
436
437quint32 VirtualDesktopInfo::WaylandPrivate::position(const QVariant &desktop) const
438{
439 return std::distance(virtualDesktops.begin(), findDesktop(desktop.toString()));
440}
441
442QVariantList VirtualDesktopInfo::WaylandPrivate::desktopIds() const
443{
444 QVariantList ids;
445 ids.reserve(virtualDesktops.size());
446
447 std::transform(virtualDesktops.cbegin(), virtualDesktops.cend(), std::back_inserter(ids), [](const std::unique_ptr<PlasmaVirtualDesktop> &desktop) {
448 return desktop->id;
449 });
450 return ids;
451}
452
453QStringList VirtualDesktopInfo::WaylandPrivate::desktopNames() const
454{
455 if (!virtualDesktopManagement->isActive()) {
456 return QStringList();
457 }
458 QStringList names;
459 names.reserve(virtualDesktops.size());
460
461 std::transform(virtualDesktops.cbegin(), virtualDesktops.cend(), std::back_inserter(names), [](const std::unique_ptr<PlasmaVirtualDesktop> &desktop) {
462 return desktop->name;
463 });
464 return names;
465}
466
467int VirtualDesktopInfo::WaylandPrivate::desktopLayoutRows() const
468{
469 if (!virtualDesktopManagement->isActive()) {
470 return 0;
471 }
472
473 return rows;
474}
475
476void VirtualDesktopInfo::WaylandPrivate::requestActivate(const QVariant &desktop)
477{
478 if (!virtualDesktopManagement->isActive()) {
479 return;
480 }
481
482 if (auto it = findDesktop(desktop.toString()); it != virtualDesktops.end()) {
483 (*it)->request_activate();
484 }
485}
486
487void VirtualDesktopInfo::WaylandPrivate::requestCreateDesktop(quint32 position)
488{
489 if (!virtualDesktopManagement->isActive()) {
490 return;
491 }
492 virtualDesktopManagement->request_create_virtual_desktop(i18n("New Desktop"), position);
493}
494
495void VirtualDesktopInfo::WaylandPrivate::requestRemoveDesktop(quint32 position)
496{
497 if (!virtualDesktopManagement->isActive()) {
498 return;
499 }
500 if (virtualDesktops.size() == 1) {
501 return;
502 }
503
504 if (position > (virtualDesktops.size() - 1)) {
505 return;
506 }
507
508 virtualDesktopManagement->request_remove_virtual_desktop(virtualDesktops.at(position)->id);
509}
510
511VirtualDesktopInfo::Private *VirtualDesktopInfo::d = nullptr;
512
513VirtualDesktopInfo::VirtualDesktopInfo(QObject *parent)
514 : QObject(parent)
515{
516 if (!d) {
517#if HAVE_X11
519 d = new VirtualDesktopInfo::XWindowPrivate;
520 } else
521#endif // HAVE_X11
522 {
523 d = new VirtualDesktopInfo::WaylandPrivate;
524 }
525 } else {
526 ++d->refCount;
527 }
528
529 connect(d, &VirtualDesktopInfo::Private::currentDesktopChanged, this, &VirtualDesktopInfo::currentDesktopChanged);
530 connect(d, &VirtualDesktopInfo::Private::numberOfDesktopsChanged, this, &VirtualDesktopInfo::numberOfDesktopsChanged);
531 connect(d, &VirtualDesktopInfo::Private::desktopIdsChanged, this, &VirtualDesktopInfo::desktopIdsChanged);
532 connect(d, &VirtualDesktopInfo::Private::desktopNamesChanged, this, &VirtualDesktopInfo::desktopNamesChanged);
533 connect(d, &VirtualDesktopInfo::Private::desktopLayoutRowsChanged, this, &VirtualDesktopInfo::desktopLayoutRowsChanged);
534}
535
536VirtualDesktopInfo::~VirtualDesktopInfo()
537{
538 --d->refCount;
539
540 if (!d->refCount) {
541 delete d;
542 d = nullptr;
543 }
544}
545
546QVariant VirtualDesktopInfo::currentDesktop() const
547{
548 return d->currentDesktop();
549}
550
551int VirtualDesktopInfo::numberOfDesktops() const
552{
553 return d->numberOfDesktops();
554}
555
556QVariantList VirtualDesktopInfo::desktopIds() const
557{
558 return d->desktopIds();
559}
560
561QStringList VirtualDesktopInfo::desktopNames() const
562{
563 return d->desktopNames();
564}
565
566quint32 VirtualDesktopInfo::position(const QVariant &desktop) const
567{
568 return d->position(desktop);
569}
570
571int VirtualDesktopInfo::desktopLayoutRows() const
572{
573 return d->desktopLayoutRows();
574}
575
576void VirtualDesktopInfo::requestActivate(const QVariant &desktop)
577{
578 d->requestActivate(desktop);
579}
580
581void VirtualDesktopInfo::requestCreateDesktop(quint32 position)
582{
583 return d->requestCreateDesktop(position);
584}
585
586void VirtualDesktopInfo::requestRemoveDesktop(quint32 position)
587{
588 return d->requestRemoveDesktop(position);
589}
590
591bool VirtualDesktopInfo::navigationWrappingAround() const
592{
593 return d->navigationWrappingAround;
594}
595
596}
597
598#include "virtualdesktopinfo.moc"
static bool isPlatformX11()
static bool isPlatformWayland()
void numberOfDesktopsChanged(int num)
static int currentDesktop()
static int numberOfDesktops()
static void setCurrentDesktop(int desktop)
void desktopNamesChanged()
static QString desktopName(int desktop)
void currentDesktopChanged(int desktop)
QString i18n(const char *text, const TYPE &arg...)
QString name(StandardShortcut id)
QCA_EXPORT void init()
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
QDBusConnection sessionBus()
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
void setArguments(const QList< QVariant > &arguments)
void finished(QDBusPendingCallWatcher *self)
bool isError() const const
typename Select< 0 >::Type value() const const
void reserve(qsizetype size)
Q_EMITQ_EMIT
Q_SIGNALSQ_SIGNALS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
T & get(QVariant &v)
int toInt(bool *ok) const const
QString toString() const const
uint toUInt(bool *ok) 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.