File indexing completed on 2022-11-23 12:18:32

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 "dbusobjectmanagerserver.h"
0005 
0006 #include <QDBusConnection>
0007 #include <QDBusConnectionInterface>
0008 #include <QDBusMetaType>
0009 
0010 #include "kded_debug.h"
0011 
0012 // QtDBus doesn't implement PropertiesChanged for some reason.
0013 // This is meant to be childed' on an object to track that
0014 // objects' property notifications.
0015 class KDBusPropertiesChangedAdaptor : public QObject
0016 {
0017     Q_OBJECT
0018 public:
0019     KDBusPropertiesChangedAdaptor(const QString &objectPath, QObject *adaptee)
0020         : QObject(adaptee)
0021         , m_objectPath(objectPath)
0022     {
0023         auto mo = adaptee->metaObject();
0024         for (int i = 0; i < mo->propertyCount(); ++i) {
0025             QMetaProperty property = mo->property(i);
0026             if (!property.hasNotifySignal()) {
0027                 continue;
0028             }
0029             const int fooIndex = metaObject()->indexOfMethod("onPropertyChanged()"); // of adaptor
0030             Q_ASSERT(fooIndex != -1);
0031             connect(adaptee, property.notifySignal(), this, metaObject()->method(fooIndex));
0032         }
0033     }
0034 
0035 private Q_SLOTS:
0036     void onPropertyChanged()
0037     {
0038         if (!sender() || senderSignalIndex() == -1) {
0039             return;
0040         }
0041         auto mo = sender()->metaObject();
0042         for (int i = 0; i < mo->propertyCount(); ++i) {
0043             QMetaProperty property = mo->property(i);
0044             if (!property.hasNotifySignal()) {
0045                 continue;
0046             }
0047             if (property.notifySignalIndex() != senderSignalIndex()) {
0048                 continue;
0049             }
0050             const int ciid = mo->indexOfClassInfo("D-Bus Interface");
0051             if (ciid == -1) {
0052                 continue;
0053             }
0054             QDBusMessage signal = QDBusMessage::createSignal(m_objectPath, //
0055                                                              QStringLiteral("org.freedesktop.DBus.Properties"),
0056                                                              QStringLiteral("PropertiesChanged"));
0057             signal << mo->classInfo(ciid).value();
0058             signal << QVariantMap({{QString::fromLatin1(property.name()), property.read(sender())}}); // changed properties DICT<STRING,VARIANT>
0059             signal << QStringList(); // invalidated property names no clue what invalidation means
0060             QDBusConnection::sessionBus().send(signal);
0061         }
0062     }
0063 
0064 private:
0065     const QString m_objectPath;
0066 };
0067 
0068 KDBusObjectManagerServer::KDBusObjectManagerServer(QObject *parent)
0069     : QObject(parent)
0070 {
0071     registerTypes();
0072     QDBusConnection connection = QDBusConnection::sessionBus();
0073     if (!connection.registerObject(m_path,
0074                                    this,
0075                                    QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals | QDBusConnection::ExportAllProperties
0076                                        | QDBusConnection::ExportAllInvokables | QDBusConnection::ExportAllContents | QDBusConnection::ExportAdaptors)) {
0077         qCDebug(KDED) << "failed to register" << m_path;
0078         return;
0079     }
0080 }
0081 
0082 bool KDBusObjectManagerServer::serve(QObject *object)
0083 {
0084     m_managedObjects << object;
0085     Q_EMIT InterfacesAdded(path(object), interfacePropertiesMap(object));
0086     connect(object, &QObject::destroyed, this, [this](QObject *obj) {
0087         unserve(obj);
0088     }); // auto-unserve
0089     const QString dbusPath = path(object).path();
0090     new KDBusPropertiesChangedAdaptor(dbusPath, object);
0091     QDBusConnection connection = QDBusConnection::sessionBus();
0092     return connection.registerObject(dbusPath,
0093                                      object,
0094                                      QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals | QDBusConnection::ExportAllProperties
0095                                          | QDBusConnection::ExportAllInvokables | QDBusConnection::ExportAllContents | QDBusConnection::ExportAdaptors);
0096 }
0097 
0098 void KDBusObjectManagerServer::unserve(QObject *object)
0099 {
0100     const QStringList interfaces = metaObjectsFor(object).keys();
0101     Q_EMIT InterfacesRemoved(path(object), {interfaces});
0102     QDBusConnection::sessionBus().unregisterObject(path(object).path());
0103     m_managedObjects.removeAll(object);
0104 }
0105 
0106 void KDBusObjectManagerServer::registerTypes()
0107 {
0108     static bool registered = false;
0109     if (registered) {
0110         return;
0111     }
0112     registered = true;
0113 
0114     qDBusRegisterMetaType<KDBusObjectManagerPropertiesMap>();
0115     qDBusRegisterMetaType<KDBusObjectManagerInterfacePropertiesMap>();
0116     qDBusRegisterMetaType<KDBusObjectManagerObjectPathInterfacePropertiesMap>();
0117 
0118     // For some reason we need to manually map the names. No idea why it works
0119     // for the maps though.
0120     qRegisterMetaType<KDBusObjectManagerInterfaceList>("KDBusObjectManagerInterfaceList");
0121     qDBusRegisterMetaType<KDBusObjectManagerInterfaceList>();
0122 }
0123 
0124 KDBusObjectManagerObjectPathInterfacePropertiesMap KDBusObjectManagerServer::GetManagedObjects()
0125 {
0126     QMap<QDBusObjectPath, QMap<QString, QMap<QString, QVariant>>> map;
0127     for (const auto *object : m_managedObjects) {
0128         const QDBusObjectPath dbusPath = path(object);
0129         if (dbusPath.path().isEmpty()) {
0130             qCDebug(KDED) << "Invalid dbus path for" << object->objectName();
0131             continue;
0132         }
0133         map[dbusPath] = interfacePropertiesMap(object);
0134     }
0135     return map;
0136 }
0137 
0138 QDBusObjectPath KDBusObjectManagerServer::path(const QObject *object)
0139 {
0140     const QString path = m_path + "/" + object->objectName();
0141 
0142     qCDebug(KDED) << "path for " << object->objectName() << object->metaObject()->className() << ":" << path;
0143     return QDBusObjectPath(path);
0144 }
0145 
0146 KDBusObjectManagerInterfacePropertiesMap KDBusObjectManagerServer::interfacePropertiesMap(const QObject *child)
0147 {
0148     // Technically this is duplicating qdbus' internal GetAll implementation, so we could actually
0149     // shoot dbus queries to ourself and have Qt figure things out. All we need to do is
0150     // compose it into the dbuspath tree we manage.
0151 
0152     QMap<QString, QVariantMap> interfaceMap;
0153 
0154     const auto metaObjectHash = metaObjectsFor(child);
0155     for (auto it = metaObjectHash.cbegin(); it != metaObjectHash.cend(); ++it) {
0156         const QString interface = it.key();
0157         const QMetaObject *mo = it.value();
0158         QVariantMap properties;
0159         for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) {
0160             auto property = mo->property(i);
0161             properties[property.name()] = property.read(child);
0162         }
0163         interfaceMap[interface] = properties;
0164     }
0165 
0166     return interfaceMap;
0167 }
0168 
0169 KDBusObjectManagerServer::InterfaceMetaObjectHash KDBusObjectManagerServer::metaObjectsFor(const QObject *object)
0170 {
0171     InterfaceMetaObjectHash map;
0172     for (auto mo = object->metaObject(); mo; mo = mo->superClass()) {
0173         if (strcmp(mo->className(), "QObject") == 0) {
0174             continue;
0175         }
0176 
0177         int ciid = mo->indexOfClassInfo("D-Bus Interface");
0178         if (ciid == -1) {
0179             qCWarning(KDED) << mo->className() << "defines no interface";
0180             continue;
0181         }
0182 
0183         map[QString::fromLatin1(mo->classInfo(ciid).value())] = mo;
0184     }
0185     return map;
0186 }
0187 
0188 #include "dbusobjectmanagerserver.moc"