File indexing completed on 2024-04-14 15:39:43

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("org.kde.kded5", 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);
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, 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);
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("org.kde.kded.smart.Device", 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("org.kde.kded5", 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(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("org.kde.kded5", "/modules/smart/devices", QDBusConnection::sessionBus(), this);
0241     connect(m_iface, &OrgFreedesktopDBusObjectManagerInterface::InterfacesAdded, this, &DeviceModel::addObject);
0242     connect(m_iface, &OrgFreedesktopDBusObjectManagerInterface::InterfacesRemoved, this, &DeviceModel::removeObject);
0243 
0244     Q_EMIT validChanged();
0245 
0246     // Load existing objects.
0247     if (m_getManagedObjectsWatcher) {
0248         // Last reload didn't finish before this one, so throw away the last watcher
0249         m_getManagedObjectsWatcher->deleteLater();
0250     }
0251     m_getManagedObjectsWatcher = new QDBusPendingCallWatcher(m_iface->GetManagedObjects(), this);
0252     Q_EMIT waitingChanged();
0253     connect(m_getManagedObjectsWatcher, &QDBusPendingCallWatcher::finished, this, [this] {
0254         QDBusPendingReply<KDBusObjectManagerObjectPathInterfacePropertiesMap> call = *m_getManagedObjectsWatcher;
0255         auto map = call.value();
0256         for (auto it = map.cbegin(); it != map.cend(); ++it) {
0257             addObject(it.key(), it.value());
0258         }
0259         m_getManagedObjectsWatcher->deleteLater();
0260         m_getManagedObjectsWatcher = nullptr;
0261         Q_EMIT waitingChanged();
0262     });
0263 }
0264 
0265 #include "devicemodel.moc"