Continuing with the development of Satyr, which doesn't have any GUI. I thought it would be faster to make a DBus interface that a good GUI, not to mention more interesting.

The code snippet of today is this:

class DBusServer (dbus.service.Object):
    def __init__ (self):
        bus= dbus.SessionBus ()
        bus_name= dbus.service.BusName ('org.kde.satyr.dbus.test', bus=bus)
        dbus.service.Object.__init__ (self, bus_name, "/server")

    @dbus.service.method('org.kde.satyr.dbus.test', in_signature='', out_signature='')
    def bing (self):
        print "bing!"

    @dbus.service.method('org.kde.satyr.dbus.test', in_signature='', out_signature='')
    def quit (self):
        app.quit ()

dbus.mainloop.qt.DBusQtMainLoop (set_as_default=True)
dbs= DBusServer ()

sys.exit (app.exec_ ())

This simply defines a class which registers itself with the session bus under the name org.kde.satyr.dbus.test, exporting itself under the path /server and then defining a method that goes bing! :) and another one that quits the app. Note the decorator for the methods.

You might notice the dbus.mainloop.qt.DBusQtMainLoop (set_as_default=True) call. This is needed because both Qt and DBus in asyncronous mode (which is the one we're using and the only one that works under Qt or Gtk, AFAIK) both have their own event loops, and that makes some kind of magic that let both loops coexist without blocking the other. This must be called before connecting to the bus; otherwise, you get this error:

<span class="createlink">RuntimeError</span>: To make asynchronous calls, receive signals or export objects, 
D-Bus connections must be attached to a main loop by passing mainloop=... 
to the constructor or calling dbus.set_default_main_loop(...)

So, let's test the beast. We run the script in one terminal and in the other:

mdione@mustang:~/src/projects/satyr/live$ qdbus | grep satyr
 org.kde.satyr.py-10154
 org.kde.satyr.dbus.test
mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test
/
/server
mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test /server
method void org.kde.satyr.dbus.test.bing()
method QString org.freedesktop.DBus.Introspectable.Introspect()
mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test /server org.kde.satyr.dbus.test.bing
mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test /server org.kde.satyr.dbus.test.quit

And in the other console:

mdione@mustang:~/src/projects/satyr/live$ python dbus_test.py
bing!

It goes bing!... and then finishes. The next step is to make my Player class to export its methods via DBus and that's it!. More info in the Python DBus tutorial.

dbus python pykde satyr

Posted Wed 27 Jan 2010 11:55:55 PM CET Tags: dbus

In my last post I said «The next step is to make my Player class to export its methods via DBus and that's it!». Well, tell you what: is not that easy. If you try to inherit from QObject and dbus.service.Object you get this error:

In [3]: class Klass (QtCore.QObject, dbus.service.Object): pass
<span class="createlink">TypeError</span>: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a
    (non-strict) subclass of the metaclasses of all its bases

This occurs when both ancestors have their own metaclasses. Unluckily Python doesn't resolve it for you. The answer is to create a intermediate metaclass which inherits from both metaclasses (which we can obtain with type()) and make it the metaclass of our class. In code:

class <span class="createlink">MetaPlayer</span> (type (QObject), type (dbus.service.Object)):
    """Dummy metaclass that allows us to inherit from both QObject and
    d.s.Object"""
    pass

class Player (QObject, dbus.service.Object):
    __metaclass__= <span class="createlink">MetaPlayer</span>
    [...]

Is that it now? Can I go and do my code? Unfortunately no. See this:

qdbus org.kde.satyr
/
/player
Cannot introspect object /player at org.kde.satyr:
org.freedesktop.DBus.Python.KeyError (Traceback (most recent call last):
  File "/usr/lib/pymodules/python2.5/dbus/service.py", line 702, in _message_cb
    retval = candidate_method(self, *args, **keywords)
  File "/usr/lib/pymodules/python2.5/dbus/service.py", line 759, in Introspect
    interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
<span class="createlink">KeyError</span>: '__main__.Player'
)

This is the class dbus.service.Object complaining something else. It's getting late here and I'm tired, so I'll continue tomorrow.

dbus python pykde satyr

Posted Wed 27 Jan 2010 11:55:55 PM CET Tags: dbus

I seem to have fixed the bug I mentioned in my last post. This is what I had:

class <span class="createlink">MetaPlayer</span> (type (QObject), type (dbus.service.Object)):
    """Dummy metaclass that allows us to inherit from both QObject and
    d.s.Object"""
    pass

class Player (QObject, dbus.service.Object):
    __metaclass__= <span class="createlink">MetaPlayer</span>
    [...]

Notice that MetaPlayer doesn't have a explicit __init__() method; one would spect that Python would take are of that. Here's the fixing code:

<span class="createlink">MetaQObject</span>= type (QObject)
<span class="createlink">MetaObject</span>= type (dbus.service.Object)

class <span class="createlink">MetaPlayer</span> (MetaQObject, <span class="createlink">MetaObject</span>):
    """Dummy metaclass that allows us to inherit from both QObject and d.s.Object"""
    def __init__(cls, name, bases, dct):
        <span class="createlink">MetaObject</span>.__init__ (cls, name, bases, dct)
        <span class="createlink">MetaQObject</span>.__init__ (cls, name, bases, dct)

I really don't understand why I have to be so explicit. Maybe it's because the metaclass for dbus.service.Object, dbus.service.InterfaceType, inherits from the type type[1]; this type is a new style class[2], but doesn't inherits from object. Thus, I think, the inherited __init__() methods are not called automatically.

In any case, now I can mix QObject and dbus.service.Object, and it works fine. For instance, this call works:

$ qdbus org.kde.satyr /player quit

dbus python pykde


[1] the type type is of type type! here:

In [1]: type (type)
Out[1]: <type 'type'>

[2] its type is not instance but type, as mentioned above.

Posted Wed 27 Jan 2010 11:55:55 PM CET Tags: dbus