File indexing completed on 2024-05-05 05:38:37

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