File indexing completed on 2024-11-10 04:56:48
0001 /* 0002 SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org> 0003 SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "desktopsmodel.h" 0009 0010 #include <cmath> 0011 0012 #include <KLocalizedString> 0013 0014 #include <QDBusArgument> 0015 #include <QDBusConnection> 0016 #include <QDBusMessage> 0017 #include <QDBusMetaType> 0018 #include <QDBusPendingCall> 0019 #include <QDBusPendingCallWatcher> 0020 #include <QDBusPendingReply> 0021 #include <QDBusServiceWatcher> 0022 #include <QDBusVariant> 0023 #include <QMetaEnum> 0024 #include <QRegularExpression> 0025 #include <QUuid> 0026 0027 namespace KWin 0028 { 0029 0030 static const QString s_serviceName(QStringLiteral("org.kde.KWin")); 0031 static const QString s_virtualDesktopsInterface(QStringLiteral("org.kde.KWin.VirtualDesktopManager")); 0032 static const QString s_virtDesktopsPath(QStringLiteral("/VirtualDesktopManager")); 0033 static const QString s_fdoPropertiesInterface(QStringLiteral("org.freedesktop.DBus.Properties")); 0034 0035 DesktopsModel::DesktopsModel(QObject *parent) 0036 : QAbstractListModel(parent) 0037 , m_userModified(false) 0038 , m_serverModified(false) 0039 , m_serverSideRows(-1) 0040 , m_rows(-1) 0041 { 0042 qDBusRegisterMetaType<KWin::DBusDesktopDataStruct>(); 0043 qDBusRegisterMetaType<KWin::DBusDesktopDataVector>(); 0044 0045 m_serviceWatcher = new QDBusServiceWatcher(s_serviceName, 0046 QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange); 0047 0048 QObject::connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered, 0049 this, [this]() { 0050 reset(); 0051 }); 0052 0053 QObject::connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, 0054 this, [this]() { 0055 QDBusConnection::sessionBus().disconnect( 0056 s_serviceName, 0057 s_virtDesktopsPath, 0058 s_virtualDesktopsInterface, 0059 QStringLiteral("desktopCreated"), 0060 this, 0061 SLOT(desktopCreated(QString, KWin::DBusDesktopDataStruct))); 0062 0063 QDBusConnection::sessionBus().disconnect( 0064 s_serviceName, 0065 s_virtDesktopsPath, 0066 s_virtualDesktopsInterface, 0067 QStringLiteral("desktopRemoved"), 0068 this, 0069 SLOT(desktopRemoved(QString))); 0070 0071 QDBusConnection::sessionBus().disconnect( 0072 s_serviceName, 0073 s_virtDesktopsPath, 0074 s_virtualDesktopsInterface, 0075 QStringLiteral("desktopDataChanged"), 0076 this, 0077 SLOT(desktopDataChanged(QString, KWin::DBusDesktopDataStruct))); 0078 0079 QDBusConnection::sessionBus().disconnect( 0080 s_serviceName, 0081 s_virtDesktopsPath, 0082 s_virtualDesktopsInterface, 0083 QStringLiteral("rowsChanged"), 0084 this, 0085 SLOT(desktopRowsChanged(uint))); 0086 }); 0087 0088 reset(); 0089 } 0090 0091 DesktopsModel::~DesktopsModel() 0092 { 0093 } 0094 0095 QHash<int, QByteArray> DesktopsModel::roleNames() const 0096 { 0097 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); 0098 0099 QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); 0100 0101 for (int i = 0; i < e.keyCount(); ++i) { 0102 roles.insert(e.value(i), e.key(i)); 0103 } 0104 0105 return roles; 0106 } 0107 0108 QVariant DesktopsModel::data(const QModelIndex &index, int role) const 0109 { 0110 if (!index.isValid() || index.row() < 0 || index.row() > (m_desktops.count() - 1)) { 0111 return QVariant(); 0112 } 0113 0114 if (role == Qt::DisplayRole) { 0115 return m_names.value(m_desktops.at(index.row())); 0116 } else if (role == Id) { 0117 return m_desktops.at(index.row()); 0118 } else if (role == DesktopRow) { 0119 const int rows = std::max(m_rows, 1); 0120 const int perRow = std::ceil((qreal)m_desktops.count() / (qreal)rows); 0121 0122 return (index.row() / perRow) + 1; 0123 0124 } else if (role == IsDefault) { 0125 // According to defaults(), first desktop is default 0126 return index.row() == 0; 0127 } 0128 0129 return QVariant(); 0130 } 0131 0132 int DesktopsModel::rowCount(const QModelIndex &parent) const 0133 { 0134 if (parent.isValid()) { 0135 return 0; 0136 } 0137 0138 return m_desktops.count(); 0139 } 0140 0141 bool DesktopsModel::ready() const 0142 { 0143 return !m_desktops.isEmpty(); 0144 } 0145 0146 QString DesktopsModel::error() const 0147 { 0148 return m_error; 0149 } 0150 0151 bool DesktopsModel::userModified() const 0152 { 0153 return m_userModified; 0154 } 0155 0156 bool DesktopsModel::serverModified() const 0157 { 0158 return m_serverModified; 0159 } 0160 0161 int DesktopsModel::rows() const 0162 { 0163 return m_rows; 0164 } 0165 0166 void DesktopsModel::setRows(int rows) 0167 { 0168 if (!ready()) { 0169 return; 0170 } 0171 0172 if (m_rows != rows) { 0173 m_rows = rows; 0174 0175 Q_EMIT rowsChanged(); 0176 Q_EMIT dataChanged(index(0, 0), index(m_desktops.count() - 1, 0), QList<int>{DesktopRow}); 0177 0178 updateModifiedState(); 0179 } 0180 } 0181 0182 int DesktopsModel::desktopCount() const 0183 { 0184 return rowCount(); 0185 } 0186 0187 QString DesktopsModel::createDesktopName() const 0188 { 0189 const QStringList nameValues = m_names.values(); 0190 for (int index = 1;; ++index) { 0191 const QString desktopName = i18ncp("A numbered name for virtual desktops", "Desktop %1", "Desktop %1", index); 0192 if (!nameValues.contains(desktopName)) { 0193 return desktopName; 0194 } 0195 } 0196 } 0197 0198 void DesktopsModel::createDesktop() 0199 { 0200 if (!ready()) { 0201 return; 0202 } 0203 0204 beginInsertRows(QModelIndex(), m_desktops.count(), m_desktops.count()); 0205 0206 const QString &dummyId = QUuid::createUuid().toString(QUuid::WithoutBraces); 0207 0208 m_desktops.append(dummyId); 0209 m_names[dummyId] = createDesktopName(); 0210 0211 endInsertRows(); 0212 Q_EMIT desktopCountChanged(); 0213 0214 updateModifiedState(); 0215 } 0216 0217 void DesktopsModel::removeDesktop(const QString &id) 0218 { 0219 if (!ready() || !m_desktops.contains(id)) { 0220 return; 0221 } 0222 0223 const int desktopIndex = m_desktops.indexOf(id); 0224 0225 beginRemoveRows(QModelIndex(), desktopIndex, desktopIndex); 0226 0227 m_desktops.removeAt(desktopIndex); 0228 m_names.remove(id); 0229 0230 endRemoveRows(); 0231 Q_EMIT desktopCountChanged(); 0232 0233 updateModifiedState(); 0234 } 0235 0236 void DesktopsModel::setDesktopName(const QString &id, const QString &name) 0237 { 0238 if (!ready() || !m_desktops.contains(id)) { 0239 return; 0240 } 0241 0242 m_names[id] = name; 0243 0244 const QModelIndex &idx = index(m_desktops.indexOf(id), 0); 0245 0246 Q_EMIT dataChanged(idx, idx, QList<int>{Qt::DisplayRole}); 0247 0248 updateModifiedState(); 0249 } 0250 0251 void DesktopsModel::syncWithServer() 0252 { 0253 auto callFinished = [this](QDBusPendingCallWatcher *call) { 0254 QDBusPendingReply<void> reply = *call; 0255 0256 if (reply.isError()) { 0257 handleCallError(); 0258 } 0259 0260 --m_pendingCalls; 0261 0262 call->deleteLater(); 0263 }; 0264 0265 if (m_desktops.count() > m_serverSideDesktops.count()) { 0266 auto call = QDBusMessage::createMethodCall( 0267 s_serviceName, 0268 s_virtDesktopsPath, 0269 s_virtualDesktopsInterface, 0270 QStringLiteral("createDesktop")); 0271 0272 const int newIndex = m_serverSideDesktops.count(); 0273 0274 call.setArguments({(uint)newIndex, m_names.value(m_desktops.at(newIndex))}); 0275 0276 ++m_pendingCalls; 0277 QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); 0278 0279 const auto *watcher = new QDBusPendingCallWatcher(pending, this); 0280 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); 0281 0282 return; // The change-handling slot will call syncWithServer() again, 0283 // until everything is in sync. 0284 } 0285 0286 if (m_desktops.count() < m_serverSideDesktops.count()) { 0287 QStringListIterator i(m_serverSideDesktops); 0288 0289 i.toBack(); 0290 0291 while (i.hasPrevious()) { 0292 const QString &previous = i.previous(); 0293 0294 if (!m_desktops.contains(previous)) { 0295 auto call = QDBusMessage::createMethodCall( 0296 s_serviceName, 0297 s_virtDesktopsPath, 0298 s_virtualDesktopsInterface, 0299 QStringLiteral("removeDesktop")); 0300 0301 call.setArguments({previous}); 0302 0303 ++m_pendingCalls; 0304 QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); 0305 0306 const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); 0307 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); 0308 0309 return; // The change-handling slot will call syncWithServer() again, 0310 // until everything is in sync. 0311 } 0312 } 0313 } 0314 0315 // Sync ids. Replace dummy ids in the process. 0316 for (int i = 0; i < m_serverSideDesktops.count(); ++i) { 0317 const QString oldId = m_desktops.at(i); 0318 const QString &newId = m_serverSideDesktops.at(i); 0319 m_desktops[i] = newId; 0320 m_names[newId] = m_names.take(oldId); 0321 } 0322 0323 Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), QList<int>{Qt::DisplayRole}); 0324 0325 // Sync names. 0326 if (m_names != m_serverSideNames) { 0327 QHashIterator<QString, QString> i(m_names); 0328 0329 while (i.hasNext()) { 0330 i.next(); 0331 0332 if (i.value() != m_serverSideNames.value(i.key())) { 0333 auto call = QDBusMessage::createMethodCall( 0334 s_serviceName, 0335 s_virtDesktopsPath, 0336 s_virtualDesktopsInterface, 0337 QStringLiteral("setDesktopName")); 0338 0339 call.setArguments({i.key(), i.value()}); 0340 0341 ++m_pendingCalls; 0342 QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); 0343 0344 const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); 0345 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); 0346 0347 break; 0348 } 0349 } 0350 0351 return; // The change-handling slot will call syncWithServer() again, 0352 // until everything is in sync.. 0353 } 0354 0355 // Sync rows. 0356 if (m_rows != m_serverSideRows) { 0357 auto call = QDBusMessage::createMethodCall( 0358 s_serviceName, 0359 s_virtDesktopsPath, 0360 s_fdoPropertiesInterface, 0361 QStringLiteral("Set")); 0362 0363 call.setArguments({s_virtualDesktopsInterface, 0364 QStringLiteral("rows"), QVariant::fromValue(QDBusVariant(QVariant((uint)m_rows)))}); 0365 0366 ++m_pendingCalls; 0367 QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); 0368 0369 const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); 0370 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); 0371 } 0372 } 0373 0374 void DesktopsModel::reset() 0375 { 0376 auto getAllAndConnectCall = QDBusMessage::createMethodCall( 0377 s_serviceName, 0378 s_virtDesktopsPath, 0379 s_fdoPropertiesInterface, 0380 QStringLiteral("GetAll")); 0381 0382 getAllAndConnectCall.setArguments({s_virtualDesktopsInterface}); 0383 0384 QDBusConnection::sessionBus().callWithCallback( 0385 getAllAndConnectCall, 0386 this, 0387 SLOT(getAllAndConnect(QDBusMessage)), 0388 SLOT(handleCallError())); 0389 } 0390 0391 bool DesktopsModel::needsSave() const 0392 { 0393 return m_userModified; 0394 } 0395 0396 bool DesktopsModel::isDefaults() const 0397 { 0398 return m_rows == 2 && m_desktops.count() == 1; 0399 } 0400 0401 void DesktopsModel::defaults() 0402 { 0403 beginResetModel(); 0404 // default is 1 desktop with 2 rows 0405 // see kwin/virtualdesktops.cpp VirtualDesktopGrid::VirtualDesktopGrid 0406 while (m_desktops.count() > 1) { 0407 const auto desktop = m_desktops.takeLast(); 0408 m_names.remove(desktop); 0409 } 0410 setRows(2); 0411 0412 endResetModel(); 0413 0414 m_userModified = true; 0415 updateModifiedState(); 0416 } 0417 0418 void DesktopsModel::load() 0419 { 0420 beginResetModel(); 0421 m_desktops = m_serverSideDesktops; 0422 m_names = m_serverSideNames; 0423 setRows(m_serverSideRows); 0424 endResetModel(); 0425 0426 m_userModified = true; 0427 updateModifiedState(); 0428 } 0429 0430 void DesktopsModel::getAllAndConnect(const QDBusMessage &msg) 0431 { 0432 const QVariantMap &data = qdbus_cast<QVariantMap>(msg.arguments().at(0).value<QDBusArgument>()); 0433 0434 const KWin::DBusDesktopDataVector &desktops = qdbus_cast<KWin::DBusDesktopDataVector>( 0435 data.value(QStringLiteral("desktops")).value<QDBusArgument>()); 0436 0437 const int newServerSideRows = data.value(QStringLiteral("rows")).toUInt(); 0438 QStringList newServerSideDesktops; 0439 QHash<QString, QString> newServerSideNames; 0440 0441 for (const KWin::DBusDesktopDataStruct &d : desktops) { 0442 newServerSideDesktops.append(d.id); 0443 newServerSideNames[d.id] = d.name; 0444 } 0445 0446 // If the server-side state changed during a KWin restart, and the 0447 // user had made notifications, the model should notify about the 0448 // change. 0449 if (m_serverSideDesktops != newServerSideDesktops 0450 || m_serverSideNames != newServerSideNames 0451 || m_serverSideRows != newServerSideRows) { 0452 if (!m_serverSideDesktops.isEmpty() || m_userModified) { 0453 m_serverModified = true; 0454 Q_EMIT serverModifiedChanged(); 0455 } 0456 0457 m_serverSideDesktops = newServerSideDesktops; 0458 m_serverSideNames = newServerSideNames; 0459 m_serverSideRows = newServerSideRows; 0460 } 0461 0462 // For the case KWin restarts while the KCM was open: If the user had 0463 // made no modifications, just reset to the server data. E.g. perhaps 0464 // the user intentionally nuked the KWin config while it was down, so 0465 // we should follow. 0466 if (!m_userModified || m_desktops.empty()) { 0467 beginResetModel(); 0468 m_desktops = m_serverSideDesktops; 0469 m_names = m_serverSideNames; 0470 m_rows = m_serverSideRows; 0471 endResetModel(); 0472 0473 Q_EMIT rowsChanged(); 0474 } 0475 0476 Q_EMIT readyChanged(); 0477 0478 auto handleConnectionError = [this]() { 0479 m_error = i18n("There was an error connecting to the compositor."); 0480 Q_EMIT errorChanged(); 0481 }; 0482 0483 bool connected = QDBusConnection::sessionBus().connect( 0484 s_serviceName, 0485 s_virtDesktopsPath, 0486 s_virtualDesktopsInterface, 0487 QStringLiteral("desktopCreated"), 0488 this, 0489 SLOT(desktopCreated(QString, KWin::DBusDesktopDataStruct))); 0490 0491 if (!connected) { 0492 handleConnectionError(); 0493 0494 return; 0495 } 0496 0497 connected = QDBusConnection::sessionBus().connect( 0498 s_serviceName, 0499 s_virtDesktopsPath, 0500 s_virtualDesktopsInterface, 0501 QStringLiteral("desktopRemoved"), 0502 this, 0503 SLOT(desktopRemoved(QString))); 0504 0505 if (!connected) { 0506 handleConnectionError(); 0507 0508 return; 0509 } 0510 0511 connected = QDBusConnection::sessionBus().connect( 0512 s_serviceName, 0513 s_virtDesktopsPath, 0514 s_virtualDesktopsInterface, 0515 QStringLiteral("desktopDataChanged"), 0516 this, 0517 SLOT(desktopDataChanged(QString, KWin::DBusDesktopDataStruct))); 0518 0519 if (!connected) { 0520 handleConnectionError(); 0521 0522 return; 0523 } 0524 0525 connected = QDBusConnection::sessionBus().connect( 0526 s_serviceName, 0527 s_virtDesktopsPath, 0528 s_virtualDesktopsInterface, 0529 QStringLiteral("rowsChanged"), 0530 this, 0531 SLOT(desktopRowsChanged(uint))); 0532 0533 if (!connected) { 0534 handleConnectionError(); 0535 0536 return; 0537 } 0538 } 0539 0540 void DesktopsModel::desktopCreated(const QString &id, const KWin::DBusDesktopDataStruct &data) 0541 { 0542 m_serverSideDesktops.insert(data.position, id); 0543 m_serverSideNames[data.id] = data.name; 0544 0545 // If the user didn't make any changes, we can just stay in sync. 0546 if (!m_userModified) { 0547 beginInsertRows(QModelIndex(), data.position, data.position); 0548 0549 m_desktops = m_serverSideDesktops; 0550 m_names = m_serverSideNames; 0551 0552 endInsertRows(); 0553 } else { 0554 // Remove dummy data. 0555 const QString dummyId = m_desktops.at(data.position); 0556 m_desktops[data.position] = id; 0557 m_names.remove(dummyId); 0558 m_names[id] = data.name; 0559 const QModelIndex &idx = index(data.position, 0); 0560 Q_EMIT dataChanged(idx, idx, QList<int>{Id}); 0561 0562 updateModifiedState(/* server */ true); 0563 } 0564 } 0565 0566 void DesktopsModel::desktopRemoved(const QString &id) 0567 { 0568 const int desktopIndex = m_serverSideDesktops.indexOf(id); 0569 0570 m_serverSideDesktops.removeAt(desktopIndex); 0571 m_serverSideNames.remove(id); 0572 0573 // If the user didn't make any changes, we can just stay in sync. 0574 if (!m_userModified) { 0575 beginRemoveRows(QModelIndex(), desktopIndex, desktopIndex); 0576 0577 m_desktops = m_serverSideDesktops; 0578 m_names = m_serverSideNames; 0579 0580 endRemoveRows(); 0581 } else { 0582 updateModifiedState(/* server */ true); 0583 } 0584 } 0585 0586 void DesktopsModel::desktopDataChanged(const QString &id, const KWin::DBusDesktopDataStruct &data) 0587 { 0588 const int desktopIndex = m_serverSideDesktops.indexOf(id); 0589 0590 m_serverSideDesktops[desktopIndex] = id; 0591 m_serverSideNames[id] = data.name; 0592 0593 // If the user didn't make any changes, we can just stay in sync. 0594 if (!m_userModified) { 0595 m_desktops = m_serverSideDesktops; 0596 m_names = m_serverSideNames; 0597 0598 const QModelIndex &idx = index(desktopIndex, 0); 0599 0600 Q_EMIT dataChanged(idx, idx, QList<int>{Qt::DisplayRole}); 0601 } else { 0602 updateModifiedState(/* server */ true); 0603 } 0604 } 0605 0606 void DesktopsModel::desktopRowsChanged(uint rows) 0607 { 0608 // Unfortunately we sometimes get this signal from the server with an unchanged value. 0609 if ((int)rows == m_serverSideRows) { 0610 return; 0611 } 0612 0613 m_serverSideRows = rows; 0614 0615 // If the user didn't make any changes, we can just stay in sync. 0616 if (!m_userModified) { 0617 m_rows = m_serverSideRows; 0618 0619 Q_EMIT rowsChanged(); 0620 Q_EMIT dataChanged(index(0, 0), index(m_desktops.count() - 1, 0), QList<int>{DesktopRow}); 0621 } else { 0622 updateModifiedState(/* server */ true); 0623 } 0624 } 0625 0626 void DesktopsModel::updateModifiedState(bool server) 0627 { 0628 // Count is the same but contents are not: The user may have 0629 // removed and created new desktops in the UI, but there were 0630 // no changes to send to the server because number and names 0631 // have remained the same. In that case we can just clean 0632 // that up here. 0633 if (m_desktops.count() == m_serverSideDesktops.count() 0634 && m_desktops != m_serverSideDesktops) { 0635 0636 for (int i = 0; i < m_serverSideDesktops.count(); ++i) { 0637 const QString oldId = m_desktops.at(i); 0638 const QString &newId = m_serverSideDesktops.at(i); 0639 m_desktops[i] = newId; 0640 m_names[newId] = m_names.take(oldId); 0641 } 0642 0643 Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), QList<int>{Qt::DisplayRole}); 0644 } 0645 0646 if (m_desktops == m_serverSideDesktops 0647 && m_names == m_serverSideNames 0648 && m_rows == m_serverSideRows) { 0649 0650 m_userModified = false; 0651 Q_EMIT userModifiedChanged(); 0652 0653 m_serverModified = false; 0654 Q_EMIT serverModifiedChanged(); 0655 } else { 0656 if (m_pendingCalls > 0) { 0657 m_serverModified = false; 0658 Q_EMIT serverModifiedChanged(); 0659 0660 syncWithServer(); 0661 } else if (server) { 0662 m_serverModified = true; 0663 Q_EMIT serverModifiedChanged(); 0664 } else { 0665 m_userModified = true; 0666 Q_EMIT userModifiedChanged(); 0667 } 0668 } 0669 } 0670 0671 void DesktopsModel::handleCallError() 0672 { 0673 if (m_pendingCalls > 0) { 0674 0675 m_serverModified = false; 0676 Q_EMIT serverModifiedChanged(); 0677 0678 m_error = i18n("There was an error saving the settings to the compositor."); 0679 Q_EMIT errorChanged(); 0680 } else { 0681 m_error = i18n("There was an error requesting information from the compositor."); 0682 Q_EMIT errorChanged(); 0683 } 0684 } 0685 0686 } 0687 0688 #include "moc_desktopsmodel.cpp"