diff --git a/configure.ac b/configure.ac
index 5c25a60..b187e74 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,6 +70,7 @@ LIBECAL_REQUIRED=1.6.0
 LIBEDATASERVER_REQUIRED=1.2.0
 LIBEDATASERVERUI2_REQUIRED=1.2.0
 LIBEDATASERVERUI3_REQUIRED=2.91.6
+TELEPATHY_GLIB_MIN_VERSION=0.13.12
 
 
 # Collect more than 20 libraries for a prize!
@@ -84,7 +85,8 @@ PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION
 				 clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
                                  libstartup-notification-1.0
                                  gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
-				 libcanberra)
+				 libcanberra
+                                 telepathy-glib >= $TELEPATHY_GLIB_MIN_VERSION)
 
 GJS_VERSION=`$PKG_CONFIG --modversion gjs-internals-1.0`
 AC_DEFINE_UNQUOTED([GJS_VERSION], ["$GJS_VERSION"], [The version of GJS we're linking to])
diff --git a/js/Makefile.am b/js/Makefile.am
index 955c308..7531c4d 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -8,7 +8,6 @@ nobase_dist_js_DATA = 	\
 	misc/format.js		\
 	misc/gnomeSession.js	\
 	misc/params.js		\
-	misc/telepathy.js	\
 	misc/util.js		\
 	perf/core.js		\
 	ui/altTab.js		\
diff --git a/js/misc/telepathy.js b/js/misc/telepathy.js
deleted file mode 100644
index 6de80c0..0000000
--- a/js/misc/telepathy.js
+++ /dev/null
@@ -1,361 +0,0 @@
-/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
-
-const DBus = imports.dbus;
-
-// D-Bus utils
-function nameToPath(name) {
-    return '/' + name.replace(/\./g, '/');
-};
-
-function pathToName(path) {
-    if (path[0] != '/')
-        throw new Error('not a D-Bus path: ' + path);
-    return path.substr(1).replace(/\//g, '.');
-};
-
-// This is tp_escape_as_identifier() from telepathy-glib
-function escapeAsIdentifier(name) {
-    if (!name)
-        return '_';
-
-    // first char is replaced with _XX if it's non-alpha,
-    // later chars are replaced with _XX if they're non-alphanumeric
-    if (name.length == 1) {
-        return name.replace(/[^a-zA-Z]/, _hexEscape);
-    } else {
-        return (name[0].replace(/[^a-zA-Z]/, _hexEscape) +
-                name.substring(1).replace(/[^a-zA-Z0-9]/g, _hexEscape));
-    }
-}
-
-function _hexEscape(ch) {
-    return '_' + ch.charCodeAt(0).toString(16);
-}
-
-// Telepathy D-Bus interface definitions. Note that most of these are
-// incomplete, and only cover the methods/properties/signals that
-// we're currently using.
-
-const TELEPATHY = 'org.freedesktop.Telepathy';
-
-const CLIENT_NAME = TELEPATHY + '.Client';
-const ClientIface = {
-    name: CLIENT_NAME,
-    properties: [
-        { name: 'Interfaces',
-          signature: 'as',
-          access: 'read' }
-    ]
-};
-
-const CLIENT_APPROVER_NAME = TELEPATHY + '.Client.Approver';
-const ClientApproverIface = {
-    name: CLIENT_APPROVER_NAME,
-    methods: [
-        { name: 'AddDispatchOperation',
-          inSignature: 'a(oa{sv})oa{sv}',
-          outSignature: '' }
-    ],
-    properties: [
-        { name: 'ApproverChannelFilter',
-          signature: 'aa{sv}',
-          access: 'read' }
-    ]
-};
-
-const CLIENT_HANDLER_NAME = TELEPATHY + '.Client.Handler';
-const ClientHandlerIface = {
-    name: CLIENT_HANDLER_NAME,
-    methods: [
-        { name: 'HandleChannels',
-          inSignature: 'ooa(oa{sv})aota{sv}',
-          outSignature: '' }
-    ],
-    properties: [
-        { name: 'HandlerChannelFilter',
-          signature: 'aa{sv}',
-          access: 'read' }
-    ]
-};
-
-const CLIENT_OBSERVER_NAME = TELEPATHY + '.Client.Observer';
-const ClientObserverIface = {
-    name: CLIENT_OBSERVER_NAME,
-    methods: [
-        { name: 'ObserveChannels',
-          inSignature: 'ooa(oa{sv})oaoa{sv}',
-          outSignature: '' }
-    ],
-    properties: [
-        { name: 'ObserverChannelFilter',
-          signature: 'aa{sv}',
-          access: 'read' }
-    ]
-};
-
-const CHANNEL_DISPATCH_OPERATION_NAME = TELEPATHY + '.ChannelDispatchOperation';
-const ChannelDispatchOperationIface = {
-    name: CHANNEL_DISPATCH_OPERATION_NAME,
-    methods: [
-        { name: 'HandleWith',
-          inSignature: 's',
-          outSignature: '' },
-        { name: 'Claim',
-          inSignature: '',
-          outSignature: '' }
-    ]
-};
-let ChannelDispatchOperation = DBus.makeProxyClass(ChannelDispatchOperationIface);
-
-const CONNECTION_NAME = TELEPATHY + '.Connection';
-const ConnectionIface = {
-    name: CONNECTION_NAME,
-    signals: [
-        { name: 'StatusChanged',
-          inSignature: 'uu' }
-    ]
-};
-let Connection = DBus.makeProxyClass(ConnectionIface);
-
-const ConnectionStatus = {
-    CONNECTED:    0,
-    CONNECTING:   1,
-    DISCONNECTED: 2
-};
-
-const CONNECTION_ALIASING_NAME = CONNECTION_NAME + '.Interface.Aliasing';
-const ConnectionAliasingIface = {
-    name: CONNECTION_ALIASING_NAME,
-    methods: [
-        { name: 'RequestAliases',
-          inSignature: 'au',
-          outSignature: 'as'
-        }
-    ],
-    signals: [
-        { name: 'AliasesChanged',
-          inSignature: 'a(us)' }
-    ]
-};
-let ConnectionAliasing = DBus.makeProxyClass(ConnectionAliasingIface);
-
-const CONNECTION_AVATARS_NAME = CONNECTION_NAME + '.Interface.Avatars';
-const ConnectionAvatarsIface = {
-    name: CONNECTION_AVATARS_NAME,
-    methods: [
-        { name: 'GetKnownAvatarTokens',
-          inSignature: 'au',
-          outSignature: 'a{us}'
-        },
-        { name: 'RequestAvatars',
-          inSignature: 'au',
-          outSignature: ''
-        }
-    ],
-    signals: [
-        { name: 'AvatarRetrieved',
-          inSignature: 'usays'
-        },
-        { name: 'AvatarUpdated',
-          inSignature: 'us'
-        }
-    ]
-};
-let ConnectionAvatars = DBus.makeProxyClass(ConnectionAvatarsIface);
-
-const CONNECTION_CONTACTS_NAME = CONNECTION_NAME + '.Interface.Contacts';
-const ConnectionContactsIface = {
-    name: CONNECTION_CONTACTS_NAME,
-    methods: [
-        { name: 'GetContactAttributes',
-          inSignature: 'auasb',
-          outSignature: 'a{ua{sv}}'
-        }
-    ]
-};
-let ConnectionContacts = DBus.makeProxyClass(ConnectionContactsIface);
-
-const CONNECTION_REQUESTS_NAME = CONNECTION_NAME + '.Interface.Requests';
-const ConnectionRequestsIface = {
-    name: CONNECTION_REQUESTS_NAME,
-    methods: [
-        { name: 'CreateChannel',
-          inSignature: 'a{sv}',
-          outSignature: 'oa{sv}'
-        },
-        { name: 'EnsureChannel',
-          inSignature: 'a{sv}',
-          outSignature: 'boa{sv}'
-        }
-    ],
-    properties: [
-        { name: 'Channels',
-          signature: 'a(oa{sv})',
-          access: 'read' }
-    ],
-    signals: [
-        { name: 'NewChannels',
-          inSignature: 'a(oa{sv})'
-        },
-        { name: 'ChannelClosed',
-          inSignature: 'o'
-        }
-    ]
-};
-let ConnectionRequests = DBus.makeProxyClass(ConnectionRequestsIface);
-
-const CONNECTION_SIMPLE_PRESENCE_NAME = CONNECTION_NAME + '.Interface.SimplePresence';
-const ConnectionSimplePresenceIface = {
-    name: CONNECTION_SIMPLE_PRESENCE_NAME,
-    methods: [
-        { name: 'SetPresence',
-          inSignature: 'ss'
-        },
-        { name: 'GetPresences',
-          inSignature: 'au',
-          outSignature: 'a{u(uss)}'
-        }
-    ],
-    signals: [
-        { name: 'PresencesChanged',
-          inSignature: 'a{u(uss)}' }
-    ]
-};
-let ConnectionSimplePresence = DBus.makeProxyClass(ConnectionSimplePresenceIface);
-
-const ConnectionPresenceType = {
-    UNSET:         0,
-    OFFLINE:       1,
-    AVAILABLE:     2,
-    AWAY:          3,
-    EXTENDED_AWAY: 4,
-    HIDDEN:        5,
-    BUSY:          6,
-    UNKNOWN:       7,
-    ERROR:         8
-};
-
-const HandleType = {
-    NONE:    0,
-    CONTACT: 1,
-    ROOM:    2,
-    LIST:    3,
-    GROUP:   4
-};
-
-const CHANNEL_NAME = TELEPATHY + '.Channel';
-const ChannelIface = {
-    name: CHANNEL_NAME,
-    signals: [
-        { name: 'Closed',
-          inSignature: '' }
-    ]
-};
-let Channel = DBus.makeProxyClass(ChannelIface);
-
-const CHANNEL_TEXT_NAME = CHANNEL_NAME + '.Type.Text';
-const ChannelTextIface = {
-    name: CHANNEL_TEXT_NAME,
-    methods: [
-        { name: 'ListPendingMessages',
-          inSignature: 'b',
-          outSignature: 'a(uuuuus)'
-        },
-        { name: 'AcknowledgePendingMessages',
-          inSignature: 'au',
-          outSignature: ''
-        },
-        { name: 'Send',
-          inSignature: 'us',
-          outSignature: ''
-        }
-    ],
-    signals: [
-        { name: 'Received',
-          inSignature: 'uuuuus' },
-        { name: 'Sent',
-          inSignature: 'uus' }
-    ]
-};
-let ChannelText = DBus.makeProxyClass(ChannelTextIface);
-
-const ChannelTextMessageType = {
-    NORMAL: 0,
-    ACTION: 1,
-    NOTICE: 2,
-    AUTO_REPLY: 3,
-    DELIVERY_REPORT: 4
-};
-
-const CHANNEL_CONTACT_LIST_NAME = CHANNEL_NAME + '.Type.ContactList';
-// There is no interface associated with ContactList; it's just a
-// special kind of Channel.Interface.Group
-
-const CHANNEL_GROUP_NAME = CHANNEL_NAME + '.Interface.Group';
-const ChannelGroupIface = {
-    name: CHANNEL_GROUP_NAME,
-    properties: [
-        { name: 'Members',
-          signature: 'au',
-          access: 'read' }
-    ],
-    signals: [
-        { name: 'MembersChanged',
-          inSignature: 'sauauauauuu' }
-    ]
-};
-let ChannelGroup = DBus.makeProxyClass(ChannelGroupIface);
-
-const ACCOUNT_MANAGER_NAME = TELEPATHY + '.AccountManager';
-const AccountManagerIface = {
-    name: ACCOUNT_MANAGER_NAME,
-    properties: [
-        { name: 'ValidAccounts',
-          signature: 'ao',
-          access: 'read' }
-    ],
-    signals: [
-        { name: 'AccountValidityChanged',
-          inSignature: 'ob' }
-    ]
-};
-let AccountManager = DBus.makeProxyClass(AccountManagerIface);
-
-const ACCOUNT_NAME = TELEPATHY + '.Account';
-const AccountIface = {
-    name: ACCOUNT_NAME,
-    properties: [
-        { name: 'Connection',
-          signature: 'o',
-          access: 'read' }
-    ]
-};
-let Account = DBus.makeProxyClass(AccountIface);
-
-const CHANNEL_DISPATCHER_NAME = TELEPATHY + '.ChannelDispatcher';
-const ChannelDispatcherIface = {
-    name: CHANNEL_DISPATCHER_NAME,
-    methods: [
-        { name: 'EnsureChannel',
-          inSignature: 'oa{sv}xs',
-          outSignature: 'o' }
-    ]
-};
-let ChannelDispatcher = DBus.makeProxyClass(ChannelDispatcherIface);
-
-const CHANNEL_REQUEST_NAME = TELEPATHY + '.ChannelRequest';
-const ChannelRequestIface = {
-    name: CHANNEL_REQUEST_NAME,
-    methods: [
-        { name: 'Proceed',
-          inSignature: '',
-          outSignature: '' }
-    ],
-    signals: [
-        { name: 'Failed',
-          signature: 'ss' },
-        { name: 'Succeeded',
-          signature: '' }
-    ]
-};
-let ChannelRequest = DBus.makeProxyClass(ChannelRequestIface);
diff --git a/js/ui/telepathyClient.js b/js/ui/telepathyClient.js
index 8dcbfed..60591d5 100644
--- a/js/ui/telepathyClient.js
+++ b/js/ui/telepathyClient.js
@@ -4,17 +4,16 @@ const DBus = imports.dbus;
 const GLib = imports.gi.GLib;
 const Lang = imports.lang;
 const Mainloop = imports.mainloop;
+const Shell = imports.gi.Shell;
 const Signals = imports.signals;
 const St = imports.gi.St;
+const Tp = imports.gi.TelepathyGLib;
 const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
 
 const Main = imports.ui.main;
 const MessageTray = imports.ui.messageTray;
-const Telepathy = imports.misc.telepathy;
 
-let contactManager;
-let channelDispatcher;
 
 // See Notification.appendMessage
 const SCROLLBACK_IMMEDIATE_TIME = 60; // 1 minute
@@ -22,33 +21,15 @@ const SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes
 const SCROLLBACK_RECENT_LENGTH = 20;
 const SCROLLBACK_IDLE_LENGTH = 5;
 
-// A 'Qualified_Property_Value_Map' that represents a single-user
-// text-based chat.
-let singleUserTextChannel = {};
-singleUserTextChannel[Telepathy.CHANNEL_NAME + '.ChannelType'] = Telepathy.CHANNEL_TEXT_NAME;
-singleUserTextChannel[Telepathy.CHANNEL_NAME + '.TargetHandleType'] = Telepathy.HandleType.CONTACT;
-
-// Some protocols only support 'multi-user' chats, and single-user
-// chats are just treated as multi-user chats with only one other
-// participant. Telepathy uses HandleType.NONE for all chats in these
-// protocols; there's no good way for us to tell if the channel is
-// single- or multi-user.
-let oneOrMoreUserTextChannel = {};
-oneOrMoreUserTextChannel[Telepathy.CHANNEL_NAME + '.ChannelType'] = Telepathy.CHANNEL_TEXT_NAME;
-oneOrMoreUserTextChannel[Telepathy.CHANNEL_NAME + '.TargetHandleType'] = Telepathy.HandleType.NONE;
-
-// The (non-chat) channel indicating the users whose presence
-// information we subscribe to
-let subscribedContactsChannel = {};
-subscribedContactsChannel[Telepathy.CHANNEL_NAME + '.ChannelType'] = Telepathy.CHANNEL_CONTACT_LIST_NAME;
-subscribedContactsChannel[Telepathy.CHANNEL_NAME + '.TargetHandleType'] = Telepathy.HandleType.LIST;
-subscribedContactsChannel[Telepathy.CHANNEL_NAME + '.TargetID'] = 'subscribe';
-
 const NotificationDirection = {
     SENT: 'chat-sent',
     RECEIVED: 'chat-received'
 };
 
+let contactFeatures = [Tp.ContactFeature.ALIAS,
+                        Tp.ContactFeature.AVATAR_DATA,
+                        Tp.ContactFeature.PRESENCE];
+
 // This is GNOME Shell's implementation of the Telepathy 'Client'
 // interface. Specifically, the shell is a Telepathy 'Observer', which
 // lets us see messages even if they belong to another app (eg,
@@ -60,489 +41,179 @@ function Client() {
 
 Client.prototype = {
     _init : function() {
-        let name = Telepathy.CLIENT_NAME + '.GnomeShell';
-        DBus.session.exportObject(Telepathy.nameToPath(name), this);
-        DBus.session.acquire_name(name, DBus.SINGLE_INSTANCE,
-                                  function (name) { /* FIXME: acquired */ },
-                                  function (name) { /* FIXME: lost */ });
-
         this._accounts = {};
+        // channel path -> Source
         this._sources = {};
 
-        contactManager = new ContactManager();
-        contactManager.connect('presence-changed', Lang.bind(this, this._presenceChanged));
-
-        channelDispatcher = new Telepathy.ChannelDispatcher(DBus.session,
-                                                            Telepathy.CHANNEL_DISPATCHER_NAME,
-                                                            Telepathy.nameToPath(Telepathy.CHANNEL_DISPATCHER_NAME));
-
-        // Acquire existing connections. (Needed to make things work
-        // through a restart.)
-        let accountManager = new Telepathy.AccountManager(DBus.session,
-                                                          Telepathy.ACCOUNT_MANAGER_NAME,
-                                                          Telepathy.nameToPath(Telepathy.ACCOUNT_MANAGER_NAME));
-        accountManager.GetRemote('ValidAccounts', Lang.bind(this,
-            function (accounts, err) {
-                if (!accounts)
-                    return;
-
-                for (let i = 0; i < accounts.length; i++)
-                    this._gotAccount(accounts[i]);
-            }));
-        accountManager.connect('AccountValidityChanged', Lang.bind(this, this._accountValidityChanged));
-    },
-
-    _accountValidityChanged: function(accountManager, accountPath, valid) {
-        if (!valid) {
-            delete this._accounts[accountPath];
-            // We don't need to clean up connections, sources, etc; they'll
-            // get Closed and cleaned up independently.
-        } else
-            this._gotAccount(accountPath);
-    },
+        // Set up a SimpleObserver, which will call _observeChannels whenever a
+        // channel matching its filters is detected.
+        // The second argument, recover, means _observeChannels will be run
+        // for any existing channel as well.
+        let dbus = Tp.DBusDaemon.dup();
+        this._observer = Tp.SimpleObserver.new(dbus, true, 'GnomeShell', true,
+                                              Lang.bind(this, this._observeChannels));
 
-    _gotAccount: function(accountPath) {
-        let account = new Telepathy.Account(DBus.session,
-                                            Telepathy.ACCOUNT_MANAGER_NAME,
-                                            accountPath);
-        this._accounts[accountPath] = account;
-        account.GetRemote('Connection', Lang.bind(this,
-            function (connPath, err) {
-                if (!connPath || connPath == '/')
-                    return;
-
-                let connReq = new Telepathy.ConnectionRequests(DBus.session,
-                                                               Telepathy.pathToName(connPath),
-                                                               connPath);
-                connReq.GetRemote('Channels', Lang.bind(this,
-                    function(channels, err) {
-                        if (!channels)
-                            return;
-
-                        this._addChannels(accountPath, connPath, channels);
-                    }));
-
-                contactManager.addConnection(connPath);
-            }));
-    },
-
-    get Interfaces() {
-        return [ Telepathy.CLIENT_OBSERVER_NAME ];
-    },
-
-    get ObserverChannelFilter() {
-        return [ singleUserTextChannel, oneOrMoreUserTextChannel ];
-    },
+        // We only care about single-user text-based chats
+        let props = {};
+        props[Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE] = Tp.IFACE_CHANNEL_TYPE_TEXT;
+        props[Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE] = Tp.HandleType.CONTACT;
+        this._observer.add_observer_filter(props);
 
-    ObserveChannels: function(accountPath, connPath, channels,
-                              dispatchOperation, requestsSatisfied,
-                              observerInfo) {
-        this._addChannels(accountPath, connPath, channels);
+        try {
+            this._observer.register();
+        } catch (e) {
+            throw new Error('Couldn\'t register SimpleObserver. Error: \n' + e);
+        }
     },
 
-    _addChannels: function(accountPath, connPath, channelDetailsList) {
-        for (let i = 0; i < channelDetailsList.length; i++) {
-            let [channelPath, props] = channelDetailsList[i];
-
-            // If this is being called from the startup code then it
-            // won't have passed through our filters, so we need to
-            // check the channel/targetHandle type ourselves.
-
-            let channelType = props[Telepathy.CHANNEL_NAME + '.ChannelType'];
-            if (channelType != Telepathy.CHANNEL_TEXT_NAME)
-                continue;
-
-            let targetHandleType = props[Telepathy.CHANNEL_NAME + '.TargetHandleType'];
-            if (targetHandleType != Telepathy.HandleType.CONTACT &&
-                targetHandleType != Telepathy.HandleType.NONE)
-                continue;
-
-            let targetHandle = props[Telepathy.CHANNEL_NAME + '.TargetHandle'];
-            let targetId = props[Telepathy.CHANNEL_NAME + '.TargetID'];
-
-            if (this._sources[connPath + ':' + targetHandle])
-                continue;
+    _observeChannels: function(observer, account, conn, channels,
+                               dispatchOp, requests, context) {
+        let connPath = conn.get_object_path();
+        let len = channels.length;
+        for (let i = 0; i < len; i++) {
+            let channel = channels[i];
+            let [targetHandle, targetHandleType] = channel.get_handle();
+
+            /* Only observe contact text channels */
+            if ((!(channel instanceof Tp.TextChannel)) ||
+               targetHandleType != Tp.HandleType.CONTACT)
+               continue;
+
+            /* Request a TpContact */
+            Shell.get_tp_contacts(conn, 1, [targetHandle],
+                    contactFeatures.length, contactFeatures,
+                    Lang.bind(this,  function (connection, contacts, failed) {
+                        if (contacts.length < 1)
+                            return;
 
-            let source = new Source(accountPath, connPath, channelPath,
-                                    targetHandle, targetHandleType, targetId);
-            this._sources[connPath + ':' + targetHandle] = source;
-            source.connect('destroy', Lang.bind(this,
-                function() {
-                    delete this._sources[connPath + ':' + targetHandle];
-                }));
+                        /* We got the TpContact */
+                        this._createSource(account, conn, channel, contacts[0]);
+                    }), null);
         }
+
+        // Allow dbus method to return
+        context.accept();
     },
 
-    _presenceChanged: function(contactManager, connPath, handle,
-                               type, message) {
-        let source = this._sources[connPath + ':' + handle];
-        if (!source)
+    _createSource: function(account, conn, channel, contact) {
+        if (this._sources[channel.get_object_path()])
             return;
 
-        source.setPresence(type, message);
+        let source = new Source(account, conn, channel, contact);
+
+        this._sources[channel.get_object_path()] = source;
+        source.connect('destroy', Lang.bind(this,
+                       function() {
+                           delete this._sources[channel.get_object_path()];
+                       }));
     }
 };
-DBus.conformExport(Client.prototype, Telepathy.ClientIface);
-DBus.conformExport(Client.prototype, Telepathy.ClientObserverIface);
 
+function Source(account, conn, channel, contact) {
+    this._init(account, conn, channel, contact);
+}
 
-function ContactManager() {
-    this._init();
-};
+Source.prototype = {
+    __proto__:  MessageTray.Source.prototype,
 
-ContactManager.prototype = {
-    _init: function() {
-        this._connections = {};
-        // Note that if we changed this to '/telepathy/avatars' then
-        // we would share cache files with empathy. But since this is
-        // not documented/guaranteed, it seems a little sketchy
-        this._cacheDir = GLib.get_user_cache_dir() + '/gnome-shell/avatars';
-    },
+    _init: function(account, conn, channel, contact) {
+        MessageTray.Source.prototype._init.call(this, channel.get_identifier());
 
-    addConnection: function(connPath) {
-        let info = this._connections[connPath];
-        if (info)
-            return info;
-
-        info = {};
-
-        // Figure out the cache subdirectory for this connection by
-        // parsing the connection manager name (eg, 'gabble') and
-        // protocol name (eg, 'jabber') from the Connection's path.
-        // Telepathy requires the D-Bus path for a connection to have
-        // a specific form, and explicitly says that clients are
-        // allowed to parse it.
-        let match = connPath.match(/\/org\/freedesktop\/Telepathy\/Connection\/([^\/]*\/[^\/]*)\/.*/);
-        if (!match)
-            throw new Error('Could not parse connection path ' + connPath);
-
-        info.cacheDir = this._cacheDir + '/' + match[1];
-        GLib.mkdir_with_parents(info.cacheDir, 0x1c0); // 0x1c0 = octal 0700
-
-        // info.names[handle] is @handle's real name
-        // info.tokens[handle] is the token for @handle's avatar
-        info.names = {};
-        info.tokens = {};
-
-        // info.icons[handle] is an array of the icon actors currently
-        // being displayed for @handle. These will be updated
-        // automatically if @handle's avatar changes.
-        info.icons = {};
-
-        let connName = Telepathy.pathToName(connPath);
-
-        info.connectionAvatars = new Telepathy.ConnectionAvatars(DBus.session, connName, connPath);
-        info.updatedId = info.connectionAvatars.connect(
-            'AvatarUpdated', Lang.bind(this, this._avatarUpdated));
-        info.retrievedId = info.connectionAvatars.connect(
-            'AvatarRetrieved', Lang.bind(this, this._avatarRetrieved));
-
-        info.connectionContacts = new Telepathy.ConnectionContacts(DBus.session, connName, connPath);
-
-        info.connectionPresence = new Telepathy.ConnectionSimplePresence(DBus.session, connName, connPath);
-        info.presenceChangedId = info.connectionPresence.connect(
-            'PresencesChanged', Lang.bind(this, this._presencesChanged));
-
-        let conn = new Telepathy.Connection(DBus.session, connName, connPath);
-        info.statusChangedId = conn.connect('StatusChanged', Lang.bind(this,
-            function (status, reason) {
-                if (status == Telepathy.ConnectionStatus.DISCONNECTED)
-                    this._removeConnection(conn);
-            }));
-
-        let connReq = new Telepathy.ConnectionRequests(DBus.session,
-                                                       connName, connPath);
-        connReq.EnsureChannelRemote(subscribedContactsChannel, Lang.bind(this,
-            function (result, err) {
-                if (!result)
-                    return;
-
-                let [mine, channelPath, props] = result;
-                this._gotContactsChannel(connPath, channelPath, props);
-            }));
-
-        this._connections[connPath] = info;
-        return info;
-    },
+        this.isChat = true;
 
-    _gotContactsChannel: function(connPath, channelPath, props) {
-        let info = this._connections[connPath];
-        if (!info)
-            return;
+        this._account = account;
+        this._contact = contact;
 
-        info.contactsGroup = new Telepathy.ChannelGroup(DBus.session,
-                                                        Telepathy.pathToName(connPath),
-                                                        channelPath);
-        info.contactsListChangedId =
-            info.contactsGroup.connect('MembersChanged', Lang.bind(this, this._contactsListChanged, info));
-
-        info.contactsGroup.GetRemote('Members', Lang.bind(this,
-            function(contacts, err) {
-                if (!contacts)
-                    return;
-
-                info.connectionContacts.GetContactAttributesRemote(
-                    contacts, [Telepathy.CONNECTION_ALIASING_NAME], false,
-                    Lang.bind(this, this._gotContactAttributes, info));
-            }));
-    },
+        this._conn = conn;
+        this._channel = channel;
+        this._closedId = this._channel.connect('invalidated', Lang.bind(this, this._channelClosed));
 
-    _contactsListChanged: function(group, message, added, removed,
-                                   local_pending, remote_pending,
-                                   actor, reason, info) {
-        for (let i = 0; i < removed.length; i++)
-            delete info.names[removed[i]];
+        this._updateAlias();
 
-        info.connectionContacts.GetContactAttributesRemote(
-            added, [Telepathy.CONNECTION_ALIASING_NAME], false,
-            Lang.bind(this, this._gotContactAttributes, info));
-    },
+        this._notification = new Notification(this);
+        this._notification.setUrgency(MessageTray.Urgency.HIGH);
 
-    _gotContactAttributes: function(attrs, err, info) {
-        if (!attrs)
-            return;
+        this._presence = contact.get_presence_type();
 
-        for (let handle in attrs)
-            info.names[handle] = attrs[handle][Telepathy.CONNECTION_ALIASING_NAME + '/alias'];
-    },
+        this._sentId = this._channel.connect('message-sent', Lang.bind(this, this._messageSent));
+        this._receivedId = this._channel.connect('message-received', Lang.bind(this, this._messageReceived));
 
-    _presencesChanged: function(conn, presences, err) {
-        if (!presences)
-            return;
+        this._setSummaryIcon(this.createNotificationIcon());
 
-        let info = this._connections[conn.getPath()];
-        if (!info)
-            return;
+        this._notifyAliasId = this._contact.connect('notify::alias', Lang.bind(this, this._updateAlias));
+        this._notifyAvatarId = this._contact.connect('notify::avatar-file', Lang.bind(this, this._updateAvatarIcon));
+        this._presenceChangedId = this._contact.connect('presence-changed', Lang.bind(this, this._presenceChanged));
 
-        for (let handle in presences) {
-            let [type, status, message] = presences[handle];
-            this.emit('presence-changed', conn.getPath(), handle, type, message);
-        }
+        this._displayPendingMessages();
     },
 
-    _removeConnection: function(conn) {
-        let info = this._connections[conn.getPath()];
-        if (!info)
-            return;
+    _updateAlias: function() {
+        this.title = this._contact.get_alias();
+    },
 
-        conn.disconnect(info.statusChangedId);
-        info.connectionAvatars.disconnect(info.updatedId);
-        info.connectionAvatars.disconnect(info.retrievedId);
-        info.connectionPresence.disconnect(info.presenceChangedId);
-        info.contactsGroup.disconnect(info.contactsListChangedId);
+    createNotificationIcon: function() {
+        this._iconBox = new St.Bin({ style_class: 'avatar-box' });
+        this._iconBox._size = this.ICON_SIZE;
 
-        delete this._connections[conn.getPath()];
-    },
+        this._updateAvatarIcon();
 
-    _getFileForToken: function(info, token) {
-        return info.cacheDir + '/' + Telepathy.escapeAsIdentifier(token);
+        return this._iconBox;
     },
 
-    _setIcon: function(iconBox, info, handle) {
+    _updateAvatarIcon: function() {
         let textureCache = St.TextureCache.get_default();
-        let token = info.tokens[handle];
-        let file;
-
-        if (token) {
-            file = this._getFileForToken(info, token);
-            if (!GLib.file_test(file, GLib.FileTest.EXISTS))
-                file = null;
-        }
+        let file = this._contact.get_avatar_file();
 
         if (file) {
-            let uri = GLib.filename_to_uri(file, null);
-            iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size);
+            let uri = file.get_uri();
+            this._iconBox.child = textureCache.load_uri_async(uri, this._iconBox._size, this._iconBox._size);
         } else {
-            iconBox.child = new St.Icon({ icon_name: 'stock_person',
-                                          icon_type: St.IconType.FULLCOLOR,
-                                          icon_size: iconBox._size });
+            this._iconBox.child = new St.Icon({ icon_name: 'stock_person',
+                                                icon_type: St.IconType.FULLCOLOR,
+                                                icon_size: this._iconBox._size });
         }
     },
 
-    _updateIcons: function(info, handle) {
-        if (!info.icons[handle])
-            return;
-
-        for (let i = 0; i < info.icons[handle].length; i++) {
-            let iconBox = info.icons[handle][i];
-            this._setIcon(iconBox, info, handle);
-        }
-    },
-
-    _avatarUpdated: function(conn, handle, token) {
-        let info = this._connections[conn.getPath()];
-        if (!info)
-            return;
-
-        if (info.tokens[handle] == token)
-            return;
-
-        info.tokens[handle] = token;
-        if (token != '') {
-            let file = this._getFileForToken(info, token);
-            if (!GLib.file_test(file, GLib.FileTest.EXISTS)) {
-                info.connectionAvatars.RequestAvatarsRemote([handle]);
-                return;
-            }
-        }
-
-        this._updateIcons(info, handle);
-    },
-
-    _avatarRetrieved: function(conn, handle, token, avatarData, mimeType) {
-        let info = this._connections[conn.getPath()];
-        if (!info)
-            return;
-
-        let file = this._getFileForToken(info, token);
-        let success = false;
-        try {
-            success = GLib.file_set_contents(file, avatarData, avatarData.length);
-        } catch (e) {
-            logError(e, 'Error caching avatar data');
-        }
-
-        if (success)
-            this._updateIcons(info, handle);
-    },
-
-    createAvatar: function(conn, handle, size) {
-        let iconBox = new St.Bin({ style_class: 'avatar-box' });
-        iconBox._size = size;
-
-        let info = this._connections[conn.getPath()];
-        if (!info)
-            info = this.addConnection(conn.getPath());
-
-        if (!info.icons[handle])
-            info.icons[handle] = [];
-        info.icons[handle].push(iconBox);
-
-        iconBox.connect('destroy', Lang.bind(this,
-            function() {
-                let i = info.icons[handle].indexOf(iconBox);
-                if (i != -1)
-                    info.icons[handle].splice(i, 1);
-            }));
-
-        // If we already have the icon cached and know its token, this
-        // will fill it in. Otherwise it will fill in the default
-        // icon.
-        this._setIcon(iconBox, info, handle);
-
-        // Asynchronously load the real avatar if we don't have it yet.
-        if (info.tokens[handle] == null) {
-            info.connectionAvatars.GetKnownAvatarTokensRemote([handle], Lang.bind(this,
-                function (tokens, err) {
-                    let token = tokens && tokens[handle] ? tokens[handle] : '';
-                    this._avatarUpdated(conn, handle, token);
-                }));
-        }
-
-        return iconBox;
-    }
-};
-Signals.addSignalMethods(ContactManager.prototype);
-
-
-function Source(accountPath, connPath, channelPath, targetHandle, targetHandleType, targetId) {
-    this._init(accountPath, connPath, channelPath, targetHandle, targetHandleType, targetId);
-}
-
-Source.prototype = {
-    __proto__:  MessageTray.Source.prototype,
-
-    _init: function(accountPath, connPath, channelPath, targetHandle, targetHandleType, targetId) {
-        MessageTray.Source.prototype._init.call(this, targetId);
-
-        this.isChat = true;
-
-        this._accountPath = accountPath;
-
-        let connName = Telepathy.pathToName(connPath);
-        this._conn = new Telepathy.Connection(DBus.session, connName, connPath);
-        this._channel = new Telepathy.Channel(DBus.session, connName, channelPath);
-        this._closedId = this._channel.connect('Closed', Lang.bind(this, this._channelClosed));
-
-        this._targetHandle = targetHandle;
-        this._targetHandleType = targetHandleType;
-        this._targetId = targetId;
-
-        if (targetHandleType == Telepathy.HandleType.CONTACT) {
-            let aliasing = new Telepathy.ConnectionAliasing(DBus.session, connName, connPath);
-            aliasing.RequestAliasesRemote([this._targetHandle], Lang.bind(this,
-                function (aliases, err) {
-                    if (aliases && aliases.length)
-                        this.title = aliases[0];
-                }));
-        }
-
-        this._notification = new Notification(this);
-        this._notification.setUrgency(MessageTray.Urgency.HIGH);
-
-        // Since we only create sources when receiving a message, this
-        // is a plausible default
-        this._presence = Telepathy.ConnectionPresenceType.AVAILABLE;
-
-        this._channelText = new Telepathy.ChannelText(DBus.session, connName, channelPath);
-        this._sentId = this._channelText.connect('Sent', Lang.bind(this, this._messageSent));
-        this._receivedId = this._channelText.connect('Received', Lang.bind(this, this._messageReceived));
-
-        this._channelText.ListPendingMessagesRemote(false, Lang.bind(this, this._gotPendingMessages));
-
-        this._setSummaryIcon(this.createNotificationIcon());
-    },
-
-    createNotificationIcon: function() {
-        return contactManager.createAvatar(this._conn, this._targetHandle,
-                                           this.ICON_SIZE);
-    },
-
     _notificationClicked: function(notification) {
-        channelDispatcher.EnsureChannelRemote(this._accountPath,
-                                              { 'org.freedesktop.Telepathy.Channel.ChannelType': Telepathy.CHANNEL_TEXT_NAME,
-                                                'org.freedesktop.Telepathy.Channel.TargetHandle': this._targetHandle,
-                                                'org.freedesktop.Telepathy.Channel.TargetHandleType': this._targetHandleType },
-                                              global.get_current_time(),
-                                              '',
-                                              Lang.bind(this, this._gotChannelRequest));
-    },
+        let props = {};
+        props[Tp.PROP_CHANNEL_CHANNEL_TYPE] = Tp.IFACE_CHANNEL_TYPE_TEXT;
+        [props[Tp.PROP_CHANNEL_TARGET_HANDLE], props[Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE]] = this._channel.get_handle();
 
-    _gotChannelRequest: function (chanReqPath, ex) {
-        if (ex) {
-            log ('EnsureChannelRemote failed? ' + ex);
-            return;
-        }
+        let req = Tp.AccountChannelRequest.new(this._account, props, global.get_current_time());
 
-        let chanReq = new Telepathy.ChannelRequest(DBus.session, Telepathy.CHANNEL_DISPATCHER_NAME, chanReqPath);
-        chanReq.ProceedRemote();
+        req.ensure_channel_async('', null, null);
     },
 
-    _gotPendingMessages: function(msgs, err) {
-        if (!msgs)
-            return;
+    _displayPendingMessages: function() {
+        let msgs = this._channel.get_pending_messages();
 
-        for (let i = 0; i < msgs.length; i++)
-            this._messageReceived.apply(this, [this._channel].concat(msgs[i]));
+        for (let i = 0; i < msgs.length; i++) {
+            let msg = msgs[i];
+            this._messageReceived(this._channel, msg);
+        }
     },
 
     _channelClosed: function() {
         this._channel.disconnect(this._closedId);
-        this._channelText.disconnect(this._receivedId);
-        this._channelText.disconnect(this._sentId);
+        this._channel.disconnect(this._receivedId);
+        this._channel.disconnect(this._sentId);
+
+        this._contact.disconnect(this._notifyAliasId);
+        this._contact.disconnect(this._notifyAvatarId);
+        this._contact.disconnect(this._presenceChangedId);
+
         this.destroy();
     },
 
-    _messageReceived: function(channel, id, timestamp, sender,
-                               type, flags, text) {
-        this._notification.appendMessage(text, timestamp, NotificationDirection.RECEIVED);
+    _messageReceived: function(channel, message) {
+        this._notification.appendMessage(message, NotificationDirection.RECEIVED);
         this.notify();
     },
 
     // This is called for both messages we send from
     // our client and other clients as well.
-    _messageSent: function(channel, timestamp, type, text) {
-        this._notification.appendMessage(text, timestamp, NotificationDirection.SENT);
+    _messageSent: function(channel, message, flags, token) {
+        this._notification.appendMessage(message, NotificationDirection.SENT);
     },
 
     notify: function() {
@@ -553,24 +224,28 @@ Source.prototype = {
     },
 
     respond: function(text) {
-        this._channelText.SendRemote(Telepathy.ChannelTextMessageType.NORMAL, text);
+        let msg = Tp.ClientMessage.new_text(Tp.ChannelTextMessageType.NORMAL, text);
+        this._channel.send_message_async(msg, 0, null);
     },
 
-    setPresence: function(presence, message) {
+    _presenceChanged: function (contact, presence, type, status, message) {
         let msg, shouldNotify;
 
-        if (presence == Telepathy.ConnectionPresenceType.AVAILABLE) {
+        if (this._presence == presence)
+          return;
+
+        if (presence == Tp.ConnectionPresenceType.AVAILABLE) {
             msg = _("%s is online.").format(this.title);
-            shouldNotify = (this._presence == Telepathy.ConnectionPresenceType.OFFLINE);
-        } else if (presence == Telepathy.ConnectionPresenceType.OFFLINE ||
-                   presence == Telepathy.ConnectionPresenceType.EXTENDED_AWAY) {
-            presence = Telepathy.ConnectionPresenceType.OFFLINE;
+            shouldNotify = (this._presence == Tp.ConnectionPresenceType.OFFLINE);
+        } else if (presence == Tp.ConnectionPresenceType.OFFLINE ||
+                   presence == Tp.ConnectionPresenceType.EXTENDED_AWAY) {
+            presence = Tp.ConnectionPresenceType.OFFLINE;
             msg = _("%s is offline.").format(this.title);
-            shouldNotify = (this._presence != Telepathy.ConnectionPresenceType.OFFLINE);
-        } else if (presence == Telepathy.ConnectionPresenceType.AWAY) {
+            shouldNotify = (this._presence != Tp.ConnectionPresenceType.OFFLINE);
+        } else if (presence == Tp.ConnectionPresenceType.AWAY) {
             msg = _("%s is away.").format(this.title);
             shouldNotify = false;
-        } else if (presence == Telepathy.ConnectionPresenceType.BUSY) {
+        } else if (presence == Tp.ConnectionPresenceType.BUSY) {
             msg = _("%s is busy.").format(this.title);
             shouldNotify = false;
         } else
@@ -606,7 +281,10 @@ Notification.prototype = {
         this._timestampTimeoutId = 0;
     },
 
-    appendMessage: function(text, timestamp, direction) {
+    appendMessage: function(message, direction) {
+        let [text, flags] = message.to_text();
+        let timestamp = message.get_received_timestamp();
+
         this.update(this.source.title, text, { customContent: true });
         this._append(text, direction, timestamp);
     },
diff --git a/src/Makefile.am b/src/Makefile.am
index a7a38d8..be84d6b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -234,6 +234,7 @@ Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) St-1.0.gir libgnome-shell.la Makefile
 		--include=Clutter-1.0			\
 		--include=ClutterX11-1.0		\
 		--include=Meta-2.91			\
+		--include=TelepathyGLib-0.12		\
 		--libtool="$(LIBTOOL)"			\
 		--add-include-path=$(builddir)		\
 		--include=St-1.0			\
diff --git a/src/shell-global.c b/src/shell-global.c
index ec00690..de17526 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -1964,3 +1964,63 @@ gboolean _shell_global_check_xdnd_event (ShellGlobal  *global,
 
     return FALSE;
 }
+
+/**
+ * ShellGetTpContactCb:
+ * @connection: The connection
+ * @contacts: (element-type TelepathyGLib.Contact): List of contacts
+ * @failed: Array of failed contacts
+ */
+
+static void
+shell_global_get_tp_contacts_cb (TpConnection *self,
+                                 guint n_contacts,
+                                 TpContact * const *contacts,
+                                 guint n_failed,
+                                 const TpHandle *failed,
+                                 const GError *error,
+                                 gpointer user_data,
+                                 GObject *weak_object)
+{
+  int i;
+  GList *contact_list = NULL;
+  for (i = 0; i < n_contacts; i++) {
+      contact_list = g_list_append(contact_list, contacts[i]);
+  }
+
+  TpHandle *failed_list = g_new0 (TpHandle, n_failed + 1);
+  memcpy(failed_list, failed, n_failed);
+
+  ((ShellGetTpContactCb)user_data)(self, contact_list, failed_list);
+}
+
+
+/**
+ * shell_get_tp_contacts:
+ * @self: A connection, which must be ready
+ * @n_handles: Number of handles in handles
+ * @handles: (array length=n_handles) (element-type uint): Array of handles
+ * @n_features: Number of features in features
+ * @features: (array length=n_features) (allow-none) (element-type uint):
+ *  Array of features
+ * @callback: (scope async): User callback to run when the contacts are ready
+ *
+ * Wrap tp_connection_get_contacts_by_handle so we can transform the array
+ * into a null-terminated one, which gjs can handle.
+ * We send the original callback to tp_connection_get_contacts_by_handle as
+ * user_data, and we have our own function as callback, which does the
+ * transforming.
+ */
+void
+shell_get_tp_contacts (TpConnection *self,
+                       guint n_handles,
+                       const TpHandle *handles,
+                       guint n_features,
+                       const TpContactFeature *features,
+                       ShellGetTpContactCb callback)
+{
+  tp_connection_get_contacts_by_handle(self, n_handles, handles,
+                                       n_features, features,
+                                       shell_global_get_tp_contacts_cb,
+                                       callback, NULL, NULL);
+}
diff --git a/src/shell-global.h b/src/shell-global.h
index 1afbdc4..5d1e13d 100644
--- a/src/shell-global.h
+++ b/src/shell-global.h
@@ -8,6 +8,8 @@
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include <gtk/gtk.h>
 
+#include <telepathy-glib/telepathy-glib.h>
+
 G_BEGIN_DECLS
 
 typedef struct _ShellGlobal      ShellGlobal;
@@ -142,6 +144,17 @@ void shell_global_play_theme_sound (ShellGlobal       *global,
 
 void shell_global_init_xdnd (ShellGlobal *global);
 
+typedef void (*ShellGetTpContactCb) (TpConnection *connection,
+                                     GList *contacts,
+                                     TpHandle *failed);
+
+void shell_get_tp_contacts (TpConnection *self,
+                            guint n_handles,
+                            const TpHandle *handles,
+                            guint n_features,
+                            const TpContactFeature *features,
+                            ShellGetTpContactCb callback);
+
 G_END_DECLS
 
 #endif /* __SHELL_GLOBAL_H__ */
diff --git a/tools/build/gnome-shell.modules b/tools/build/gnome-shell.modules
index 91803d6..23385f6 100644
--- a/tools/build/gnome-shell.modules
+++ b/tools/build/gnome-shell.modules
@@ -16,6 +16,8 @@
   <repository type="cvs" name="libxklavier.freedesktop.org"
       cvsroot=":pserver:anoncvs@anoncvs.freedesktop.org:/cvs/xklavier"
       password=""/>
+  <repository type="git" name="git.collabora.co.uk"
+      href="git://git.collabora.co.uk/git/"/>
 
   <autotools id="gobject-introspection">
     <branch repo="git.gnome.org" module="gobject-introspection"/>
@@ -211,6 +213,13 @@
     </dependencies>
   </autotools>
 
+  <autotools id="telepathy-glib">
+    <branch repo="git.collabora.co.uk" module="telepathy-glib"/>
+    <dependencies>
+      <dep package="gobject-introspection"/>
+    </dependencies>
+  </autotools>
+
   <autotools id="gnome-shell">
     <branch repo="git.gnome.org" module="gnome-shell"/>
     <dependencies>
@@ -227,6 +236,7 @@
         <dep package="libcanberra"/>
 	<dep package="gnome-settings-daemon"/>
 	<dep package="gnome-bluetooth"/>
+        <dep package="telepathy-glib"/>
     </dependencies>
   </autotools>
 
