Muji: multi-user Jingle

So there were two things I find exciting that got merged in gabble 0.9.12 (gabble is Telepathy’s XMPP/Jabber connection manager). First was support for SASL authentication channels, allowing you handle SASL auth mechanisms from a custom handler (e.g. by not supplying a password to Gabble, or a custom mechanism such as X-GOOGLE-TOKEN or X-FACEBOOK-PLATFORM); thanks to Eitan. Second was multi-user Jingle calls; thanks to Sjoerd.

Muji Demo Client
Muji Demo Client

This is all done as an extension XMPP/Jingle using a XMPP multi-user-chat room (the XEP). The work has not been integrated into Empathy yet, but there is a demo client (yes, it even works from Australia). Sjoerd writes more about it on his new blog.

The Meego 1.0 People Panel

So Meego 1.0 for Netbooks shipped last week. Although I miss that tubby cat, Meego 1.0 is very visually attractive and quite nifty.

One of the really neat things about Meego is how it integrates messaging right into the interface. Unsurprisingly, this messaging is powered by Telepathy, the framework that makes communications into a service that you can use throughout your product. Collabora, with its team of Telepathic Ninjas, helped Intel with some of the Telepathy integration for this release.

Meego 1.0 People Panel
Meego 1.0 People Panel

The People Panel shows your available contacts on the left, and a list of your ongoing conversations on the right. Each conversation lists the number of unread messages.

This integration requires no code in Telepathy, and is all done using a Telepathy Observer (I’ve talked about Observers before). This will work with Empathy, or any other well behaved chat client (as it turned out that Empathy originally wasn’t well behaved, that had to be fixed, those fixes are in Empathy 2.30).

Mission Control allows any application to ensure a communications channel exists, and find an appropriate application to handle that channel. This means that double clicking on a user or ongoing conversation in the People Panel causes Mission Control to signal Empathy (the handler) to bring that conversation to the user’s attention (pop it up) or open a new window if required, even though the People Panel and Empathy are completely separate applications and completely unaware of one another. Again this will work with any well behaved chat client.

This same feature could easily be integrated into GNOME Shell or anywhere else. The code to implement this in Meego is free software.

telepathy-glib GObject-Introspection and TpBaseClient

In Telepathy libraries such as telepathy-glib, telepathy-python and telepathy-qt4 there are two kinds of API. There's what we call low-level API, which is a direct wrapping of the D-Bus API exposed by Telepathy components and automatically generated from the Telepathy specification; and high-level API, which is hand-written and calls the low-level API internally with the goal of making it easier to implement things with Telepathy.

It's relatively easy to expose the low-level API into your language by writing an output module on top of our specparser and using your language's native D-Bus support. However writing a high-level API is a pain in the neck, and requires you to redo a lot of work that's already been done once.

Basically this sucks, so over the last couple of weeks a few of us have been working to support GObject-Introspection for the telepathy-glib high-level API. This means that we can now expose telepathy-glib functionality into languages such as Javascript (via GJS) and Python (via PyGI). We're choosing not to bind the low-level API that telepathy-glib exports, mostly because it's not very pretty and languages have better ways to expose this (N.B. this does create possible ordering problems because you are likely to have two separate DBusConnections). We're also working on extending telepathy-glib to make more things easier.

One such extension is TpBaseClient and its subclass TpSimpleObserver, which are designed to make it significantly easier to write a Telepathy clients such as Observers (TpSimpleHandler is coming soon).

The introspected bindings are not yet production ready, we're still going through and checking everything is annotated correctly, and we've also exposed a few limitations in GJS and PyGI (e.g. GArray support) that don't yet have fixes merged.

Still, putting this all together, it's now possible to write the basis of a Telepathy Observer in Javascript:

const Tp = imports.gi.TelepathyGLib;
const Mainloop = imports.mainloop;

function observe_channels(observer, account, connection, channels,
                          dispatch_op, requests, context, user_data)
{
    print("observe_channels");

    print("account = " + account.get_object_path());
    print("connection = " + connection.get_object_path());

    for (let i in channels) {
        let channel = channels[i];

        print("channel = " + channel.get_object_path());
    }

    if (dispatch_op != null)
      print("dispatch_op = " + dispatch_op.get_object_path());
    else
      print("dispatch_op = (null)");

    for (let i in requests) {
      let request = requests[i];

      print("request = " + request.get_object_path());
    }

    context.accept();
}

let dbus = Tp.DBusDaemon.dup();
let observer = Tp.SimpleObserver.new(dbus, true, "JSObserver", true,
                                     observe_channels);

observer.add_observer_filter({
  "org.freedesktop.Telepathy.Channel.ChannelType": Tp.IFACE_CHANNEL_TYPE_TEXT,
});

try {
    observer.register();
    Mainloop.run("");
} catch (e) {
    print("ERROR: " + e);
}

All of this work is now merged into telepathy-glib master, and will be released as part of the telepathy-glib 0.12.

a Telepathy blinkenlights "plugin"

A request that people occasionally make is that they would like a “blinkenlights” plugin for Empathy, similar to the one for Pidgin. The nice thing about using Telepathy, is that for any case where we simply want to observe the state of a conversation (or file transfer, or video call, etc.), we don't need to write a plugin per chat client, instead we can write what in Telepathy parlance we call an Observer.

An Observer does just that, it observes. The new logging service is an Observer that records all of the messages coming to and from the chat clients. Soon Zeitgeist will observe Telepathy channels to record when you started and finished conversation and VoIP calls for your activity journal (this is really awesome, I'm really looking forward to this). It's possible for an Observer to list currently ongoing conversations (Meego does this) and to count how many unread messages a conversation has (so long as you have a well-behaved client, that only acknowledges messages once it's sure the user has seen them).

And so with that in mind, I present all the Telepathy bits of a blinkenlights Observer. I say all the Telepathy bits, because it doesn't actually do any light blinking, and instead prints out TOTAL UNREAD MESSAGES: 2. Basically I'm hoping that someone else writes the light-blinking bit. If you do that, I'll package this up as a real program, that registers with Mission Control and everything and can be installed to make your lights blink to your heart's content.

For reference, it depends on tp-glib 0.11, cause I like using API I wrote last week. If someone wants to extend this example further (e.g. to listing the name of the sender on your LCD panel on your keyboard), jump on irc.freenode.net/#telepathy and we can help you do that too.

Finally, for the Python-people, here's an example Observer in Python. This Observer doesn't count unread messages (instead it tracks the lifetime of a conversation, like Zeitgeist will do), but it could be easily modified to count unread messages instead.

a hacky way of monitoring messages in Telepathy

Sometimes people come up with some creative solutions to solve their problems. The correct way to monitor data, such as messages, coming from Telepathy is to write an Observer, however sometimes you just want to get a feed of all of the text messages (e.g. so you can feed it to your keyboard's LCD or something).

The following is a pure D-Bus solution (although it includes telepathy.interfaces to cut down on typing). It listens to all Channel.Type.Text.Received signals, looks up the connection they came from and resolves the sender handle to a name. However note: it makes a lot more D-Bus calls than is required with Telepathy. Really you should cache the results for these handles and listen to the signals that tell you when that information has updated. If you were doing things properly, that's what you'd do.

import dbus, gobject
from dbus.mainloop.glib import DBusGMainLoop
from telepathy.interfaces import *
from datetime import datetime

dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()

def message(id, timestamp, sender, type, flags, text, path=None):

    # path is the object path of the channel, from this we can derive the
    # object path of the connection, and acquire a proxy to it
    service = '.'.join(path.split('/')[1:8])
    conn_path = '/' + service.replace('.', '/')
    conn = bus.get_object(service, conn_path)

    # request the alias and id of the sender handle
    d = dbus.Interface(conn, CONNECTION_INTERFACE_CONTACTS).GetContactAttributes([sender],
        [CONNECTION, CONNECTION_INTERFACE_ALIASING], False)
    alias = d[sender].get(CONNECTION_INTERFACE_ALIASING + '/alias',
        d[sender].get(CONNECTION + '/contact-id', "Unknown"))

    dt = datetime.fromtimestamp(timestamp)

    print "%s <%s> %s" % (dt.strftime('%H:%M'), alias, text)

# listen to all Channel.Type.Text.Received signals
bus.add_signal_receiver(message,
    dbus_interface=CHANNEL_TYPE_TEXT,
    signal_name='Received',
    path_keyword='path')

loop = gobject.MainLoop()
loop.run()

Like I said, this is not efficient use of Telepathy. If it eats your D-Bus, don't blame me.

On the other hand, this has actually started a conversation about possible new convenience classes for telepathy-python.

bowl and chopsticks

Why Telepathy is not like libpurple

It seems, even though we talk about Telepathy a lot, that there are still people out there who believe that Telepathy is really just a reimplementation of libpurple, just with an annoyingly retentive specification, and D-Bus.

The big thing to understand about Telepathy is not that it's platform independent (even though it is, and that's pretty neat). Or that it's modular (even though it is, and that's also pretty neat). The neat thing about Telepathy is that it's a service. Telepathy provides communications as a service.

What does this mean? This means that Telepathy is not just a library for enabling communications in Empathy, or Kopete. Telepathy can enable communications in everything. Any program can listen to and interact with Telepathy. This means that you can send a user files, straight from Nautilus; or share your desktop with Vinagre/Vino, via a Telepathy Tube; all without having to set up your accounts in each of these programs (this information is stored in the Account Manager, a session-wide Telepathy service responsible for maintaining accounts and connections — Empathy's accounts dialog is just a user interface to this service) or needing to establish a connection per application.

Telepathy components on the bus
Because the information you need is available everywhere, this allows communications features to be integrated into places where they make the most sense in your desktop, rather than implemented in your contact roster (like another chat client does). For instance, mail notifications that Telepathy learns about from webmail services such as MSN and Yahoo (note: not yet implemented) could be plugged into the existing mail-notification applet, or into Evolution to hint when to pull from IMAP. GNOME Shell could provide an embedded GMail-like chat-UI, with popout chat-windows provided by Empathy. All of this is possible without those applications having to have their own preferences dialog for your accounts.

telepathy-glib provides classes for talking to the the Account Manager and Channel Dispatcher, setting up channels and handling contacts. In the future this will be expanded to make it much easier to develop Tube clients and other common tasks (note: avoid libtelepathy and libmission-control, they are deprecated traps and not to be used). Hopefully soon there will be a telepathy-gtk to provide common, reusable GTK+ widgets that applications can use. Telepathy-Qt4 has made it's first shared-library pre-release for those who prefer the other toolkit. If you're interested, I've been working on the Telepathy Developer's Manual.

bump

So, I quite like the idea of Bump, a technology that enables data transfer by matching a bump read by your phone's motion sensors along with information like your GPS location. However, it does require sending all of your contact information to a 3rd party server, maybe that's fine? Maybe it's not.

It seems like this is exactly the sort of thing you could achieve using telepathy-salut (link-local XMPP) and Telepathy Tubes. You would have a MUC (multi-user, many-to-many) D-Bus tube where people's devices announced bumps, when clients found a match. Clients could then open up a 1-to-1 tube to transfer their data. Matching it slightly easier, because you'd only have to look at bumps on your MUC (plus you have the bump sensor reading + GPS)

Unfortunately this idea does full down with one problem. Multicast (as used in link-local XMPP) requires you to be on the same network segment, which it not something that can always been ensured, especially for phones and especially if there is no wireless network around. If the radios were also joined to an ad-hoc network for finding people nearby, this could work. Some wireless chipsets can multiplex their radios onto several networks, but you probably wouldn't be able to do this portably across many devices.

decorator factory for dbus-python methods

This is a crazy idea I had; that I want to share with people.

When you're implementing an object in dbus-python, you decorate your published method calls like this:

class ExampleObserver(dbus.service.Object):
    ...

    @dbus.service.method(dbus_interface=telepathy.interfaces.CLIENT_OBSERVER,
                         in_signature='ooa(oa{sv})oaoa{sv}',
                         out_signature='')
    def ObserveChannels(self, account, connection, channels, dispatch_operation,
                        requests_satisfied, observer_info):
        ...

The input and output signatures are incredibly easy to get wrong. The thing is, most D-Bus APIs (e.g. Telepathy) have a specification that contains these arguments. Some APIs (e.g. Telepathy-Python) provide generated code including interface names and constants. So why can't we do something more like?

class ExampleObserver(dbus.service.Object):
    ...

    @telepathy.decorators.Telepathy.Client.Observer.ObserveChannels
    def ObserveChannels(self, account, connection, channels, dispatch_operation,
                        requests_satisfied, observer_info):
        ...

With a decorator factory that looks up the parameters and then wraps the dbus.service.method factory.

Well, I just wrote a proof-of-concept. It looks something like this:

class Decorators(object):
    methods = {
        'org.freedesktop.DBus.Properties.GetAll': [ 's', 'a{sv}' ],
        'org.freedesktop.DBus.Properties.Get': [ 'ss', 'v' ],
        'org.freedesktop.Telepathy.Client.Observer.ObserveChannels': [ 'ooa(oa{sv})oaoa{sv}', '' ],
    }

    def __init__(self, namespace):
        self._namespace = namespace

    def __getattr__(self, key):
        return Decorators('%s.%s' % (self._namespace, key))

    def __call__(self, func):
        iface = self._namespace.rsplit('.', 1)[0]
        in_sig, out_sig = self.methods[self._namespace]
        return dbus.service.method(dbus_interface=iface,
                                   in_signature=in_sig,
                                   out_signature=out_sig)(func)

    def __str__(self):
        return self._namespace

decorators = Decorators('org.freedesktop')

Obviously in the real version, it would have a generated map of functions, or map of interfaces each with a map of functions, and a way to handle signals, but neat huh?