File indexing completed on 2024-02-25 17:23:57

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2009 Lucas Murray <lmurray@undefinedfire.com>
0006     SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 #include "virtualdesktops.h"
0011 #include "input.h"
0012 #include "wayland/plasmavirtualdesktop_interface.h"
0013 // KDE
0014 #include <KConfigGroup>
0015 #include <KGlobalAccel>
0016 #include <KLocalizedString>
0017 #include <NETWM>
0018 
0019 // Qt
0020 #include <QAction>
0021 #include <QDebug>
0022 #include <QUuid>
0023 
0024 #include <algorithm>
0025 
0026 namespace KWin
0027 {
0028 
0029 static bool s_loadingDesktopSettings = false;
0030 static const double GESTURE_SWITCH_THRESHOLD = .25;
0031 
0032 static QString generateDesktopId()
0033 {
0034     return QUuid::createUuid().toString(QUuid::WithoutBraces);
0035 }
0036 
0037 VirtualDesktop::VirtualDesktop(QObject *parent)
0038     : QObject(parent)
0039 {
0040 }
0041 
0042 VirtualDesktop::~VirtualDesktop()
0043 {
0044     Q_EMIT aboutToBeDestroyed();
0045 }
0046 
0047 void VirtualDesktopManager::setVirtualDesktopManagement(KWaylandServer::PlasmaVirtualDesktopManagementInterface *management)
0048 {
0049     using namespace KWaylandServer;
0050     Q_ASSERT(!m_virtualDesktopManagement);
0051     m_virtualDesktopManagement = management;
0052 
0053     auto createPlasmaVirtualDesktop = [this](VirtualDesktop *desktop) {
0054         PlasmaVirtualDesktopInterface *pvd = m_virtualDesktopManagement->createDesktop(desktop->id(), desktop->x11DesktopNumber() - 1);
0055         pvd->setName(desktop->name());
0056         pvd->sendDone();
0057 
0058         connect(desktop, &VirtualDesktop::nameChanged, pvd, [desktop, pvd]() {
0059             pvd->setName(desktop->name());
0060             pvd->sendDone();
0061         });
0062         connect(pvd, &PlasmaVirtualDesktopInterface::activateRequested, this, [this, desktop]() {
0063             setCurrent(desktop);
0064         });
0065     };
0066 
0067     connect(this, &VirtualDesktopManager::desktopCreated, m_virtualDesktopManagement, createPlasmaVirtualDesktop);
0068 
0069     connect(this, &VirtualDesktopManager::rowsChanged, m_virtualDesktopManagement, [this](uint rows) {
0070         m_virtualDesktopManagement->setRows(rows);
0071         m_virtualDesktopManagement->sendDone();
0072     });
0073 
0074     // handle removed: from VirtualDesktopManager to the wayland interface
0075     connect(this, &VirtualDesktopManager::desktopRemoved, m_virtualDesktopManagement, [this](VirtualDesktop *desktop) {
0076         m_virtualDesktopManagement->removeDesktop(desktop->id());
0077     });
0078 
0079     // create a new desktop when the client asks to
0080     connect(m_virtualDesktopManagement, &PlasmaVirtualDesktopManagementInterface::desktopCreateRequested, this, [this](const QString &name, quint32 position) {
0081         createVirtualDesktop(position, name);
0082     });
0083 
0084     // remove when the client asks to
0085     connect(m_virtualDesktopManagement, &PlasmaVirtualDesktopManagementInterface::desktopRemoveRequested, this, [this](const QString &id) {
0086         // here there can be some nice kauthorized check?
0087         // remove only from VirtualDesktopManager, the other connections will remove it from m_virtualDesktopManagement as well
0088         removeVirtualDesktop(id);
0089     });
0090 
0091     std::for_each(m_desktops.constBegin(), m_desktops.constEnd(), createPlasmaVirtualDesktop);
0092 
0093     // Now we are sure all ids are there
0094     save();
0095 
0096     connect(this, &VirtualDesktopManager::currentChanged, m_virtualDesktopManagement, [this]() {
0097         const QList<PlasmaVirtualDesktopInterface *> deskIfaces = m_virtualDesktopManagement->desktops();
0098         for (auto *deskInt : deskIfaces) {
0099             if (deskInt->id() == currentDesktop()->id()) {
0100                 deskInt->setActive(true);
0101             } else {
0102                 deskInt->setActive(false);
0103             }
0104         }
0105     });
0106 }
0107 
0108 void VirtualDesktop::setId(const QString &id)
0109 {
0110     Q_ASSERT(m_id.isEmpty());
0111     m_id = id;
0112 }
0113 
0114 void VirtualDesktop::setX11DesktopNumber(uint number)
0115 {
0116     // x11DesktopNumber can be changed now
0117     if (static_cast<uint>(m_x11DesktopNumber) == number) {
0118         return;
0119     }
0120 
0121     m_x11DesktopNumber = number;
0122 
0123     if (m_x11DesktopNumber != 0) {
0124         Q_EMIT x11DesktopNumberChanged();
0125     }
0126 }
0127 
0128 void VirtualDesktop::setName(const QString &name)
0129 {
0130     if (m_name == name) {
0131         return;
0132     }
0133     m_name = name;
0134     Q_EMIT nameChanged();
0135 }
0136 
0137 VirtualDesktopGrid::VirtualDesktopGrid()
0138     : m_size(1, 2) // Default to tow rows
0139     , m_grid(QVector<QVector<VirtualDesktop *>>{QVector<VirtualDesktop *>{}, QVector<VirtualDesktop *>{}})
0140 {
0141 }
0142 
0143 VirtualDesktopGrid::~VirtualDesktopGrid() = default;
0144 
0145 void VirtualDesktopGrid::update(const QSize &size, Qt::Orientation orientation, const QVector<VirtualDesktop *> &desktops)
0146 {
0147     // Set private variables
0148     m_size = size;
0149     const uint width = size.width();
0150     const uint height = size.height();
0151 
0152     m_grid.clear();
0153     auto it = desktops.begin();
0154     auto end = desktops.end();
0155     if (orientation == Qt::Horizontal) {
0156         for (uint y = 0; y < height; ++y) {
0157             QVector<VirtualDesktop *> row;
0158             for (uint x = 0; x < width && it != end; ++x) {
0159                 row << *it;
0160                 it++;
0161             }
0162             m_grid << row;
0163         }
0164     } else {
0165         for (uint y = 0; y < height; ++y) {
0166             m_grid << QVector<VirtualDesktop *>();
0167         }
0168         for (uint x = 0; x < width; ++x) {
0169             for (uint y = 0; y < height && it != end; ++y) {
0170                 auto &row = m_grid[y];
0171                 row << *it;
0172                 it++;
0173             }
0174         }
0175     }
0176 }
0177 
0178 QPoint VirtualDesktopGrid::gridCoords(uint id) const
0179 {
0180     return gridCoords(VirtualDesktopManager::self()->desktopForX11Id(id));
0181 }
0182 
0183 QPoint VirtualDesktopGrid::gridCoords(VirtualDesktop *vd) const
0184 {
0185     for (int y = 0; y < m_grid.count(); ++y) {
0186         const auto &row = m_grid.at(y);
0187         for (int x = 0; x < row.count(); ++x) {
0188             if (row.at(x) == vd) {
0189                 return QPoint(x, y);
0190             }
0191         }
0192     }
0193     return QPoint(-1, -1);
0194 }
0195 
0196 VirtualDesktop *VirtualDesktopGrid::at(const QPoint &coords) const
0197 {
0198     if (coords.y() >= m_grid.count()) {
0199         return nullptr;
0200     }
0201     const auto &row = m_grid.at(coords.y());
0202     if (coords.x() >= row.count()) {
0203         return nullptr;
0204     }
0205     return row.at(coords.x());
0206 }
0207 
0208 KWIN_SINGLETON_FACTORY_VARIABLE(VirtualDesktopManager, s_manager)
0209 
0210 VirtualDesktopManager::VirtualDesktopManager(QObject *parent)
0211     : QObject(parent)
0212     , m_navigationWrapsAround(false)
0213     , m_rootInfo(nullptr)
0214     , m_swipeGestureReleasedY(new QAction(this))
0215     , m_swipeGestureReleasedX(new QAction(this))
0216 {
0217 }
0218 
0219 VirtualDesktopManager::~VirtualDesktopManager()
0220 {
0221     s_manager = nullptr;
0222 }
0223 
0224 void VirtualDesktopManager::setRootInfo(NETRootInfo *info)
0225 {
0226     m_rootInfo = info;
0227 
0228     // Nothing will be connected to rootInfo
0229     if (m_rootInfo) {
0230         int columns = count() / m_rows;
0231         if (count() % m_rows > 0) {
0232             columns++;
0233         }
0234         m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, m_rows, NET::DesktopLayoutCornerTopLeft);
0235         updateRootInfo();
0236         m_rootInfo->setCurrentDesktop(currentDesktop()->x11DesktopNumber());
0237         for (auto *vd : std::as_const(m_desktops)) {
0238             m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data());
0239         }
0240     }
0241 }
0242 
0243 VirtualDesktop *VirtualDesktopManager::inDirection(VirtualDesktop *desktop, Direction direction, bool wrap)
0244 {
0245     switch (direction) {
0246     case Direction::Up:
0247         return above(desktop, wrap);
0248     case Direction::Down:
0249         return below(desktop, wrap);
0250     case Direction::Right:
0251         return toRight(desktop, wrap);
0252     case Direction::Left:
0253         return toLeft(desktop, wrap);
0254     case Direction::Next:
0255         return next(desktop, wrap);
0256     case Direction::Previous:
0257         return previous(desktop, wrap);
0258     }
0259     Q_UNREACHABLE();
0260 }
0261 
0262 uint VirtualDesktopManager::inDirection(uint desktop, Direction direction, bool wrap)
0263 {
0264     return inDirection(desktopForX11Id(desktop), direction, wrap)->x11DesktopNumber();
0265 }
0266 
0267 void VirtualDesktopManager::moveTo(Direction direction, bool wrap)
0268 {
0269     setCurrent(inDirection(nullptr, direction, wrap));
0270 }
0271 
0272 VirtualDesktop *VirtualDesktopManager::above(VirtualDesktop *desktop, bool wrap) const
0273 {
0274     Q_ASSERT(m_current);
0275     if (!desktop) {
0276         desktop = m_current;
0277     }
0278     QPoint coords = m_grid.gridCoords(desktop);
0279     Q_ASSERT(coords.x() >= 0);
0280     while (true) {
0281         coords.ry()--;
0282         if (coords.y() < 0) {
0283             if (wrap) {
0284                 coords.setY(m_grid.height() - 1);
0285             } else {
0286                 return desktop; // Already at the top-most desktop
0287             }
0288         }
0289         if (VirtualDesktop *vd = m_grid.at(coords)) {
0290             return vd;
0291         }
0292     }
0293     return nullptr;
0294 }
0295 
0296 VirtualDesktop *VirtualDesktopManager::toRight(VirtualDesktop *desktop, bool wrap) const
0297 {
0298     Q_ASSERT(m_current);
0299     if (!desktop) {
0300         desktop = m_current;
0301     }
0302     QPoint coords = m_grid.gridCoords(desktop);
0303     Q_ASSERT(coords.x() >= 0);
0304     while (true) {
0305         coords.rx()++;
0306         if (coords.x() >= m_grid.width()) {
0307             if (wrap) {
0308                 coords.setX(0);
0309             } else {
0310                 return desktop; // Already at the right-most desktop
0311             }
0312         }
0313         if (VirtualDesktop *vd = m_grid.at(coords)) {
0314             return vd;
0315         }
0316     }
0317     return nullptr;
0318 }
0319 
0320 VirtualDesktop *VirtualDesktopManager::below(VirtualDesktop *desktop, bool wrap) const
0321 {
0322     Q_ASSERT(m_current);
0323     if (!desktop) {
0324         desktop = m_current;
0325     }
0326     QPoint coords = m_grid.gridCoords(desktop);
0327     Q_ASSERT(coords.x() >= 0);
0328     while (true) {
0329         coords.ry()++;
0330         if (coords.y() >= m_grid.height()) {
0331             if (wrap) {
0332                 coords.setY(0);
0333             } else {
0334                 // Already at the bottom-most desktop
0335                 return desktop;
0336             }
0337         }
0338         if (VirtualDesktop *vd = m_grid.at(coords)) {
0339             return vd;
0340         }
0341     }
0342     return nullptr;
0343 }
0344 
0345 VirtualDesktop *VirtualDesktopManager::toLeft(VirtualDesktop *desktop, bool wrap) const
0346 {
0347     Q_ASSERT(m_current);
0348     if (!desktop) {
0349         desktop = m_current;
0350     }
0351     QPoint coords = m_grid.gridCoords(desktop);
0352     Q_ASSERT(coords.x() >= 0);
0353     while (true) {
0354         coords.rx()--;
0355         if (coords.x() < 0) {
0356             if (wrap) {
0357                 coords.setX(m_grid.width() - 1);
0358             } else {
0359                 return desktop; // Already at the left-most desktop
0360             }
0361         }
0362         if (VirtualDesktop *vd = m_grid.at(coords)) {
0363             return vd;
0364         }
0365     }
0366     return nullptr;
0367 }
0368 
0369 VirtualDesktop *VirtualDesktopManager::next(VirtualDesktop *desktop, bool wrap) const
0370 {
0371     Q_ASSERT(m_current);
0372     if (!desktop) {
0373         desktop = m_current;
0374     }
0375     auto it = std::find(m_desktops.begin(), m_desktops.end(), desktop);
0376     Q_ASSERT(it != m_desktops.end());
0377     it++;
0378     if (it == m_desktops.end()) {
0379         if (wrap) {
0380             return m_desktops.first();
0381         } else {
0382             return desktop;
0383         }
0384     }
0385     return *it;
0386 }
0387 
0388 VirtualDesktop *VirtualDesktopManager::previous(VirtualDesktop *desktop, bool wrap) const
0389 {
0390     Q_ASSERT(m_current);
0391     if (!desktop) {
0392         desktop = m_current;
0393     }
0394     auto it = std::find(m_desktops.begin(), m_desktops.end(), desktop);
0395     Q_ASSERT(it != m_desktops.end());
0396     if (it == m_desktops.begin()) {
0397         if (wrap) {
0398             return m_desktops.last();
0399         } else {
0400             return desktop;
0401         }
0402     }
0403     it--;
0404     return *it;
0405 }
0406 
0407 VirtualDesktop *VirtualDesktopManager::desktopForX11Id(uint id) const
0408 {
0409     if (id == 0 || id > count()) {
0410         return nullptr;
0411     }
0412     return m_desktops.at(id - 1);
0413 }
0414 
0415 VirtualDesktop *VirtualDesktopManager::desktopForId(const QString &id) const
0416 {
0417     auto desk = std::find_if(
0418         m_desktops.constBegin(),
0419         m_desktops.constEnd(),
0420         [id](const VirtualDesktop *desk) {
0421             return desk->id() == id;
0422         });
0423 
0424     if (desk != m_desktops.constEnd()) {
0425         return *desk;
0426     }
0427 
0428     return nullptr;
0429 }
0430 
0431 VirtualDesktop *VirtualDesktopManager::createVirtualDesktop(uint position, const QString &name)
0432 {
0433     // too many, can't insert new ones
0434     if ((uint)m_desktops.count() == VirtualDesktopManager::maximum()) {
0435         return nullptr;
0436     }
0437 
0438     position = std::clamp(position, 0u, static_cast<uint>(m_desktops.count()));
0439 
0440     QString desktopName = name;
0441     if (desktopName.isEmpty()) {
0442         desktopName = defaultName(position + 1);
0443     }
0444 
0445     auto *vd = new VirtualDesktop(this);
0446     vd->setX11DesktopNumber(position + 1);
0447     vd->setId(generateDesktopId());
0448     vd->setName(desktopName);
0449 
0450     connect(vd, &VirtualDesktop::nameChanged, this, [this, vd]() {
0451         if (m_rootInfo) {
0452             m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data());
0453         }
0454     });
0455 
0456     if (m_rootInfo) {
0457         m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data());
0458     }
0459 
0460     m_desktops.insert(position, vd);
0461 
0462     // update the id of displaced desktops
0463     for (uint i = position + 1; i < (uint)m_desktops.count(); ++i) {
0464         m_desktops[i]->setX11DesktopNumber(i + 1);
0465         if (m_rootInfo) {
0466             m_rootInfo->setDesktopName(i + 1, m_desktops[i]->name().toUtf8().data());
0467         }
0468     }
0469 
0470     save();
0471 
0472     updateRootInfo();
0473     Q_EMIT desktopCreated(vd);
0474     Q_EMIT countChanged(m_desktops.count() - 1, m_desktops.count());
0475     return vd;
0476 }
0477 
0478 void VirtualDesktopManager::removeVirtualDesktop(const QString &id)
0479 {
0480     auto desktop = desktopForId(id);
0481     if (desktop) {
0482         removeVirtualDesktop(desktop);
0483     }
0484 }
0485 
0486 void VirtualDesktopManager::removeVirtualDesktop(VirtualDesktop *desktop)
0487 {
0488     // don't end up without any desktop
0489     if (m_desktops.count() == 1) {
0490         return;
0491     }
0492 
0493     const uint oldCurrent = m_current->x11DesktopNumber();
0494     const uint i = desktop->x11DesktopNumber() - 1;
0495     m_desktops.remove(i);
0496 
0497     for (uint j = i; j < (uint)m_desktops.count(); ++j) {
0498         m_desktops[j]->setX11DesktopNumber(j + 1);
0499         if (m_rootInfo) {
0500             m_rootInfo->setDesktopName(j + 1, m_desktops[j]->name().toUtf8().data());
0501         }
0502     }
0503 
0504     const uint newCurrent = std::min(oldCurrent, (uint)m_desktops.count());
0505     m_current = m_desktops.at(newCurrent - 1);
0506     if (oldCurrent != newCurrent) {
0507         Q_EMIT currentChanged(oldCurrent, newCurrent);
0508     }
0509 
0510     updateRootInfo();
0511     save();
0512 
0513     Q_EMIT desktopRemoved(desktop);
0514     Q_EMIT countChanged(m_desktops.count() + 1, m_desktops.count());
0515 
0516     desktop->deleteLater();
0517 }
0518 
0519 uint VirtualDesktopManager::current() const
0520 {
0521     return m_current ? m_current->x11DesktopNumber() : 0;
0522 }
0523 
0524 VirtualDesktop *VirtualDesktopManager::currentDesktop() const
0525 {
0526     return m_current;
0527 }
0528 
0529 bool VirtualDesktopManager::setCurrent(uint newDesktop)
0530 {
0531     if (newDesktop < 1 || newDesktop > count()) {
0532         return false;
0533     }
0534     auto d = desktopForX11Id(newDesktop);
0535     Q_ASSERT(d);
0536     return setCurrent(d);
0537 }
0538 
0539 bool VirtualDesktopManager::setCurrent(VirtualDesktop *newDesktop)
0540 {
0541     Q_ASSERT(newDesktop);
0542     if (m_current == newDesktop) {
0543         return false;
0544     }
0545     const uint oldDesktop = current();
0546     m_current = newDesktop;
0547     Q_EMIT currentChanged(oldDesktop, newDesktop->x11DesktopNumber());
0548     return true;
0549 }
0550 
0551 void VirtualDesktopManager::setCount(uint count)
0552 {
0553     count = std::clamp<uint>(count, 1, VirtualDesktopManager::maximum());
0554     if (count == uint(m_desktops.count())) {
0555         // nothing to change
0556         return;
0557     }
0558     QList<VirtualDesktop *> newDesktops;
0559     const uint oldCount = m_desktops.count();
0560     // this explicit check makes it more readable
0561     if ((uint)m_desktops.count() > count) {
0562         const auto desktopsToRemove = m_desktops.mid(count);
0563         m_desktops.resize(count);
0564         if (m_current) {
0565             uint oldCurrent = current();
0566             uint newCurrent = std::min(oldCurrent, count);
0567             m_current = m_desktops.at(newCurrent - 1);
0568             if (oldCurrent != newCurrent) {
0569                 Q_EMIT currentChanged(oldCurrent, newCurrent);
0570             }
0571         }
0572         for (auto desktop : desktopsToRemove) {
0573             Q_EMIT desktopRemoved(desktop);
0574             desktop->deleteLater();
0575         }
0576     } else {
0577         while (uint(m_desktops.count()) < count) {
0578             auto vd = new VirtualDesktop(this);
0579             const int x11Number = m_desktops.count() + 1;
0580             vd->setX11DesktopNumber(x11Number);
0581             vd->setName(defaultName(x11Number));
0582             if (!s_loadingDesktopSettings) {
0583                 vd->setId(generateDesktopId());
0584             }
0585             m_desktops << vd;
0586             newDesktops << vd;
0587             connect(vd, &VirtualDesktop::nameChanged, this, [this, vd]() {
0588                 if (m_rootInfo) {
0589                     m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data());
0590                 }
0591             });
0592             if (m_rootInfo) {
0593                 m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data());
0594             }
0595         }
0596     }
0597 
0598     updateRootInfo();
0599 
0600     if (!s_loadingDesktopSettings) {
0601         save();
0602     }
0603     for (auto vd : std::as_const(newDesktops)) {
0604         Q_EMIT desktopCreated(vd);
0605     }
0606     Q_EMIT countChanged(oldCount, m_desktops.count());
0607 }
0608 
0609 uint VirtualDesktopManager::rows() const
0610 {
0611     return m_rows;
0612 }
0613 
0614 void VirtualDesktopManager::setRows(uint rows)
0615 {
0616     if (rows == 0 || rows > count() || rows == m_rows) {
0617         return;
0618     }
0619 
0620     m_rows = rows;
0621 
0622     int columns = count() / m_rows;
0623     if (count() % m_rows > 0) {
0624         columns++;
0625     }
0626     if (m_rootInfo) {
0627         m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, m_rows, NET::DesktopLayoutCornerTopLeft);
0628         m_rootInfo->activate();
0629     }
0630 
0631     updateLayout();
0632 
0633     // rowsChanged will be emitted by setNETDesktopLayout called by updateLayout
0634 }
0635 
0636 void VirtualDesktopManager::updateRootInfo()
0637 {
0638     if (!m_rootInfo) {
0639         // Make sure the layout is still valid
0640         updateLayout();
0641         return;
0642     }
0643     const int n = count();
0644     m_rootInfo->setNumberOfDesktops(n);
0645     NETPoint *viewports = new NETPoint[n];
0646     m_rootInfo->setDesktopViewport(n, *viewports);
0647     delete[] viewports;
0648     // Make sure the layout is still valid
0649     updateLayout();
0650 }
0651 
0652 void VirtualDesktopManager::updateLayout()
0653 {
0654     m_rows = std::min(m_rows, count());
0655     int columns = count() / m_rows;
0656     Qt::Orientation orientation = Qt::Horizontal;
0657     if (m_rootInfo) {
0658         // TODO: Is there a sane way to avoid overriding the existing grid?
0659         columns = m_rootInfo->desktopLayoutColumnsRows().width();
0660         m_rows = std::max(1, m_rootInfo->desktopLayoutColumnsRows().height());
0661         orientation = m_rootInfo->desktopLayoutOrientation() == NET::OrientationHorizontal ? Qt::Horizontal : Qt::Vertical;
0662     }
0663 
0664     if (columns == 0) {
0665         // Not given, set default layout
0666         m_rows = count() == 1u ? 1 : 2;
0667         columns = count() / m_rows;
0668     }
0669 
0670     // Patch to make desktop grid size equal 1 when 1 desktop for desktop switching animations
0671     if (m_desktops.size() == 1) {
0672         m_rows = 1;
0673         columns = 1;
0674     }
0675 
0676     setNETDesktopLayout(orientation,
0677                         columns, m_rows, 0 // rootInfo->desktopLayoutCorner() // Not really worth implementing right now.
0678     );
0679 }
0680 
0681 void VirtualDesktopManager::load()
0682 {
0683     s_loadingDesktopSettings = true;
0684     if (!m_config) {
0685         return;
0686     }
0687 
0688     KConfigGroup group(m_config, QStringLiteral("Desktops"));
0689     const int n = group.readEntry("Number", 1);
0690     setCount(n);
0691 
0692     for (int i = 1; i <= n; i++) {
0693         QString s = group.readEntry(QStringLiteral("Name_%1").arg(i), i18n("Desktop %1", i));
0694         if (m_rootInfo) {
0695             m_rootInfo->setDesktopName(i, s.toUtf8().data());
0696         }
0697         m_desktops[i - 1]->setName(s);
0698 
0699         const QString sId = group.readEntry(QStringLiteral("Id_%1").arg(i), QString());
0700 
0701         if (m_desktops[i - 1]->id().isEmpty()) {
0702             m_desktops[i - 1]->setId(sId.isEmpty() ? generateDesktopId() : sId);
0703         } else {
0704             Q_ASSERT(sId.isEmpty() || m_desktops[i - 1]->id() == sId);
0705         }
0706 
0707         // TODO: update desktop focus chain, why?
0708         //         m_desktopFocusChain.value()[i-1] = i;
0709     }
0710 
0711     int rows = group.readEntry<int>("Rows", 2);
0712     m_rows = std::clamp(rows, 1, n);
0713 
0714     s_loadingDesktopSettings = false;
0715 }
0716 
0717 void VirtualDesktopManager::save()
0718 {
0719     if (s_loadingDesktopSettings) {
0720         return;
0721     }
0722     if (!m_config) {
0723         return;
0724     }
0725     KConfigGroup group(m_config, QStringLiteral("Desktops"));
0726 
0727     for (int i = count() + 1; group.hasKey(QStringLiteral("Id_%1").arg(i)); i++) {
0728         group.deleteEntry(QStringLiteral("Id_%1").arg(i));
0729         group.deleteEntry(QStringLiteral("Name_%1").arg(i));
0730     }
0731 
0732     group.writeEntry("Number", count());
0733     for (VirtualDesktop *desktop : std::as_const(m_desktops)) {
0734         const uint position = desktop->x11DesktopNumber();
0735 
0736         QString s = desktop->name();
0737         const QString defaultvalue = defaultName(position);
0738         if (s.isEmpty()) {
0739             s = defaultvalue;
0740             if (m_rootInfo) {
0741                 m_rootInfo->setDesktopName(position, s.toUtf8().data());
0742             }
0743         }
0744 
0745         if (s != defaultvalue) {
0746             group.writeEntry(QStringLiteral("Name_%1").arg(position), s);
0747         } else {
0748             QString currentvalue = group.readEntry(QStringLiteral("Name_%1").arg(position), QString());
0749             if (currentvalue != defaultvalue) {
0750                 group.deleteEntry(QStringLiteral("Name_%1").arg(position));
0751             }
0752         }
0753         group.writeEntry(QStringLiteral("Id_%1").arg(position), desktop->id());
0754     }
0755 
0756     group.writeEntry("Rows", m_rows);
0757 
0758     // Save to disk
0759     group.sync();
0760 }
0761 
0762 QString VirtualDesktopManager::defaultName(int desktop) const
0763 {
0764     return i18n("Desktop %1", desktop);
0765 }
0766 
0767 void VirtualDesktopManager::setNETDesktopLayout(Qt::Orientation orientation, uint width, uint height, int startingCorner)
0768 {
0769     // startingCorner is not really worth implementing right now.
0770 
0771     const uint count = m_desktops.count();
0772 
0773     // Calculate valid grid size
0774     Q_ASSERT(width > 0 || height > 0);
0775     if ((width <= 0) && (height > 0)) {
0776         width = (count + height - 1) / height;
0777     } else if ((height <= 0) && (width > 0)) {
0778         height = (count + width - 1) / width;
0779     }
0780     while (width * height < count) {
0781         if (orientation == Qt::Horizontal) {
0782             ++width;
0783         } else {
0784             ++height;
0785         }
0786     }
0787 
0788     m_rows = std::max(1u, height);
0789 
0790     m_grid.update(QSize(width, height), orientation, m_desktops);
0791     // TODO: why is there no call to m_rootInfo->setDesktopLayout?
0792     Q_EMIT layoutChanged(width, height);
0793     Q_EMIT rowsChanged(height);
0794 }
0795 
0796 void VirtualDesktopManager::initShortcuts()
0797 {
0798     initSwitchToShortcuts();
0799 
0800     addAction(QStringLiteral("Switch to Next Desktop"), i18n("Switch to Next Desktop"), QKeySequence(), &VirtualDesktopManager::slotNext);
0801     addAction(QStringLiteral("Switch to Previous Desktop"), i18n("Switch to Previous Desktop"), QKeySequence(), &VirtualDesktopManager::slotPrevious);
0802 
0803     // shortcuts
0804     addAction(QStringLiteral("Switch One Desktop to the Right"), i18n("Switch One Desktop to the Right"), QKeySequence(Qt::CTRL | Qt::META | Qt::Key_Right), &VirtualDesktopManager::slotRight);
0805     addAction(QStringLiteral("Switch One Desktop to the Left"), i18n("Switch One Desktop to the Left"), QKeySequence(Qt::CTRL | Qt::META | Qt::Key_Left), &VirtualDesktopManager::slotLeft);
0806     addAction(QStringLiteral("Switch One Desktop Up"), i18n("Switch One Desktop Up"), QKeySequence(Qt::CTRL | Qt::META | Qt::Key_Up), &VirtualDesktopManager::slotUp);
0807     addAction(QStringLiteral("Switch One Desktop Down"), i18n("Switch One Desktop Down"), QKeySequence(Qt::CTRL | Qt::META | Qt::Key_Down), &VirtualDesktopManager::slotDown);
0808 
0809     // Gestures
0810     // These connections decide which desktop to end on after gesture ends
0811     connect(m_swipeGestureReleasedX.get(), &QAction::triggered, this, &VirtualDesktopManager::gestureReleasedX);
0812     connect(m_swipeGestureReleasedY.get(), &QAction::triggered, this, &VirtualDesktopManager::gestureReleasedY);
0813 
0814     const auto left = [this](qreal cb) {
0815         if (grid().width() > 1) {
0816             m_currentDesktopOffset.setX(cb);
0817             Q_EMIT currentChanging(current(), m_currentDesktopOffset);
0818         }
0819     };
0820     const auto right = [this](qreal cb) {
0821         if (grid().width() > 1) {
0822             m_currentDesktopOffset.setX(-cb);
0823             Q_EMIT currentChanging(current(), m_currentDesktopOffset);
0824         }
0825     };
0826     input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Left, 3, m_swipeGestureReleasedX.get(), left);
0827     input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Right, 3, m_swipeGestureReleasedX.get(), right);
0828     input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Left, 4, m_swipeGestureReleasedX.get(), left);
0829     input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Right, 4, m_swipeGestureReleasedX.get(), right);
0830     input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Down, 3, m_swipeGestureReleasedY.get(), [this](qreal cb) {
0831         if (grid().height() > 1) {
0832             m_currentDesktopOffset.setY(-cb);
0833             Q_EMIT currentChanging(current(), m_currentDesktopOffset);
0834         }
0835     });
0836     input()->registerRealtimeTouchpadSwipeShortcut(SwipeDirection::Up, 3, m_swipeGestureReleasedY.get(), [this](qreal cb) {
0837         if (grid().height() > 1) {
0838             m_currentDesktopOffset.setY(cb);
0839             Q_EMIT currentChanging(current(), m_currentDesktopOffset);
0840         }
0841     });
0842     input()->registerTouchscreenSwipeShortcut(SwipeDirection::Left, 3, m_swipeGestureReleasedX.get(), left);
0843     input()->registerTouchscreenSwipeShortcut(SwipeDirection::Right, 3, m_swipeGestureReleasedX.get(), right);
0844 
0845     // axis events
0846     input()->registerAxisShortcut(Qt::ControlModifier | Qt::AltModifier, PointerAxisDown,
0847                                   findChild<QAction *>(QStringLiteral("Switch to Next Desktop")));
0848     input()->registerAxisShortcut(Qt::ControlModifier | Qt::AltModifier, PointerAxisUp,
0849                                   findChild<QAction *>(QStringLiteral("Switch to Previous Desktop")));
0850 }
0851 
0852 void VirtualDesktopManager::gestureReleasedY()
0853 {
0854     // Note that if desktop wrapping is disabled and there's no desktop above or below,
0855     // above() and below() will return the current desktop.
0856     VirtualDesktop *target = m_current;
0857     if (m_currentDesktopOffset.y() <= -GESTURE_SWITCH_THRESHOLD) {
0858         target = above(m_current, isNavigationWrappingAround());
0859     } else if (m_currentDesktopOffset.y() >= GESTURE_SWITCH_THRESHOLD) {
0860         target = below(m_current, isNavigationWrappingAround());
0861     }
0862 
0863     // If the current desktop has not changed, consider that the gesture has been canceled.
0864     if (m_current != target) {
0865         setCurrent(target);
0866     } else {
0867         Q_EMIT currentChangingCancelled();
0868     }
0869     m_currentDesktopOffset = QPointF(0, 0);
0870 }
0871 
0872 void VirtualDesktopManager::gestureReleasedX()
0873 {
0874     // Note that if desktop wrapping is disabled and there's no desktop to left or right,
0875     // toLeft() and toRight() will return the current desktop.
0876     VirtualDesktop *target = m_current;
0877     if (m_currentDesktopOffset.x() <= -GESTURE_SWITCH_THRESHOLD) {
0878         target = toLeft(m_current, isNavigationWrappingAround());
0879     } else if (m_currentDesktopOffset.x() >= GESTURE_SWITCH_THRESHOLD) {
0880         target = toRight(m_current, isNavigationWrappingAround());
0881     }
0882 
0883     // If the current desktop has not changed, consider that the gesture has been canceled.
0884     if (m_current != target) {
0885         setCurrent(target);
0886     } else {
0887         Q_EMIT currentChangingCancelled();
0888     }
0889     m_currentDesktopOffset = QPointF(0, 0);
0890 }
0891 
0892 void VirtualDesktopManager::initSwitchToShortcuts()
0893 {
0894     const QString toDesktop = QStringLiteral("Switch to Desktop %1");
0895     const KLocalizedString toDesktopLabel = ki18n("Switch to Desktop %1");
0896     addAction(toDesktop, toDesktopLabel, 1, QKeySequence(Qt::CTRL | Qt::Key_F1), &VirtualDesktopManager::slotSwitchTo);
0897     addAction(toDesktop, toDesktopLabel, 2, QKeySequence(Qt::CTRL | Qt::Key_F2), &VirtualDesktopManager::slotSwitchTo);
0898     addAction(toDesktop, toDesktopLabel, 3, QKeySequence(Qt::CTRL | Qt::Key_F3), &VirtualDesktopManager::slotSwitchTo);
0899     addAction(toDesktop, toDesktopLabel, 4, QKeySequence(Qt::CTRL | Qt::Key_F4), &VirtualDesktopManager::slotSwitchTo);
0900 
0901     for (uint i = 5; i <= maximum(); ++i) {
0902         addAction(toDesktop, toDesktopLabel, i, QKeySequence(), &VirtualDesktopManager::slotSwitchTo);
0903     }
0904 }
0905 
0906 QAction *VirtualDesktopManager::addAction(const QString &name, const KLocalizedString &label, uint value, const QKeySequence &key, void (VirtualDesktopManager::*slot)())
0907 {
0908     QAction *a = new QAction(this);
0909     a->setProperty("componentName", QStringLiteral("kwin"));
0910     a->setObjectName(name.arg(value));
0911     a->setText(label.subs(value).toString());
0912     a->setData(value);
0913     KGlobalAccel::setGlobalShortcut(a, key);
0914     connect(a, &QAction::triggered, this, slot);
0915     return a;
0916 }
0917 
0918 QAction *VirtualDesktopManager::addAction(const QString &name, const QString &label, const QKeySequence &key, void (VirtualDesktopManager::*slot)())
0919 {
0920     QAction *a = new QAction(this);
0921     a->setProperty("componentName", QStringLiteral("kwin"));
0922     a->setObjectName(name);
0923     a->setText(label);
0924     KGlobalAccel::setGlobalShortcut(a, key);
0925     connect(a, &QAction::triggered, this, slot);
0926     return a;
0927 }
0928 
0929 void VirtualDesktopManager::slotSwitchTo()
0930 {
0931     QAction *act = qobject_cast<QAction *>(sender());
0932     if (!act) {
0933         return;
0934     }
0935     bool ok = false;
0936     const uint i = act->data().toUInt(&ok);
0937     if (!ok) {
0938         return;
0939     }
0940     setCurrent(i);
0941 }
0942 
0943 void VirtualDesktopManager::setNavigationWrappingAround(bool enabled)
0944 {
0945     if (enabled == m_navigationWrapsAround) {
0946         return;
0947     }
0948     m_navigationWrapsAround = enabled;
0949     Q_EMIT navigationWrappingAroundChanged();
0950 }
0951 
0952 void VirtualDesktopManager::slotDown()
0953 {
0954     moveTo(Direction::Down, isNavigationWrappingAround());
0955 }
0956 
0957 void VirtualDesktopManager::slotLeft()
0958 {
0959     moveTo(Direction::Left, isNavigationWrappingAround());
0960 }
0961 
0962 void VirtualDesktopManager::slotPrevious()
0963 {
0964     moveTo(Direction::Previous, isNavigationWrappingAround());
0965 }
0966 
0967 void VirtualDesktopManager::slotNext()
0968 {
0969     moveTo(Direction::Next, isNavigationWrappingAround());
0970 }
0971 
0972 void VirtualDesktopManager::slotRight()
0973 {
0974     moveTo(Direction::Right, isNavigationWrappingAround());
0975 }
0976 
0977 void VirtualDesktopManager::slotUp()
0978 {
0979     moveTo(Direction::Up, isNavigationWrappingAround());
0980 }
0981 
0982 } // KWin