c++linuxqtqt4dbus

Waiting for a DBus service to be available in Qt


With a Qt DBus proxy built on QDbusAbstractInterface (via qdbusxml2cpp), what's the best way to handle the service/object you want to interface to not being available when you start? Note: I'm not interested in simply knowing it (you can use BlahService.isValid() to find that out); I want to be able to know if it's valid, and know when it becomes valid so I can change state (and broadcast that state change with a signal), and on that state change do other stuff. Conversely, I want to know when it's no longer valid for similar reasons.

Without tracking the state of the service:

#define CONNECT_DBUS_SIG(x,y) connect(blah,SIGNAL(x),this,SLOT(y))

// FIX - should watch for service, and also handle it going away and
// coming back
blah = new BlahService("com.xyzzy.BlahService", "/com/xyzzy/BlahService",
                           QDBusConnection::sessionBus(), this);
if (!blah)
    return 0;
if (blah.isValid())
{
    CONNECT_DBUS_SIG(foo(),Event_foo());
}
else
{
    // Since we aren't watching for registration, what can we do but exit?
}

Probably we need to watch for NameOwnerChanged on the DBus connection object - unless QT's dbus code does this for us - and then when we get that signal change state, and if needed connect or disconnect the signals from the object.

All the examples I find either ignore the issue or simply exit if the server object doesn't exist, and don't deal with it going away. The Car/Controller Qt example at least notices if the server goes away and prints "Disconnected" if isValid() becomes false during use, but it's polling isValid().

Added:

Note that QtDbusAbtractInterface registers for changes of ownership of the server (NameOwnerChanged), and updates isValid() when changes occur. So I suspect you can connect to that serverOwnerChanged signal directly to find out about changes to ownership and use that as an indicator to try again - though you won't be able to trust isValid since it may be updated before or after you get signaled.

Alternatively (ugly) you can set up a timer and poll for isValid().


Solution

  • Ok, since no one answered, I've found the answer in the meantime:

    You want to watch NameOwnerChanged:

    // subscribe to notifications about when a service is registered/unregistered
       connect(QDBusConnection::sessionBus().interface(),
               SIGNAL(serviceOwnerChanged(QString,QString,QString)),
               this,SLOT(serviceOwnerChanged(QString,QString,QString)));
    

    and

    void 
    VcsApplicationController::serviceOwnerChanged(const QString &name,
                                                  const QString &oldOwner,
                                                  const QString &newOwner)
    {
        Q_UNUSED(oldOwner);
        if (name == "com.foo.bar.FooService")
        {
            qLog(Whatever) << "serviceOwnerChanged" << name << oldOwner << newOwner;
            if (!newOwner.isEmpty())
            {
                // New owner in town
                emit Initialized();
                // or if you control the interface and both sides, you can wait for
                // a "Ready()" signal before declaring FooService ready for business.
            }
            else
            {
                // indicate we've lost connection, etc
                emit Uninitialized();
            }
        }
    }
    

    Note that there may be race conditions with doing methods on FooService from within serviceOwnerChanged - I'm not sure yet if they're a side-effect of the binding (dbus-c++ in my test case), or inherent in the design of dbus (possible - no on on the dbus mailing list will answer the question). If there is a real race condition, you can wait on a Ready()/whatever signal, if you control the DBus API. If you don't control the other end, you can add a very short delay or you can also watch AddMatch() to make sure the new owner has added a match on the name as well.