diff --git a/.gitignore b/.gitignore
index e77e1df..73b3a63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,8 @@ Makefile
 *.pyc
 *.loT
 .semanticache
+.\#*
+\#*\#
 
 ChangeLog
 stamp-h1
diff --git a/extensions/Connection_Interface_Client_Types.xml b/extensions/Connection_Interface_Client_Types.xml
new file mode 100644
index 0000000..0822122
--- /dev/null
+++ b/extensions/Connection_Interface_Client_Types.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Client_Types"
+  xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+  <tp:copyright>Copyright (C) 2010 Collabora Ltd.</tp:copyright>
+  <tp:license xmlns="http://www.w3.org/1999/xhtml">
+    <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+  </tp:license>
+  <interface name="org.freedesktop.Telepathy.Connection.Interface.ClientTypes.DRAFT"
+    tp:causes-havoc="experimental">
+    <tp:added version="0.19.UNRELEASED">(as draft)</tp:added>
+    <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+    <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+      <p>An interface on connections to support protocols which allows users to
+        subscribe to the client types of their contacts.</p>
+
+      <p>One can connect to instant messaging networks on a huge variety of
+        devices, from PCs, to phones to consoles. It can be useful for users
+        to know what kind of device a contact is using so that he or she
+        can decide not to send that big file or start a video chat. This
+        interface exposes exactly this information for clients to display.</p>
+
+      <p>The client types are represented in strings, using the values
+        <a href="http://xmpp.org/registrar/disco-categories.html#client">
+        documented by the XMPP registrar</a> with some additional types
+        added for other protocols. A contact can set one or more client types
+        so this interface returns a list of strings to denote client types
+        for a contact. The well-known client types to be used are:</p>
+
+      <ul>
+        <li>bot</li>
+        <li>console (minimal non-GUI client used on dumb terminals or
+          text-only screens, <strong>not</strong> a games console)</li>
+        <li>handheld</li>
+        <li>pc</li>
+        <li>phone</li>
+        <li>web</li>
+        <li>sms (the client is not actually an instant messaging client
+          but all messages sent to this contact will be delivered as SMSs)</li>
+        <li>game (a gaming device)</li>
+      </ul>
+
+      <p>If the empty list is given as the client types, this means that
+        details about the contact's client types are unknown. If there are
+        multiple resources of a contact online at one point in time, the
+        client types of the most available resource will be returned. In
+        other words, the client types are the types of the client whose
+        SimplePresence we see. For example, if a contact has two
+        resources:</p>
+
+      <ul>
+        <li>one his phone, with presence "available", and</li>
+        <li>one his pc, with presence "busy",</li>
+      </ul>
+
+      <p>then the methods in this interface will return an array (with
+      one element: "phone") as the client types as that is the more
+      available resource. If some time later his phone's presence
+      moves to "away", then the
+      <tp:member-ref>ClientTypesUpdated</tp:member-ref> signal will
+      notify that his client type have changed from "phone" to "pc",
+      because "busy" is a more available presence than "away".</p>
+
+    </tp:docstring>
+
+    <tp:mapping name="Contact_Client_Types">
+      <tp:docstring>
+        A mapping from contact handle to client types.
+      </tp:docstring>
+      <tp:member type="u" tp:type="Contact_Handle" name="Contact">
+        <tp:docstring>
+          A contact.
+        </tp:docstring>
+      </tp:member>
+      <tp:member type="as" name="Client_Types">
+        <tp:docstring>
+          The contact's client types as documented earlier in this interface.
+        </tp:docstring>
+      </tp:member>
+    </tp:mapping>
+
+    <method name="GetClientTypes" tp:name-for-bindings="Get_Client_Types">
+      <tp:docstring>
+        Return the client types of the given contacts, if they are
+        already known. If any of the given contacts' client types are
+        not known, request their current client types, but return
+        immediately without waiting for a reply; if a reply with a
+        non-empty client type array is later received for those
+        contacts, the
+        <tp:member-ref>ClientTypesUpdated</tp:member-ref> signal will
+        be emitted for them.
+
+        <tp:rationale>
+          This method is appropriate for "lazy" client type finding, for instance
+          displaying the client types (if available) of everyone in your contact
+          list.
+        </tp:rationale>
+      </tp:docstring>
+
+      <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+        <tp:docstring>
+          The contacts whose client types should be returned or signalled.
+        </tp:docstring>
+      </arg>
+
+      <arg direction="out" name="Client_Types" type="a{uas}"
+        tp:type="Contact_Client_Types">
+        <tp:docstring>
+          The contacts' client types, if already known. Contacts whose client
+          types are not already known are omitted from the mapping; contacts known
+          to have no client type information appear in the mapping with an empty
+          list.
+        </tp:docstring>
+      </arg>
+
+      <tp:possible-errors>
+        <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+      </tp:possible-errors>
+    </method>
+
+    <method name="RequestClientTypes" tp:name-for-bindings="Request_Client_Types">
+      <tp:docstring>
+        Return the current client types of the given contact. If necessary, make
+        a request to the server for up-to-date information, and wait for a
+        reply.
+
+        <tp:rationale>
+          This method is appropriate for use in a "Contact Information..."
+          dialog; it can be used to show progress information (while waiting
+          for the method to return), and can distinguish between various error
+          conditions.
+        </tp:rationale>
+      </tp:docstring>
+
+      <arg direction="in" name="Contact" type="u" tp:type="Contact_Handle">
+        <tp:docstring>
+          The contact whose client types should be returned.
+        </tp:docstring>
+      </arg>
+
+      <arg direction="out" name="Client_Types" type="as">
+        <tp:docstring>
+          The contact's client types. It MAY be empty, indicating that no client
+          type information was found.
+        </tp:docstring>
+      </arg>
+
+      <tp:possible-errors>
+        <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied">
+          <tp:docstring>
+            The requested contact does not allow the local user to see their
+            client type information.
+          </tp:docstring>
+        </tp:error>
+      </tp:possible-errors>
+    </method>
+
+    <signal name="ClientTypesUpdated" tp:name-for-bindings="Client_Types_Updated">
+      <tp:docstring>
+        Emitted when a contact's client types change or become known.
+      </tp:docstring>
+
+      <arg name="Contact" type="u" tp:type="Contact_Handle">
+        <tp:docstring>
+          The contact.
+        </tp:docstring>
+      </arg>
+      <arg name="Client_Types" type="as">
+        <tp:docstring>
+          The contact's client types, or an empty list to indicate that nothing
+          is known about the contact's client types.
+        </tp:docstring>
+      </arg>
+    </signal>
+
+    <tp:contact-attribute name="client-types" type="as">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+        <p>The same mapping that would be returned by
+          <tp:member-ref>GetClientTypes</tp:member-ref> for this contact.
+          Omitted from the result if the contact's client types are not
+          known.</p>
+      </tp:docstring>
+    </tp:contact-attribute>
+
+  </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
index a60994b..cb47a9e 100644
--- a/extensions/Makefile.am
+++ b/extensions/Makefile.am
@@ -14,6 +14,7 @@ EXTRA_DIST = \
     misc.xml \
     Debug.xml \
     Channel_Interface_Conference.xml \
+    Connection_Interface_Client_Types.xml \
     Logger.xml \
     $(NULL)
 
diff --git a/extensions/misc.xml b/extensions/misc.xml
index 6fe06d8..91a51a9 100644
--- a/extensions/misc.xml
+++ b/extensions/misc.xml
@@ -7,5 +7,6 @@
 <xi:include href="Debug.xml" />
 <xi:include href="Channel_Interface_Conference.xml" />
 <xi:include href="Logger.xml" />
+<xi:include href="Connection_Interface_Client_Types.xml" />
 
 </tp:spec>
diff --git a/help/.gitignore b/help/.gitignore
index 00b46d3..ca48443 100644
--- a/help/.gitignore
+++ b/help/.gitignore
@@ -13,6 +13,7 @@ gl/*.xml
 it/*.xml
 ru/*.xml
 sv/*.xml
+zh_CN/*.xml
 
 ca/*.page
 cs/*.page
@@ -26,3 +27,4 @@ gl/*.page
 it/*.page
 ru/*.page
 sv/*.page
+zh_CN/*.page
\ No newline at end of file
diff --git a/libempathy-gtk/empathy-contact-widget.c b/libempathy-gtk/empathy-contact-widget.c
index 4ff25b9..6cc86f4 100644
--- a/libempathy-gtk/empathy-contact-widget.c
+++ b/libempathy-gtk/empathy-contact-widget.c
@@ -83,6 +83,7 @@ typedef struct
   EmpathyContactWidgetFlags flags;
   guint widget_id_timeout;
   gulong fav_sig_id;
+  gulong client_type_notify_id;
 
   GtkWidget *vbox_contact_widget;
 
@@ -97,6 +98,7 @@ typedef struct
   GtkWidget *label_alias;
   GtkWidget *hbox_presence;
   GtkWidget *image_state;
+  GtkWidget *hbox_client_type;
   GtkWidget *label_status;
   GtkWidget *table_contact;
   GtkWidget *vbox_avatar;
@@ -893,6 +895,42 @@ location_key_to_label (const gchar *key)
 }
 
 static void
+contact_widget_notify_client_type_cb (EmpathyContact *contact,
+    GParamSpec *pspec,
+    EmpathyContactWidget *information)
+{
+  const gchar *type;
+
+  type = empathy_contact_get_client_type (contact);
+
+  if (!tp_strdiff (type, "phone"))
+    gtk_widget_show (information->hbox_client_type);
+  else
+    gtk_widget_hide (information->hbox_client_type);
+}
+
+static void
+contact_widget_client_type_update (EmpathyContactWidget *information)
+{
+  /* Client type */
+  if (information->flags & EMPATHY_CONTACT_WIDGET_SHOW_CLIENT_TYPE)
+    {
+
+      if (information->client_type_notify_id == 0)
+        {
+          information->client_type_notify_id =
+	      g_signal_connect (information->contact, "notify::client-type",
+                  G_CALLBACK (contact_widget_notify_client_type_cb),
+		  information);
+	}
+
+      contact_widget_notify_client_type_cb (information->contact, NULL,
+	  information);
+    }
+
+}
+
+static void
 contact_widget_location_update (EmpathyContactWidget *information)
 {
   GHashTable *location;
@@ -1424,6 +1462,13 @@ contact_widget_remove_contact (EmpathyContactWidget *information)
       g_signal_handlers_disconnect_by_func (information->contact,
           contact_widget_groups_notify_cb, information);
 
+      if (information->client_type_notify_id != 0)
+        {
+          g_signal_handler_disconnect (information->contact,
+	      information->client_type_notify_id);
+	  information->client_type_notify_id = 0;
+	}
+
       tp_contact = empathy_contact_get_tp_contact (information->contact);
       if (tp_contact != NULL)
         {
@@ -1562,6 +1607,7 @@ contact_widget_set_contact (EmpathyContactWidget *information,
   contact_widget_details_update (information);
   contact_widget_client_update (information);
   contact_widget_location_update (information);
+  contact_widget_client_type_update (information);
 }
 
 static void
@@ -1854,6 +1900,7 @@ empathy_contact_widget_new (EmpathyContact *contact,
 
   information = g_slice_new0 (EmpathyContactWidget);
   information->flags = flags;
+  information->client_type_notify_id = 0;
 
   filename = empathy_file_lookup ("empathy-contact-widget.ui",
       "libempathy-gtk");
@@ -1862,6 +1909,7 @@ empathy_contact_widget_new (EmpathyContact *contact,
        "hbox_contact", &information->hbox_contact,
        "hbox_presence", &information->hbox_presence,
        "label_alias", &information->label_alias,
+       "hbox_client_type", &information->hbox_client_type,
        "image_state", &information->image_state,
        "table_contact", &information->table_contact,
        "vbox_avatar", &information->vbox_avatar,
diff --git a/libempathy-gtk/empathy-contact-widget.h b/libempathy-gtk/empathy-contact-widget.h
index fb684a4..a1afabd 100644
--- a/libempathy-gtk/empathy-contact-widget.h
+++ b/libempathy-gtk/empathy-contact-widget.h
@@ -64,6 +64,7 @@ typedef enum
   EMPATHY_CONTACT_WIDGET_EDIT_FAVOURITE = 1 << 8,
   EMPATHY_CONTACT_WIDGET_SHOW_DETAILS = 1 << 9,
   EMPATHY_CONTACT_WIDGET_EDIT_DETAILS = 1 << 10,
+  EMPATHY_CONTACT_WIDGET_SHOW_CLIENT_TYPE = 1 << 11,
 } EmpathyContactWidgetFlags;
 
 GtkWidget * empathy_contact_widget_new (EmpathyContact *contact,
diff --git a/libempathy-gtk/empathy-contact-widget.ui b/libempathy-gtk/empathy-contact-widget.ui
index 0792dcf..f5fb144 100644
--- a/libempathy-gtk/empathy-contact-widget.ui
+++ b/libempathy-gtk/empathy-contact-widget.ui
@@ -92,6 +92,35 @@
                 <property name="position">1</property>
               </packing>
             </child>
+            <child>
+              <object class="GtkHBox" id="hbox_client_type">
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkImage" id="image_phone">
+                    <property name="visible">True</property>
+                    <property name="icon_name">phone</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Online from a phone or mobile device</property>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
           </object>
           <packing>
             <property name="position">0</property>
diff --git a/libempathy-gtk/empathy-individual-store.c b/libempathy-gtk/empathy-individual-store.c
index 623e356..c91f62c 100644
--- a/libempathy-gtk/empathy-individual-store.c
+++ b/libempathy-gtk/empathy-individual-store.c
@@ -47,6 +47,8 @@
 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
 #include <libempathy/empathy-debug.h>
 
+#include <extensions/extensions.h>
+
 /* Active users are those which have recently changed state
  * (e.g. online, offline or from normal to a busy state).
  */
@@ -337,6 +339,86 @@ individual_store_remove_individual (EmpathyIndividualStore *self,
   g_list_free (iters);
 }
 
+typedef struct
+{
+  const gchar *id;
+  const gchar *account;
+  GList *list;
+} FindIndividualData;
+
+static gboolean
+individual_store_client_type_notify_find_individual (GtkTreeModel *model,
+    GtkTreePath *path,
+    GtkTreeIter *iter,
+    gpointer user_data)
+{
+  FolksIndividual *individual;
+  GList *personas, *l;
+  FindIndividualData *data = user_data;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+
+  if (individual == NULL)
+    return FALSE;
+
+  personas = folks_individual_get_personas (individual);
+  for (l = personas; l != NULL; l = l->next)
+    {
+      FolksPersona *persona = l->data;
+      FolksPersonaStore *store;
+      const gchar *account_object_path;
+
+      store = folks_persona_get_store (persona);
+
+      account_object_path = tp_proxy_get_object_path (
+	  tpf_persona_store_get_account (TPF_PERSONA_STORE (store)));
+
+      if (!tp_strdiff (folks_persona_get_uid (persona), data->id)
+	  && !tp_strdiff (account_object_path, data->account))
+        {
+	  data->list = g_list_append (data->list, gtk_tree_iter_copy (iter));
+	  break;
+	}
+    }
+
+  tp_clear_object (&individual);
+
+  return FALSE;
+}
+
+static void
+individual_store_client_type_notify_cb (GObject *obj,
+    GParamSpec *pspec,
+    gpointer user_data)
+{
+  EmpathyIndividualStore *self = user_data;
+  EmpathyContact *contact = EMPATHY_CONTACT (obj);
+  GList *l;
+  FindIndividualData *data;
+
+  data = g_slice_new0 (FindIndividualData);
+  data->id = empathy_contact_get_id (contact);
+  data->account = tp_proxy_get_object_path (empathy_contact_get_account (contact));
+  data->list = NULL;
+
+  gtk_tree_model_foreach (GTK_TREE_MODEL (self),
+      individual_store_client_type_notify_find_individual,
+      data);
+
+  for (l = data->list; l != NULL; l = l->next)
+    {
+      gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+          EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPE,
+          empathy_contact_get_client_type (contact),
+          -1);
+    }
+
+  g_list_foreach (data->list, (GFunc) gtk_tree_iter_free, NULL);
+  g_list_free (data->list);
+  g_slice_free (FindIndividualData, data);
+}
+
 static void
 individual_store_add_individual (EmpathyIndividualStore *self,
     FolksIndividual *individual)
@@ -427,6 +509,10 @@ individual_store_add_individual (EmpathyIndividualStore *self,
 
   individual_store_contact_update (self, individual);
 
+  g_signal_connect (contact, "notify::client-type",
+      G_CALLBACK (individual_store_client_type_notify_cb),
+      self);
+
   tp_clear_object (&contact);
 }
 
@@ -732,6 +818,8 @@ individual_store_contact_update (EmpathyIndividualStore *self,
               EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL,
                 empathy_contact_get_capabilities (contact) &
                   EMPATHY_CAPABILITIES_VIDEO,
+              EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPE,
+                empathy_contact_get_client_type (contact),
               -1);
         }
     }
@@ -1388,6 +1476,7 @@ individual_store_setup (EmpathyIndividualStore *self)
     G_TYPE_BOOLEAN,             /* Can make video calls */
     EMPATHY_TYPE_INDIVIDUAL_MANAGER_FLAGS,      /* Flags */
     G_TYPE_BOOLEAN,             /* Is a fake group */
+    G_TYPE_STRING,              /* Client type */
   };
 
   priv = GET_PRIV (self);
diff --git a/libempathy-gtk/empathy-individual-store.h b/libempathy-gtk/empathy-individual-store.h
index 9c85582..23f439c 100644
--- a/libempathy-gtk/empathy-individual-store.h
+++ b/libempathy-gtk/empathy-individual-store.h
@@ -65,6 +65,7 @@ typedef enum
   EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL,
   EMPATHY_INDIVIDUAL_STORE_COL_FLAGS,
   EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP,
+  EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPE,
   EMPATHY_INDIVIDUAL_STORE_COL_COUNT,
 } EmpathyIndividualStoreCol;
 
diff --git a/libempathy-gtk/empathy-individual-view.c b/libempathy-gtk/empathy-individual-view.c
index 7083b23..fa0c1d3 100644
--- a/libempathy-gtk/empathy-individual-view.c
+++ b/libempathy-gtk/empathy-individual-view.c
@@ -299,7 +299,8 @@ individual_view_query_tooltip_cb (EmpathyIndividualView *view,
     {
       priv->tooltip_widget = empathy_contact_widget_new (contact,
           EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
-          EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
+          EMPATHY_CONTACT_WIDGET_SHOW_LOCATION |
+          EMPATHY_CONTACT_WIDGET_SHOW_CLIENT_TYPE);
       gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
       g_object_ref (priv->tooltip_widget);
       g_signal_connect (priv->tooltip_widget, "destroy",
@@ -1113,6 +1114,33 @@ individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
 }
 
 static void
+individual_view_client_type_cell_data_func (GtkTreeViewColumn *tree_column,
+    GtkCellRenderer *cell,
+    GtkTreeModel *model,
+    GtkTreeIter *iter,
+    EmpathyIndividualView *view)
+{
+  gboolean is_group;
+  gboolean is_active;
+  gchar *type;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
+      EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPE, &type,
+      -1);
+
+  g_object_set (cell,
+      "visible", !is_group && !tp_strdiff (type, "phone"),
+      "icon-name", "phone",
+      NULL);
+
+  g_free (type);
+
+  individual_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
     GtkCellRenderer *cell,
     GtkTreeModel *model,
@@ -1576,6 +1604,17 @@ individual_view_constructed (GObject *object)
   gtk_tree_view_column_add_attribute (col, cell,
       "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
 
+  /* Client type */
+  cell = gtk_cell_renderer_pixbuf_new ();
+  gtk_tree_view_column_pack_start (col, cell, FALSE);
+  gtk_tree_view_column_set_cell_data_func (col, cell,
+      (GtkTreeCellDataFunc) individual_view_client_type_cell_data_func,
+      view, NULL);
+
+  g_object_set (cell,
+      "visible", FALSE,
+      NULL);
+
   /* Audio Call Icon */
   cell = empathy_cell_renderer_activatable_new ();
   gtk_tree_view_column_pack_start (col, cell, FALSE);
diff --git a/libempathy/empathy-contact.c b/libempathy/empathy-contact.c
index 6842cde..8255b36 100644
--- a/libempathy/empathy-contact.c
+++ b/libempathy/empathy-contact.c
@@ -36,6 +36,8 @@
 #include <geoclue/geoclue-geocode.h>
 #endif
 
+#include <extensions/extensions.h>
+
 #include "empathy-contact.h"
 #include "empathy-utils.h"
 #include "empathy-enum-types.h"
@@ -67,8 +69,10 @@ typedef struct {
    */
   GHashTable *location;
   GHashTable *groups;
+  gchar *client_type;
 } EmpathyContactPriv;
 
+static void contact_constructed (GObject *object);
 static void contact_finalize (GObject *object);
 static void contact_get_property (GObject *object, guint param_id,
     GValue *value, GParamSpec *pspec);
@@ -103,7 +107,8 @@ enum
   PROP_HANDLE,
   PROP_CAPABILITIES,
   PROP_IS_USER,
-  PROP_LOCATION
+  PROP_LOCATION,
+  PROP_CLIENT_TYPE
 };
 
 enum {
@@ -116,6 +121,12 @@ static guint signals[LAST_SIGNAL];
 /* TpContact* -> EmpathyContact*, both borrowed ref */
 static GHashTable *contacts_table = NULL;
 
+#define LISTENING_TO_CLIENT_TYPES_UPDATED \
+  g_quark_from_static_string ("listening-to-client-types-updated")
+
+#define CONTACTS_LISTENING_TO_CLIENT_TYPE_LIST \
+  g_quark_from_static_string ("contacts-listening-to-client-type-list")
+
 static void
 tp_contact_notify_cb (TpContact *tp_contact,
                       GParamSpec *param,
@@ -217,6 +228,7 @@ empathy_contact_class_init (EmpathyContactClass *class)
 
   object_class->finalize = contact_finalize;
   object_class->dispose = contact_dispose;
+  object_class->constructed = contact_constructed;
   object_class->get_property = contact_get_property;
   object_class->set_property = contact_set_property;
 
@@ -322,6 +334,14 @@ empathy_contact_class_init (EmpathyContactClass *class)
         G_TYPE_HASH_TABLE,
         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
+  g_object_class_install_property (object_class,
+      PROP_CLIENT_TYPE,
+      g_param_spec_string ("client-type",
+        "Client type",
+        "The client type of the contact",
+        NULL,
+        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
   signals[PRESENCE_CHANGED] =
     g_signal_new ("presence-changed",
                   G_TYPE_FROM_CLASS (class),
@@ -337,6 +357,157 @@ empathy_contact_class_init (EmpathyContactClass *class)
 }
 
 static void
+contact_update_client_types (EmpathyContact *contact,
+    const gchar **client_types)
+{
+  EmpathyContactPriv *priv = GET_PRIV (contact);
+
+  if (g_strv_length ((gchar **) client_types) == 0)
+    return;
+
+  if (priv->client_type != NULL)
+    g_free (priv->client_type);
+
+  /* I'm certain this will be good for 99.9999999% of cases. */
+  priv->client_type = g_strdup (client_types[0]);
+
+  g_object_notify (G_OBJECT (contact), "client-type");
+}
+
+static void
+contact_client_types_updated_cb (TpProxy *proxy,
+    guint handle,
+    const gchar **client_types,
+    gpointer user_data,
+    GObject *weak_object)
+{
+  TpConnection *connection = TP_CONNECTION (proxy);
+  guint i;
+  GPtrArray *contacts;
+  EmpathyContact *contact = NULL;
+
+  contacts = g_object_get_qdata (G_OBJECT (connection),
+      CONTACTS_LISTENING_TO_CLIENT_TYPE_LIST);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      EmpathyContact *c = g_ptr_array_index (contacts, i);
+
+      if (handle == empathy_contact_get_handle (c))
+        {
+          /* I'm so happy this all worked out. */
+          contact = c;
+          break;
+        }
+    }
+
+  if (contact == NULL)
+    return;
+
+  contact_update_client_types (contact, client_types);
+}
+
+static void
+contact_connect_to_client_types_updated (EmpathyContact *contact)
+{
+  TpConnection *connection = empathy_contact_get_connection (contact);
+  GError *error = NULL;
+  GPtrArray *contacts;
+
+  /* Check whether we have the interface. */
+  if (!tp_proxy_has_interface_by_id (connection,
+          EMP_IFACE_QUARK_CONNECTION_INTERFACE_CLIENT_TYPES))
+    return;
+
+  /* Check whether we should connect. */
+  if (g_object_get_qdata (G_OBJECT (connection),
+          LISTENING_TO_CLIENT_TYPES_UPDATED) != NULL)
+    goto add_contact;
+
+  emp_cli_connection_interface_client_types_connect_to_client_types_updated (
+      TP_PROXY (connection), contact_client_types_updated_cb, NULL, NULL,
+      NULL, &error);
+
+  if (error != NULL)
+    {
+      DEBUG ("Failed to connect to ClientTypesUpdated: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+  else
+    {
+      /* Say to everyone else that we've connected to this signal. */
+      g_object_set_qdata (G_OBJECT (connection),
+          LISTENING_TO_CLIENT_TYPES_UPDATED, GUINT_TO_POINTER (TRUE));
+
+      contacts = g_ptr_array_new_with_free_func (
+          (GDestroyNotify) g_object_unref);
+
+      g_object_set_qdata_full (G_OBJECT (connection),
+          CONTACTS_LISTENING_TO_CLIENT_TYPE_LIST, contacts,
+          (GDestroyNotify) g_ptr_array_unref);
+    }
+
+add_contact:
+  contacts = g_object_get_qdata (G_OBJECT (connection),
+      CONTACTS_LISTENING_TO_CLIENT_TYPE_LIST);
+
+  g_ptr_array_add (contacts, g_object_ref (contact));
+}
+
+static void
+contact_get_client_types_cb (TpProxy *proxy,
+    GHashTable *client_types,
+    const GError *error,
+    gpointer user_data,
+    GObject *weak_object)
+{
+  EmpathyContact *contact = EMPATHY_CONTACT (weak_object);
+  TpHandle handle = GPOINTER_TO_UINT (user_data);
+  gchar **types;
+
+  if (error != NULL)
+    {
+      DEBUG ("error: %s", error->message);
+      return;
+    }
+
+  types = g_hash_table_lookup (client_types, GUINT_TO_POINTER (handle));
+
+  if (types == NULL)
+    return;
+
+  contact_update_client_types (contact, (const gchar **) types);
+}
+
+static void
+contact_constructed (GObject *obj)
+{
+  EmpathyContact *contact = EMPATHY_CONTACT (obj);
+  GArray *array;
+  TpHandle handle;
+  TpConnection *connection;
+
+  connection = empathy_contact_get_connection (contact);
+
+  if (connection == NULL)
+    return;
+
+  contact_connect_to_client_types_updated (contact);
+
+  array = g_array_new (FALSE, FALSE, sizeof (TpHandle));
+  handle = empathy_contact_get_handle (contact);
+  g_array_append_val (array, handle);
+
+  emp_cli_connection_interface_client_types_call_get_client_types (
+      TP_PROXY (connection), -1,
+      array, contact_get_client_types_cb, GUINT_TO_POINTER (handle),
+      NULL, G_OBJECT (contact));
+
+  g_array_unref (array);
+}
+
+static void
 empathy_contact_init (EmpathyContact *contact)
 {
   EmpathyContactPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (contact,
@@ -346,6 +517,7 @@ empathy_contact_init (EmpathyContact *contact)
 
   priv->location = NULL;
   priv->groups = NULL;
+  priv->client_type = NULL;
 }
 
 static void
@@ -361,6 +533,7 @@ contact_finalize (GObject *object)
     g_hash_table_destroy (priv->groups);
   g_free (priv->alias);
   g_free (priv->id);
+  g_free (priv->client_type);
 
   G_OBJECT_CLASS (empathy_contact_parent_class)->finalize (object);
 }
@@ -1696,3 +1869,14 @@ empathy_contact_dup_from_tp_contact (TpContact *tp_contact)
   return contact;
 }
 
+const gchar *
+empathy_contact_get_client_type (EmpathyContact *contact)
+{
+  EmpathyContactPriv *priv;
+
+  g_return_val_if_fail (EMPATHY_CONTACT (contact), NULL);
+
+  priv = GET_PRIV (contact);
+
+  return priv->client_type;
+}
diff --git a/libempathy/empathy-contact.h b/libempathy/empathy-contact.h
index 3c92330..d4d4e41 100644
--- a/libempathy/empathy-contact.h
+++ b/libempathy/empathy-contact.h
@@ -135,6 +135,8 @@ gboolean empathy_contact_equal (gconstpointer contact1,
 
 EmpathyContact *empathy_contact_dup_from_tp_contact (TpContact *tp_contact);
 
+const gchar *empathy_contact_get_client_type (EmpathyContact *contact);
+
 G_END_DECLS
 
 #endif /* __EMPATHY_CONTACT_H__ */
