From c88c129144f33e9a1e123a67ef71915b0c28c963 Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Fri, 13 Aug 2021 08:01:19 +0000 Subject: [PATCH] [nemo-qml-plugin-dbus] Add an auto quit feature for DBus adaptors. Contributes to JB#54789 Add an option to a acquire an event loop locker at startup and release it after a timeout. This will close the application if no windows are visible at that time. And allow additional interfaces to defined for an object by grouping multiple adaptors into an object for a path. For applications which need to support multiple interfaces but can group them on a common path this allows them to continue letting an object register the service without conflicts or race conditions between the adaptors. Applications which have to support multiple objects will also have to register themselves. --- src/plugin/declarativedbusabstractobject.cpp | 245 ++++++++++++ src/plugin/declarativedbusabstractobject.h | 120 ++++++ src/plugin/declarativedbusadaptor.cpp | 368 ++++++++----------- src/plugin/declarativedbusadaptor.h | 68 ++-- src/plugin/declarativedbusobject.cpp | 287 +++++++++++++++ src/plugin/declarativedbusobject.h | 81 ++++ src/plugin/plugin.cpp | 2 + src/plugin/plugin.pro | 7 +- 8 files changed, 922 insertions(+), 256 deletions(-) create mode 100644 src/plugin/declarativedbusabstractobject.cpp create mode 100644 src/plugin/declarativedbusabstractobject.h create mode 100644 src/plugin/declarativedbusobject.cpp create mode 100644 src/plugin/declarativedbusobject.h diff --git a/src/plugin/declarativedbusabstractobject.cpp b/src/plugin/declarativedbusabstractobject.cpp new file mode 100644 index 0000000..1f82714 --- /dev/null +++ b/src/plugin/declarativedbusabstractobject.cpp @@ -0,0 +1,245 @@ +/**************************************************************************************** +** +** Copyright (c) 2013 - 2021 Jolla Ltd. +** All rights reserved. +** +** You may use this file under the terms of the GNU Lesser General +** Public License version 2.1 as published by the Free Software Foundation +** and appearing in the file license.lgpl included in the packaging +** of this file. +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation +** and appearing in the file license.lgpl included in the packaging +** of this file. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +****************************************************************************************/ + +#include "declarativedbusabstractobject.h" + +#include "dbus.h" + +#include +#include +#include + +#include + +#include + +DeclarativeDBusAbstractObject::DeclarativeDBusAbstractObject(QObject *parent) + : QDBusVirtualObject(parent) + , m_bus(DeclarativeDBus::SessionBus) + , m_quitTimerId(-1) + , m_quitTimeout(30) + , m_quitOnTimeout(false) + , m_complete(true) +{ +} + +DeclarativeDBusAbstractObject::~DeclarativeDBusAbstractObject() +{ + QDBusConnection conn = DeclarativeDBus::connection(m_bus); + conn.unregisterObject(m_path); + + // In theory an application could have multiple AbstractObject items for different paths or + // interfaces and destroying one would unregister the whole service. + // If problems arise we could introduce refcounting for services so that + // the last service would be responsible for unregisting the service upon + // destruction. Unregisration should also take into account unregistration happening + // from C++ side. + if (!m_service.isEmpty()) { + if (!conn.unregisterService(m_service)) { + qmlInfo(this) << "Failed to unregister service " << m_service; + qmlInfo(this) << conn.lastError().message(); + } + } +} + +QString DeclarativeDBusAbstractObject::service() const +{ + return m_service; +} + +void DeclarativeDBusAbstractObject::setService(const QString &service) +{ + if (m_service != service) { + m_service = service; + emit serviceChanged(); + } +} + +QString DeclarativeDBusAbstractObject::path() const +{ + return m_path; +} + +void DeclarativeDBusAbstractObject::setPath(const QString &path) +{ + if (m_path != path) { + m_path = path; + emit pathChanged(); + } +} + +/* + The XML service description. This could be derived from the meta-object but since it's unlikely + to be needed most of the time this and type conversion is non-trivial it's not, and there is + this property instead if it is needed. +*/ + +QString DeclarativeDBusAbstractObject::xml() const +{ + return m_xml; +} + +void DeclarativeDBusAbstractObject::setXml(const QString &xml) +{ + if (m_xml != xml) { + m_xml = xml; + emit xmlChanged(); + } +} + +DeclarativeDBus::BusType DeclarativeDBusAbstractObject::bus() const +{ + return m_bus; +} + +void DeclarativeDBusAbstractObject::setBus(DeclarativeDBus::BusType bus) +{ + if (m_bus != bus) { + m_bus = bus; + emit busChanged(); + } +} + +int DeclarativeDBusAbstractObject::quitTimeout() const +{ + return m_quitTimeout; +} + +void DeclarativeDBusAbstractObject::setQuitTimeout(int timeout) +{ + if (m_quitTimeout != timeout) { + m_quitTimeout = timeout; + + emit quitTimeoutChanged(); + } +} + + +bool DeclarativeDBusAbstractObject::quitOnTimeout() const +{ + return m_quitOnTimeout; +} + +void DeclarativeDBusAbstractObject::setQuitOnTimeout(bool quit) +{ + if (m_quitOnTimeout != quit && (!m_complete || !quit)) { + m_quitOnTimeout = quit; + + if (m_quitTimerId != -1) { + killTimer(m_quitTimerId); + m_quitTimerId = -1; + } + + emit quitOnTimeoutChanged(); + + m_quitLocker.reset(); + } +} + +void DeclarativeDBusAbstractObject::classBegin() +{ + m_complete = false; +} + +void DeclarativeDBusAbstractObject::componentComplete() +{ + m_complete = true; + + QDBusConnection conn = DeclarativeDBus::connection(m_bus); + + // It is still valid to publish an object on the bus without first registering a service name, + // a remote process would have to connect directly to the DBus address. + if (!m_path.isEmpty() && !conn.registerVirtualObject(m_path, this)) { + qmlInfo(this) << "Failed to register object " << m_path; + qmlInfo(this) << conn.lastError().message(); + } + + // Register service name only if it has been set. + if (!m_service.isEmpty()) { + if (!conn.registerService(m_service)) { + qmlInfo(this) << "Failed to register service " << m_service; + qmlInfo(this) << conn.lastError().message(); + } + } + + if (m_quitOnTimeout && m_quitTimeout > 0) { + m_quitLocker.reset(new QEventLoopLocker); + + m_quitTimerId = startTimer(m_quitTimeout * 1000); + } +} + +QString DeclarativeDBusAbstractObject::introspect(const QString &) const +{ + return m_xml; +} + +bool DeclarativeDBusAbstractObject::handleMessage( + const QDBusMessage &message, const QDBusConnection &connection) +{ + const QVariantList dbusArguments = message.arguments(); + + QString member = message.member(); + QString interface = message.interface(); + + // Don't try to handle introspect call. It will be handled + // internally for QDBusVirtualObject derived classes. + if (interface == QLatin1String("org.freedesktop.DBus.Introspectable")) { + return false; + } else if (interface == QLatin1String("org.freedesktop.DBus.Properties")) { + if (member == QLatin1String("Get")) { + return getProperty( + message, + connection, + dbusArguments.value(0).toString(), + dbusArguments.value(1).toString()); + } else if (member == QLatin1String("GetAll")) { + return getProperties( + message, + connection, + dbusArguments.value(0).toString()); + } else if (member == QLatin1String("Set")) { + return setProperty( + dbusArguments.value(0).toString(), + dbusArguments.value(1).toString(), + NemoDBus::demarshallDBusArgument(dbusArguments.value(2))); + } + } else { + return invoke(message, connection, interface, member, dbusArguments); + } + + return false; +} + +void DeclarativeDBusAbstractObject::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_quitTimerId) { + killTimer(m_quitTimerId); + + m_quitTimerId = -1; + + m_quitLocker.reset(); + } else { + QDBusVirtualObject::timerEvent(event); + } +} diff --git a/src/plugin/declarativedbusabstractobject.h b/src/plugin/declarativedbusabstractobject.h new file mode 100644 index 0000000..3924e45 --- /dev/null +++ b/src/plugin/declarativedbusabstractobject.h @@ -0,0 +1,120 @@ +/**************************************************************************************** +** +** Copyright (c) 2013 - 2021 Jolla Ltd. +** All rights reserved. +** +** You may use this file under the terms of the GNU Lesser General +** Public License version 2.1 as published by the Free Software Foundation +** and appearing in the file license.lgpl included in the packaging +** of this file. +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation +** and appearing in the file license.lgpl included in the packaging +** of this file. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +****************************************************************************************/ + +#ifndef DECLARATIVEDBUSABSTRACTOBJECT_H +#define DECLARATIVEDBUSABSTRACTOBJECT_H + +#include +#include +#include +#include + +#include + +#include + +#include "declarativedbus.h" + +class DeclarativeDBusAbstractObject : public QDBusVirtualObject, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QString service READ service WRITE setService NOTIFY serviceChanged) + Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QString xml READ xml WRITE setXml NOTIFY xmlChanged) + Q_PROPERTY(DeclarativeDBus::BusType bus READ bus WRITE setBus NOTIFY busChanged) + Q_PROPERTY(bool quitOnTimeout READ quitOnTimeout WRITE setQuitOnTimeout NOTIFY quitOnTimeoutChanged) + Q_PROPERTY(int quitTimeout READ quitTimeout WRITE setQuitTimeout NOTIFY quitTimeoutChanged) + + Q_INTERFACES(QQmlParserStatus) + +public: + explicit DeclarativeDBusAbstractObject(QObject *parent = nullptr); + ~DeclarativeDBusAbstractObject() override; + + QString service() const; + void setService(const QString &service); + + QString path() const; + void setPath(const QString &path); + + QString xml() const; + void setXml(const QString &xml); + + DeclarativeDBus::BusType bus() const; + void setBus(DeclarativeDBus::BusType bus); + + bool quitOnTimeout() const; + void setQuitOnTimeout(bool quit); + + int quitTimeout() const; + void setQuitTimeout(int timeout); + + void classBegin() override; + void componentComplete() override; + + QString introspect(const QString &path) const override; + bool handleMessage(const QDBusMessage &message, const QDBusConnection &connection) override; + +signals: + void serviceChanged(); + void pathChanged(); + void xmlChanged(); + void busChanged(); + void quitOnTimeoutChanged(); + void quitTimeoutChanged(); + +protected: + void timerEvent(QTimerEvent *event) override; + + virtual bool getProperty( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface, + const QString &member) = 0; + virtual bool getProperties( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface) = 0; + virtual bool setProperty( + const QString &interface, const QString &member, const QVariant &value) = 0; + virtual bool invoke( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface, + const QString &name, + const QVariantList &dbusArguments) = 0; + +private: + + QScopedPointer m_quitLocker; + QString m_service; + QString m_path; + QString m_xml; + DeclarativeDBus::BusType m_bus; + int m_quitTimerId; + int m_quitTimeout; + bool m_quitOnTimeout; + bool m_complete; +}; + +#endif diff --git a/src/plugin/declarativedbusadaptor.cpp b/src/plugin/declarativedbusadaptor.cpp index 84ca506..21038fc 100644 --- a/src/plugin/declarativedbusadaptor.cpp +++ b/src/plugin/declarativedbusadaptor.cpp @@ -1,7 +1,6 @@ /**************************************************************************************** ** -** Copyright (C) 2013 Jolla Ltd. -** Contact: Andrew den Exter +** Copyright (c) 2013 - 2021 Jolla Ltd. ** All rights reserved. ** ** You may use this file under the terms of the GNU Lesser General @@ -73,6 +72,8 @@ iface: 'com.example.service' path: '/com/example/service' + quitOnTimeout: !Qt.application.active + xml: ' \n' + ' \n' + ' \n' + @@ -87,64 +88,29 @@ */ DeclarativeDBusAdaptor::DeclarativeDBusAdaptor(QObject *parent) - : QDBusVirtualObject(parent), m_bus(DeclarativeDBus::SessionBus) + : DeclarativeDBusAbstractObject(parent) { } DeclarativeDBusAdaptor::~DeclarativeDBusAdaptor() { - QDBusConnection conn = DeclarativeDBus::connection(m_bus); - conn.unregisterObject(m_path); - - // In theory an application could have multiple adaptor items for different paths or - // interfaces and destroying one would unregister the whole service. - // If problems arise we could introduce refcounting for services so that - // the last service would be responsible for unregisting the service upon - // destruction. Unregisration should also take into account unregistration happening - // from C++ side. - if (!m_service.isEmpty()) { - if (!conn.unregisterService(m_service)) { - qmlInfo(this) << "Failed to unregister service " << m_service; - qmlInfo(this) << conn.lastError().message(); - } - } } /*! \qmlproperty string DBusAdaptor::service This property holds the registered service name. Typically this is in reversed domain-name. -*/ -QString DeclarativeDBusAdaptor::service() const -{ - return m_service; -} -void DeclarativeDBusAdaptor::setService(const QString &service) -{ - if (m_service != service) { - m_service = service; - emit serviceChanged(); - } -} + If an application implements multiple D-Bus interfaces for the same service, this property + should be left blank and the service should be registered using QDBusConnection::registerService() + after constructing the QML scene to ensure all objects have been registered first. +*/ /*! \qmlproperty string DBusAdaptor::path This property holds the object path that this object will be published at. */ -QString DeclarativeDBusAdaptor::path() const -{ - return m_path; -} - -void DeclarativeDBusAdaptor::setPath(const QString &path) -{ - if (m_path != path) { - m_path = path; - emit pathChanged(); - } -} /*! \qmlproperty string DBusAdaptor::iface @@ -164,29 +130,11 @@ void DeclarativeDBusAdaptor::setInterface(const QString &interface) } } -/* - The XML service description. This could be derived from the meta-object but since it's unlikely - to be needed most of the time this and type conversion is non-trivial it's not, and there is - this property instead if it is needed. -*/ - /*! \qmlproperty string DBusAdaptor::xml This property holds the D-Bus introspection metadata snippet for this object/interface. */ -QString DeclarativeDBusAdaptor::xml() const -{ - return m_xml; -} - -void DeclarativeDBusAdaptor::setXml(const QString &xml) -{ - if (m_xml != xml) { - m_xml = xml; - emit xmlChanged(); - } -} /*! \qmlproperty enum DBusAdaptor::bus @@ -198,47 +146,32 @@ void DeclarativeDBusAdaptor::setXml(const QString &xml) \li DBus.SystemBus - The D-Bus system bus \endlist */ -DeclarativeDBus::BusType DeclarativeDBusAdaptor::bus() const -{ - return m_bus; -} - -void DeclarativeDBusAdaptor::setBus(DeclarativeDBus::BusType bus) -{ - if (m_bus != bus) { - m_bus = bus; - emit busChanged(); - } -} - -void DeclarativeDBusAdaptor::classBegin() -{ -} - -void DeclarativeDBusAdaptor::componentComplete() -{ - QDBusConnection conn = DeclarativeDBus::connection(m_bus); - // It is still valid to publish an object on the bus without first registering a service name, - // a remote process would have to connect directly to the DBus address. - if (!conn.registerVirtualObject(m_path, this)) { - qmlInfo(this) << "Failed to register object " << m_path; - qmlInfo(this) << conn.lastError().message(); - } +/*! + \qmlproperty bool DBusAdaptor::quitOnTimeout + + This property holds whether the adaptor will attempt to quit the application if no windows are + visible when the \l {quitTimeout}{quit timeout} expires. + + Use this if an application may be auto started to handle a DBus method call that may not show + any windows. The application will be kept alive until the timeout expires and then if there are + no visible windows that will also keep it alive it will exit. Even if all the D-Bus methods + provided do show a window it can still be helpful to enable this as a safety measure in case + the application is started but no method is invoked because of external errors. + + Setting the this property to false after component construction has completed will cancel the + timeout and it cannot be reenabled again after that. It is recommeded that the quit be cancelled + after showing a window as turning the display off or switching to another application can + temporarily hide the application windows causing it to exit erroneously if the timeout expires + during this time. +*/ - // Register service name only if it has been set. - if (!m_service.isEmpty()) { - if (!conn.registerService(m_service)) { - qmlInfo(this) << "Failed to register service " << m_service; - qmlInfo(this) << conn.lastError().message(); - } - } -} +/*! + \qmlproperty int DBusAdaptor::quitTimeout -QString DeclarativeDBusAdaptor::introspect(const QString &) const -{ - return m_xml; -} + The property holds the amount of time in seconds from component construction that the adaptor + will wait before quitting if \l quitOnTimeout is true. +*/ QDBusArgument &operator << (QDBusArgument &argument, const QVariant &value) { @@ -261,132 +194,149 @@ QDBusArgument &operator << (QDBusArgument &argument, const QVariant &value) } } -bool DeclarativeDBusAdaptor::handleMessage(const QDBusMessage &message, - const QDBusConnection &connection) + +bool DeclarativeDBusAdaptor::getProperty( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface, + const QString &name) { - QVariant variants[10]; - QGenericArgument arguments[10]; + Q_UNUSED(interface); + + QString member = name; const QMetaObject *const meta = metaObject(); - const QVariantList dbusArguments = message.arguments(); - - QString member = message.member(); - QString interface = message.interface(); - - // Don't try to handle introspect call. It will be handled - // internally for QDBusVirtualObject derived classes. - if (interface == QLatin1String("org.freedesktop.DBus.Introspectable")) { - return false; - } else if (interface == QLatin1String("org.freedesktop.DBus.Properties")) { - if (member == QLatin1String("Get")) { - interface = dbusArguments.value(0).toString(); - member = dbusArguments.value(1).toString(); - - const QMetaObject *const meta = metaObject(); - if (!member.isEmpty() && member.at(0).isUpper()) - member = "rc" + member; - - for (int propertyIndex = meta->propertyOffset(); - propertyIndex < meta->propertyCount(); - ++propertyIndex) { - QMetaProperty property = meta->property(propertyIndex); - - if (QLatin1String(property.name()) != member) - continue; - - QVariant value = property.read(this); - if (value.userType() == qMetaTypeId()) - value = value.value().toVariant(); - - if (value.userType() == QVariant::List) { - QVariantList variantList = value.toList(); - if (variantList.count() > 0) { - - QDBusArgument list; - list.beginArray(variantList.first().userType()); - foreach (const QVariant &listValue, variantList) { - list << listValue; - } - list.endArray(); - value = QVariant::fromValue(list); - } - } + if (!member.isEmpty() && member.at(0).isUpper()) + member = "rc" + member; - QDBusMessage reply = message.createReply(QVariantList() << value); - connection.call(reply, QDBus::NoBlock); + for (int propertyIndex = meta->propertyOffset(); + propertyIndex < meta->propertyCount(); + ++propertyIndex) { + QMetaProperty property = meta->property(propertyIndex); - return true; - } - } else if (member == QLatin1String("GetAll")) { - interface = dbusArguments.value(0).toString(); - - QDBusArgument map; - map.beginMap(qMetaTypeId(), qMetaTypeId()); - - for (int propertyIndex = meta->propertyOffset(); - propertyIndex < meta->propertyCount(); - ++propertyIndex) { - QMetaProperty property = meta->property(propertyIndex); - - QString propertyName = QLatin1String(property.name()); - if (propertyName.startsWith(QLatin1String("rc"))) - propertyName = propertyName.mid(2); - - QVariant value = property.read(this); - if (value.userType() == qMetaTypeId()) - value = value.value().toVariant(); - - if (value.userType() == QVariant::List) { - QVariantList variantList = value.toList(); - if (variantList.count() > 0) { - - QDBusArgument list; - list.beginArray(variantList.first().userType()); - foreach (const QVariant &listValue, variantList) { - list << listValue; - } - list.endArray(); - value = QVariant::fromValue(list); - } - } + if (QLatin1String(property.name()) != member) + continue; - if (value.isValid()) { - map.beginMapEntry(); - map << propertyName; - map << QDBusVariant(value); - map.endMapEntry(); + QVariant value = property.read(this); + if (value.userType() == qMetaTypeId()) + value = value.value().toVariant(); + + if (value.userType() == QVariant::List) { + QVariantList variantList = value.toList(); + if (variantList.count() > 0) { + + QDBusArgument list; + list.beginArray(variantList.first().userType()); + foreach (const QVariant &listValue, variantList) { + list << listValue; } + list.endArray(); + value = QVariant::fromValue(list); } - map.endMap(); + } + + QDBusMessage reply = message.createReply(QVariantList() << value); + connection.call(reply, QDBus::NoBlock); + + return true; + } - QDBusMessage reply = message.createReply(QVariantList() << QVariant::fromValue(map)); - connection.call(reply, QDBus::NoBlock); + return false; +} + +bool DeclarativeDBusAdaptor::getProperties( + const QDBusMessage &message, const QDBusConnection &connection, const QString &interface) +{ + Q_UNUSED(interface); + + const QMetaObject *const meta = metaObject(); - return true; - } else if (member == QLatin1String("Set")) { - interface = dbusArguments.value(0).toString(); - member = dbusArguments.value(1).toString(); + QDBusArgument map; + map.beginMap(qMetaTypeId(), qMetaTypeId()); - const QMetaObject *const meta = metaObject(); - if (!member.isEmpty() && member.at(0).isUpper()) - member = "rc" + member; + for (int propertyIndex = meta->propertyOffset(); + propertyIndex < meta->propertyCount(); + ++propertyIndex) { + QMetaProperty property = meta->property(propertyIndex); - for (int propertyIndex = meta->propertyOffset(); - propertyIndex < meta->propertyCount(); - ++propertyIndex) { - QMetaProperty property = meta->property(propertyIndex); + QString propertyName = QLatin1String(property.name()); + if (propertyName.startsWith(QLatin1String("rc"))) + propertyName = propertyName.mid(2); - if (QLatin1String(property.name()) != member) - continue; + QVariant value = property.read(this); + if (value.userType() == qMetaTypeId()) + value = value.value().toVariant(); - QVariant value = NemoDBus::demarshallDBusArgument(dbusArguments.value(2)); + if (value.userType() == QVariant::List) { + QVariantList variantList = value.toList(); + if (variantList.count() > 0) { - return property.write(this, value); + QDBusArgument list; + list.beginArray(variantList.first().userType()); + foreach (const QVariant &listValue, variantList) { + list << listValue; + } + list.endArray(); + value = QVariant::fromValue(list); } } - return false; + + if (value.isValid()) { + map.beginMapEntry(); + map << propertyName; + map << QDBusVariant(value); + map.endMapEntry(); + } + } + map.endMap(); + + QDBusMessage reply = message.createReply(QVariantList() << QVariant::fromValue(map)); + connection.call(reply, QDBus::NoBlock); + + return true; +} + +bool DeclarativeDBusAdaptor::setProperty( + const QString &interface, const QString &name, const QVariant &value) +{ + Q_UNUSED(interface); + + QString member = name; + + const QMetaObject *const meta = metaObject(); + if (!member.isEmpty() && member.at(0).isUpper()) + member = "rc" + member; + + for (int propertyIndex = meta->propertyOffset(); + propertyIndex < meta->propertyCount(); + ++propertyIndex) { + QMetaProperty property = meta->property(propertyIndex); + + if (QLatin1String(property.name()) != member) + continue; + + return property.write(this, value); } + return false; +} + +bool DeclarativeDBusAdaptor::invoke( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface, + const QString &name, + const QVariantList &dbusArguments) +{ + Q_UNUSED(interface); + + const QMetaObject *const meta = metaObject(); + + QVariant variants[10]; + QGenericArgument arguments[10]; + + QString member = name; + // Support interfaces with method names starting with an uppercase letter. // com.example.interface.Foo -> com.example.interface.rcFoo (remote-call Foo). if (!member.isEmpty() && member.at(0).isUpper()) @@ -451,8 +401,8 @@ bool DeclarativeDBusAdaptor::handleMessage(const QDBusMessage &message, arguments[9]); if (success) { QDBusMessage reply = retVal.isValid() ? message.createReply(retVal) : message.createReply(); - QDBusConnection conn = DeclarativeDBus::connection(m_bus); - conn.send(reply); + + connection.send(reply); } return success; } @@ -478,8 +428,8 @@ bool DeclarativeDBusAdaptor::handleMessage(const QDBusMessage &message, */ void DeclarativeDBusAdaptor::emitSignal(const QString &name, const QJSValue &arguments) { - QDBusMessage signal = QDBusMessage::createSignal(m_path, m_interface, name); - QDBusConnection conn = DeclarativeDBus::connection(m_bus); + QDBusMessage signal = QDBusMessage::createSignal(path(), m_interface, name); + QDBusConnection conn = DeclarativeDBus::connection(bus()); if (!arguments.isUndefined()) { signal.setArguments(DeclarativeDBusInterface::argumentsFromScriptValue(arguments)); diff --git a/src/plugin/declarativedbusadaptor.h b/src/plugin/declarativedbusadaptor.h index 55b6e08..62ed9ae 100644 --- a/src/plugin/declarativedbusadaptor.h +++ b/src/plugin/declarativedbusadaptor.h @@ -1,7 +1,6 @@ /**************************************************************************************** ** -** Copyright (C) 2013 Jolla Ltd. -** Contact: Andrew den Exter +** Copyright (C) 2013 - 2021 Jolla Ltd. ** All rights reserved. ** ** You may use this file under the terms of the GNU Lesser General @@ -25,68 +24,47 @@ #ifndef DECLARATIVEDBUSADAPTOR_H #define DECLARATIVEDBUSADAPTOR_H -#include -#include -#include -#include -#include +#include "declarativedbusabstractobject.h" -#include - -#include "declarativedbus.h" - -class DeclarativeDBusAdaptor : public QDBusVirtualObject, public QQmlParserStatus +class DeclarativeDBusAdaptor : public DeclarativeDBusAbstractObject { Q_OBJECT - Q_PROPERTY(QString service READ service WRITE setService NOTIFY serviceChanged) - Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QString iface READ interface WRITE setInterface NOTIFY interfaceChanged) - Q_PROPERTY(QString xml READ xml WRITE setXml NOTIFY xmlChanged) - Q_PROPERTY(DeclarativeDBus::BusType bus READ bus WRITE setBus NOTIFY busChanged) - Q_INTERFACES(QQmlParserStatus) public: - DeclarativeDBusAdaptor(QObject *parent = 0); - ~DeclarativeDBusAdaptor(); - - QString service() const; - void setService(const QString &service); - - QString path() const; - void setPath(const QString &path); + explicit DeclarativeDBusAdaptor(QObject *parent = nullptr); + ~DeclarativeDBusAdaptor() override; QString interface() const; void setInterface(const QString &interface); - QString xml() const; - void setXml(const QString &xml); - - DeclarativeDBus::BusType bus() const; - void setBus(DeclarativeDBus::BusType bus); - - void classBegin(); - void componentComplete(); - - QString introspect(const QString &path) const; - bool handleMessage(const QDBusMessage &message, const QDBusConnection &connection); - Q_INVOKABLE void emitSignal(const QString &name, const QJSValue &arguments = QJSValue::UndefinedValue); + bool getProperty( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface, + const QString &member) override; + bool getProperties( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface) override; + bool setProperty( + const QString &interface, const QString &member, const QVariant &value) override; + bool invoke( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface, + const QString &name, + const QVariantList &dbusArguments) override; + signals: - void serviceChanged(); - void pathChanged(); void interfaceChanged(); - void xmlChanged(); - void busChanged(); private: - QString m_service; - QString m_path; QString m_interface; - QString m_xml; - DeclarativeDBus::BusType m_bus; }; #endif diff --git a/src/plugin/declarativedbusobject.cpp b/src/plugin/declarativedbusobject.cpp new file mode 100644 index 0000000..fd26869 --- /dev/null +++ b/src/plugin/declarativedbusobject.cpp @@ -0,0 +1,287 @@ +/**************************************************************************************** +** +** Copyright (c) 2021 Jolla Ltd. +** All rights reserved. +** +** You may use this file under the terms of the GNU Lesser General +** Public License version 2.1 as published by the Free Software Foundation +** and appearing in the file license.lgpl included in the packaging +** of this file. +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation +** and appearing in the file license.lgpl included in the packaging +** of this file. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +****************************************************************************************/ + +#include "declarativedbusobject.h" + +#include "declarativedbusadaptor.h" + +/*! + \qmltype DBusObject + \inqmlmodule Nemo.DBus + \brief Provides a service on D-Bus + + The DBusObject object can be used to register a D-Bus object implementing multiple interfaces + on the system or session bus. + + Each interface implemented by the object is defined in an individual DBusAdaptor in the body + of the DBusObject and will be registered as belonging to the same path and the same optional + service. + + \section2 Exposing an object on D-Bus + + The following code demonstrates how to expose an object on the session bus. The + \c {com.example.service} service name will be registered and an object at + \c {/com/example/service} will be registered supporting the \c {com.example.service} and + \c {org.freedesktop.Application} interfaces in addition to the common interfaces + \c {org.freedesktop.DBus.Properties}, \c {org.freedesktop.DBus.Introspectable} + and \c {org.freedesktop.DBus.Peer}. + + + \code + import QtQuick 2.0 + import Nemo.DBus 2.0 + + ApplicationWindow { + id: window + + DBusObject { + id: dbus + + property bool needUpdate: true + + quitOnTimeout: !Qt.application.active + + service: "com.example.service" + path: "/com/example/service" + + xml: " + + + + + + + + + + + + + + + + + + " + + DBusAdaptor { + iface: "com.example.service" + + function update() { + console.log("Update called") + } + } + + DBusAdaptor { + iface: 'org.freedesktop.Application' + + function rcActivate(platformData) { + window.activate() + } + function rcOpen(uris, platformData) { + if (uris.length > 0) { + // open file + } + window.activate() + } + function rcActivateAction(name, parameters, platformData) { + } + } + + } + } + \endcode +*/ + +DeclarativeDBusObject::DeclarativeDBusObject(QObject *parent) + : DeclarativeDBusAbstractObject(parent) +{ +} + +DeclarativeDBusObject::~DeclarativeDBusObject() +{ +} + +/*! + \qmlproperty string DBusObject::service + + This property holds the registered service name. Typically this is in reversed domain-name. + + If an application implements multiple D-Bus interfaces for the same service, this property + should be left blank and the service should be registered using QDBusConnection::registerService() + after constructing the QML scene to ensure all objects have been registered first. +*/ + +/*! + \qmlproperty string DBusObject::path + + This property holds the object path that this object will be published at. +*/ + +/*! + \qmlproperty list DBusObject::adaptors + + This property holds a list of adaptors for interfaces implemented by the object. +*/ + +QQmlListProperty DeclarativeDBusObject::adaptors() +{ + return QQmlListProperty( + this, 0, adaptor_append, adaptor_count, adaptor_at, adaptor_clear); +} + +void DeclarativeDBusObject::componentComplete() +{ + for (QObject *object : m_objects) { + if (DeclarativeDBusAdaptor *adaptor = qobject_cast(object)) { + const QString interface = adaptor->interface(); + + if (!interface.isEmpty() && adaptor->service().isEmpty() && adaptor->path().isEmpty()) { + adaptor->setPath(path()); + adaptor->setBus(bus()); + + m_adaptors.insert(interface, adaptor); + } + } + } + + DeclarativeDBusObject::componentComplete(); +} + +void DeclarativeDBusObject::adaptor_append(QQmlListProperty *property, QObject *value) +{ + DeclarativeDBusObject * const object = static_cast(property->object); + + object->m_objects.append(value); + + emit object->adaptorsChanged(); +} + +QObject *DeclarativeDBusObject::adaptor_at(QQmlListProperty *property, int index) +{ + return static_cast(property->object)->m_objects.value(index); +} + +int DeclarativeDBusObject::adaptor_count(QQmlListProperty *property) +{ + return static_cast(property->object)->m_objects.count(); +} + +void DeclarativeDBusObject::adaptor_clear(QQmlListProperty *property) +{ + DeclarativeDBusObject * const object = static_cast(property->object); + + object->m_objects.clear(); + + emit object->adaptorsChanged(); +} + +/*! + \qmlproperty string DBusObject::xml + + This property holds the D-Bus introspection metadata snippet for this object. +*/ + +/*! + \qmlproperty enum DBusObject::bus + + This property holds whether to use the session or system D-Bus. + + \list + \li DBus.SessionBus - The D-Bus session bus + \li DBus.SystemBus - The D-Bus system bus + \endlist +*/ + +/*! + \qmlproperty bool DBusObject::quitOnTimeout + + This property holds whether the object will attempt to quit the application if no windows are + visible when the \l {quitTimeout}{quit timeout} expires. + + Use this if an application may be auto started to handle a DBus method call that may not show + any windows. The application will be kept alive until the timeout expires and then if there are + no visible windows that will also keep it alive it will exit. Even if all the D-Bus methods + provided do show a window it can still be helpful to enable this as a safety measure in case + the application is started but no method is invoked because of external errors. + + Setting the this property to false after component construction has completed will cancel the + timeout and it cannot be reenabled again after that. It is recommeded that the quit be cancelled + after showing a window as turning the display off or switching to another application can + temporarily hide the application windows causing it to exit erroneously if the timeout expires + during this time. +*/ + +/*! + \qmlproperty int DBusObject::quitTimeout + + The property holds the amount of time in seconds from component construction that the object + will wait before quitting if \l quitOnTimeout is true. +*/ + +bool DeclarativeDBusObject::getProperty( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface, + const QString &name) +{ + if (DeclarativeDBusAdaptor * const adaptor = m_adaptors.value(interface)) { + return adaptor->getProperty(message, connection, interface, name); + } else { + return false; + } +} + +bool DeclarativeDBusObject::getProperties( + const QDBusMessage &message, const QDBusConnection &connection, const QString &interface) +{ + if (DeclarativeDBusAdaptor * const adaptor = m_adaptors.value(interface)) { + return adaptor->getProperties(message, connection, interface); + } else { + return false; + } +} + +bool DeclarativeDBusObject::setProperty( + const QString &interface, const QString &name, const QVariant &value) +{ + if (DeclarativeDBusAdaptor * const adaptor = m_adaptors.value(interface)) { + return adaptor->setProperty(interface, name, value); + } else { + return false; + } +} + +bool DeclarativeDBusObject::invoke( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface, + const QString &name, + const QVariantList &dbusArguments) +{ + if (DeclarativeDBusAdaptor * const adaptor = m_adaptors.value(interface)) { + return adaptor->invoke(message, connection, interface, name, dbusArguments); + } else { + return false; + } +} diff --git a/src/plugin/declarativedbusobject.h b/src/plugin/declarativedbusobject.h new file mode 100644 index 0000000..11304fc --- /dev/null +++ b/src/plugin/declarativedbusobject.h @@ -0,0 +1,81 @@ +/**************************************************************************************** +** +** Copyright (c) 2021 Jolla Ltd. +** All rights reserved. +** +** You may use this file under the terms of the GNU Lesser General +** Public License version 2.1 as published by the Free Software Foundation +** and appearing in the file license.lgpl included in the packaging +** of this file. +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation +** and appearing in the file license.lgpl included in the packaging +** of this file. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +****************************************************************************************/ + +#ifndef DECLARATIVEDBUSOBJECT_H +#define DECLARATIVEDBUSOBJECT_H + +#include "declarativedbusabstractobject.h" + +#include +#include + +class DeclarativeDBusAdaptor; + +class DeclarativeDBusObject : public DeclarativeDBusAbstractObject +{ + Q_OBJECT + Q_PROPERTY(QQmlListProperty adaptors READ adaptors NOTIFY adaptorsChanged) + Q_INTERFACES(QQmlParserStatus) + Q_CLASSINFO("DefaultProperty", "adaptors") + +public: + explicit DeclarativeDBusObject(QObject *parent = nullptr); + ~DeclarativeDBusObject() override; + + QQmlListProperty adaptors(); + + void componentComplete() override; + +signals: + void adaptorsChanged(); + +protected: + bool getProperty( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface, + const QString &member) override; + bool getProperties( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface) override; + bool setProperty( + const QString &interface, const QString &member, const QVariant &value) override; + bool invoke( + const QDBusMessage &message, + const QDBusConnection &connection, + const QString &interface, + const QString &name, + const QVariantList &dbusArguments) override; + +private: + static void adaptor_append(QQmlListProperty *property, QObject *value); + static QObject *adaptor_at(QQmlListProperty *property, int index); + static int adaptor_count(QQmlListProperty *property); + static void adaptor_clear(QQmlListProperty *property); + + QHash m_adaptors; + QVector m_objects; +}; + +#endif diff --git a/src/plugin/plugin.cpp b/src/plugin/plugin.cpp index 13a7d5d..cd6b7e2 100644 --- a/src/plugin/plugin.cpp +++ b/src/plugin/plugin.cpp @@ -38,6 +38,7 @@ #include "declarativedbus.h" #include "declarativedbusadaptor.h" #include "declarativedbusinterface.h" +#include "declarativedbusobject.h" #include "dbus.h" @@ -55,6 +56,7 @@ class Q_DECL_EXPORT NemoDBusPlugin : public QQmlExtensionPlugin qmlRegisterUncreatableType(uri, 2, 0, "DBus", "Cannot create DBus objects"); qmlRegisterType(uri, 2, 0, "DBusAdaptor"); qmlRegisterType(uri, 2, 0, "DBusInterface"); + qmlRegisterType(uri, 2, 0, "DBusObject"); } }; diff --git a/src/plugin/plugin.pro b/src/plugin/plugin.pro index d5c3723..e19d209 100644 --- a/src/plugin/plugin.pro +++ b/src/plugin/plugin.pro @@ -1,8 +1,7 @@ TARGET = nemodbus PLUGIN_IMPORT_PATH = Nemo/DBus -QT += dbus qml - QT -= gui +QT += dbus qml TEMPLATE = lib CONFIG += qt plugin hide_symbols @@ -25,10 +24,14 @@ QMAKE_EXTRA_TARGETS += qmltypes SOURCES += \ plugin.cpp \ declarativedbus.cpp \ + declarativedbusabstractobject.cpp \ declarativedbusadaptor.cpp \ declarativedbusinterface.cpp \ + declarativedbusobject.cpp HEADERS += \ declarativedbus.h \ + declarativedbusabstractobject.h \ declarativedbusadaptor.h \ declarativedbusinterface.h \ + declarativedbusobject.h