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 "virtualdesktopinfo.h"
0008 #include "libtaskmanager_debug.h"
0009 
0010 #include <KLocalizedString>
0011 #include <KWayland/Client/connection_thread.h>
0012 #include <KWayland/Client/plasmavirtualdesktop.h>
0013 #include <KWayland/Client/registry.h>
0014 #include <KWindowSystem>
0015 #include <KX11Extras>
0016 
0017 #include <QDBusConnection>
0018 #include <QDBusMessage>
0019 #include <QDBusPendingCallWatcher>
0020 #include <QDBusPendingReply>
0021 #include <QDebug>
0022 
0023 #include <config-X11.h>
0024 
0025 #if HAVE_X11
0026 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0027 #include <private/qtx11extras_p.h>
0028 #else
0029 #include <QX11Info>
0030 #endif
0031 #include <netwm.h>
0032 #endif // HAVE_X11
0033 
0034 namespace TaskManager
0035 {
0036 class Q_DECL_HIDDEN VirtualDesktopInfo::Private : public QObject
0037 {
0038     Q_OBJECT
0039 
0040 public:
0041     Private();
0042     virtual ~Private()
0043     {
0044     }
0045 
0046     uint refCount = 1;
0047     // Fall back to true if we get an invalid DBus response when asking for the
0048     // user's preference since that's what it was for years and years while
0049     // 425787 was broken.
0050     bool navigationWrappingAround = true;
0051 
0052     virtual void init() = 0;
0053     virtual QVariant currentDesktop() const = 0;
0054     virtual int numberOfDesktops() const = 0;
0055     virtual QVariantList desktopIds() const = 0;
0056     virtual QStringList desktopNames() const = 0;
0057     virtual quint32 position(const QVariant &desktop) const = 0;
0058     virtual int desktopLayoutRows() const = 0;
0059     virtual void requestActivate(const QVariant &desktop) = 0;
0060     virtual void requestCreateDesktop(quint32 position) = 0;
0061     virtual void requestRemoveDesktop(quint32 position) = 0;
0062 
0063 Q_SIGNALS:
0064     void currentDesktopChanged() const;
0065     void numberOfDesktopsChanged() const;
0066     void desktopIdsChanged() const;
0067     void desktopNamesChanged() const;
0068     void desktopLayoutRowsChanged() const;
0069     void navigationWrappingAroundChanged() const;
0070 
0071 protected Q_SLOTS:
0072     void navigationWrappingAroundChanged(bool newVal);
0073 };
0074 
0075 VirtualDesktopInfo::Private::Private()
0076 {
0077     // Connect to navigationWrappingAroundChanged signal
0078     const bool connection = QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.KWin"),
0079                                                                   QStringLiteral("/VirtualDesktopManager"),
0080                                                                   QStringLiteral("org.kde.KWin.VirtualDesktopManager"),
0081                                                                   QStringLiteral("navigationWrappingAroundChanged"),
0082                                                                   this,
0083                                                                   SLOT(navigationWrappingAroundChanged(bool)));
0084     if (!connection) {
0085         qCWarning(TASKMANAGER_DEBUG) << "Could not connect to org.kde.KWin.VirtualDesktopManager.navigationWrappingAroundChanged signal";
0086     }
0087 
0088     // ...Then get the property's current value
0089     QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
0090                                                       QStringLiteral("/VirtualDesktopManager"),
0091                                                       QStringLiteral("org.freedesktop.DBus.Properties"),
0092                                                       QStringLiteral("Get"));
0093     msg.setArguments({QStringLiteral("org.kde.KWin.VirtualDesktopManager"), QStringLiteral("navigationWrappingAround")});
0094     auto *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(msg), this);
0095     QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0096         QDBusPendingReply<QVariant> reply = *watcher;
0097         watcher->deleteLater();
0098 
0099         if (reply.isError()) {
0100             qCWarning(TASKMANAGER_DEBUG) << "Failed to determine whether virtual desktop navigation wrapping is enabled: " << reply.error().message();
0101             return;
0102         }
0103 
0104         navigationWrappingAroundChanged(reply.value().toBool());
0105     });
0106 }
0107 
0108 void VirtualDesktopInfo::Private::navigationWrappingAroundChanged(bool newVal)
0109 {
0110     if (navigationWrappingAround == newVal) {
0111         return;
0112     }
0113     navigationWrappingAround = newVal;
0114     Q_EMIT navigationWrappingAroundChanged();
0115 }
0116 
0117 #if HAVE_X11
0118 class Q_DECL_HIDDEN VirtualDesktopInfo::XWindowPrivate : public VirtualDesktopInfo::Private
0119 {
0120     Q_OBJECT
0121 public:
0122     XWindowPrivate();
0123 
0124     void init() override;
0125     QVariant currentDesktop() const override;
0126     int numberOfDesktops() const override;
0127     QVariantList desktopIds() const override;
0128     QStringList desktopNames() const override;
0129     quint32 position(const QVariant &desktop) const override;
0130     int desktopLayoutRows() const override;
0131     void requestActivate(const QVariant &desktop) override;
0132     void requestCreateDesktop(quint32 position) override;
0133     void requestRemoveDesktop(quint32 position) override;
0134 };
0135 
0136 VirtualDesktopInfo::XWindowPrivate::XWindowPrivate()
0137     : VirtualDesktopInfo::Private()
0138 {
0139     init();
0140 }
0141 
0142 void VirtualDesktopInfo::XWindowPrivate::init()
0143 {
0144     connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &VirtualDesktopInfo::XWindowPrivate::currentDesktopChanged);
0145 
0146     connect(KX11Extras::self(), &KX11Extras::numberOfDesktopsChanged, this, &VirtualDesktopInfo::XWindowPrivate::numberOfDesktopsChanged);
0147 
0148     connect(KX11Extras::self(), &KX11Extras::desktopNamesChanged, this, &VirtualDesktopInfo::XWindowPrivate::desktopNamesChanged);
0149 
0150     QDBusConnection dbus = QDBusConnection::sessionBus();
0151     dbus.connect(QString(),
0152                  QStringLiteral("/VirtualDesktopManager"),
0153                  QStringLiteral("org.kde.KWin.VirtualDesktopManager"),
0154                  QStringLiteral("rowsChanged"),
0155                  this,
0156                  SIGNAL(desktopLayoutRowsChanged()));
0157 }
0158 
0159 QVariant VirtualDesktopInfo::XWindowPrivate::currentDesktop() const
0160 {
0161     return KX11Extras::currentDesktop();
0162 }
0163 
0164 int VirtualDesktopInfo::XWindowPrivate::numberOfDesktops() const
0165 {
0166     return KX11Extras::numberOfDesktops();
0167 }
0168 
0169 QVariantList VirtualDesktopInfo::XWindowPrivate::desktopIds() const
0170 {
0171     QVariantList ids;
0172 
0173     for (int i = 1; i <= KX11Extras::numberOfDesktops(); ++i) {
0174         ids << i;
0175     }
0176 
0177     return ids;
0178 }
0179 
0180 QStringList VirtualDesktopInfo::XWindowPrivate::desktopNames() const
0181 {
0182     QStringList names;
0183 
0184     // Virtual desktop numbers start at 1.
0185     for (int i = 1; i <= KX11Extras::numberOfDesktops(); ++i) {
0186         names << KX11Extras::desktopName(i);
0187     }
0188 
0189     return names;
0190 }
0191 
0192 quint32 VirtualDesktopInfo::XWindowPrivate::position(const QVariant &desktop) const
0193 {
0194     bool ok = false;
0195 
0196     const quint32 desktopNumber = desktop.toUInt(&ok);
0197 
0198     if (!ok) {
0199         return -1;
0200     }
0201 
0202     return desktopNumber;
0203 }
0204 
0205 int VirtualDesktopInfo::XWindowPrivate::desktopLayoutRows() const
0206 {
0207     const NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout);
0208     return info.desktopLayoutColumnsRows().height();
0209 }
0210 
0211 void VirtualDesktopInfo::XWindowPrivate::requestActivate(const QVariant &desktop)
0212 {
0213     bool ok = false;
0214     const int desktopNumber = desktop.toInt(&ok);
0215 
0216     // Virtual desktop numbers start at 1.
0217     if (ok && desktopNumber > 0 && desktopNumber <= KX11Extras::numberOfDesktops()) {
0218         KX11Extras::setCurrentDesktop(desktopNumber);
0219     }
0220 }
0221 
0222 void VirtualDesktopInfo::XWindowPrivate::requestCreateDesktop(quint32 position)
0223 {
0224     Q_UNUSED(position)
0225 
0226     NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops);
0227     info.setNumberOfDesktops(info.numberOfDesktops() + 1);
0228 }
0229 
0230 void VirtualDesktopInfo::XWindowPrivate::requestRemoveDesktop(quint32 position)
0231 {
0232     Q_UNUSED(position)
0233 
0234     NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops);
0235 
0236     if (info.numberOfDesktops() > 1) {
0237         info.setNumberOfDesktops(info.numberOfDesktops() - 1);
0238     }
0239 }
0240 #endif // HAVE_X11
0241 
0242 class Q_DECL_HIDDEN VirtualDesktopInfo::WaylandPrivate : public VirtualDesktopInfo::Private
0243 {
0244     Q_OBJECT
0245 public:
0246     WaylandPrivate();
0247 
0248     QVariant currentVirtualDesktop;
0249     QStringList virtualDesktops;
0250     KWayland::Client::PlasmaVirtualDesktopManagement *virtualDesktopManagement = nullptr;
0251 
0252     void init() override;
0253     void addDesktop(const QString &id, quint32 position);
0254     QVariant currentDesktop() const override;
0255     int numberOfDesktops() const override;
0256     QVariantList desktopIds() const override;
0257     QStringList desktopNames() const override;
0258     quint32 position(const QVariant &desktop) const override;
0259     int desktopLayoutRows() const override;
0260     void requestActivate(const QVariant &desktop) override;
0261     void requestCreateDesktop(quint32 position) override;
0262     void requestRemoveDesktop(quint32 position) override;
0263 };
0264 
0265 VirtualDesktopInfo::WaylandPrivate::WaylandPrivate()
0266     : VirtualDesktopInfo::Private()
0267 {
0268     init();
0269 }
0270 
0271 void VirtualDesktopInfo::WaylandPrivate::init()
0272 {
0273     if (!KWindowSystem::isPlatformWayland()) {
0274         return;
0275     }
0276 
0277     KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(this);
0278 
0279     if (!connection) {
0280         return;
0281     }
0282 
0283     KWayland::Client::Registry *registry = new KWayland::Client::Registry(this);
0284     registry->create(connection);
0285 
0286     QObject::connect(registry, &KWayland::Client::Registry::plasmaVirtualDesktopManagementAnnounced, [this, registry](quint32 name, quint32 version) {
0287         virtualDesktopManagement = registry->createPlasmaVirtualDesktopManagement(name, version, this);
0288 
0289         QObject::connect(virtualDesktopManagement,
0290                          &KWayland::Client::PlasmaVirtualDesktopManagement::desktopCreated,
0291                          this,
0292                          [this](const QString &id, quint32 position) {
0293                              addDesktop(id, position);
0294                          });
0295 
0296         QObject::connect(virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopRemoved, this, [this](const QString &id) {
0297             virtualDesktops.removeOne(id);
0298 
0299             Q_EMIT numberOfDesktopsChanged();
0300             Q_EMIT desktopIdsChanged();
0301             Q_EMIT desktopNamesChanged();
0302 
0303             if (currentVirtualDesktop == id) {
0304                 currentVirtualDesktop.clear();
0305                 Q_EMIT currentDesktopChanged();
0306             }
0307         });
0308 
0309         QObject::connect(virtualDesktopManagement,
0310                          &KWayland::Client::PlasmaVirtualDesktopManagement::rowsChanged,
0311                          this,
0312                          &VirtualDesktopInfo::WaylandPrivate::desktopLayoutRowsChanged);
0313     });
0314 
0315     registry->setup();
0316 }
0317 
0318 void VirtualDesktopInfo::WaylandPrivate::addDesktop(const QString &id, quint32 position)
0319 {
0320     if (virtualDesktops.indexOf(id) != -1) {
0321         return;
0322     }
0323 
0324     virtualDesktops.insert(position, id);
0325 
0326     Q_EMIT numberOfDesktopsChanged();
0327     Q_EMIT desktopIdsChanged();
0328     Q_EMIT desktopNamesChanged();
0329 
0330     const KWayland::Client::PlasmaVirtualDesktop *desktop = virtualDesktopManagement->getVirtualDesktop(id);
0331 
0332     QObject::connect(desktop, &KWayland::Client::PlasmaVirtualDesktop::activated, this, [desktop, this]() {
0333         currentVirtualDesktop = desktop->id();
0334         Q_EMIT currentDesktopChanged();
0335     });
0336 
0337     QObject::connect(desktop, &KWayland::Client::PlasmaVirtualDesktop::done, this, [this]() {
0338         Q_EMIT desktopNamesChanged();
0339     });
0340 
0341     if (desktop->isActive()) {
0342         currentVirtualDesktop = id;
0343         Q_EMIT currentDesktopChanged();
0344     }
0345 }
0346 
0347 QVariant VirtualDesktopInfo::WaylandPrivate::currentDesktop() const
0348 {
0349     return currentVirtualDesktop;
0350 }
0351 
0352 int VirtualDesktopInfo::WaylandPrivate::numberOfDesktops() const
0353 {
0354     return virtualDesktops.count();
0355 }
0356 
0357 quint32 VirtualDesktopInfo::WaylandPrivate::position(const QVariant &desktop) const
0358 {
0359     return virtualDesktops.indexOf(desktop.toString());
0360 }
0361 
0362 QVariantList VirtualDesktopInfo::WaylandPrivate::desktopIds() const
0363 {
0364     QVariantList ids;
0365 
0366     foreach (const QString &id, virtualDesktops) {
0367         ids << id;
0368     }
0369 
0370     return ids;
0371 }
0372 
0373 QStringList VirtualDesktopInfo::WaylandPrivate::desktopNames() const
0374 {
0375     if (!virtualDesktopManagement) {
0376         return QStringList();
0377     }
0378     QStringList names;
0379 
0380     foreach (const QString &id, virtualDesktops) {
0381         const KWayland::Client::PlasmaVirtualDesktop *desktop = virtualDesktopManagement->getVirtualDesktop(id);
0382 
0383         if (desktop) {
0384             names << desktop->name();
0385         }
0386     }
0387 
0388     return names;
0389 }
0390 
0391 int VirtualDesktopInfo::WaylandPrivate::desktopLayoutRows() const
0392 {
0393     if (!virtualDesktopManagement) {
0394         return 0;
0395     }
0396 
0397     return virtualDesktopManagement->rows();
0398 }
0399 
0400 void VirtualDesktopInfo::WaylandPrivate::requestActivate(const QVariant &desktop)
0401 {
0402     if (!virtualDesktopManagement) {
0403         return;
0404     }
0405     KWayland::Client::PlasmaVirtualDesktop *desktopObj = virtualDesktopManagement->getVirtualDesktop(desktop.toString());
0406 
0407     if (desktopObj) {
0408         desktopObj->requestActivate();
0409     }
0410 }
0411 
0412 void VirtualDesktopInfo::WaylandPrivate::requestCreateDesktop(quint32 position)
0413 {
0414     if (!virtualDesktopManagement) {
0415         return;
0416     }
0417     virtualDesktopManagement->requestCreateVirtualDesktop(i18n("New Desktop"), position);
0418 }
0419 
0420 void VirtualDesktopInfo::WaylandPrivate::requestRemoveDesktop(quint32 position)
0421 {
0422     if (!virtualDesktopManagement) {
0423         return;
0424     }
0425     if (virtualDesktops.count() == 1) {
0426         return;
0427     }
0428 
0429     if (position > ((quint32)virtualDesktops.count() - 1)) {
0430         return;
0431     }
0432 
0433     virtualDesktopManagement->requestRemoveVirtualDesktop(virtualDesktops.at(position));
0434 }
0435 
0436 VirtualDesktopInfo::Private *VirtualDesktopInfo::d = nullptr;
0437 
0438 VirtualDesktopInfo::VirtualDesktopInfo(QObject *parent)
0439     : QObject(parent)
0440 {
0441     if (!d) {
0442 #if HAVE_X11
0443         if (KWindowSystem::isPlatformX11()) {
0444             d = new VirtualDesktopInfo::XWindowPrivate;
0445         } else
0446 #endif // HAVE_X11
0447         {
0448             d = new VirtualDesktopInfo::WaylandPrivate;
0449         }
0450     } else {
0451         ++d->refCount;
0452     }
0453 
0454     connect(d, &VirtualDesktopInfo::Private::currentDesktopChanged, this, &VirtualDesktopInfo::currentDesktopChanged);
0455     connect(d, &VirtualDesktopInfo::Private::numberOfDesktopsChanged, this, &VirtualDesktopInfo::numberOfDesktopsChanged);
0456     connect(d, &VirtualDesktopInfo::Private::desktopIdsChanged, this, &VirtualDesktopInfo::desktopIdsChanged);
0457     connect(d, &VirtualDesktopInfo::Private::desktopNamesChanged, this, &VirtualDesktopInfo::desktopNamesChanged);
0458     connect(d, &VirtualDesktopInfo::Private::desktopLayoutRowsChanged, this, &VirtualDesktopInfo::desktopLayoutRowsChanged);
0459 }
0460 
0461 VirtualDesktopInfo::~VirtualDesktopInfo()
0462 {
0463     --d->refCount;
0464 
0465     if (!d->refCount) {
0466         delete d;
0467         d = nullptr;
0468     }
0469 }
0470 
0471 QVariant VirtualDesktopInfo::currentDesktop() const
0472 {
0473     return d->currentDesktop();
0474 }
0475 
0476 int VirtualDesktopInfo::numberOfDesktops() const
0477 {
0478     return d->numberOfDesktops();
0479 }
0480 
0481 QVariantList VirtualDesktopInfo::desktopIds() const
0482 {
0483     return d->desktopIds();
0484 }
0485 
0486 QStringList VirtualDesktopInfo::desktopNames() const
0487 {
0488     return d->desktopNames();
0489 }
0490 
0491 quint32 VirtualDesktopInfo::position(const QVariant &desktop) const
0492 {
0493     return d->position(desktop);
0494 }
0495 
0496 int VirtualDesktopInfo::desktopLayoutRows() const
0497 {
0498     return d->desktopLayoutRows();
0499 }
0500 
0501 void VirtualDesktopInfo::requestActivate(const QVariant &desktop)
0502 {
0503     d->requestActivate(desktop);
0504 }
0505 
0506 void VirtualDesktopInfo::requestCreateDesktop(quint32 position)
0507 {
0508     return d->requestCreateDesktop(position);
0509 }
0510 
0511 void VirtualDesktopInfo::requestRemoveDesktop(quint32 position)
0512 {
0513     return d->requestRemoveDesktop(position);
0514 }
0515 
0516 bool VirtualDesktopInfo::navigationWrappingAround() const
0517 {
0518     return d->navigationWrappingAround;
0519 }
0520 
0521 }
0522 
0523 #include "virtualdesktopinfo.moc"