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 flickr.com

    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 Connection.py:

#!/usr/bin/python

import dbus, weakref
from telepathy.server import Connection, ConnectionInterfaceRequests, Handle
from telepathy import HANDLE_TYPE_CONTACT, CONNECTION_STATUS_CONNECTING,\
    CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED,\
    CONNECTION_STATUS_REASON_REQUESTED

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,
    ConnectionInterfaceRequests,
    caveContacts):

    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)
        ConnectionInterfaceRequests.__init__(self)
        caveContacts.__init__(self)

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

    # 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):
        self.StatusChanged(CONNECTION_STATUS_CONNECTED,
            CONNECTION_STATUS_REASON_REQUESTED)

    def Disconnect(self):
        self.StatusChanged(CONNECTION_STATUS_DISCONNECTED,
            CONNECTION_STATUS_REASON_REQUESTED)
        # stop handling all channels
        self._channel_manager.close()
        # stop handling this connection
        self._manager.disconnected(self)

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 Contacts.py):

#!/usr/bin/python

import dbus
from telepathy.server import ConnectionInterfaceContacts
from telepathy import CONNECTION, CONNECTION_INTERFACE_CONTACTS,\
    HANDLE_TYPE_CONTACT

# Contacts interface with our minimal requirements implemented
class caveContacts(ConnectionInterfaceContacts):
    def __init__(self):
        ConnectionInterfaceContacts.__init__(self)
        self._implement_property_get(CONNECTION_INTERFACE_CONTACTS,
            {'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 ChannelManager.py):

#!/usr/bin/python

import dbus
from telepathy.server import ChannelManager
from telepathy import CHANNEL_INTERFACE, CHANNEL_TYPE_TEXT, HANDLE_TYPE_CONTACT

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
        self.implement_channel_classes(
            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':
                dbus.UInt32(HANDLE_TYPE_CONTACT)},
            [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,
            object_path=path)

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:

#!/usr/bin/python

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):
        self.remove_from_connection()
        self.Closed()

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.

About these ads

2 thoughts on “telepathy-python ‘echo’ protocol – step 2

  1. eri says:

    i tried to implement this, but chat window does not appear at new dialog…

    • whyohwhyohwhyoh says:

      yup. i’m running this on fedora 17 now and the ‘echo’ protocol doesn’t even appear in the ‘what kind of chat account do you have’ list. i think the newer versions of ‘telepathy mission control’ want more of the telepathy API implemented before they’ll find/show your protocol.

      i’ll look into it at some point. however, i’m not too hopeful that it’ll be easy or even possible – i know that the python library wasn’t high on the telepathy community’s agenda last time i contacted them.

      obviously, do let me know if you figure it out for yourself. sorry it didn’t work for you straight away.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s