Skip to content

[nemo-qml-plugin-dbus] Add an auto quit feature for DBus adaptors. Contributes to JB#54789 #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 245 additions & 0 deletions src/plugin/declarativedbusabstractobject.cpp
Original file line number Diff line number Diff line change
@@ -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 <QDBusArgument>
#include <QDBusMessage>
#include <QDBusConnection>

#include <QTimerEvent>

#include <qqmlinfo.h>

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old, but should have isEmpty() check?


// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also old code, but what should happen if this or later properties get adjusted after Component.onComplete? If we don't want to support that, could at least warn?

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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this supports turning off the autoquit but not back on later? The documentation example is "quitOnTimeout: !Qt.application.active"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the app won't be active initially so auto quit is enabled on component complete, when the application becomes active later it be disabled cancelling the timeout and it can't be turned on again.

In theory, on the application becoming active there are guaranteed visible windows and removing the event loop locker won't exit the application. And it's less laborious than explicitly cancelling the quit on every method call that results in a window being shown, and in the case where the application isn't started by dbus. I would have preferred to use ApplicationWindow.visible but that was true initially despite item visibility following window visibility and the window not yet having been shown.

Unfortunately I forgot about starting a second application while a first is still launching, in which case the first application will never hit that application.active state despite having shown a window and it will exit after a bit. So that's no good either, and we're trending back to so convoluted to use that you're better off rolling your own so you can at least follow what's happening territory.

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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, the QDBusVirtualObject is documented but registerVirtualObject() is marked \internal.

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);
}
}
120 changes: 120 additions & 0 deletions src/plugin/declarativedbusabstractobject.h
Original file line number Diff line number Diff line change
@@ -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 <QQmlListProperty>
#include <QQmlParserStatus>
#include <QUrl>
#include <QJSValue>

#include <QDBusVirtualObject>

#include <QEventLoopLocker>

#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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed feelings for these autoquit properties here. They do support d-bus related use-case, but then again the implementation side is not much related to d-bus adaptors, and the same property is available on two different components. Potentially later on third if we add a component to represent the d-bus service.

No clear home for the functionality, but pondering if a separate component in Silica could be justified. Perhaps not all auto-quit needs are related to d-bus.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even focusing on the really specific use case of exiting because a d-bus thing didn't happen or happened but didn't result in an application window being shown it's difficult to come up with something that isn't a foot gun.

It's here because I was looking for a way to cancel the quit after handling a method if there were visible windows or something, but that doesn't cover non-prestart, or the existance other dbus adaptors. But the other reason is that keeps expectations low, it does this one thing and so long as you follow the example your applications not going to exit because you got a phone call that coincided with an exit if not seemingly open use timeout. Something in silica that could be used for hypothetical non dbus auto start timeout use cases will come with an expectation that it will actually work for those use cases.


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<QEventLoopLocker> 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
Loading