File indexing completed on 2024-04-28 09:28:13
0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0002 // SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org> 0003 0004 #include "devicemodel.h" 0005 0006 #include "org.freedesktop.DBus.Properties.h" 0007 #include "org.kde.kded.smart.Device.h" 0008 0009 // TODO: look into refactoring the dbus logic out maybe. 0010 // It could probably sit in a different object that controls the Model, that way modelling 0011 // would be more separate from the actual data access. 0012 DeviceModel::DeviceModel(QObject *parent) 0013 : QAbstractListModel(parent) 0014 { 0015 KDBusObjectManagerServer::registerTypes(); 0016 0017 auto watcher = new QDBusServiceWatcher(QStringLiteral("org.kde.kded6"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); 0018 connect(watcher, 0019 &QDBusServiceWatcher::serviceOwnerChanged, 0020 this, 0021 [this](const QString & /*service*/, const QString & /*oldOwner*/, const QString &newOwner) { 0022 if (!newOwner.isEmpty()) { // this is a registration even not a loss event 0023 reload(); 0024 } else { 0025 reset(); 0026 } 0027 }); 0028 0029 reload(); 0030 } 0031 0032 QHash<int, QByteArray> DeviceModel::roleNames() const 0033 { 0034 return m_roles; 0035 } 0036 0037 int DeviceModel::rowCount(const QModelIndex &parent) const 0038 { 0039 Q_UNUSED(parent); // this is a flat list we decidedly don't care about the parent 0040 return m_objects.count(); 0041 } 0042 0043 QVariant DeviceModel::data(const QModelIndex &index, int role) const 0044 { 0045 if (!hasIndex(index.row(), index.column())) { 0046 return QVariant(); 0047 } 0048 QObject *obj = m_objects.at(index.row()); 0049 if (role == ObjectRole) { 0050 return QVariant::fromValue(obj); 0051 } 0052 const QByteArray prop = m_objectPoperties.value(role); 0053 if (prop.isEmpty()) { 0054 return QVariant(); 0055 } 0056 return obj->property(prop.data()); 0057 } 0058 0059 bool DeviceModel::setData(const QModelIndex &index, const QVariant &value, int role) 0060 { 0061 if (!hasIndex(index.row(), index.column())) { 0062 return false; 0063 } 0064 QObject *obj = m_objects.at(index.row()); 0065 if (role == ObjectRole) { 0066 return false; // cannot set object! 0067 } 0068 const QByteArray prop = m_objectPoperties.value(role); 0069 if (prop.isEmpty()) { 0070 return false; 0071 } 0072 return obj->setProperty(prop.data(), value); 0073 } 0074 0075 int DeviceModel::role(const QByteArray &roleName) const 0076 { 0077 return m_roles.key(roleName, -1); 0078 } 0079 0080 bool DeviceModel::valid() const 0081 { 0082 return m_iface != nullptr; 0083 } 0084 0085 bool DeviceModel::waiting() const 0086 { 0087 return m_getManagedObjectsWatcher != nullptr; 0088 } 0089 0090 // Event filter for runtime QObjects properties changing. 0091 class RuntimePropertyChangeFilter : public QObject 0092 { 0093 Q_OBJECT 0094 public: 0095 RuntimePropertyChangeFilter(OrgFreedesktopDBusPropertiesInterface *parent) 0096 : QObject(parent) 0097 , m_dbusObject(parent) 0098 { 0099 } 0100 0101 protected: 0102 bool eventFilter(QObject *obj, QEvent *event) override 0103 { 0104 if (event->type() == QEvent::DynamicPropertyChange) { 0105 auto change = static_cast<QDynamicPropertyChangeEvent *>(event); 0106 const auto name = change->propertyName(); 0107 const auto value = m_dbusObject->property(name.data()); 0108 // WARNING: should we want to rely on the actual interfaces a property relies on 0109 // that needs implementing first. On addObject we ignore the interface and so we 0110 // use a dummy interface here. 0111 m_dbusObject->Set(QStringLiteral("org.kde.kded.smart.Device"), QLatin1String(name), QDBusVariant(value)); 0112 } 0113 return QObject::eventFilter(obj, event); 0114 } 0115 0116 private: 0117 OrgFreedesktopDBusPropertiesInterface *const m_dbusObject; 0118 }; 0119 0120 void DeviceModel::addObject(const QDBusObjectPath &dbusPath, const KDBusObjectManagerInterfacePropertiesMap &interfacePropertyMap) 0121 { 0122 const QString path = dbusPath.path(); 0123 0124 int newIndex = 0; 0125 for (auto it = m_objects.cbegin(); it != m_objects.cend(); ++it) { 0126 if ((*it)->objectName() == path) { 0127 return; // already tracked 0128 } 0129 ++newIndex; 0130 } 0131 0132 beginInsertRows(QModelIndex(), newIndex, newIndex); 0133 0134 // QDBus doesn't manage to map notifiable properties for its generated interface classes 0135 // so it brings literally nothing to the table for our Device class. 0136 // Use QObjects with dynamic properties instead to model the remote objects. 0137 // Property changes are abstracted via the ListModel anyway. 0138 auto obj = new OrgFreedesktopDBusPropertiesInterface(QStringLiteral("org.kde.kded6"), path, QDBusConnection::sessionBus(), this); 0139 m_objects << obj; 0140 obj->setObjectName(path); 0141 // Don't care about interfaces, iterate the values i.e. propertymap 0142 for (const auto &propertyMap : interfacePropertyMap) { 0143 for (auto propertyIt = propertyMap.cbegin(); propertyIt != propertyMap.cend(); ++propertyIt) { 0144 obj->setProperty(qPrintable(propertyIt.key()), propertyIt.value()); 0145 } 0146 } 0147 obj->installEventFilter(new RuntimePropertyChangeFilter(obj)); 0148 0149 connect(obj, 0150 &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, 0151 this, 0152 [this, obj](const QString & /*interface*/, const QVariantMap &properties, const QStringList & /*invalidated*/) { 0153 for (auto it = properties.cbegin(); it != properties.cend(); ++it) { 0154 obj->setProperty(qPrintable(it.key()), it.value()); 0155 0156 // Technically we need an event filter to monitor dynamic prop changes, 0157 // but since this is the only place they can change we'll take a shortcut 0158 // and notify the views immediately. 0159 const int role = m_objectPoperties.key(it.key().toLatin1(), -1); 0160 Q_ASSERT(role != -1); 0161 const int index = m_objects.indexOf(obj); 0162 Q_ASSERT(index != -1); 0163 Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {role}); 0164 } 0165 }); 0166 0167 if (m_roles.isEmpty()) { 0168 initRoleNames(obj); 0169 } 0170 0171 endInsertRows(); 0172 } 0173 0174 void DeviceModel::removeObject(const QDBusObjectPath &dbusPath) 0175 { 0176 const QString path = dbusPath.path(); 0177 auto it = std::find_if(m_objects.begin(), m_objects.end(), [path](const QObject *o) { 0178 return o->objectName() == path; 0179 }); 0180 if (it == m_objects.end()) { 0181 return; // not tracked 0182 } 0183 auto index = std::distance(m_objects.begin(), it); 0184 beginRemoveRows(QModelIndex(), index, index); 0185 (*it)->deleteLater(); 0186 m_objects.erase(it); 0187 endRemoveRows(); 0188 } 0189 0190 void DeviceModel::initRoleNames(QObject *object) 0191 { 0192 m_roles[ObjectRole] = QByteArrayLiteral("object"); 0193 0194 int maxEnumValue = ObjectRole; 0195 Q_ASSERT(maxEnumValue != -1); 0196 auto mo = *object->metaObject(); 0197 for (int i = 0; i < mo.propertyCount(); ++i) { 0198 QMetaProperty property = mo.property(i); 0199 QString name = QLatin1String(property.name()); 0200 m_roles[++maxEnumValue] = name.toLatin1(); 0201 m_objectPoperties.insert(maxEnumValue, property.name()); 0202 if (!property.hasNotifySignal()) { 0203 continue; 0204 } 0205 m_signalIndexToProperties.insert(property.notifySignalIndex(), maxEnumValue); 0206 } 0207 for (const auto &dynProperty : object->dynamicPropertyNames()) { 0208 m_roles[++maxEnumValue] = dynProperty; 0209 m_objectPoperties.insert(maxEnumValue, dynProperty); 0210 } 0211 } 0212 0213 void DeviceModel::reset() 0214 { 0215 beginResetModel(); 0216 0217 qDeleteAll(m_objects); 0218 m_objects.clear(); 0219 0220 if (m_iface) { 0221 m_iface->disconnect(this); 0222 m_iface->deleteLater(); 0223 m_iface = nullptr; 0224 Q_EMIT validChanged(); 0225 } 0226 0227 if (m_getManagedObjectsWatcher) { 0228 m_getManagedObjectsWatcher->deleteLater(); 0229 m_getManagedObjectsWatcher = nullptr; 0230 Q_EMIT waitingChanged(); 0231 } 0232 0233 endResetModel(); 0234 } 0235 0236 void DeviceModel::reload() 0237 { 0238 reset(); 0239 0240 m_iface = new OrgFreedesktopDBusObjectManagerInterface(QStringLiteral("org.kde.kded6"), 0241 QStringLiteral("/modules/smart/devices"), 0242 QDBusConnection::sessionBus(), 0243 this); 0244 connect(m_iface, &OrgFreedesktopDBusObjectManagerInterface::InterfacesAdded, this, &DeviceModel::addObject); 0245 connect(m_iface, &OrgFreedesktopDBusObjectManagerInterface::InterfacesRemoved, this, &DeviceModel::removeObject); 0246 0247 Q_EMIT validChanged(); 0248 0249 // Load existing objects. 0250 if (m_getManagedObjectsWatcher) { 0251 // Last reload didn't finish before this one, so throw away the last watcher 0252 m_getManagedObjectsWatcher->deleteLater(); 0253 } 0254 m_getManagedObjectsWatcher = new QDBusPendingCallWatcher(m_iface->GetManagedObjects(), this); 0255 Q_EMIT waitingChanged(); 0256 connect(m_getManagedObjectsWatcher, &QDBusPendingCallWatcher::finished, this, [this] { 0257 QDBusPendingReply<KDBusObjectManagerObjectPathInterfacePropertiesMap> call = *m_getManagedObjectsWatcher; 0258 auto map = call.value(); 0259 for (auto it = map.cbegin(); it != map.cend(); ++it) { 0260 addObject(it.key(), it.value()); 0261 } 0262 m_getManagedObjectsWatcher->deleteLater(); 0263 m_getManagedObjectsWatcher = nullptr; 0264 Q_EMIT waitingChanged(); 0265 }); 0266 } 0267 0268 #include "devicemodel.moc" 0269 0270 #include "moc_devicemodel.cpp"