File indexing completed on 2024-05-19 09:25:44

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