right then! let’s get straight into it – we need to add the following to our ‘caveConnection’ class from step 1:
-
a Connection.Interface.Requests interface (as required by 0.17.23 of the telepathy spec) for creating new communication channels
- 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
- a Channel Manager (not an interface) which can spawn Channel objects on request
- a Connect method that issues the required signals
- a Disconnect method which does the signals and cleanly shuts down the connection
- 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:
- implement the ContactAttributeInterfaces property
- make the GetContactAttributes return some minimal contact details
- 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:
- override the Send method to issue the appropriate signals including the received ‘echo’ of the sent message
- 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.
next up, extend the Connection and Channel to talk to a Real Thing – any Real Thing you like! i’m using email. 🙂
do leave a comment if you use this to ‘chat’ with any other non-chat things.