telepathy-python ‘echo’ protocol – step 2

right then!  let’s get straight into it – we need to add the following to our ‘caveConnection’ class from step 1:

  1. long-exposure photograph of circular light trails in a tunnel

    sciee di luce by i k o on

    a Connection.Interface.Requests interface (as required by 0.17.23 of the telepathy spec) for creating new communication channels

  2. a Connection.Interface.Contacts interface (as required by 0.17.23 of the telepathy spec) so we can say who we want to talk to .. even though we’re actually not talking to anyone yet
  3. a Channel Manager (not an interface) which can spawn Channel objects on request
  4. a Connect method that issues the required signals
  5. a Disconnect method which does the signals and cleanly shuts down the connection
  6. some extra magic in support of the above gleaned from the telepathy-python source and butterfly source.

the actual Contacts interface and Channel Manager are split off into more separate files we’ll come to in a minute, so here’s the resultant, updated


import dbus, weakref
from telepathy.server import Connection, ConnectionInterfaceRequests, Handle

from Constants import PROTOCOL, PROGRAM
from Contacts import caveContacts
from ChannelManager import caveChannelManager

# many connections per manager -> class for connections
# make a fancy new-type connection with a 'Requests' interface
class caveConnection(Connection,

    def __init__(self, manager, parameters):
        self._manager = weakref.proxy(manager)
        # create a new channel manager and tell it we're it's connection
        self._channel_manager = caveChannelManager(self)

        # assume we have an 'account' name passed to us
        Connection.__init__(self, PROTOCOL, parameters['account'], PROGRAM)

        self._self_handle = Handle(
            self.get_handle_id(), HANDLE_TYPE_CONTACT,
        self._handles[HANDLE_TYPE_CONTACT, self._self_handle.get_id()] =\

    # borrowed from butterfly, required by telepathy's channel init
    def handle(self, handle_type, handle_id):
        self.check_handle(handle_type, handle_id)
        return self._handles[handle_type, handle_id]

    def Connect(self):

    def Disconnect(self):
        # stop handling all channels
        # stop handling this connection

so, getting somewhat more involved but still not too hairy!

the Contacts interface is handled by extending a telepathy-python ConnectionInterfaceContacts object, just as we have extended its Connection object above.  This time we need to add two things:

  1. implement the ContactAttributeInterfaces property
  2. make the GetContactAttributes return some minimal contact details
  3. a bit more magic copied from butterfly

and that looks like this (which i’ve saved as


import dbus
from telepathy.server import ConnectionInterfaceContacts

# Contacts interface with our minimal requirements implemented
class caveContacts(ConnectionInterfaceContacts):
    def __init__(self):
            {'ContactAttributeInterfaces' :
            lambda:  dbus.Array([CONNECTION], signature='s')})

    # Overwrite the dbus attribute to get the sender argument
    @dbus.service.method(CONNECTION_INTERFACE_CONTACTS, in_signature='auasb',
        out_signature='a{ua{sv}}', sender_keyword='sender')
    def GetContactAttributes(self, handles, interfaces, hold, sender):

        # this is required to allow the channel to close down correctly
        if hold: self.HoldHandles(HANDLE_TYPE_CONTACT, handles, sender)

        ret = dbus.Dictionary(signature='ua{sv}')
        for handle in handles:
            ret[handle] = dbus.Dictionary(signature='sv')
            ret[handle][CONNECTION + "/contact-id"] =\
                self.InspectHandles(HANDLE_TYPE_CONTACT, [handle])[0]
        return ret

next up is the Channel Manager.  here we extend the telepathy-python ChannelManager by specifying which channel types can be created (just the text type here) and by providing a callback to create a new channel of that type.

that looks like this (saved as


import dbus
from telepathy.server import ChannelManager

from Channel import caveTextChannel

# a Channel Manager with our required channels built in
class caveChannelManager(ChannelManager):
    def __init__(self, conn):
        self.__text_channel_id = 0
        ChannelManager.__init__(self, conn)
        # ChannelManager magic for handling channels
            CHANNEL_TYPE_TEXT, self._get_text_channel, [
            # accepting text channels to/from a contact allows empathy to
            # offer 'new conversation'
            ({CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_TEXT,
            CHANNEL_INTERFACE + '.TargetHandleType':
            [CHANNEL_INTERFACE + '.TargetHandle',
            CHANNEL_INTERFACE + '.TargetID'])

    def _get_text_channel(self, props):
        # make up a name for the channel
        path = "TextChannel%d" % self.__text_channel_id
        self.__text_channel_id += 1
        return caveTextChannel(self._conn, self, props,

you can see at the end there that the callback returns a caveTextChannel object.  can you guess what that is?!  yup, we’re extending telepathy-python’s ChannelTypeText class in order to provide a Channel.Type.Text interface for our Channel object.  here we add the following:

  1. override the Send method to issue the appropriate signals including the received ‘echo’ of the sent message
  2. override the Close method to shut things down nicely and issue signals

we really ought to use the newer SendMessage method but the old Send is simpler and, i think, sufficient for this demonstration.

so, that looks like this:


from time import time
from telepathy.server import ChannelTypeText

class caveTextChannel(ChannelTypeText):
    def Send(self, message_type, text):
        # tell the chat client, "yeah, i sent that"
        self.Sent(int(time()), message_type, text)
        # now tell the chat client we got a reply 🙂
        self.Received(0, int(time()), self._handle.get_id(),\
            message_type, 0, text)

# make sure the channel shuts down properly
    def Close(self):

and we’re done!

save that lot and fire up empathy (or equivalent) and add an ‘echo’ account.  since we haven’t implemented a contact list (as there aren’t actually any real contacts out there) you need to start conversations ‘blind’ – go to ‘New Conversation…’ in the ‘Chat’ menu, make up a contact name and click ‘Chat’.  a chat window should appear and any message you ‘send’ should get sent right back to you.

screenshot of 'echo' chat

the "five year old child" chat protocol

next up, extend the Connection and Channel to talk to a Real Thingany Real Thing you like!  i’m using email.  🙂

do leave a comment if you use this to ‘chat’ with any other non-chat things.

telepathy-python ‘echo’ protocol – step 1

i thought i’d make a telepathy plugin so that i could use empathy to talk to some colleagues who don’t have access to an IM client and instead send me many short messages over email.

the telepathy dbus API is documented and there is documentation for telepathy-python but the examples are all on the client side.  for a server-side example the docs refer us to the ‘butterfly’ MSN connector (citation required) .. which is quite big.

a shaft of light in a sandy cave

"Beam Me Up" by Kevin Eddy on flickr

so, several days of experimentation, reading telepathy-python and butterfly source got me a working email connector and i thought it’d be useful to strip that down even further to an “echo” connector – which just repeats anything you say, like an annoying kid brother, as a reference and example.

what i’ve NOT created is a “reference implementation” – i don’t understand the dbus API docs well enough for that.  i think i’ve left out lots of required elements.  but it Works™ – at least empathy will let me create an account, start a new conversation and type stuff, which gets repeated.  i can disable and re-enable the account without breaking anything – which is nice.

so, today:

step 1 – advertising the service

you will need: dbus, a telepathy client (e.g. empathy), the telepathy python module (and whatever modules it depends on)

the spec says we should have a name for the messaging protocol we’re implementing and a different name for the program which implements it.  so i’m calling the protocol “echo” and the program “cave”.  the actual script will be called “telepathy-cave” in line with similar programs.

here’s the code for the main program:


# IMPORTANT! makes asynchronous dbus things work
from dbus.mainloop.glib import DBusGMainLoop

# get the mainloop before creating the ConnectionManager
# so that dbus (telepathy) can use it
from gobject import MainLoop
ml = MainLoop()

from cave import caveConnectionManager

# get telepathy to start listening for our dbus stuff via the mainloop
# and ... Go!

the most obvious thing is that it’s running a gobject MainLoop – you wouldn’t want it setting up the Connection Manager and then quitting!  the dbus python library uses the mainloop to do it’s event-driven stuff.

that DBusGMainLoop() had me foxed for days.  the error message you get if you miss it out:

RuntimeError: 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(…)

isn’t all that helpful – since we can’t fix it by “passing mainloop=” or by “calling dbus.set_default_main_loop” because it’s telepathy-python that’s doing that bit.  googling for telepathy gets a whole bunch of unhelpful E.S.P. articles!  and the butterfly code uses the old way of doing this .. which is Evil™! – it goes like this:

import dbus.glib

nope, i haven’t missed anything out there – just importing the module makes a critical something happen.  like i said, not good.  glad they’ve got rid of that – just need to update butterfly to use the new method!  once i’d figured this out i noticed that all the current examples in the documentation do this right – it’s even the very first piece of example code – unfortunately i’d given up looking there because it all seemed client-based.

anyhoo, the last thing that happens before we kick off the MainLoop is we import and create the caveConnectionManager.  that’s my telepathy Connection Manager which looks like this (you can save this as


# create a telepathy ConnectionManager for 'cave'
import dbus
from telepathy.server import ConnectionManager

from Constants import PROTOCOL, PROGRAM
from Connection import caveConnection

class caveConnectionManager(ConnectionManager):
    def __init__(self):
        ConnectionManager.__init__(self, PROGRAM)
        # use telepathy magic to provide required methods
        self._protos[PROTOCOL] = caveConnection

    def GetParameters(self, proto):
        from telepathy import NotImplemented, CONN_MGR_PARAM_FLAG_REQUIRED
        if proto != PROTOCOL:
            raise NotImplemented('unknown protocol %s' % proto)
        return [('account', CONN_MGR_PARAM_FLAG_REQUIRED, 's', '')]

the connection manager sets itself up on the dbus session bus letting the world know that it can create telepathy connections to handle a certain set of protocols – in this case, only “echo”.  when a client requests (over dbus) a ‘echo’ connection the connection manager will create a new Connection instance to handle the request.

you can see that the telepathy module has done plenty of the work for us – there’s no code here for talking to dbus or handling client requests – we just need to tell it our name and what protocol we’re providing and what class does the actual work.  it’s even set up the GetParameters dbus method for us – we just override it to provide the correct response.

to complete the picture we need a couple more files – and  Constants is nice and easy:


PROTOCOL = 'echo'
PROGRAM = 'cave'

i just put them there so they didn’t get duplicated across files. will get a bit more involved later on but here’s what it looks like for now while it’s not doing anything at all:


from telepathy.server import Connection
from Constants import PROTOCOL, PROGRAM

class caveConnection(Connection):

    def __init__(self, manager, parameters):
         # assume we have an 'account' name passed to us
        Connection.__init__(self, PROTOCOL, parameters['account'], PROGRAM)

so, much like the Connection Manager, we let the telepathy module do the work, implementing the basis of the Connection interface API for us.

if you save that lot and run telepathy-cave then you should see “echo” in the list of protocols available in empathy’s “add new account” interface and you should be able to create a new account using it.

next up, creating a channel and sending messages on it!