[gnome-chess/chess-telepathy-networking-support-664946-rebase: 14/64] [libgames-contacts] Add classes to manage contacts and initiate networking



commit 74f9ad9f4dedbd0b2f58d666155acc9ecebd9f14
Author: Chandni Verma <chandniverma2112 gmail com>
Date:   Tue May 29 09:00:08 2012 +0530

    [libgames-contacts] Add classes to manage contacts and initiate networking
    
    -GamesIndividualManager: Manages folks individuals
    -GamesIndividualStore: Is a GtkTreeStore to model individuals
    -GamesIndivudualGroups: Cache individual group expanders
    -GamesIndividualStoreManager: Manages entry and removal of individuals from
                                  GamesIndividualStore
    -GamesContact: Are 1:1 with TpContact (or TpfPersona).
                   They are here to keep a track of capabilities not yet
                   provided by folks
    -helper class for common utilities

 configure.ac                                       |    2 +
 libgames-contacts/Makefile.am                      |   86 +-
 libgames-contacts/games-contact.c                  | 1383 +++++++++++++++
 libgames-contacts/games-contact.h                  |  122 ++
 libgames-contacts/games-individual-groups.c        |  287 ++++
 libgames-contacts/games-individual-groups.dtd      |   16 +
 libgames-contacts/games-individual-groups.h        |   38 +
 libgames-contacts/games-individual-manager.c       |  436 +++++
 libgames-contacts/games-individual-manager.h       |   67 +
 libgames-contacts/games-individual-store-manager.c |  321 ++++
 libgames-contacts/games-individual-store-manager.h |   61 +
 libgames-contacts/games-individual-store.c         | 1756 ++++++++++++++++++++
 libgames-contacts/games-individual-store.h         |  165 ++
 libgames-contacts/games-ui-utils.c                 | 1091 ++++++++++++
 libgames-contacts/games-ui-utils.h                 |  143 ++
 15 files changed, 5972 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 06b4d0f..ba138a3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,8 @@
 AC_INIT(gnome-chess, 3.7.2)
 LT_INIT
 
+AC_CONFIG_HEADERS([config.h])
+
 AM_INIT_AUTOMAKE([1.11 no-dist-gzip dist-xz foreign])
 AM_SILENT_RULES([yes])
 AM_MAINTAINER_MODE
diff --git a/libgames-contacts/Makefile.am b/libgames-contacts/Makefile.am
index c4d3364..b2e7788 100644
--- a/libgames-contacts/Makefile.am
+++ b/libgames-contacts/Makefile.am
@@ -1,11 +1,37 @@
 noinst_LTLIBRARIES = 
+BUILT_SOURCES =
 
 if ENABLE_NETWORKING
+BUILT_SOURCES += \
+	games-enum-types.c \
+	games-enum-types.h
+
 noinst_LTLIBRARIES += libgames-contacts.la
 endif
 
-libgames_contacts_la_SOURCES =
-	
+networking_headers = \
+	games-individual-manager.h \
+	games-individual-store.h \
+	games-individual-store-manager.h \
+	games-individual-groups.h \
+	games-ui-utils.h \
+	games-contact.h
+
+networking_sources = \
+	$(networking_headers) \
+	games-individual-manager.c \
+	games-individual-store.c \
+	games-individual-store-manager.c \
+	games-individual-groups.c \
+	games-ui-utils.c \
+	games-contact.c
+
+libgames_contacts_la_SOURCES = \
+	$(networking_sources)
+
+nodist_libgames_contacts_la_SOURCES = \
+	$(BUILT_SOURCES)
+
 libgames_contacts_la_CPPFLAGS = \
 	-DPKGDATADIR="\"$(pkgdatadir)\""			\
 	-DPREFIX="\"$(prefix)\"" \
@@ -31,12 +57,68 @@ libgames_contacts_la_LIBADD = \
 EXTRA_DIST = \
 	GamesContacts-1.0.vapi
 
+if ENABLE_NETWORKING
+dtddir = $(pkgdatadir)
+dtd_DATA = \
+	games-individual-groups.dtd
+
+EXTRA_DIST += \
+	$(dtd_DATA)
+endif #ENABLE_NETWORKING
+
+
 CLEANFILES =
+if ENABLE_NETWORKING
+CLEANFILES += \
+	$(BUILT_SOURCES) \
+	stamp-games-enum-types.h
+endif #ENABLE_NETWORKING
 
 DISTCLEANFILES =
 
+# Generation of Enum types
+games-enum-types.h: stamp-games-enum-types.h
+	$(AM_V_GEN)true
+stamp-games-enum-types.h: $(networking_headers) Makefile
+	$(AM_V_GEN)(cd $(srcdir) \
+	&& glib-mkenums \
+			--fhead "#ifndef __GAMES_ENUM_TYPES_H__\n" \
+			--fhead "#define __GAMES_ENUM_TYPES_H__ 1\n\n" \
+			--fhead "#include <glib-object.h>\n\n" \
+			--fhead "G_BEGIN_DECLS\n\n" \
+			--ftail "G_END_DECLS\n\n" \
+			--ftail "#endif /* __GAMES_ENUM_TYPES_H__ */\n" \
+			--fprod "#include \"@filename \"\n" \
+			--eprod "#define GAMES_TYPE_ ENUMSHORT@ @enum_name _get_type()\n" \
+			--eprod "GType @enum_name _get_type (void);\n" \
+		$(networking_headers) ) > xgen-gth \
+	&& (cmp -s xgen-gth games-enum-type.h || cp xgen-gth games-enum-types.h) \
+	&& rm -f xgen-gth \
+	&& echo timestamp > $(@F)
+
+games-enum-types.c: $(networking_headers) Makefile
+	$(AM_V_GEN)(cd $(srcdir) \
+	&& glib-mkenums \
+			--fhead "#include <config.h>\n" \
+			--fhead "#include <glib-object.h>\n" \
+			--fhead "#include \"games-enum-types.h\"\n\n" \
+			--fprod "\n/* enumerations from \"@filename \" */" \
+			--vhead "static const G Type@Value _ enum_name@_values[] = {" \
+			--vprod "  { @VALUENAME@, \"@VALUENAME \", \"@valuenick \" }," \
+			--vtail "  { 0, NULL, NULL }\n};\n\n" \
+			--vtail "GType\n enum_name@_get_type (void)\n{\n" \
+			--vtail "  static GType type = 0;\n\n" \
+			--vtail "  if (!type)\n" \
+			--vtail "    type = g_ type@_register_static (\"@EnumName \", _ enum_name@_values);\n\n" \
+			--vtail "  return type;\n}\n\n" \
+		$(networking_headers) ) > xgen-gtc \
+	&& cp xgen-gtc $(@F) \
+	&& rm -f xgen-gtc
+
+
 @INTLTOOL_XML_NOMERGE_RULE@
 
 @GSETTINGS_RULES@
 
 -include $(top_srcdir)/git.mk
+
diff --git a/libgames-contacts/games-contact.c b/libgames-contacts/games-contact.c
new file mode 100644
index 0000000..d7b7ad3
--- /dev/null
+++ b/libgames-contacts/games-contact.c
@@ -0,0 +1,1383 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/*
+ * games-contact.c: A wrapper for TpContact to keep a track of contacts'
+ * gaming capabilities since folks doesn't directly provide them for TpfPersona
+ * (bgo#626179). As TpContact, GamesContact is one for each persona for an
+ * individual, logically.
+ *
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <telepathy-glib/account-manager.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+
+#include "games-contact.h"
+#include "games-individual-manager.h"
+#include "games-enum-types.h"
+
+/* Much of code here is a simplified adaptation of the way contacts are
+ * wrapped in Empathy, a chat application using Folks.
+ * (https://live.gnome.org/Empathy)
+ * Copyright (C) 2007-2009 Collabora Ltd.
+ */
+
+typedef struct {
+  TpContact *tp_contact;
+  TpAccount *account;
+  FolksPersona *persona;
+  gchar *id;
+  gchar *alias;
+  GamesAvatar *avatar;
+  TpConnectionPresenceType presence;
+  guint handle;
+  GamesCapabilities capabilities;
+  gboolean is_user;
+} GamesContactPriv;
+
+static void contact_finalize (GObject *object);
+static void contact_get_property (GObject *object, guint param_id,
+    GValue *value, GParamSpec *pspec);
+static void contact_set_property (GObject *object, guint param_id,
+    const GValue *value, GParamSpec *pspec);
+
+static void set_capabilities_from_tp_caps (GamesContact *self,
+    TpCapabilities *caps);
+
+static void contact_set_avatar (GamesContact *contact,
+    GamesAvatar *avatar);
+static void contact_set_avatar_from_tp_contact (GamesContact *contact);
+
+G_DEFINE_TYPE (GamesContact, games_contact, G_TYPE_OBJECT);
+
+enum
+{
+  PROP_0,
+  PROP_TP_CONTACT,
+  PROP_ACCOUNT,
+  PROP_PERSONA,
+  PROP_ID,
+  PROP_ALIAS,
+  PROP_LOGGED_ALIAS,
+  PROP_AVATAR,
+  PROP_PRESENCE,
+  PROP_PRESENCE_MESSAGE,
+  PROP_HANDLE,
+  PROP_CAPABILITIES,
+  PROP_IS_USER,
+};
+
+enum {
+  PRESENCE_CHANGED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+/* TpContact* -> GamesContact*, both borrowed ref */
+static GHashTable *contacts_table = NULL;
+
+static void
+tp_contact_notify_cb (TpContact *tp_contact,
+                      GParamSpec *param,
+                      GObject *contact)
+{
+  GamesContactPriv *priv = (GAMES_CONTACT (contact))->priv;
+
+  /* Forward property notifications */
+  if (!tp_strdiff (param->name, "alias"))
+    g_object_notify (contact, "alias");
+  else if (!tp_strdiff (param->name, "presence-type")) {
+    TpConnectionPresenceType presence;
+
+    presence = games_contact_get_presence (GAMES_CONTACT (contact));
+    g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence,
+      priv->presence);
+    priv->presence = presence;
+    g_object_notify (contact, "presence");
+  }
+  else if (!tp_strdiff (param->name, "identifier"))
+    g_object_notify (contact, "id");
+  else if (!tp_strdiff (param->name, "handle"))
+    g_object_notify (contact, "handle");
+  else if (!tp_strdiff (param->name, "capabilities"))
+    {
+      set_capabilities_from_tp_caps (GAMES_CONTACT (contact),
+          tp_contact_get_capabilities (tp_contact));
+    }
+  else if (!tp_strdiff (param->name, "avatar-file"))
+    {
+      contact_set_avatar_from_tp_contact (GAMES_CONTACT (contact));
+    }
+}
+
+static void
+folks_persona_notify_cb (FolksPersona *folks_persona,
+                         GParamSpec *param,
+                         GObject *contact)
+{
+  if (!tp_strdiff (param->name, "presence-message"))
+    g_object_notify (contact, "presence-message");
+}
+
+static void
+contact_dispose (GObject *object)
+{
+  GamesContactPriv *priv = (GAMES_CONTACT (object))->priv;
+
+  if (priv->tp_contact != NULL)
+    {
+      g_signal_handlers_disconnect_by_func (priv->tp_contact,
+          tp_contact_notify_cb, object);
+    }
+  tp_clear_object (&priv->tp_contact);
+
+  if (priv->account)
+    g_object_unref (priv->account);
+  priv->account = NULL;
+
+  if (priv->persona)
+    {
+      g_signal_handlers_disconnect_by_func (priv->persona,
+          folks_persona_notify_cb, object);
+      g_object_unref (priv->persona);
+    }
+  priv->persona = NULL;
+
+  if (priv->avatar != NULL)
+    {
+      games_avatar_unref (priv->avatar);
+      priv->avatar = NULL;
+    }
+
+  G_OBJECT_CLASS (games_contact_parent_class)->dispose (object);
+}
+
+static void
+contact_constructed (GObject *object)
+{
+  GamesContact *contact = (GamesContact *) object;
+  GamesContactPriv *priv = contact->priv;
+  TpContact *self_contact;
+
+  if (priv->tp_contact == NULL)
+    return;
+
+  priv->presence = games_contact_get_presence (contact);
+
+  set_capabilities_from_tp_caps (contact,
+      tp_contact_get_capabilities (priv->tp_contact));
+
+  contact_set_avatar_from_tp_contact (contact);
+
+  /* Set is-user property. We could use handles too */
+  self_contact = tp_connection_get_self_contact (
+      tp_contact_get_connection (priv->tp_contact));
+  games_contact_set_is_user (contact, self_contact == priv->tp_contact);
+
+  g_signal_connect (priv->tp_contact, "notify",
+    G_CALLBACK (tp_contact_notify_cb), contact);
+}
+
+static void
+games_contact_class_init (GamesContactClass *class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = contact_finalize;
+  object_class->dispose = contact_dispose;
+  object_class->get_property = contact_get_property;
+  object_class->set_property = contact_set_property;
+  object_class->constructed = contact_constructed;
+
+  g_object_class_install_property (object_class,
+      PROP_TP_CONTACT,
+      g_param_spec_object ("tp-contact",
+        "TpContact",
+        "The TpContact associated with the contact",
+        TP_TYPE_CONTACT,
+        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_ACCOUNT,
+      g_param_spec_object ("account",
+        "The account",
+        "The account associated with the contact",
+        TP_TYPE_ACCOUNT,
+        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_PERSONA,
+      g_param_spec_object ("persona",
+        "Persona",
+        "The FolksPersona associated with the contact",
+        FOLKS_TYPE_PERSONA,
+        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_ID,
+      g_param_spec_string ("id",
+        "Contact id",
+        "String identifying contact",
+        NULL,
+        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_ALIAS,
+      g_param_spec_string ("alias",
+        "Contact alias",
+        "An alias for the contact",
+        NULL,
+        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_AVATAR,
+      g_param_spec_boxed ("avatar",
+        "Avatar image",
+        "The avatar image",
+        GAMES_TYPE_AVATAR,
+        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_PRESENCE,
+      g_param_spec_uint ("presence",
+        "Contact presence",
+        "Presence of contact",
+        TP_CONNECTION_PRESENCE_TYPE_UNSET,
+        NUM_TP_CONNECTION_PRESENCE_TYPES,
+        TP_CONNECTION_PRESENCE_TYPE_UNSET,
+        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_PRESENCE_MESSAGE,
+      g_param_spec_string ("presence-message",
+        "Contact presence message",
+        "Presence message of contact",
+        NULL,
+        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_HANDLE,
+      g_param_spec_uint ("handle",
+        "Contact Handle",
+        "The handle of the contact",
+        0,
+        G_MAXUINT,
+        0,
+        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_CAPABILITIES,
+      g_param_spec_flags ("capabilities",
+        "Contact Capabilities",
+        "Capabilities of the contact",
+        GAMES_TYPE_CAPABILITIES,
+        GAMES_CAPABILITIES_UNKNOWN,
+        G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+      PROP_IS_USER,
+      g_param_spec_boolean ("is-user",
+        "Contact is-user",
+        "Is contact the user",
+        FALSE,
+        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  signals[PRESENCE_CHANGED] =
+    g_signal_new ("presence-changed",
+                  G_TYPE_FROM_CLASS (class),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_generic,
+                  G_TYPE_NONE,
+                  2, G_TYPE_UINT,
+                  G_TYPE_UINT);
+
+  g_type_class_add_private (object_class, sizeof (GamesContactPriv));
+}
+
+static void
+games_contact_init (GamesContact *contact)
+{
+  GamesContactPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (contact,
+    GAMES_TYPE_CONTACT, GamesContactPriv);
+
+  contact->priv = priv;
+}
+
+static void
+contact_finalize (GObject *object)
+{
+  GamesContactPriv *priv;
+
+  priv = (GAMES_CONTACT (object))->priv;
+
+  g_debug ("finalize: %p", object);
+
+  g_free (priv->alias);
+  g_free (priv->id);
+
+  G_OBJECT_CLASS (games_contact_parent_class)->finalize (object);
+}
+
+static void
+games_contact_set_capabilities (GamesContact *contact,
+                                  GamesCapabilities capabilities)
+{
+  GamesContactPriv *priv;
+
+  g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+  priv = contact->priv;
+
+  if (priv->capabilities == capabilities)
+    return;
+
+  priv->capabilities = capabilities;
+
+  g_object_notify (G_OBJECT (contact), "capabilities");
+}
+
+static void
+games_contact_set_id (GamesContact *contact,
+                        const gchar *id)
+{
+  GamesContactPriv *priv;
+
+  g_return_if_fail (GAMES_IS_CONTACT (contact));
+  g_return_if_fail (id != NULL);
+
+  priv = contact->priv;
+
+  /* We temporally ref the contact because it could be destroyed
+   * during the signal emition */
+  g_object_ref (contact);
+  if (tp_strdiff (id, priv->id))
+    {
+      g_free (priv->id);
+      priv->id = g_strdup (id);
+
+      g_object_notify (G_OBJECT (contact), "id");
+
+      /* And since alias defaults to ID */
+      if (priv->alias == NULL || g_strcmp0 (priv->alias, "") == 0)
+          g_object_notify (G_OBJECT (contact), "alias");
+    }
+
+  g_object_unref (contact);
+}
+
+static void
+games_contact_set_presence (GamesContact *contact,
+                              TpConnectionPresenceType presence)
+{
+  GamesContactPriv *priv;
+  TpConnectionPresenceType old_presence;
+
+  g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+  priv = contact->priv;
+
+  if (presence == priv->presence)
+    return;
+
+  old_presence = priv->presence;
+  priv->presence = presence;
+
+  g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence, old_presence);
+
+  g_object_notify (G_OBJECT (contact), "presence");
+}
+
+static void
+games_contact_set_presence_message (GamesContact *contact,
+                                      const gchar *message)
+{
+  GamesContactPriv *priv = contact->priv;
+
+  g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+  if (priv->persona != NULL)
+    {
+      folks_presence_details_set_presence_message (
+          FOLKS_PRESENCE_DETAILS (priv->persona), message);
+    }
+}
+
+static void
+games_contact_set_handle (GamesContact *contact,
+                            guint handle)
+{
+  GamesContactPriv *priv;
+
+  g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+  priv = contact->priv;
+
+  g_object_ref (contact);
+  if (handle != priv->handle)
+    {
+      priv->handle = handle;
+      g_object_notify (G_OBJECT (contact), "handle");
+    }
+  g_object_unref (contact);
+}
+
+static void
+contact_get_property (GObject *object,
+                      guint param_id,
+                      GValue *value,
+                      GParamSpec *pspec)
+{
+  GamesContact *contact = GAMES_CONTACT (object);
+
+  switch (param_id)
+    {
+      case PROP_TP_CONTACT:
+        g_value_set_object (value, games_contact_get_tp_contact (contact));
+        break;
+      case PROP_ACCOUNT:
+        g_value_set_object (value, games_contact_get_account (contact));
+        break;
+      case PROP_PERSONA:
+        g_value_set_object (value, games_contact_get_persona (contact));
+        break;
+      case PROP_ID:
+        g_value_set_string (value, games_contact_get_id (contact));
+        break;
+      case PROP_ALIAS:
+        g_value_set_string (value, games_contact_get_alias (contact));
+        break;
+      case PROP_AVATAR:
+        g_value_set_boxed (value, games_contact_get_avatar (contact));
+        break;
+      case PROP_PRESENCE:
+        g_value_set_uint (value, games_contact_get_presence (contact));
+        break;
+      case PROP_PRESENCE_MESSAGE:
+        g_value_set_string (value, games_contact_get_presence_message (contact));
+        break;
+      case PROP_HANDLE:
+        g_value_set_uint (value, games_contact_get_handle (contact));
+        break;
+      case PROP_CAPABILITIES:
+        g_value_set_flags (value, games_contact_get_capabilities (contact));
+        break;
+      case PROP_IS_USER:
+        g_value_set_boolean (value, games_contact_is_user (contact));
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+        break;
+    };
+}
+
+static void
+contact_set_property (GObject *object,
+                      guint param_id,
+                      const GValue *value,
+                      GParamSpec *pspec)
+{
+  GamesContact *contact = GAMES_CONTACT (object);
+  GamesContactPriv *priv = contact->priv;
+
+  switch (param_id)
+    {
+      case PROP_TP_CONTACT:
+        priv->tp_contact = g_value_dup_object (value);
+        break;
+      case PROP_ACCOUNT:
+        g_assert (priv->account == NULL);
+        priv->account = g_value_dup_object (value);
+        break;
+      case PROP_PERSONA:
+        games_contact_set_persona (contact, g_value_get_object (value));
+        break;
+      case PROP_ID:
+        games_contact_set_id (contact, g_value_get_string (value));
+        break;
+      case PROP_ALIAS:
+        games_contact_set_alias (contact, g_value_get_string (value));
+        break;
+      case PROP_PRESENCE:
+        games_contact_set_presence (contact, g_value_get_uint (value));
+        break;
+      case PROP_PRESENCE_MESSAGE:
+        games_contact_set_presence_message (contact, g_value_get_string (value));
+        break;
+      case PROP_HANDLE:
+        games_contact_set_handle (contact, g_value_get_uint (value));
+        break;
+      case PROP_CAPABILITIES:
+        games_contact_set_capabilities (contact, g_value_get_flags (value));
+        break;
+      case PROP_IS_USER:
+        games_contact_set_is_user (contact, g_value_get_boolean (value));
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+        break;
+    };
+}
+
+static void
+remove_tp_contact (gpointer data,
+    GObject *object)
+{
+  g_hash_table_remove (contacts_table, data);
+}
+
+static GamesContact *
+games_contact_new (TpContact *tp_contact)
+{
+  GamesContact *retval;
+
+  g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
+
+  retval = g_object_new (GAMES_TYPE_CONTACT,
+      "tp-contact", tp_contact,
+      NULL);
+
+  g_object_weak_ref (G_OBJECT (retval), remove_tp_contact, tp_contact);
+
+  return retval;
+}
+
+TpContact *
+games_contact_get_tp_contact (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+  priv = contact->priv;
+
+  return priv->tp_contact;
+}
+
+const gchar *
+games_contact_get_id (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+  priv = contact->priv;
+
+  if (priv->tp_contact != NULL)
+    return tp_contact_get_identifier (priv->tp_contact);
+
+  return priv->id;
+}
+
+const gchar *
+games_contact_get_alias (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+  const gchar        *alias = NULL;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+  priv = contact->priv;
+
+  if (priv->alias != NULL && g_strcmp0 (priv->alias, "") != 0)
+    alias = priv->alias;
+  else if (priv->tp_contact != NULL)
+    alias = tp_contact_get_alias (priv->tp_contact);
+
+  if (alias != NULL && g_strcmp0 (alias, "") != 0)
+    return alias;
+  else
+    return games_contact_get_id (contact);
+}
+
+void
+games_contact_set_alias (GamesContact *contact,
+                          const gchar *alias)
+{
+  GamesContactPriv *priv;
+  FolksPersona *persona;
+
+  g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+  priv = contact->priv;
+
+  g_object_ref (contact);
+
+  /* Set the alias on the persona */
+  persona = games_contact_get_persona (contact);
+  /* We are not creating contacts hence all our GamesContacts have to have
+   * personas beforehand */
+  g_assert (persona != NULL);
+
+  if (FOLKS_IS_ALIAS_DETAILS (persona))
+    {
+      g_debug ("Setting alias for contact %s to %s",
+          games_contact_get_id (contact), alias);
+
+      folks_alias_details_set_alias (FOLKS_ALIAS_DETAILS (persona), alias);
+    }
+
+  if (tp_strdiff (alias, priv->alias))
+    {
+      g_free (priv->alias);
+      priv->alias = g_strdup (alias);
+      g_object_notify (G_OBJECT (contact), "alias");
+    }
+
+  g_object_unref (contact);
+}
+
+GamesAvatar *
+games_contact_get_avatar (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+  priv = contact->priv;
+
+  return priv->avatar;
+}
+
+static void
+contact_set_avatar (GamesContact *contact,
+                    GamesAvatar *avatar)
+{
+  GamesContactPriv *priv;
+
+  g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+  priv = contact->priv;
+
+  if (priv->avatar == avatar)
+    return;
+
+  if (priv->avatar)
+    {
+      games_avatar_unref (priv->avatar);
+      priv->avatar = NULL;
+    }
+
+  if (avatar)
+      priv->avatar = games_avatar_ref (avatar);
+
+  g_object_notify (G_OBJECT (contact), "avatar");
+}
+
+TpAccount *
+games_contact_get_account (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+  priv = contact->priv;
+
+  if (priv->account == NULL && priv->tp_contact != NULL)
+    {
+      TpConnection *connection;
+
+      /* FIXME: This assume the account manager already exists */
+      connection = tp_contact_get_connection (priv->tp_contact);
+      priv->account =
+        g_object_ref (tp_connection_get_account (connection));
+    }
+
+  return priv->account;
+}
+
+FolksPersona *
+games_contact_get_persona (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+  priv = contact->priv;
+
+  if (priv->persona == NULL && priv->tp_contact != NULL)
+    {
+      TpfPersona *persona;
+
+      persona = tpf_persona_dup_for_contact (priv->tp_contact);
+      if (persona != NULL)
+        {
+          games_contact_set_persona (contact, (FolksPersona *) persona);
+          g_object_unref (persona);
+        }
+    }
+
+  return priv->persona;
+}
+
+void
+games_contact_set_persona (GamesContact *contact,
+    FolksPersona *persona)
+{
+  GamesContactPriv *priv;
+
+  g_return_if_fail (GAMES_IS_CONTACT (contact));
+  g_return_if_fail (TPF_IS_PERSONA (persona));
+
+  priv = contact->priv;
+
+  if (persona == priv->persona)
+    return;
+
+  if (priv->persona != NULL)
+    {
+      g_signal_handlers_disconnect_by_func (priv->persona,
+          folks_persona_notify_cb, contact);
+      g_object_unref (priv->persona);
+    }
+  priv->persona = g_object_ref (persona);
+
+  g_signal_connect (priv->persona, "notify",
+    G_CALLBACK (folks_persona_notify_cb), contact);
+
+  g_object_notify (G_OBJECT (contact), "persona");
+
+  /* Set the persona's alias, since ours could've been set using
+   * games_contact_set_alias() before we had a persona; this happens when
+   * adding a contact. */
+  if (priv->alias != NULL)
+    games_contact_set_alias (contact, priv->alias);
+}
+
+TpConnection *
+games_contact_get_connection (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+  priv = contact->priv;
+
+  if (priv->tp_contact != NULL)
+    return tp_contact_get_connection (priv->tp_contact);
+
+  return NULL;
+}
+
+TpConnectionPresenceType
+games_contact_get_presence (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact),
+    TP_CONNECTION_PRESENCE_TYPE_UNSET);
+
+  priv = contact->priv;
+
+  if (priv->tp_contact != NULL)
+    return tp_contact_get_presence_type (priv->tp_contact);
+
+  return priv->presence;
+}
+
+const gchar *
+games_contact_get_presence_message (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+  priv = contact->priv;
+
+  if (priv->persona != NULL)
+    return folks_presence_details_get_presence_message (
+        FOLKS_PRESENCE_DETAILS (priv->persona));
+
+  if (priv->tp_contact != NULL)
+    return tp_contact_get_presence_message (priv->tp_contact);
+
+  return NULL;
+}
+
+guint
+games_contact_get_handle (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), 0);
+
+  priv = contact->priv;
+
+  if (priv->tp_contact != NULL)
+    return tp_contact_get_handle (priv->tp_contact);
+
+  return priv->handle;
+}
+
+GamesCapabilities
+games_contact_get_capabilities (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), 0);
+
+  priv = contact->priv;
+
+  return priv->capabilities;
+}
+
+gboolean
+games_contact_is_user (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), FALSE);
+
+  priv = contact->priv;
+
+  return priv->is_user;
+}
+
+void
+games_contact_set_is_user (GamesContact *contact,
+                             gboolean is_user)
+{
+  GamesContactPriv *priv;
+
+  g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+  priv = contact->priv;
+
+  if (priv->is_user == is_user)
+    return;
+
+  priv->is_user = is_user;
+
+  g_object_notify (G_OBJECT (contact), "is-user");
+}
+
+gboolean
+games_contact_is_online (GamesContact *contact)
+{
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), FALSE);
+
+  switch (games_contact_get_presence (contact))
+    {
+      case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
+      case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
+      case TP_CONNECTION_PRESENCE_TYPE_ERROR:
+        return FALSE;
+      /* Contacts without presence are considered online so we can display IRC
+       * contacts in rooms. */
+      case TP_CONNECTION_PRESENCE_TYPE_UNSET:
+      case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
+      case TP_CONNECTION_PRESENCE_TYPE_AWAY:
+      case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
+      case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
+      case TP_CONNECTION_PRESENCE_TYPE_BUSY:
+      default:
+        return TRUE;
+    }
+}
+
+const gchar *
+games_presence_get_default_message (TpConnectionPresenceType presence)
+{
+  switch (presence)
+    {
+      case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
+        return _("Available");
+      case TP_CONNECTION_PRESENCE_TYPE_BUSY:
+        return _("Busy");
+      case TP_CONNECTION_PRESENCE_TYPE_AWAY:
+      case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
+        return _("Away");
+      case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
+        return _("Invisible");
+      case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
+        return _("Offline");
+      case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
+        /* translators: presence type is unknown */
+        return C_("presence", "Unknown");
+      case TP_CONNECTION_PRESENCE_TYPE_UNSET:
+      case TP_CONNECTION_PRESENCE_TYPE_ERROR:
+      default:
+        return NULL;
+    }
+
+  return NULL;
+}
+
+const gchar *
+games_contact_get_status (GamesContact *contact)
+{
+  const gchar *message;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), "");
+
+  message = games_contact_get_presence_message (contact);
+  if (message != NULL && g_strcmp0 (message, "") != 0)
+    return message;
+
+  return games_presence_get_default_message (
+      games_contact_get_presence (contact));
+}
+
+gboolean
+games_contact_can_play_glchess (GamesContact *contact)
+{
+  GamesContactPriv *priv;
+
+  g_return_val_if_fail (GAMES_IS_CONTACT (contact), FALSE);
+
+  priv = contact->priv;
+
+  return priv->capabilities & GAMES_CAPABILITIES_TUBE_GLCHESS;
+}
+
+gboolean
+games_contact_can_do_action (GamesContact *self,
+    GamesActionType action_type)
+{
+  gboolean sensitivity = FALSE;
+
+  switch (action_type)
+    {
+      case GAMES_ACTION_CHAT:
+        sensitivity = TRUE;
+        break;
+      case GAMES_ACTION_PLAY_GLCHESS:
+        sensitivity = games_contact_can_play_glchess (self);
+        break;
+      default:
+        g_assert_not_reached ();
+    }
+
+  return (sensitivity ? TRUE : FALSE);
+}
+
+GType
+games_avatar_get_type (void)
+{
+  static GType type_id = 0;
+
+  if (!type_id)
+    {
+      type_id = g_boxed_type_register_static ("GamesAvatar",
+          (GBoxedCopyFunc) games_avatar_ref,
+          (GBoxedFreeFunc) games_avatar_unref);
+    }
+
+  return type_id;
+}
+
+/**
+ * games_avatar_new:
+ * @data: the avatar data
+ * @len: the size of avatar data
+ * @format: the mime type of the avatar image
+ * @filename: the filename where the avatar is stored in cache
+ *
+ * Create a #GamesAvatar from the provided data.
+ *
+ * Returns: a new #GamesAvatar
+ */
+GamesAvatar *
+games_avatar_new (const guchar *data,
+                    gsize len,
+                    const gchar *format,
+                    const gchar *filename)
+{
+  GamesAvatar *avatar;
+
+  avatar = g_slice_new0 (GamesAvatar);
+  avatar->data = g_memdup (data, len);
+  avatar->len = len;
+  avatar->format = g_strdup (format);
+  avatar->filename = g_strdup (filename);
+  avatar->refcount = 1;
+
+  return avatar;
+}
+
+void
+games_avatar_unref (GamesAvatar *avatar)
+{
+  g_return_if_fail (avatar != NULL);
+
+  avatar->refcount--;
+  if (avatar->refcount == 0)
+    {
+      g_free (avatar->data);
+      g_free (avatar->format);
+      g_free (avatar->filename);
+      g_slice_free (GamesAvatar, avatar);
+    }
+}
+
+GamesAvatar *
+games_avatar_ref (GamesAvatar *avatar)
+{
+  g_return_val_if_fail (avatar != NULL, NULL);
+
+  avatar->refcount++;
+
+  return avatar;
+}
+
+/**
+ * games_avatar_save_to_file:
+ * @avatar: the avatar
+ * @filename: name of a file to write avatar to
+ * @error: return location for a GError, or NULL
+ *
+ * Save the avatar to a file named filename
+ *
+ * Returns: %TRUE on success, %FALSE if an error occurred
+ */
+gboolean
+games_avatar_save_to_file (GamesAvatar *self,
+                             const gchar *filename,
+                             GError **error)
+{
+  return g_file_set_contents (filename, (const gchar *) self->data, self->len,
+      error);
+}
+
+/**
+ * games_contact_equal:
+ * @contact1: an #GamesContact
+ * @contact2: an #GamesContact
+ *
+ * Returns FALSE if one of the contacts is NULL but the other is not.
+ * Otherwise returns TRUE if both pointer are equal or if they bith
+ * refer to the same id.
+ * It's only necessary to call this function if your contact objects
+ * come from logs where contacts are created dynamically and comparing
+ * pointers is not enough.
+ */
+gboolean
+games_contact_equal (gconstpointer contact1,
+                       gconstpointer contact2)
+{
+  GamesContact *c1;
+  GamesContact *c2;
+  const gchar *id1;
+  const gchar *id2;
+
+  if ((contact1 == NULL) != (contact2 == NULL)) {
+    return FALSE;
+  }
+  if (contact1 == contact2) {
+    return TRUE;
+  }
+  c1 = GAMES_CONTACT (contact1);
+  c2 = GAMES_CONTACT (contact2);
+  id1 = games_contact_get_id (c1);
+  id2 = games_contact_get_id (c2);
+  if (!tp_strdiff (id1, id2)) {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+static GamesCapabilities
+tp_caps_to_capabilities (TpCapabilities *caps)
+{
+  GamesCapabilities capabilities = 0;
+
+  if (tp_capabilities_supports_dbus_tubes (caps, TP_HANDLE_TYPE_CONTACT,
+      "org.gnome.glchess"))
+    capabilities |= GAMES_CAPABILITIES_TUBE_GLCHESS;
+
+  return capabilities;
+}
+
+static void
+set_capabilities_from_tp_caps (GamesContact *self,
+    TpCapabilities *caps)
+{
+  GamesCapabilities capabilities;
+
+  if (caps == NULL)
+    return;
+
+  capabilities = tp_caps_to_capabilities (caps);
+  games_contact_set_capabilities (self, capabilities);
+}
+
+static void
+contact_set_avatar_from_tp_contact (GamesContact *contact)
+{
+  GamesContactPriv *priv = contact->priv;
+  const gchar *mime;
+  GFile *file;
+
+  mime = tp_contact_get_avatar_mime_type (priv->tp_contact);
+  file = tp_contact_get_avatar_file (priv->tp_contact);
+
+  if (file != NULL)
+    {
+      GamesAvatar *avatar;
+      gchar *data;
+      gsize len;
+      gchar *path;
+      GError *error = NULL;
+
+      if (!g_file_load_contents (file, NULL, &data, &len, NULL, &error))
+        {
+          g_debug ("Failed to load avatar: %s", error->message);
+
+          g_error_free (error);
+          contact_set_avatar (contact, NULL);
+          return;
+        }
+
+      path = g_file_get_path (file);
+
+      avatar = games_avatar_new ((guchar *) data, len, mime, path);
+
+      contact_set_avatar (contact, avatar);
+      games_avatar_unref (avatar);
+      g_free (path);
+      g_free (data);
+    }
+  else
+    {
+      contact_set_avatar (contact, NULL);
+    }
+}
+
+GamesContact *
+games_contact_dup_from_tp_contact (TpContact *tp_contact)
+{
+  GamesContact *contact = NULL;
+
+  g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
+
+  if (contacts_table == NULL)
+    contacts_table = g_hash_table_new (g_direct_hash, g_direct_equal);
+  else
+    contact = g_hash_table_lookup (contacts_table, tp_contact);
+
+  if (contact == NULL)
+    {
+      contact = games_contact_new (tp_contact);
+
+      /* The hash table does not keep any ref.
+       * contact keeps a ref to tp_contact, and is removed from the table in
+       * contact_dispose() */
+      g_hash_table_insert (contacts_table, tp_contact, contact);
+    }
+  else
+    {
+      g_object_ref (contact);
+    }
+
+  return contact;
+}
+
+static int
+presence_cmp_func (GamesContact *a,
+    GamesContact *b)
+{
+  FolksPresenceDetails *presence_a, *presence_b;
+
+  presence_a = FOLKS_PRESENCE_DETAILS (games_contact_get_persona (a));
+  presence_b = FOLKS_PRESENCE_DETAILS (games_contact_get_persona (b));
+
+  /* We negate the result because we're sorting in reverse order (i.e. such that
+   * the Personas with the highest presence are at the beginning of the list. */
+  return -folks_presence_details_typecmp (
+      folks_presence_details_get_presence_type (presence_a),
+      folks_presence_details_get_presence_type (presence_b));
+}
+
+static gint
+glchess_tube_cmp_func (GamesContact *a,
+    GamesContact *b)
+{
+  gboolean can_play_glchess_a, can_play_glchess_b;
+
+  can_play_glchess_a = games_contact_can_play_glchess (a);
+  can_play_glchess_b = games_contact_can_play_glchess (b);
+
+  if (can_play_glchess_a == can_play_glchess_b)
+    /* Fallback to sort by presence to break ties */
+    return presence_cmp_func (a, b);
+  else if (can_play_glchess_a)
+    return -1;
+  else
+    return 1;
+}
+
+static GCompareFunc
+get_sort_func_for_action (GamesActionType action_type)
+{
+  switch (action_type)
+    {
+      case GAMES_ACTION_CHAT:
+        return (GCompareFunc) presence_cmp_func;
+      case GAMES_ACTION_PLAY_GLCHESS:
+        return (GCompareFunc) glchess_tube_cmp_func;
+      default:
+        g_assert_not_reached ();
+    }
+}
+
+/**
+ * games_contact_dup_best_for_action:
+ * @individual: a #FolksIndividual
+ * @action_type: the type of action to be performed on the contact
+ *
+ * Chooses a #FolksPersona from the given @individual which is best-suited for
+ * the given @action_type. "Best-suited" is determined by choosing the persona
+ * with the highest presence out of all the personas which can perform the
+ * given @action_type (e.g. are capable of playing glchess remotely).
+ *
+ * Return value: an #GamesContact for the best persona, or %NULL;
+ * unref with g_object_unref()
+ */
+GamesContact *
+games_contact_dup_best_for_action (FolksIndividual *individual,
+    GamesActionType action_type)
+{
+  GeeSet *personas;
+  GeeIterator *iter;
+  GList *contacts;
+  GamesContact *best_contact = NULL;
+
+  /* Build a list of GamesContacts that we can sort */
+  personas = folks_individual_get_personas (individual);
+  contacts = NULL;
+
+  iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+  while (gee_iterator_next (iter))
+    {
+      FolksPersona *persona = gee_iterator_get (iter);
+      TpContact *tp_contact;
+      GamesContact *contact = NULL;
+
+      if (!games_folks_persona_is_interesting (persona))
+        goto while_finish;
+
+      tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+      if (tp_contact == NULL)
+        goto while_finish;
+
+      contact = games_contact_dup_from_tp_contact (tp_contact);
+      games_contact_set_persona (contact, FOLKS_PERSONA (persona));
+
+      /* Only choose the contact if they're actually capable of the specified
+       * action. */
+      if (games_contact_can_do_action (contact, action_type))
+        contacts = g_list_prepend (contacts, g_object_ref (contact));
+
+while_finish:
+      g_clear_object (&contact);
+      g_clear_object (&persona);
+    }
+  g_clear_object (&iter);
+
+  /* Sort the contacts by some heuristic based on the action type, then take
+   * the top contact. */
+  if (contacts != NULL)
+    {
+      contacts = g_list_sort (contacts, get_sort_func_for_action (action_type));
+      best_contact = g_object_ref (contacts->data);
+    }
+
+  g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
+  g_list_free (contacts);
+
+  return best_contact;
+}
+
+/*
+ * Returns whether a given persona wraps a usable TpContact
+ */
+gboolean
+games_folks_persona_is_interesting (FolksPersona *persona)
+{
+  /* We're not interested in non-Telepathy personas */
+  if (!TPF_IS_PERSONA (persona))
+    return FALSE;
+
+  /* We're not interested in user personas which haven't been added to the
+   * contact list (see bgo#637151). */
+  if (folks_persona_get_is_user (persona) &&
+      !tpf_persona_get_is_in_contact_list (TPF_PERSONA (persona)))
+    {
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+/* Returns the GamesContact corresponding to the first TpContact contained
+ * within the given Individual.
+ * Note that this is a temporary convenience since actually,
+ * FolksIndividuals are not 1:1 to TpContacts.
+ *
+ * The returned GamesContact object must be unreffed when done with it.
+ */
+GamesContact *
+games_contact_dup_from_folks_individual (FolksIndividual *individual)
+{
+  GeeSet *personas;
+  GeeIterator *iter;
+  GamesContact *contact = NULL;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  personas = folks_individual_get_personas (individual);
+  iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+  while (gee_iterator_next (iter) && (contact == NULL))
+    {
+      TpfPersona *persona = gee_iterator_get (iter);
+
+      if (games_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
+        {
+          TpContact *tp_contact;
+
+          tp_contact = tpf_persona_get_contact (persona);
+          if (tp_contact != NULL)
+            {
+              contact = games_contact_dup_from_tp_contact (tp_contact);
+              games_contact_set_persona (contact, FOLKS_PERSONA (persona));
+            }
+        }
+      g_clear_object (&persona);
+    }
+  g_clear_object (&iter);
+
+  if (contact == NULL)
+    {
+      g_debug ("Can't create an GamesContact for Individual %s",
+          folks_individual_get_id (individual));
+    }
+
+  return contact;
+}
diff --git a/libgames-contacts/games-contact.h b/libgames-contacts/games-contact.h
new file mode 100644
index 0000000..a91c073
--- /dev/null
+++ b/libgames-contacts/games-contact.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __GAMES_CONTACT_H__
+#define __GAMES_CONTACT_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/contact.h>
+#include <telepathy-glib/account.h>
+#include <folks/folks.h>
+
+G_BEGIN_DECLS
+
+#define GAMES_TYPE_CONTACT         (games_contact_get_type ())
+#define GAMES_CONTACT(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GAMES_TYPE_CONTACT, GamesContact))
+#define GAMES_CONTACT_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GAMES_TYPE_CONTACT, GamesContactClass))
+#define GAMES_IS_CONTACT(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GAMES_TYPE_CONTACT))
+#define GAMES_IS_CONTACT_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GAMES_TYPE_CONTACT))
+#define GAMES_CONTACT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GAMES_TYPE_CONTACT, GamesContactClass))
+
+typedef struct _GamesContact      GamesContact;
+typedef struct _GamesContactClass GamesContactClass;
+
+struct _GamesContact
+{
+  GObject parent;
+  gpointer priv;
+};
+
+struct _GamesContactClass
+{
+  GObjectClass parent_class;
+};
+
+typedef struct {
+  guchar *data;
+  gsize len;
+  gchar *format;
+  gchar *token;
+  gchar *filename;
+  guint refcount;
+} GamesAvatar;
+
+typedef enum {
+  GAMES_CAPABILITIES_NONE = 0,
+  GAMES_CAPABILITIES_TUBE_GLCHESS = 1 << 0,
+  GAMES_CAPABILITIES_UNKNOWN = 1 << 1
+} GamesCapabilities;
+
+GType                     games_contact_get_type             (void) G_GNUC_CONST;
+TpContact                *games_contact_get_tp_contact       (GamesContact *contact);
+const gchar              *games_contact_get_id               (GamesContact *contact);
+const gchar              *games_contact_get_alias            (GamesContact *contact);
+void                      games_contact_set_alias            (GamesContact *contact,
+                                                              const gchar *alias);
+GamesAvatar              *games_contact_get_avatar           (GamesContact *contact);
+TpAccount                *games_contact_get_account          (GamesContact *contact);
+FolksPersona             *games_contact_get_persona          (GamesContact *contact);
+void                      games_contact_set_persona          (GamesContact *contact,
+                                                              FolksPersona *persona);
+TpConnection             *games_contact_get_connection       (GamesContact *contact);
+TpConnectionPresenceType  games_contact_get_presence         (GamesContact *contact);
+const gchar              *games_contact_get_presence_message (GamesContact *contact);
+guint                     games_contact_get_handle           (GamesContact *contact);
+GamesCapabilities         games_contact_get_capabilities     (GamesContact *contact);
+gboolean                  games_contact_is_user              (GamesContact *contact);
+void                      games_contact_set_is_user          (GamesContact *contact,
+                                                              gboolean is_user);
+gboolean                  games_contact_is_online            (GamesContact *contact);
+const gchar              *games_presence_get_default_message (TpConnectionPresenceType presence);
+const gchar              *games_contact_get_status           (GamesContact *contact);
+gboolean                  games_contact_can_play_glchess     (GamesContact *contact);
+gboolean                  games_folks_persona_is_interesting (FolksPersona *persona);
+GamesContact             *games_contact_dup_from_folks_individual (FolksIndividual *individual);
+
+typedef enum {
+  GAMES_ACTION_CHAT,
+  GAMES_ACTION_PLAY_GLCHESS,
+} GamesActionType;
+
+gboolean                  games_contact_can_do_action        (GamesContact *self,
+                                                              GamesActionType action_type);
+
+#define GAMES_TYPE_AVATAR (games_avatar_get_type ())
+GType                     games_avatar_get_type              (void) G_GNUC_CONST;
+GamesAvatar              *games_avatar_new                   (const guchar *data,
+                                                              gsize len,
+                                                              const gchar *format,
+                                                              const gchar *filename);
+GamesAvatar              *games_avatar_ref                   (GamesAvatar *avatar);
+void                      games_avatar_unref                 (GamesAvatar *avatar);
+
+gboolean                  games_avatar_save_to_file          (GamesAvatar *avatar,
+                                                              const gchar *filename, GError **error);
+
+gboolean                  games_contact_equal                (gconstpointer contact1,
+                                                              gconstpointer contact2);
+
+GamesContact             *games_contact_dup_from_tp_contact  (TpContact *tp_contact);
+GamesContact             *games_contact_dup_best_for_action  (FolksIndividual *individual,
+                                                              GamesActionType action_type);
+
+G_END_DECLS
+
+#endif /* __GAMES_CONTACT_H__ */
diff --git a/libgames-contacts/games-individual-groups.c b/libgames-contacts/games-individual-groups.c
new file mode 100644
index 0000000..bb22a96
--- /dev/null
+++ b/libgames-contacts/games-individual-groups.c
@@ -0,0 +1,287 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* games-individual-groups.c: This class tracks server-saved and local 
+ * user-defined groups of individuals as provided by folks. It makes the
+ * expanded state of groups persistent in a configuration file so the last
+ * state is retrievable by any game displaying groups.
+ *
+ * Copyright  2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
+ * Authors: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include "games-ui-utils.h"
+#include "games-individual-groups.h"
+
+#define INDIVIDUAL_GROUPS_XML_FILENAME "individual-groups.xml"
+#define INDIVIDUAL_GROUPS_DTD_FILENAME "games-individual-groups.dtd"
+
+typedef struct {
+	gchar    *name;
+	gboolean  expanded;
+} IndividualGroup;
+
+static void             individual_groups_file_parse (const gchar  *filename);
+static gboolean         individual_groups_file_save  (void);
+static IndividualGroup *individual_group_new         (const gchar  *name,
+                                          					  gboolean      expanded);
+static void             individual_group_free        (IndividualGroup *group);
+
+static GList           *groups = NULL;
+
+void
+games_individual_groups_get_all (void)
+{
+	gchar *dir;
+	gchar *file_with_path;
+
+	/* If already set up clean up first */
+	if (groups) {
+		g_list_foreach (groups, (GFunc)individual_group_free, NULL);
+		g_list_free (groups);
+		groups = NULL;
+	}
+
+	dir = g_build_filename (g_get_user_config_dir (), PACKAGE, NULL);
+	file_with_path = g_build_filename (dir, INDIVIDUAL_GROUPS_XML_FILENAME, NULL);
+	g_free (dir);
+
+	if (g_file_test (file_with_path, G_FILE_TEST_EXISTS)) {
+		individual_groups_file_parse (file_with_path);
+	}
+
+	g_free (file_with_path);
+}
+
+static void
+individual_groups_file_parse (const gchar *filename)
+{
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr        doc;
+	xmlNodePtr       contacts;
+	xmlNodePtr       account;
+	xmlNodePtr       node;
+
+	g_debug ("Attempting to parse file:'%s'...", filename);
+
+	ctxt = xmlNewParserCtxt ();
+
+	/* Parse and validate the file. */
+	doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
+	if (!doc) {
+		g_warning ("Failed to parse file:'%s'", filename);
+		xmlFreeParserCtxt (ctxt);
+		return;
+	}
+
+	if (!games_xml_validate (doc, INDIVIDUAL_GROUPS_DTD_FILENAME)) {
+		g_warning ("Failed to validate file:'%s'", filename);
+		xmlFreeDoc (doc);
+		xmlFreeParserCtxt (ctxt);
+		return;
+	}
+
+	/* The root node, contacts. */
+	contacts = xmlDocGetRootElement (doc);
+
+	account = NULL;
+	node = contacts->children;
+	while (node) {
+		if (strcmp ((gchar *) node->name, "account") == 0) {
+			account = node;
+			break;
+		}
+		node = node->next;
+	}
+
+	node = NULL;
+	if (account) {
+		node = account->children;
+	}
+
+	while (node) {
+		if (strcmp ((gchar *) node->name, "group") == 0) {
+			gchar        *name;
+			gchar        *expanded_str;
+			gboolean      expanded;
+			IndividualGroup *individual_group;
+
+			name = (gchar *) xmlGetProp (node, (const xmlChar *) "name");
+			expanded_str = (gchar *) xmlGetProp (node, (const xmlChar *) "expanded");
+
+			if (expanded_str && strcmp (expanded_str, "yes") == 0) {
+				expanded = TRUE;
+			} else {
+				expanded = FALSE;
+			}
+
+			individual_group = individual_group_new (name, expanded);
+			groups = g_list_append (groups, individual_group);
+
+			xmlFree (name);
+			xmlFree (expanded_str);
+		}
+
+		node = node->next;
+	}
+
+	g_debug ("Parsed %d contact groups", g_list_length (groups));
+
+	xmlFreeDoc (doc);
+	xmlFreeParserCtxt (ctxt);
+}
+
+static IndividualGroup *
+individual_group_new (const gchar *name,
+		   gboolean     expanded)
+{
+	IndividualGroup *group;
+
+	group = g_new0 (IndividualGroup, 1);
+
+	group->name = g_strdup (name);
+	group->expanded = expanded;
+
+	return group;
+}
+
+static void
+individual_group_free (IndividualGroup *group)
+{
+	g_return_if_fail (group != NULL);
+
+	g_free (group->name);
+
+	g_free (group);
+}
+
+static gboolean
+individual_groups_file_save (void)
+{
+	xmlDocPtr   doc;
+	xmlNodePtr  root;
+	xmlNodePtr  node;
+	GList      *l;
+	gchar      *dir;
+	gchar      *file;
+
+	dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
+	g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+	file = g_build_filename (dir, INDIVIDUAL_GROUPS_XML_FILENAME, NULL);
+	g_free (dir);
+
+	doc = xmlNewDoc ((const xmlChar *) "1.0");
+	root = xmlNewNode (NULL, (const xmlChar *) "individuals");
+	xmlDocSetRootElement (doc, root);
+
+	node = xmlNewChild (root, NULL, (const xmlChar *) "account", NULL);
+	xmlNewProp (node, (const xmlChar *) "name", (const xmlChar *) "Default");
+
+	for (l = groups; l; l = l->next) {
+		IndividualGroup *cg;
+		xmlNodePtr    subnode;
+
+		cg = l->data;
+
+		subnode = xmlNewChild (node, NULL, (const xmlChar *) "group", NULL);
+		xmlNewProp (subnode, (const xmlChar *) "expanded", cg->expanded ?
+				(const xmlChar *) "yes" : (const xmlChar *) "no");
+		xmlNewProp (subnode, (const xmlChar *) "name", (const xmlChar *) cg->name);
+	}
+
+	/* Make sure the XML is indented properly */
+	xmlIndentTreeOutput = 1;
+
+	g_debug ("Saving file:'%s'", file);
+	xmlSaveFormatFileEnc (file, doc, "utf-8", 1);
+	xmlFreeDoc (doc);
+
+	xmlMemoryDump ();
+
+	g_free (file);
+
+	return TRUE;
+}
+
+gboolean
+games_individual_group_get_expanded (const gchar *group)
+{
+	GList    *l;
+	gboolean  default_val = TRUE;
+
+	g_return_val_if_fail (group != NULL, default_val);
+
+	for (l = groups; l; l = l->next) {
+		IndividualGroup *cg = l->data;
+
+		if (!cg || !cg->name) {
+			continue;
+		}
+
+		if (strcmp (cg->name, group) == 0) {
+			return cg->expanded;
+		}
+	}
+
+	return default_val;
+}
+
+void
+games_individual_group_set_expanded (const gchar *group,
+				   gboolean     expanded)
+{
+	GList        *l;
+	IndividualGroup *cg;
+	gboolean      changed = FALSE;
+
+	g_return_if_fail (group != NULL);
+
+	for (l = groups; l; l = l->next) {
+		cg = l->data;
+
+		if (!cg || !cg->name) {
+			continue;
+		}
+
+		if (strcmp (cg->name, group) == 0) {
+			cg->expanded = expanded;
+			changed = TRUE;
+			break;
+		}
+	}
+
+	/* if here... we don't have an IndividualGroup for the group. */
+	if (!changed) {
+		cg = individual_group_new (group, expanded);
+		groups = g_list_append (groups, cg);
+	}
+
+	individual_groups_file_save ();
+}
diff --git a/libgames-contacts/games-individual-groups.dtd b/libgames-contacts/games-individual-groups.dtd
new file mode 100644
index 0000000..ff8b1b8
--- /dev/null
+++ b/libgames-contacts/games-individual-groups.dtd
@@ -0,0 +1,16 @@
+<!--
+  DTD for groups of folks' individuals.
+-->
+
+<!-- Root element -->
+<!ELEMENT individuals (account)>
+
+<!ELEMENT account (group)+>
+<!ATTLIST account 
+	name CDATA #REQUIRED>
+
+<!-- Groups in roster -->
+<!ELEMENT group EMPTY>
+<!ATTLIST group
+   name CDATA #REQUIRED
+   expanded CDATA #REQUIRED>
diff --git a/libgames-contacts/games-individual-groups.h b/libgames-contacts/games-individual-groups.h
new file mode 100644
index 0000000..7ec25d7
--- /dev/null
+++ b/libgames-contacts/games-individual-groups.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright  2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
+ * Authors: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+#ifndef __GAMES_INDIVIDUAL_GROUPS_H__
+#define __GAMES_INDIVIDUAL_GROUPS_H__
+
+G_BEGIN_DECLS
+
+#include <glib.h>
+
+void     games_individual_groups_get_all     (void);
+
+gboolean games_individual_group_get_expanded (const gchar *group);
+void     games_individual_group_set_expanded (const gchar *group,
+                                              gboolean     expanded);
+
+G_END_DECLS
+
+#endif /* __GAMES_INDIVIDUAL_GROUPS_H__ */
diff --git a/libgames-contacts/games-individual-manager.c b/libgames-contacts/games-individual-manager.c
new file mode 100644
index 0000000..4ebbbab
--- /dev/null
+++ b/libgames-contacts/games-individual-manager.c
@@ -0,0 +1,436 @@
+/* games-individual-manager.c: Store, manage and ref Folks individuals
+ *  who contain a GamesContact
+ *
+ * Copyright  2012 Chandni Verma
+ *
+ * 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 of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+#include <folks/folks-telepathy.h>
+
+#include "games-contact.h"
+#include "games-individual-manager.h"
+
+#define GAMES_INDIVIDUAL_MANAGER_GET_PRIV(obj) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GAMES_TYPE_INDIVIDUAL_MANAGER, \
+      GamesIndividualManagerPriv))
+
+/* Much of code has been influenced by Empathy, a chat
+ * application based on Telepathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ * */
+
+struct _GamesIndividualManagerPriv{
+  FolksIndividualAggregator *aggregator;
+
+  /* individual.id -> individual map */
+  GHashTable *individuals;
+
+  gboolean contacts_loaded;
+};
+
+enum {
+  GROUPS_CHANGED,
+  FAVOURITES_CHANGED,
+  MEMBERS_CHANGED,
+  CONTACTS_LOADED,
+  LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 }; 
+
+G_DEFINE_TYPE (GamesIndividualManager, games_individual_manager, G_TYPE_OBJECT);
+
+static GamesIndividualManager *manager_singleton = NULL;
+
+static GObject *
+individual_manager_constructor (GType type,
+                             guint n_construct_properties,
+                             GObjectConstructParam *construct_properties)
+{
+  GObject *retval;
+
+  if (manager_singleton == NULL)
+  {
+    retval = G_OBJECT_CLASS (games_individual_manager_parent_class)->
+        constructor (type, n_construct_properties, construct_properties);
+
+    manager_singleton = GAMES_INDIVIDUAL_MANAGER (retval);
+    g_object_add_weak_pointer (retval, (gpointer) &manager_singleton);
+  }
+  else
+  {
+    retval = g_object_ref (manager_singleton);
+  }
+
+  return retval;
+}
+
+static void
+individual_group_changed_cb (FolksIndividual *individual,
+    gchar *group,
+    gboolean is_member,
+    GamesIndividualManager *self)
+{
+  g_signal_emit (self, signals[GROUPS_CHANGED], 0, individual, group,
+      is_member);
+}
+
+static void
+individual_notify_is_favourite_cb (FolksIndividual *individual,
+    GParamSpec *pspec,
+    GamesIndividualManager *self)
+{
+  gboolean is_favourite = folks_favourite_details_get_is_favourite (
+      FOLKS_FAVOURITE_DETAILS (individual));
+  g_signal_emit (self, signals[FAVOURITES_CHANGED], 0, individual,
+      is_favourite);
+}
+
+static void
+add_individual (GamesIndividualManager *self, FolksIndividual *individual)
+{
+  GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+  g_hash_table_insert (priv->individuals,
+      g_strdup (folks_individual_get_id (individual)),
+      g_object_ref (individual));
+
+  g_signal_connect (individual, "group-changed",
+      G_CALLBACK (individual_group_changed_cb), self);
+  g_signal_connect (individual, "notify::is-favourite",
+      G_CALLBACK (individual_notify_is_favourite_cb), self);
+}
+
+static void
+remove_individual (GamesIndividualManager *self, FolksIndividual *individual)
+{
+  GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+  g_signal_handlers_disconnect_by_func (individual,
+      individual_group_changed_cb, self);
+  g_signal_handlers_disconnect_by_func (individual,
+      individual_notify_is_favourite_cb, self);
+
+  g_hash_table_remove (priv->individuals, folks_individual_get_id (individual));
+}
+
+/* Returns TRUE if the given Individual contains a TpContact */
+static gboolean
+individual_contains_tp_contact (FolksIndividual *individual)
+{
+  GeeSet *personas;
+  GeeIterator *iter;
+  gboolean retval = FALSE;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), FALSE);
+
+  personas = folks_individual_get_personas (individual);
+  iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+  while (!retval && gee_iterator_next (iter))
+    {
+      FolksPersona *persona = gee_iterator_get (iter);
+      TpContact *contact = NULL;
+
+      if (games_folks_persona_is_interesting (persona))
+        contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+
+      g_clear_object (&persona);
+
+      if (contact != NULL)
+        retval = TRUE;
+    }
+  g_clear_object (&iter);
+
+  return retval;
+}
+
+/* This is emitted for *all* individuals in the individual aggregator (not
+ * just the ones we keep a reference to), to allow for the case where a new
+ * individual doesn't contain a GamesContact, but later has a persona added
+ * which does. */
+static void
+individual_notify_personas_cb (FolksIndividual *individual,
+                               GParamSpec *pspec,
+                               GamesIndividualManager *self)
+{
+  GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+  const gchar *id = folks_individual_get_id (individual);
+  gboolean has_contact = individual_contains_tp_contact (individual);
+  gboolean had_contact = (g_hash_table_lookup (priv->individuals,
+      id) != NULL) ? TRUE : FALSE;
+
+  if (had_contact == TRUE && has_contact == FALSE)
+    {
+      GList *removed = NULL;
+
+      /* The Individual has lost its GamesContact */
+      removed = g_list_prepend (removed, individual);
+      g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, NULL, removed,
+          TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
+      g_list_free (removed);
+
+      remove_individual (self, individual);
+    }
+  else if (had_contact == FALSE && has_contact == TRUE)
+    {
+      GList *added = NULL;
+
+      /* The Individual has gained its first GamesContact */
+      add_individual (self, individual);
+
+      added = g_list_prepend (added, individual);
+      g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, added, NULL,
+          TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
+      g_list_free (added);
+    }
+}
+
+static void
+aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator,
+    GeeMultiMap *changes,
+    GamesIndividualManager *self)
+{
+  GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+  GeeIterator *iter;
+  GeeSet *removed;
+  GeeCollection *added;
+  GList *added_set = NULL, *added_filtered = NULL, *removed_list = NULL;
+
+  /* We're not interested in the relationships between the added and removed
+   * individuals, so just extract collections of them. Note that the added
+   * collection may contain duplicates, being a collection, while the removed
+   * set won't. */
+  removed = gee_multi_map_get_keys (changes);
+  added = gee_multi_map_get_values (changes);
+
+  /* Handle the removals first, as one of the added Individuals might have the
+   * same ID as one of the removed Individuals (due to linking). */
+  iter = gee_iterable_iterator (GEE_ITERABLE (removed));
+  while (gee_iterator_next (iter))
+    {
+      FolksIndividual *ind = gee_iterator_get (iter);
+
+      if (ind == NULL)
+        continue;
+
+      g_signal_handlers_disconnect_by_func (ind,
+          individual_notify_personas_cb, self);
+
+      if (g_hash_table_lookup (priv->individuals,
+          folks_individual_get_id (ind)) != NULL)
+        {
+          remove_individual (self, ind);
+          removed_list = g_list_prepend (removed_list, ind);
+        }
+
+      g_clear_object (&ind);
+    }
+  g_clear_object (&iter);
+
+  /* Filter the individuals for ones which contain GamesContacts */
+  iter = gee_iterable_iterator (GEE_ITERABLE (added));
+  while (gee_iterator_next (iter))
+    {
+      FolksIndividual *ind = gee_iterator_get (iter);
+
+      /* Make sure we handle each added individual only once. */
+      if (ind == NULL || g_list_find (added_set, ind) != NULL)
+        continue;
+      added_set = g_list_prepend (added_set, ind);
+
+      g_signal_connect (ind, "notify::personas",
+          G_CALLBACK (individual_notify_personas_cb), self);
+
+      /* We add all individuals having TpContacts since this set of 
+       * individuals and the manager singleton is common to all games.
+       * We leave filtering of individuals having a specific gaming
+       * capability upto respective GamesIndividualViews */
+      if (individual_contains_tp_contact (ind))
+        {
+          add_individual (self, ind);
+          added_filtered = g_list_prepend (added_filtered, ind);
+        }
+
+      g_clear_object (&ind);
+    }
+  g_clear_object (&iter);
+
+  g_list_free (added_set);
+
+  g_object_unref (added);
+  g_object_unref (removed);
+
+  /* Bail if we have no individuals left */
+  if (added_filtered == NULL && removed == NULL)
+    return;
+
+  added_filtered = g_list_reverse (added_filtered);
+
+  g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL,
+      added_filtered, removed_list,
+      TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+      TRUE);
+
+  g_list_free (added_filtered);
+  g_list_free (removed_list);
+}
+
+static void
+aggregator_is_quiescent_notify_cb (FolksIndividualAggregator *aggregator,
+                                   GParamSpec *spec,
+                                   GamesIndividualManager *self)
+{
+  GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+  gboolean is_quiescent;
+
+  if (priv->contacts_loaded)
+    return;
+
+  g_object_get (aggregator, "is-quiescent", &is_quiescent, NULL);
+
+  if (!is_quiescent)
+    return;
+
+  priv->contacts_loaded = TRUE;
+
+  g_signal_emit (self, signals[CONTACTS_LOADED], 0);
+}
+
+static void
+individual_manager_dispose (GObject *object)
+{
+  GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (object);
+
+  g_hash_table_unref (priv->individuals);
+
+  g_signal_handlers_disconnect_by_func (priv->aggregator,
+      aggregator_individuals_changed_cb, object);
+  g_signal_handlers_disconnect_by_func (priv->aggregator,
+      aggregator_is_quiescent_notify_cb, object);
+  tp_clear_object (&priv->aggregator);
+
+  G_OBJECT_CLASS (games_individual_manager_parent_class)->dispose (object);
+}
+
+static void
+games_individual_manager_class_init (GamesIndividualManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (GamesIndividualManagerPriv));
+
+  object_class->dispose = individual_manager_dispose;
+  object_class->constructor = individual_manager_constructor;
+
+  signals[GROUPS_CHANGED] =
+      g_signal_new ("groups-changed",
+          G_TYPE_FROM_CLASS (klass),
+          G_SIGNAL_RUN_LAST,
+          0,
+          NULL, NULL,
+          g_cclosure_marshal_generic,
+          G_TYPE_NONE, 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_STRING, G_TYPE_BOOLEAN);
+
+  signals[FAVOURITES_CHANGED] =
+      g_signal_new ("favourites-changed",
+          G_TYPE_FROM_CLASS (klass),
+          G_SIGNAL_RUN_LAST,
+          0,
+          NULL, NULL,
+          g_cclosure_marshal_generic,
+          G_TYPE_NONE, 2, FOLKS_TYPE_INDIVIDUAL, G_TYPE_BOOLEAN);
+
+  signals[MEMBERS_CHANGED] =
+      g_signal_new ("members-changed",
+          G_TYPE_FROM_CLASS (klass),
+          G_SIGNAL_RUN_LAST,
+          0,
+          NULL, NULL,
+          g_cclosure_marshal_generic,
+          G_TYPE_NONE,
+          4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_UINT);
+
+  signals[CONTACTS_LOADED] =
+      g_signal_new ("contacts-loaded",
+          G_TYPE_FROM_CLASS (klass),
+          G_SIGNAL_RUN_LAST,
+          0,
+          NULL, NULL,
+          g_cclosure_marshal_generic,
+          G_TYPE_NONE,
+          0);
+}
+
+static void
+games_individual_manager_init (GamesIndividualManager *self)
+{
+  GamesIndividualManagerPriv *priv;
+
+  self->priv = priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+  priv->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
+      g_free, g_object_unref);
+
+  priv->aggregator = folks_individual_aggregator_new ();
+  g_signal_connect (priv->aggregator, "individuals-changed-detailed",
+      G_CALLBACK (aggregator_individuals_changed_cb), self);
+  g_signal_connect (priv->aggregator, "notify::is-quiescent",
+      G_CALLBACK (aggregator_is_quiescent_notify_cb), self);
+
+  /* Prepare individual aggregator for use */
+  folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
+}
+
+GamesIndividualManager *
+games_individual_manager_dup_singleton (void)
+{
+  return g_object_new (GAMES_TYPE_INDIVIDUAL_MANAGER, NULL);
+}
+
+GList *
+games_individual_manager_get_members (GamesIndividualManager *self)
+{
+  GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+  g_return_val_if_fail (GAMES_IS_INDIVIDUAL_MANAGER (self), NULL);
+
+  return g_hash_table_get_values (priv->individuals);
+}
+
+gboolean
+games_individual_manager_get_contacts_loaded (GamesIndividualManager *self)
+{
+  GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+  return priv->contacts_loaded;
+}
+
+FolksIndividual *
+games_individual_manager_lookup_member (GamesIndividualManager *self,
+    const gchar *id)
+{
+  GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+  g_return_val_if_fail (GAMES_IS_INDIVIDUAL_MANAGER (self), NULL);
+
+  return g_hash_table_lookup (priv->individuals, id);
+}
+
+
diff --git a/libgames-contacts/games-individual-manager.h b/libgames-contacts/games-individual-manager.h
new file mode 100644
index 0000000..cb786ba
--- /dev/null
+++ b/libgames-contacts/games-individual-manager.h
@@ -0,0 +1,67 @@
+/* games-individual-manager.h: Manage Folks individuals 
+ *
+ * Copyright  2012 Chandni Verma
+ *
+ * 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 of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+#ifndef GAMES_INDIVIDUAL_MANAGER_H
+#define GAMES_INDIVIDUAL_MANAGER_H
+
+#include <glib.h>
+#include <folks/folks.h>
+
+G_BEGIN_DECLS
+
+#define GAMES_TYPE_INDIVIDUAL_MANAGER            (games_individual_manager_get_type ())
+#define GAMES_INDIVIDUAL_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAMES_TYPE_INDIVIDUAL_MANAGER, GamesIndividualManager))
+#define GAMES_INDIVIDUAL_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAMES_TYPE_INDIVIDUAL_MANAGER, GamesIndividualManagerClass))
+#define GAMES_IS_INDIVIDUAL_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAMES_TYPE_INDIVIDUAL_MANAGER))
+#define GAMES_IS_INDIVIDUAL_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAMES_TYPE_INDIVIDUAL_MANAGER))
+#define GAMES_INDIVIDUAL_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GAMES_TYPE_INDIVIDUAL_MANAGER))
+
+typedef struct _GamesIndividualManager        GamesIndividualManager;
+typedef struct _GamesIndividualManagerClass   GamesIndividualManagerClass;
+typedef struct _GamesIndividualManagerPriv    GamesIndividualManagerPriv;
+
+struct _GamesIndividualManager{
+  GObject parent;
+
+  /*< private >*/
+  GamesIndividualManagerPriv *priv;
+};
+
+struct _GamesIndividualManagerClass{
+  GObjectClass parent_class;
+};
+
+/* used by GAMES_TYPE_INDIVIDUAL_MANAGER */
+GType                 games_individual_manager_get_type                      (void);
+
+/* Function prototypes */
+GamesIndividualManager  *games_individual_manager_dup_singleton              (void);
+GList                *games_individual_manager_get_members                   (GamesIndividualManager *self);
+FolksIndividual      *games_individual_manager_lookup_member                 (GamesIndividualManager *manager,
+                                                                              const gchar            *id);
+
+gboolean              games_individual_manager_get_contacts_loaded           (GamesIndividualManager *self);
+
+
+G_END_DECLS
+#endif /* GAMES_INDIVIDUAL_MANAGER_H */
+/* EOF */
diff --git a/libgames-contacts/games-individual-store-manager.c b/libgames-contacts/games-individual-store-manager.c
new file mode 100644
index 0000000..1c87fc9
--- /dev/null
+++ b/libgames-contacts/games-individual-store-manager.c
@@ -0,0 +1,321 @@
+/* games-individual-store-manager.c: Is a GamesIndividualStore which connects to
+ *                                   GamesIndividualManager signals to update
+ *                                   itself and manages initial loading of
+ *                                   contacts.
+ *
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ */
+
+/*
+ * This code has been influenced by folks usage in Empathy,
+ * a chat application based on Telepathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2005-2007 Imendio ABdd
+ * Copyright (C) 2007-2011 Collabora Ltd.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+#include <telepathy-glib/util.h>
+
+/*#include <libgames-support/games-utils.h>*/
+#include "games-enum-types.h"
+#include "games-individual-manager.h"
+
+#include "games-individual-store-manager.h"
+
+#include "games-ui-utils.h"
+#include "games-enum-types.h"
+
+struct _GamesIndividualStoreManagerPriv
+{
+  GamesIndividualManager *manager;
+  gboolean setup_idle_id;
+};
+
+enum
+{
+  PROP_0,
+  PROP_INDIVIDUAL_MANAGER,
+};
+
+
+G_DEFINE_TYPE (GamesIndividualStoreManager, games_individual_store_manager,
+    GAMES_TYPE_INDIVIDUAL_STORE);
+
+static void
+individual_store_manager_members_changed_cb (GamesIndividualManager *manager,
+    const gchar *message,
+    GList *added,
+    GList *removed,
+    guint reason,
+    GamesIndividualStoreManager *self)
+{
+  GList *l;
+  GamesIndividualStore *store = GAMES_INDIVIDUAL_STORE (self);
+
+  for (l = removed; l; l = l->next)
+    {
+      g_debug ("Individual %s (%s) %s",
+          folks_individual_get_id (l->data),
+          folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (l->data)),
+          "removed");
+
+      individual_store_remove_individual_and_disconnect (store, l->data);
+    }
+
+  for (l = added; l; l = l->next)
+    {
+      g_debug ("Individual %s (%s) %s", folks_individual_get_id (l->data),
+          folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (l->data)),
+          "added");
+
+      individual_store_add_individual_and_connect (store, l->data);
+    }
+}
+
+static void
+individual_store_manager_groups_changed_cb (GamesIndividualManager *manager,
+    FolksIndividual *individual,
+    gchar *group,
+    gboolean is_member,
+    GamesIndividualStoreManager *self)
+{
+  GamesIndividualStore *store = GAMES_INDIVIDUAL_STORE (self);
+
+  g_debug ("Updating groups for individual %s (%s)",
+      folks_individual_get_id (individual),
+      folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
+
+  /* We do this to make sure the groups are correct, if not, we
+   * would have to check the groups already set up for each
+   * contact and then see what has been updated.
+   */
+  games_individual_store_refresh_individual (store, individual);
+}
+
+static void
+individual_store_manager_add_existing_individuals (
+    GamesIndividualStoreManager *self,
+    const gchar *message)
+{
+  GList *individuals;
+
+  individuals = games_individual_manager_get_members (self->priv->manager);
+  if (individuals != NULL)
+    {
+      individual_store_manager_members_changed_cb (self->priv->manager,
+          message, individuals, NULL, 0, self);
+      g_list_free (individuals);
+    }
+}
+
+static gboolean
+individual_store_manager_manager_setup (gpointer user_data)
+{
+  GamesIndividualStoreManager *self = user_data;
+
+  /* Signal connection. */
+
+  g_debug ("handling individual renames unimplemented");
+
+  g_signal_connect (self->priv->manager,
+      "members-changed",
+      G_CALLBACK (individual_store_manager_members_changed_cb), self);
+
+  g_signal_connect (self->priv->manager,
+      "groups-changed",
+      G_CALLBACK (individual_store_manager_groups_changed_cb), self);
+
+  /* Add contacts already created. */
+  individual_store_manager_add_existing_individuals (self, "initial add");
+
+  self->priv->setup_idle_id = 0;
+  return FALSE;
+}
+
+static void
+individual_store_manager_set_individual_manager (
+    GamesIndividualStoreManager *self,
+    GamesIndividualManager *manager)
+{
+  g_assert (self->priv->manager == NULL); /* construct only */
+  self->priv->manager = g_object_ref (manager);
+
+  /* Let a chance to have all properties set before populating. They are all
+   * set after constructed () gets called and before this object is returned
+   * to the code creating it.
+   *
+   * Loading @manager is added to an idle source here to avoid having every
+   * code creating a GamesIndividualManagerStore object do it (since loading
+   * can take some time) after obtaining this object. */
+  self->priv->setup_idle_id = g_idle_add (
+      individual_store_manager_manager_setup, self);
+}
+
+static void
+individual_store_manager_dispose (GObject *object)
+{
+  GamesIndividualStoreManager *self = GAMES_INDIVIDUAL_STORE_MANAGER (
+      object);
+  GamesIndividualStore *store = GAMES_INDIVIDUAL_STORE (object);
+  GList *individuals, *l;
+
+  individuals = games_individual_manager_get_members (self->priv->manager);
+  for (l = individuals; l; l = l->next)
+    {
+      games_individual_store_disconnect_individual (store,
+          FOLKS_INDIVIDUAL (l->data));
+    }
+  tp_clear_pointer (&individuals, g_list_free);
+
+  if (self->priv->manager != NULL)
+    {
+      g_signal_handlers_disconnect_by_func (self->priv->manager,
+          G_CALLBACK (individual_store_manager_members_changed_cb), object);
+      g_signal_handlers_disconnect_by_func (self->priv->manager,
+          G_CALLBACK (individual_store_manager_groups_changed_cb), object);
+      g_clear_object (&self->priv->manager);
+    }
+
+  if (self->priv->setup_idle_id != 0)
+    {
+      g_source_remove (self->priv->setup_idle_id);
+      self->priv->setup_idle_id = 0;
+    }
+
+  G_OBJECT_CLASS (games_individual_store_manager_parent_class)->dispose (
+      object);
+}
+
+static void
+individual_store_manager_get_property (GObject *object,
+    guint param_id,
+    GValue *value,
+    GParamSpec *pspec)
+{
+  GamesIndividualStoreManager *self = GAMES_INDIVIDUAL_STORE_MANAGER (
+      object);
+
+  switch (param_id)
+    {
+    case PROP_INDIVIDUAL_MANAGER:
+      g_value_set_object (value, self->priv->manager);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    };
+}
+
+static void
+individual_store_manager_set_property (GObject *object,
+    guint param_id,
+    const GValue *value,
+    GParamSpec *pspec)
+{
+  switch (param_id)
+    {
+    case PROP_INDIVIDUAL_MANAGER:
+      individual_store_manager_set_individual_manager (
+          GAMES_INDIVIDUAL_STORE_MANAGER (object),
+          g_value_get_object (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    };
+}
+
+static void
+individual_store_manager_reload_individuals (GamesIndividualStore *store)
+{
+  GamesIndividualStoreManager *self = GAMES_INDIVIDUAL_STORE_MANAGER (
+      store);
+
+  individual_store_manager_add_existing_individuals (self,
+      "re-adding members: toggled group visibility");
+}
+
+static gboolean
+individual_store_manager_initial_loading (GamesIndividualStore *store)
+{
+  GamesIndividualStoreManager *self = GAMES_INDIVIDUAL_STORE_MANAGER (
+      store);
+
+  return self->priv->setup_idle_id != 0;
+}
+
+static void
+games_individual_store_manager_class_init (
+    GamesIndividualStoreManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GamesIndividualStoreClass *store_class = GAMES_INDIVIDUAL_STORE_CLASS (
+      klass);
+
+  object_class->dispose = individual_store_manager_dispose;
+  object_class->get_property = individual_store_manager_get_property;
+  object_class->set_property = individual_store_manager_set_property;
+
+  store_class->reload_individuals = individual_store_manager_reload_individuals;
+  store_class->initial_loading = individual_store_manager_initial_loading;
+
+  g_object_class_install_property (object_class,
+      PROP_INDIVIDUAL_MANAGER,
+      g_param_spec_object ("individual-manager",
+          "Individual manager",
+          "Individual manager",
+          GAMES_TYPE_INDIVIDUAL_MANAGER,
+          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+  g_type_class_add_private (object_class,
+      sizeof (GamesIndividualStoreManagerPriv));
+}
+
+static void
+games_individual_store_manager_init (GamesIndividualStoreManager *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      GAMES_TYPE_INDIVIDUAL_STORE_MANAGER, GamesIndividualStoreManagerPriv);
+}
+
+GamesIndividualStoreManager *
+games_individual_store_manager_new (GamesIndividualManager *manager)
+{
+  g_return_val_if_fail (GAMES_IS_INDIVIDUAL_MANAGER (manager), NULL);
+
+  return g_object_new (GAMES_TYPE_INDIVIDUAL_STORE_MANAGER,
+      "individual-manager", manager, NULL);
+}
+
+GamesIndividualManager *
+games_individual_store_manager_get_manager (
+    GamesIndividualStoreManager *self)
+{
+  g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE_MANAGER (self), FALSE);
+
+  return self->priv->manager;
+}
diff --git a/libgames-contacts/games-individual-store-manager.h b/libgames-contacts/games-individual-store-manager.h
new file mode 100644
index 0000000..f330585
--- /dev/null
+++ b/libgames-contacts/games-individual-store-manager.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GAMES_INDIVIDUAL_STORE_MANAGER_H__
+#define __GAMES_INDIVIDUAL_STORE_MANAGER_H__
+
+#include <gtk/gtk.h>
+
+#include "games-individual-manager.h"
+
+#include "games-individual-store.h"
+
+G_BEGIN_DECLS
+#define GAMES_TYPE_INDIVIDUAL_STORE_MANAGER         (games_individual_store_manager_get_type ())
+#define GAMES_INDIVIDUAL_STORE_MANAGER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GAMES_TYPE_INDIVIDUAL_STORE_MANAGER, GamesIndividualStoreManager))
+#define GAMES_INDIVIDUAL_STORE_MANAGER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GAMES_TYPE_INDIVIDUAL_STORE_MANAGER, GamesIndividualStoreManagerClass))
+#define GAMES_IS_INDIVIDUAL_STORE_MANAGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GAMES_TYPE_INDIVIDUAL_STORE_MANAGER))
+#define GAMES_IS_INDIVIDUAL_STORE_MANAGER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GAMES_TYPE_INDIVIDUAL_STORE_MANAGER))
+#define GAMES_INDIVIDUAL_STORE_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GAMES_TYPE_INDIVIDUAL_STORE_MANAGER, GamesIndividualStoreManagerClass))
+
+typedef struct _GamesIndividualStoreManager GamesIndividualStoreManager;
+typedef struct _GamesIndividualStoreManagerClass GamesIndividualStoreManagerClass;
+typedef struct _GamesIndividualStoreManagerPriv GamesIndividualStoreManagerPriv;
+
+struct _GamesIndividualStoreManager
+{
+  GamesIndividualStore parent;
+  GamesIndividualStoreManagerPriv *priv;
+};
+
+struct _GamesIndividualStoreManagerClass
+{
+  GamesIndividualStoreClass parent_class;
+};
+
+GType games_individual_store_manager_get_type (void) G_GNUC_CONST;
+
+GamesIndividualStoreManager *games_individual_store_manager_new (
+    GamesIndividualManager *manager);
+
+GamesIndividualManager *games_individual_store_manager_get_manager (
+    GamesIndividualStoreManager *store);
+
+G_END_DECLS
+#endif /* __GAMES_INDIVIDUAL_STORE_MANAGER_H__ */
diff --git a/libgames-contacts/games-individual-store.c b/libgames-contacts/games-individual-store.c
new file mode 100644
index 0000000..e147908
--- /dev/null
+++ b/libgames-contacts/games-individual-store.c
@@ -0,0 +1,1756 @@
+/* games-individual-store.c: A GtkListStore to model Folks individuals
+ *  obtained from GamesIndividualManager and their capabilities.
+ *  Managing functions are provided by GamesIndividualStoreManager, an
+ *  extension of GamesIndividualStore.
+ *
+ * Copyright  2012 Chandni Verma
+ *
+ * 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 of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "games-individual-store.h"
+#include "games-individual-manager.h"
+#include "games-contact.h"
+#include "games-enum-types.h"
+#include "games-ui-utils.h"
+
+#define GAMES_INDIVIDUAL_STORE_GET_PRIV(obj) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GAMES_TYPE_INDIVIDUAL_STORE, \
+      GamesIndividualStorePriv))
+
+/* Much of code here is a simplified version of Folks usage in Empathy,
+ * a chat application based on Telepathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ * */
+
+/* Active users are those which have recently changed state
+ * (e.g. online, offline or from normal to a busy state).
+ */
+
+/* Time in seconds user is shown as active */
+#define ACTIVE_USER_SHOW_TIME 7
+
+/* Time in seconds after connecting which we wait before active users are enabled */
+#define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5
+
+struct _GamesIndividualStorePriv{
+  gboolean show_avatars;
+  gboolean show_groups;
+  gboolean is_compact;
+  gboolean show_protocols;
+  GamesIndividualStoreSort sort_criterium;
+  guint inhibit_active;
+  gboolean dispose_has_run;
+  GHashTable *status_icons;
+  /* List of owned GCancellables for each pending avatar load operation */
+  GList *avatar_cancellables;
+  /* Hash: FolksIndividual* -> GQueue (GtkTreeIter *) */
+  GHashTable                  *folks_individual_cache;
+  /* Hash: char *groupname -> GtkTreeIter * */
+  GHashTable                  *contact_group_cache;
+  gboolean show_active;
+};
+
+typedef struct
+{
+  GamesIndividualStore *self;
+  FolksIndividual *individual;
+  gboolean remove;
+  guint timeout;
+} ShowActiveData;
+
+enum
+{
+  PROP_0,
+  PROP_SHOW_AVATARS,
+  PROP_SHOW_PROTOCOLS,
+  PROP_SHOW_GROUPS,
+  PROP_IS_COMPACT,
+  PROP_SORT_CRITERIUM
+};
+
+/* Forward declarations */
+static void individual_store_contact_update (GamesIndividualStore *self,
+    FolksIndividual *individual);
+
+G_DEFINE_TYPE (GamesIndividualStore, games_individual_store, GTK_TYPE_TREE_STORE);
+
+/**
+ * individual_store_get_individual_gaming_capabilities:
+ * @individual: the individual whose gaming capabilities are to be gauged
+ *
+ * Returns: All gaming capabilities in a single pass through an individual's
+ * personas as ORed GamesCapabilities flags.
+ */
+static GamesCapabilities
+individual_store_get_individual_gaming_capabilities (
+    FolksIndividual *individual)
+{
+  GeeSet *personas;
+  GeeIterator *iter;
+  GamesCapabilities individual_caps = GAMES_CAPABILITIES_NONE;
+
+  personas = folks_individual_get_personas (individual);
+  iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+
+  while (gee_iterator_next (iter))
+  {
+    FolksPersona *persona;
+    TpContact *tp_contact;
+    GamesContact *contact;
+    GamesCapabilities contact_caps;
+
+    persona = gee_iterator_get (iter);
+    /* Ignore non-telepathy personas */
+    if (!games_folks_persona_is_interesting (persona))
+      goto while_finally;
+
+    tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+    if (tp_contact == NULL)
+        /* Possibly network disconnected */
+        goto while_finally;
+
+    contact = games_contact_dup_from_tp_contact (tp_contact);
+
+    contact_caps = games_contact_get_capabilities (contact);
+    individual_caps |= contact_caps;
+
+    g_object_unref (contact);
+    while_finally:
+    g_clear_object (&persona);
+  }
+
+  g_clear_object (&iter);
+  return individual_caps;
+}
+
+static void
+add_individual_to_store (GtkTreeStore *store,
+    GtkTreeIter *iter,
+    GtkTreeIter *parent,
+    FolksIndividual *individual)
+{
+  GamesIndividualStore *self = GAMES_INDIVIDUAL_STORE (store);
+  GamesCapabilities individual_caps =
+      individual_store_get_individual_gaming_capabilities (individual);
+  GQueue *queue;
+
+  gtk_tree_store_insert_with_values (store, iter, parent, 0,
+      GAMES_INDIVIDUAL_STORE_COL_NAME,
+      folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
+      GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, individual,
+      GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, FALSE,
+      GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
+      GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL_CAPABILITIES, individual_caps,
+      -1);
+
+  queue = g_hash_table_lookup (self->priv->folks_individual_cache, individual);
+  if (queue)
+    {
+      g_queue_push_tail (queue, gtk_tree_iter_copy (iter));
+    }
+  else
+    {
+      queue = g_queue_new ();
+      g_queue_push_tail (queue, gtk_tree_iter_copy (iter));
+      g_hash_table_insert (self->priv->folks_individual_cache, individual,
+          queue);
+    }
+}
+
+static void
+individual_store_get_group (GamesIndividualStore *self,
+    const gchar *name,
+    GtkTreeIter *iter_group_to_set,
+    GtkTreeIter *iter_separator_to_set,
+    gboolean *created,
+    gboolean is_fake_group)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter_group;
+  GtkTreeIter iter_separator;
+  GtkTreeIter *iter;
+
+  model = GTK_TREE_MODEL (self);
+  iter = g_hash_table_lookup (self->priv->contact_group_cache, name);
+
+  if (iter == NULL)
+    {
+      if (created)
+        *created = TRUE;
+
+      gtk_tree_store_insert_with_values (GTK_TREE_STORE (self), &iter_group,
+          NULL, 0,
+          GAMES_INDIVIDUAL_STORE_COL_ICON_STATUS, NULL,
+          GAMES_INDIVIDUAL_STORE_COL_NAME, name,
+          GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, TRUE,
+          GAMES_INDIVIDUAL_STORE_COL_IS_ACTIVE, FALSE,
+          GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
+          GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake_group,
+          -1);
+
+      g_hash_table_insert (self->priv->contact_group_cache, g_strdup (name),
+          gtk_tree_iter_copy (&iter_group));
+
+      if (iter_group_to_set)
+        *iter_group_to_set = iter_group;
+
+      gtk_tree_store_insert_with_values (GTK_TREE_STORE (self), &iter_separator,
+          &iter_group, 0,
+          GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, TRUE,
+          -1);
+
+      if (iter_separator_to_set)
+        *iter_separator_to_set = iter_separator;
+    }
+  else
+    {
+      if (created)
+        *created = FALSE;
+
+      if (iter_group_to_set)
+        *iter_group_to_set = *iter;
+
+      iter_separator = *iter;
+
+      if (gtk_tree_model_iter_next (model, &iter_separator))
+        {
+          gboolean is_separator;
+
+          gtk_tree_model_get (model, &iter_separator,
+              GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator, -1);
+
+          if (is_separator && iter_separator_to_set)
+            *iter_separator_to_set = iter_separator;
+        }
+    }
+}
+
+static GList *
+individual_store_find_contact (GamesIndividualStore *self,
+    FolksIndividual *individual)
+{
+  GQueue *row_refs_queue;
+  GList *i;
+  GList *iters_list = NULL;
+
+  row_refs_queue = g_hash_table_lookup (self->priv->folks_individual_cache,
+      individual);
+  if (!row_refs_queue)
+    return NULL;
+
+  for (i = g_queue_peek_head_link (row_refs_queue) ; i != NULL ; i = i->next)
+    {
+      GtkTreeIter *iter = i->data;
+
+      iters_list = g_list_prepend (iters_list, gtk_tree_iter_copy (iter));
+    }
+
+  return iters_list;
+}
+
+static void
+free_iters (GList *iters)
+{
+  g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+  g_list_free (iters);
+}
+
+void
+games_individual_store_remove_individual (GamesIndividualStore *self,
+    FolksIndividual *individual)
+{
+  GtkTreeModel *model;
+  GQueue *row_refs;
+  GList *l;
+
+  row_refs = g_hash_table_lookup (self->priv->folks_individual_cache,
+      individual);
+  if (!row_refs)
+    return;
+
+  /* Clean up model */
+  model = GTK_TREE_MODEL (self);
+
+  for (l = g_queue_peek_head_link (row_refs); l; l = l->next)
+    {
+      GtkTreeIter *iter = l->data;
+      GtkTreeIter parent;
+
+      /* NOTE: it is only <= 2 here because we have
+       * separators after the group name, otherwise it
+       * should be 1.
+       */
+      if (gtk_tree_model_iter_parent (model, &parent, iter) &&
+          gtk_tree_model_iter_n_children (model, &parent) <= 2)
+        {
+          gchar *group_name;
+          gtk_tree_model_get (model, &parent,
+              GAMES_INDIVIDUAL_STORE_COL_NAME, &group_name,
+              -1);
+          g_hash_table_remove (self->priv->contact_group_cache,
+              group_name);
+          gtk_tree_store_remove (GTK_TREE_STORE (self), &parent);
+        }
+      else
+        {
+          gtk_tree_store_remove (GTK_TREE_STORE (self), iter);
+        }
+    }
+
+  g_hash_table_remove (self->priv->folks_individual_cache, individual);
+}
+
+gboolean
+games_individual_store_get_show_avatars (GamesIndividualStore *self)
+{
+  g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (self), TRUE);
+
+  return self->priv->show_avatars;
+}
+
+static gboolean
+individual_store_update_list_mode_foreach (GtkTreeModel *model,
+    GtkTreePath *path,
+    GtkTreeIter *iter,
+    GamesIndividualStore *self)
+{
+  gboolean show_avatar = FALSE;
+  FolksIndividual *individual;
+  GdkPixbuf *pixbuf_status;
+
+  if (self->priv->show_avatars && !self->priv->is_compact)
+    {
+      show_avatar = TRUE;
+    }
+
+  gtk_tree_model_get (model, iter,
+      GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+
+  if (individual == NULL)
+    {
+      return FALSE;
+    }
+  /* get icon from hash_table */
+  pixbuf_status =
+      games_individual_store_get_individual_status_icon (self, individual);
+
+  gtk_tree_store_set (GTK_TREE_STORE (self), iter,
+      GAMES_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf_status,
+      GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+      GAMES_INDIVIDUAL_STORE_COL_COMPACT, self->priv->is_compact, -1);
+
+  g_object_unref (individual);
+
+  return FALSE;
+}
+
+void
+games_individual_store_set_show_avatars (GamesIndividualStore *self,
+    gboolean show_avatars)
+{
+  GtkTreeModel *model;
+
+  g_return_if_fail (GAMES_IS_INDIVIDUAL_STORE (self));
+
+  self->priv->show_avatars = show_avatars;
+
+  model = GTK_TREE_MODEL (self);
+
+  gtk_tree_model_foreach (model,
+      (GtkTreeModelForeachFunc)
+      individual_store_update_list_mode_foreach, self);
+
+  g_object_notify (G_OBJECT (self), "show-avatars");
+}
+
+gboolean
+games_individual_store_get_show_protocols (GamesIndividualStore *self)
+{
+  g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (self), TRUE);
+
+  return self->priv->show_protocols;
+}
+
+void
+games_individual_store_set_show_protocols (GamesIndividualStore *self,
+    gboolean show_protocols)
+{
+  GtkTreeModel *model;
+
+  g_return_if_fail (GAMES_IS_INDIVIDUAL_STORE (self));
+
+  self->priv->show_protocols = show_protocols;
+
+  model = GTK_TREE_MODEL (self);
+
+  gtk_tree_model_foreach (model,
+      (GtkTreeModelForeachFunc)
+      individual_store_update_list_mode_foreach, self);
+
+  g_object_notify (G_OBJECT (self), "show-protocols");
+}
+
+gboolean
+games_individual_store_get_show_groups (GamesIndividualStore *self)
+{
+  g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (self), TRUE);
+
+  return self->priv->show_groups;
+}
+
+void
+games_individual_store_set_show_groups (GamesIndividualStore *self,
+    gboolean show_groups)
+{
+  GamesIndividualStoreClass *klass;
+
+  g_return_if_fail (GAMES_IS_INDIVIDUAL_STORE (self));
+
+  klass = GAMES_INDIVIDUAL_STORE_GET_CLASS ( self);
+
+  if (self->priv->show_groups == show_groups)
+    {
+      return;
+    }
+
+  self->priv->show_groups = show_groups;
+
+  if (!klass->initial_loading (self))
+    {
+      /* Remove all contacts and add them back, not optimized but
+       * that's the easy way :)
+       *
+       * This is only done if there's not a pending setup idle
+       * callback, otherwise it will race and the contacts will get
+       * added twice */
+
+      gtk_tree_store_clear (GTK_TREE_STORE (self));
+      /* Also clear the cache */
+      g_hash_table_remove_all (self->priv->folks_individual_cache);
+      g_hash_table_remove_all (self->priv->contact_group_cache);
+
+      klass->reload_individuals (self);
+    }
+
+  g_object_notify (G_OBJECT (self), "show-groups");
+}
+
+gboolean
+games_individual_store_get_is_compact (GamesIndividualStore *self)
+{
+  g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (self), TRUE);
+
+  return self->priv->is_compact;
+}
+
+void
+games_individual_store_set_is_compact (GamesIndividualStore *self,
+    gboolean is_compact)
+{
+  GtkTreeModel *model;
+
+  g_return_if_fail (GAMES_IS_INDIVIDUAL_STORE (self));
+
+  self->priv->is_compact = is_compact;
+
+  model = GTK_TREE_MODEL (self);
+
+  gtk_tree_model_foreach (model,
+      (GtkTreeModelForeachFunc)
+      individual_store_update_list_mode_foreach, self);
+
+  g_object_notify (G_OBJECT (self), "is-compact");
+}
+
+GamesIndividualStoreSort
+games_individual_store_get_sort_criterium (GamesIndividualStore *self)
+{
+  g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (self), 0);
+
+  return self->priv->sort_criterium;
+}
+
+void
+games_individual_store_set_sort_criterium (GamesIndividualStore *self,
+    GamesIndividualStoreSort sort_criterium)
+{
+  g_return_if_fail (GAMES_IS_INDIVIDUAL_STORE (self));
+
+  self->priv->sort_criterium = sort_criterium;
+
+  switch (sort_criterium)
+    {
+    case GAMES_INDIVIDUAL_STORE_SORT_STATE:
+      gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
+          GAMES_INDIVIDUAL_STORE_COL_STATUS, GTK_SORT_ASCENDING);
+      break;
+
+    case GAMES_INDIVIDUAL_STORE_SORT_NAME:
+      gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
+          GAMES_INDIVIDUAL_STORE_COL_NAME, GTK_SORT_ASCENDING);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+
+  g_object_notify (G_OBJECT (self), "sort-criterium");
+}
+
+static gint
+get_position (const char **strv,
+    const char *str)
+{
+  int i;
+
+  for (i = 0; strv[i] != NULL; i++)
+    {
+      if (!tp_strdiff (strv[i], str))
+        return i;
+    }
+
+  return -1;
+}
+
+static gint
+compare_separator_and_groups (gboolean is_separator_a,
+    gboolean is_separator_b,
+    const gchar *name_a,
+    const gchar *name_b,
+    FolksIndividual *individual_a,
+    FolksIndividual *individual_b,
+    gboolean fake_group_a,
+    gboolean fake_group_b)
+{
+  /* these two lists are the sorted list of fake groups to include at the
+   * top and bottom of the roster */
+  const char *top_groups[] = {
+    GAMES_INDIVIDUAL_STORE_FAVORITE,
+    NULL
+  };
+
+  const char *bottom_groups[] = {
+    GAMES_INDIVIDUAL_STORE_UNGROUPED,
+    NULL
+  };
+
+  if (is_separator_a || is_separator_b)
+    {
+      /* We have at least one separator */
+      if (is_separator_a)
+        {
+          return -1;
+        }
+      else if (is_separator_b)
+        {
+          return 1;
+        }
+    }
+
+  /* One group and one contact */
+  if (!individual_a && individual_b)
+    {
+      return 1;
+    }
+  else if (individual_a && !individual_b)
+    {
+      return -1;
+    }
+  else if (!individual_a && !individual_b)
+    {
+      gboolean a_in_top, b_in_top, a_in_bottom, b_in_bottom;
+
+      a_in_top = fake_group_a && tp_strv_contains (top_groups, name_a);
+      b_in_top = fake_group_b && tp_strv_contains (top_groups, name_b);
+      a_in_bottom = fake_group_a && tp_strv_contains (bottom_groups, name_a);
+      b_in_bottom = fake_group_b && tp_strv_contains (bottom_groups, name_b);
+
+      if (a_in_top && b_in_top)
+        {
+          /* compare positions */
+          return CLAMP (get_position (top_groups, name_a) -
+              get_position (top_groups, name_b), -1, 1);
+        }
+      else if (a_in_bottom && b_in_bottom)
+        {
+          /* compare positions */
+          return CLAMP (get_position (bottom_groups, name_a) -
+              get_position (bottom_groups, name_b), -1, 1);
+        }
+      else if (a_in_top || b_in_bottom)
+        {
+          return -1;
+        }
+      else if (b_in_top || a_in_bottom)
+        {
+          return 1;
+        }
+      else
+        {
+          return g_utf8_collate (name_a, name_b);
+        }
+    }
+
+  /* Two contacts, ordering depends of the sorting policy */
+  return 0;
+}
+
+static gint
+individual_store_contact_sort (FolksIndividual *individual_a,
+    FolksIndividual *individual_b)
+{
+  gint ret_val;
+  GamesContact *contact_a = NULL, *contact_b = NULL;
+  TpAccount *account_a, *account_b;
+
+  g_return_val_if_fail (individual_a != NULL || individual_b != NULL, 0);
+
+  /* alias */
+  ret_val = g_utf8_collate (
+      folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual_a)),
+      folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual_b)));
+
+  if (ret_val != 0)
+    goto out;
+
+  contact_a = games_contact_dup_from_folks_individual (individual_a);
+  contact_b = games_contact_dup_from_folks_individual (individual_b);
+  if (contact_a != NULL && contact_b != NULL)
+    {
+      account_a = games_contact_get_account (contact_a);
+      account_b = games_contact_get_account (contact_b);
+
+      g_assert (account_a != NULL);
+      g_assert (account_b != NULL);
+
+      /* protocol */
+      ret_val = g_strcmp0 (tp_account_get_protocol_name (account_a),
+          tp_account_get_protocol_name (account_b));
+
+      if (ret_val != 0)
+        goto out;
+
+      /* account ID */
+      ret_val = g_strcmp0 (tp_proxy_get_object_path (account_a),
+          tp_proxy_get_object_path (account_b));
+
+      if (ret_val != 0)
+        goto out;
+    }
+
+  /* identifier */
+  ret_val = g_utf8_collate (folks_individual_get_id (individual_a),
+      folks_individual_get_id (individual_b));
+
+out:
+  tp_clear_object (&contact_a);
+  tp_clear_object (&contact_b);
+
+  return ret_val;
+}
+
+static gint
+individual_store_state_sort_func (GtkTreeModel *model,
+    GtkTreeIter *iter_a,
+    GtkTreeIter *iter_b,
+    gpointer user_data)
+{
+  gint ret_val;
+  FolksIndividual *individual_a, *individual_b;
+  gchar *name_a, *name_b;
+  gboolean is_separator_a, is_separator_b;
+  gboolean fake_group_a, fake_group_b;
+  FolksPresenceType folks_presence_type_a, folks_presence_type_b;
+  TpConnectionPresenceType tp_presence_a, tp_presence_b;
+
+  gtk_tree_model_get (model, iter_a,
+      GAMES_INDIVIDUAL_STORE_COL_NAME, &name_a,
+      GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_a,
+      GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_a,
+      GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_a, -1);
+  gtk_tree_model_get (model, iter_b,
+      GAMES_INDIVIDUAL_STORE_COL_NAME, &name_b,
+      GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_b,
+      GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_b,
+      GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_b, -1);
+
+  if (individual_a == NULL || individual_b == NULL)
+    {
+      ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
+          name_a, name_b, individual_a, individual_b, fake_group_a,
+          fake_group_b);
+      goto free_and_out;
+    }
+
+  /* If we managed to get this far, we can start looking at
+   * the presences.
+   */
+  folks_presence_type_a =
+      folks_presence_details_get_presence_type (
+          FOLKS_PRESENCE_DETAILS (individual_a));
+  folks_presence_type_b =
+      folks_presence_details_get_presence_type (
+          FOLKS_PRESENCE_DETAILS (individual_b));
+  tp_presence_a = games_folks_presence_type_to_tp (folks_presence_type_a);
+  tp_presence_b = games_folks_presence_type_to_tp (folks_presence_type_b);
+
+  ret_val = -tp_connection_presence_type_cmp_availability (tp_presence_a,
+      tp_presence_b);
+
+  if (ret_val == 0)
+    {
+      /* Fallback: compare by name et al. */
+      ret_val = individual_store_contact_sort (individual_a, individual_b);
+    }
+
+free_and_out:
+  g_free (name_a);
+  g_free (name_b);
+  tp_clear_object (&individual_a);
+  tp_clear_object (&individual_b);
+
+  return ret_val;
+}
+
+static gint
+individual_store_name_sort_func (GtkTreeModel *model,
+    GtkTreeIter *iter_a,
+    GtkTreeIter *iter_b,
+    gpointer user_data)
+{
+  gchar *name_a, *name_b;
+  FolksIndividual *individual_a, *individual_b;
+  gboolean is_separator_a = FALSE, is_separator_b = FALSE;
+  gint ret_val;
+  gboolean fake_group_a, fake_group_b;
+
+  gtk_tree_model_get (model, iter_a,
+      GAMES_INDIVIDUAL_STORE_COL_NAME, &name_a,
+      GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_a,
+      GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_a,
+      GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_a, -1);
+  gtk_tree_model_get (model, iter_b,
+      GAMES_INDIVIDUAL_STORE_COL_NAME, &name_b,
+      GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_b,
+      GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_b,
+      GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_b, -1);
+
+  if (individual_a == NULL || individual_b == NULL)
+    ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
+        name_a, name_b, individual_a, individual_b, fake_group_a, fake_group_b);
+  else
+    ret_val = individual_store_contact_sort (individual_a, individual_b);
+
+  tp_clear_object (&individual_a);
+  tp_clear_object (&individual_b);
+  g_free (name_a);
+  g_free (name_b);
+
+  return ret_val;
+}
+
+static void
+individual_store_dispose (GObject *object)
+{
+  GamesIndividualStore *self = GAMES_INDIVIDUAL_STORE (object);
+  GList *l;
+
+  if (self->priv->dispose_has_run)
+    return;
+  self->priv->dispose_has_run = TRUE;
+
+  /* Cancel any pending avatar load operations */
+  for (l = self->priv->avatar_cancellables; l != NULL; l = l->next)
+    {
+      /* The cancellables are freed in individual_avatar_pixbuf_received_cb() */
+      g_cancellable_cancel (G_CANCELLABLE (l->data));
+    }
+  g_list_free (self->priv->avatar_cancellables);
+
+  if (self->priv->inhibit_active)
+    {
+      g_source_remove (self->priv->inhibit_active);
+    }
+
+  g_hash_table_unref (self->priv->status_icons);
+  g_hash_table_unref (self->priv->folks_individual_cache);
+  g_hash_table_unref (self->priv->contact_group_cache);
+
+  G_OBJECT_CLASS (games_individual_store_parent_class)->dispose (object);
+}
+
+static void
+individual_store_get_property (GObject *object,
+    guint param_id,
+    GValue *value,
+    GParamSpec *pspec)
+{
+  GamesIndividualStore *self = GAMES_INDIVIDUAL_STORE (object);
+
+  switch (param_id)
+    {
+    case PROP_SHOW_AVATARS:
+      g_value_set_boolean (value, self->priv->show_avatars);
+      break;
+    case PROP_SHOW_PROTOCOLS:
+      g_value_set_boolean (value, self->priv->show_protocols);
+      break;
+    case PROP_SHOW_GROUPS:
+      g_value_set_boolean (value, self->priv->show_groups);
+      break;
+    case PROP_IS_COMPACT:
+      g_value_set_boolean (value, self->priv->is_compact);
+      break;
+    case PROP_SORT_CRITERIUM:
+      g_value_set_enum (value, self->priv->sort_criterium);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    };
+}
+
+static void
+individual_store_set_property (GObject *object,
+    guint param_id,
+    const GValue *value,
+    GParamSpec *pspec)
+{
+  switch (param_id)
+    {
+    case PROP_SHOW_AVATARS:
+      games_individual_store_set_show_avatars (GAMES_INDIVIDUAL_STORE
+          (object), g_value_get_boolean (value));
+      break;
+    case PROP_SHOW_PROTOCOLS:
+      games_individual_store_set_show_protocols (GAMES_INDIVIDUAL_STORE
+          (object), g_value_get_boolean (value));
+      break;
+    case PROP_SHOW_GROUPS:
+      games_individual_store_set_show_groups (GAMES_INDIVIDUAL_STORE
+          (object), g_value_get_boolean (value));
+      break;
+    case PROP_IS_COMPACT:
+      games_individual_store_set_is_compact (GAMES_INDIVIDUAL_STORE
+          (object), g_value_get_boolean (value));
+      break;
+    case PROP_SORT_CRITERIUM:
+      games_individual_store_set_sort_criterium (GAMES_INDIVIDUAL_STORE
+          (object), g_value_get_enum (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    };
+}
+
+static void
+games_individual_store_class_init (GamesIndividualStoreClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (GamesIndividualStorePriv));
+
+  object_class->dispose = individual_store_dispose;
+  object_class->get_property = individual_store_get_property;
+  object_class->set_property = individual_store_set_property;
+
+  g_object_class_install_property (object_class,
+      PROP_SHOW_AVATARS,
+      g_param_spec_boolean ("show-avatars",
+          "Show Avatars",
+          "Whether contact list should display "
+          "avatars for contacts", TRUE, G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_SHOW_PROTOCOLS,
+      g_param_spec_boolean ("show-protocols",
+          "Show Protocols",
+          "Whether contact list should display "
+          "protocols for contacts", FALSE, G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_SHOW_GROUPS,
+      g_param_spec_boolean ("show-groups",
+          "Show Groups",
+          "Whether contact list should display "
+          "contact groups", TRUE, G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_IS_COMPACT,
+      g_param_spec_boolean ("is-compact",
+          "Is Compact",
+          "Whether the contact list is in compact mode or not",
+          FALSE, G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_SORT_CRITERIUM,
+      g_param_spec_enum ("sort-criterium",
+          "Sort criterium",
+          "The sort criterium to use for sorting the contact list",
+          GAMES_TYPE_INDIVIDUAL_STORE_SORT,
+          GAMES_INDIVIDUAL_STORE_SORT_STATE, G_PARAM_READWRITE));
+}
+
+static gboolean
+individual_store_inhibit_active_cb (GamesIndividualStore *self)
+{
+  self->priv->show_active = TRUE;
+  self->priv->inhibit_active = 0;
+
+  return FALSE;
+}
+
+static void
+g_queue_free_full_iter (gpointer data)
+{
+  GQueue *queue = (GQueue *) data;
+  g_queue_foreach (queue, (GFunc) gtk_tree_iter_free, NULL);
+  g_queue_free (queue);
+}
+
+static void
+games_individual_store_init (GamesIndividualStore *self)
+{
+  GamesIndividualStorePriv *priv;
+
+  self->priv = priv = GAMES_INDIVIDUAL_STORE_GET_PRIV (self);
+
+  self->priv->show_avatars = TRUE;
+  self->priv->show_groups = TRUE;
+  self->priv->show_protocols = FALSE;
+  self->priv->inhibit_active =
+      g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME,
+      (GSourceFunc) individual_store_inhibit_active_cb, self);
+  self->priv->status_icons =
+      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+  self->priv->folks_individual_cache = g_hash_table_new_full (NULL, NULL, NULL,
+      g_queue_free_full_iter);
+  self->priv->contact_group_cache = g_hash_table_new_full (g_str_hash,
+    g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
+
+  GType types[] = {
+    GDK_TYPE_PIXBUF,            /* Status pixbuf */
+    GDK_TYPE_PIXBUF,            /* Avatar pixbuf */
+    G_TYPE_BOOLEAN,             /* Avatar pixbuf visible */
+    G_TYPE_STRING,              /* Name */
+    G_TYPE_UINT,                /* Presence type */
+    G_TYPE_STRING,              /* Status string */
+    G_TYPE_BOOLEAN,             /* Compact view */
+    FOLKS_TYPE_INDIVIDUAL,      /* Individual type */
+    G_TYPE_BOOLEAN,             /* Is group */
+    G_TYPE_BOOLEAN,             /* Is active */
+    G_TYPE_BOOLEAN,             /* Is online */
+    G_TYPE_BOOLEAN,             /* Is separator */
+    G_TYPE_BOOLEAN,             /* Can play glchess */
+    G_TYPE_BOOLEAN,             /* Is a fake group */
+  };
+
+  gtk_tree_store_set_column_types (GTK_TREE_STORE (self),
+      GAMES_INDIVIDUAL_STORE_COL_COUNT, types);
+
+  /* Set up sorting */
+  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self),
+      GAMES_INDIVIDUAL_STORE_COL_NAME,
+      individual_store_name_sort_func, self, NULL);
+  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self),
+      GAMES_INDIVIDUAL_STORE_COL_STATUS,
+      individual_store_state_sort_func, self, NULL);
+
+  /* Sort by state with fallback on name (alias et al.) */
+  self->priv->sort_criterium = GAMES_INDIVIDUAL_STORE_SORT_STATE;
+
+  games_individual_store_set_sort_criterium (self,
+      self->priv->sort_criterium);
+}
+
+gboolean
+games_individual_store_row_separator_func (GtkTreeModel *model,
+    GtkTreeIter *iter,
+    gpointer data)
+{
+  gboolean is_separator = FALSE;
+
+  g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
+
+  gtk_tree_model_get (model, iter,
+      GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator, -1);
+
+  return is_separator;
+}
+
+gchar *
+games_individual_store_get_parent_group (GtkTreeModel *model,
+    GtkTreePath *path,
+    gboolean *path_is_group,
+    gboolean *is_fake_group)
+{
+  GtkTreeIter parent_iter, iter;
+  gchar *name = NULL;
+  gboolean is_group;
+  gboolean fake = FALSE;
+
+  g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
+
+  if (path_is_group)
+    {
+      *path_is_group = FALSE;
+    }
+
+  if (!gtk_tree_model_get_iter (model, &iter, path))
+    {
+      return NULL;
+    }
+
+  gtk_tree_model_get (model, &iter,
+      GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      GAMES_INDIVIDUAL_STORE_COL_NAME, &name, -1);
+
+  if (!is_group)
+    {
+      g_free (name);
+      name = NULL;
+
+      if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter))
+        {
+          return NULL;
+        }
+
+      iter = parent_iter;
+
+      gtk_tree_model_get (model, &iter,
+          GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+          GAMES_INDIVIDUAL_STORE_COL_NAME, &name,
+          GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
+      if (!is_group)
+        {
+          g_free (name);
+          return NULL;
+        }
+    }
+
+  if (path_is_group)
+    {
+      *path_is_group = TRUE;
+    }
+
+  if (is_fake_group != NULL)
+    *is_fake_group = fake;
+
+  return name;
+}
+
+static GdkPixbuf *
+individual_store_get_individual_status_icon_with_icon_name (
+    GamesIndividualStore *self,
+    FolksIndividual *individual,
+    const gchar *status_icon_name)
+{
+  GdkPixbuf *pixbuf_status;
+  const gchar *protocol_name = NULL;
+  gchar *icon_name = NULL;
+  GeeSet *personas;
+  GeeIterator *iter;
+  guint contact_count = 0;
+  GamesContact *contact = NULL;
+  gboolean show_protocols_here;
+
+  personas = folks_individual_get_personas (individual);
+  iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+  while (gee_iterator_next (iter))
+    {
+      FolksPersona *persona = gee_iterator_get (iter);
+      if (games_folks_persona_is_interesting (persona))
+        contact_count++;
+
+      g_clear_object (&persona);
+
+      if (contact_count > 1)
+        break;
+    }
+  g_clear_object (&iter);
+
+  show_protocols_here = (self->priv->show_protocols && (contact_count == 1));
+  if (show_protocols_here)
+    {
+      contact = games_contact_dup_from_folks_individual (individual);
+      if (contact != NULL)
+        {
+          protocol_name = games_protocol_name_for_contact (contact);
+          icon_name = g_strdup_printf ("%s-%s", status_icon_name,
+              protocol_name);
+        }
+      else
+        {
+          g_warning ("Cannot retrieve contact from individual '%s'",
+              folks_alias_details_get_alias (
+                FOLKS_ALIAS_DETAILS (individual)));
+
+          return NULL;
+        }
+    }
+  else
+    {
+      icon_name = g_strdup_printf ("%s", status_icon_name);
+    }
+
+  pixbuf_status = g_hash_table_lookup (self->priv->status_icons, icon_name);
+
+  if (pixbuf_status == NULL)
+    {
+      pixbuf_status =
+          games_pixbuf_contact_status_icon_with_icon_name (contact,
+          status_icon_name, show_protocols_here);
+
+      if (pixbuf_status != NULL)
+        {
+          /* pass the reference to the hash table */
+          g_hash_table_insert (self->priv->status_icons,
+              g_strdup (icon_name), pixbuf_status);
+        }
+    }
+
+  g_free (icon_name);
+  tp_clear_object (&contact);
+
+  return pixbuf_status;
+}
+
+GdkPixbuf *
+games_individual_store_get_individual_status_icon (
+    GamesIndividualStore *self,
+    FolksIndividual *individual)
+{
+  GdkPixbuf *pixbuf_status = NULL;
+  const gchar *status_icon_name = NULL;
+
+  status_icon_name = games_icon_name_for_individual (individual);
+  if (status_icon_name == NULL)
+    return NULL;
+
+  pixbuf_status =
+      individual_store_get_individual_status_icon_with_icon_name (self,
+      individual, status_icon_name);
+
+  return pixbuf_status;
+}
+
+void
+games_individual_store_refresh_individual (GamesIndividualStore *self,
+    FolksIndividual *individual)
+{
+  gboolean show_active;
+
+  show_active = self->priv->show_active;
+  self->priv->show_active = FALSE;
+  games_individual_store_remove_individual (self, individual);
+  games_individual_store_add_individual (self, individual);
+  self->priv->show_active = show_active;
+}
+
+void
+games_individual_store_add_individual (GamesIndividualStore *self,
+    FolksIndividual *individual)
+{
+  GtkTreeIter iter, iter_group;
+  GeeSet *group_set = NULL;
+  gboolean grouped = FALSE;
+  const gchar *alias =
+    folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
+
+  if (alias == NULL)
+    return;
+
+  if (!self->priv->show_groups)
+    {
+      /* add our individual to the toplevel of the store */
+      add_individual_to_store (GTK_TREE_STORE (self), &iter, NULL,
+          individual);
+
+      goto finally;
+    }
+
+  group_set = folks_group_details_get_groups (
+      FOLKS_GROUP_DETAILS (individual));
+
+  if (gee_collection_get_size (GEE_COLLECTION (group_set)) > 0)
+    {
+      /* add the contact to its groups */
+      GeeIterator *group_iter =
+        gee_iterable_iterator (GEE_ITERABLE (group_set));
+
+      while (group_iter != NULL && gee_iterator_next (group_iter))
+        {
+          gchar *group_name = gee_iterator_get (group_iter);
+
+          individual_store_get_group (self, group_name, &iter_group,
+              NULL, NULL, FALSE);
+
+          add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+              individual);
+          grouped = TRUE;
+
+          g_free (group_name);
+        }
+
+      g_clear_object (&group_iter);
+    }
+  else
+    {
+      /* fall-back groups, in case there are no named groups */
+      GamesContact *contact;
+      TpConnection *connection;
+      const gchar *protocol_name = NULL;
+
+      /* Link local-xmpp contacts cannot be linked with other personas so if
+       * the individual is visible (interesting, with the desired gaming
+       * capabilities) it has to have a single telepathy persona on just this
+       * protocol. We don't need to specify which contact we want.
+       * Just use the first persona (It will be a TpContact) */
+      contact = games_contact_dup_from_folks_individual (individual);
+      if (contact != NULL)
+        {
+          connection = games_contact_get_connection (contact);
+          protocol_name = tp_connection_get_protocol_name (connection);
+        }
+
+      if (!tp_strdiff (protocol_name, "local-xmpp"))
+        {
+          /* these are People Nearby */
+          individual_store_get_group (self,
+              GAMES_INDIVIDUAL_STORE_PEOPLE_NEARBY, &iter_group, NULL, NULL,
+              TRUE);
+          add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+              individual);
+          grouped = TRUE;
+        }
+
+      g_clear_object (&contact);
+    }
+
+  if (folks_favourite_details_get_is_favourite (
+        FOLKS_FAVOURITE_DETAILS (individual)))
+    {
+      /* Add contact to the fake 'Favorites' group */
+      individual_store_get_group (self, GAMES_INDIVIDUAL_STORE_FAVORITE,
+          &iter_group, NULL, NULL, TRUE);
+
+      add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+          individual);
+      grouped = TRUE;
+    }
+
+  if (!grouped)
+    {
+      /* Else add the contact to 'Ungrouped' */
+      individual_store_get_group (self,
+          GAMES_INDIVIDUAL_STORE_UNGROUPED,
+          &iter_group, NULL, NULL, TRUE);
+      add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+          individual);
+    }
+
+
+finally:
+  individual_store_contact_update (self, individual);
+}
+
+static void
+individual_store_contact_set_active (GamesIndividualStore *self,
+    FolksIndividual *individual,
+    gboolean active,
+    gboolean set_changed)
+{
+  GtkTreeModel *model;
+  GList *iters, *l;
+
+  model = GTK_TREE_MODEL (self);
+
+  iters = individual_store_find_contact (self, individual);
+  for (l = iters; l; l = l->next)
+    {
+      GtkTreePath *path;
+
+      gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+          GAMES_INDIVIDUAL_STORE_COL_IS_ACTIVE, active,
+          -1);
+
+      if (set_changed)
+        {
+          path = gtk_tree_model_get_path (model, l->data);
+          gtk_tree_model_row_changed (model, path, l->data);
+          gtk_tree_path_free (path);
+        }
+    }
+
+  free_iters (iters);
+}
+
+static void individual_store_contact_active_free (ShowActiveData *data);
+
+static void
+individual_store_contact_active_invalidated (ShowActiveData *data,
+    GObject *old_object)
+{
+  /* Remove the timeout and free the struct, since the individual or individual
+   * store has disappeared. */
+  g_source_remove (data->timeout);
+
+  if (old_object == (GObject *) data->self)
+    data->self = NULL;
+  else if (old_object == (GObject *) data->individual)
+    data->individual = NULL;
+  else
+    g_assert_not_reached ();
+
+  individual_store_contact_active_free (data);
+}
+
+static ShowActiveData *
+individual_store_contact_active_new (GamesIndividualStore *self,
+    FolksIndividual *individual,
+    gboolean remove_)
+{
+  ShowActiveData *data;
+
+  data = g_slice_new0 (ShowActiveData);
+
+  /* We don't actually want to force either the IndividualStore or the
+   * Individual to stay alive, since the user could quit Games or disable
+   * the account before the contact_active timeout is fired. */
+  g_object_weak_ref (G_OBJECT (self),
+      (GWeakNotify) individual_store_contact_active_invalidated, data);
+  g_object_weak_ref (G_OBJECT (individual),
+      (GWeakNotify) individual_store_contact_active_invalidated, data);
+
+  data->self = self;
+  data->individual = individual;
+  data->remove = remove_;
+  data->timeout = 0;
+
+  return data;
+}
+
+static void
+individual_store_contact_active_free (ShowActiveData *data)
+{
+  if (data->self != NULL)
+    {
+      g_object_weak_unref (G_OBJECT (data->self),
+          (GWeakNotify) individual_store_contact_active_invalidated, data);
+    }
+
+  if (data->individual != NULL)
+    {
+      g_object_weak_unref (G_OBJECT (data->individual),
+          (GWeakNotify) individual_store_contact_active_invalidated, data);
+    }
+
+  g_slice_free (ShowActiveData, data);
+}
+
+static gboolean
+individual_store_contact_active_cb (ShowActiveData *data)
+{
+  if (data->remove)
+    {
+      g_debug ("Individual'%s' active timeout, removing item",
+          folks_alias_details_get_alias (
+            FOLKS_ALIAS_DETAILS (data->individual)));
+      games_individual_store_remove_individual (data->self, data->individual);
+    }
+
+  individual_store_contact_set_active (data->self,
+      data->individual, FALSE, TRUE);
+
+  individual_store_contact_active_free (data);
+
+  return FALSE;
+}
+
+typedef struct {
+  GamesIndividualStore *store; /* weak */
+  GCancellable *cancellable; /* owned */
+} LoadAvatarData;
+
+static void
+individual_avatar_pixbuf_received_cb (FolksIndividual *individual,
+    GAsyncResult *result,
+    LoadAvatarData *data)
+{
+  GError *error = NULL;
+  GdkPixbuf *pixbuf;
+
+  pixbuf = games_pixbuf_avatar_from_individual_scaled_finish (individual,
+      result, &error);
+
+  if (error != NULL)
+    {
+      /* No need to display an error if the individal just doesn't have an
+       * avatar */
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_debug ("failed to retrieve pixbuf for individual %s: %s",
+              folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
+              error->message);
+        }
+
+      g_clear_error (&error);
+    }
+  else if (data->store != NULL)
+    {
+      GList *iters, *l;
+
+      iters = individual_store_find_contact (data->store, individual);
+      for (l = iters; l; l = l->next)
+        {
+          gtk_tree_store_set (GTK_TREE_STORE (data->store), l->data,
+              GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, pixbuf,
+              -1);
+        }
+
+      free_iters (iters);
+    }
+
+  /* Free things */
+  if (data->store != NULL)
+    {
+      g_object_remove_weak_pointer (G_OBJECT (data->store),
+          (gpointer *) &data->store);
+      data->store->priv->avatar_cancellables = g_list_remove (
+          data->store->priv->avatar_cancellables, data->cancellable);
+    }
+
+  tp_clear_object (&pixbuf);
+  g_object_unref (data->cancellable);
+  g_slice_free (LoadAvatarData, data);
+}
+
+
+static void
+individual_store_contact_update (GamesIndividualStore *self,
+    FolksIndividual *individual)
+{
+  ShowActiveData *data;
+  GtkTreeModel *model;
+  GList *iters, *l;
+  gboolean in_list;
+  gboolean was_online = TRUE;
+  gboolean now_online = FALSE;
+  gboolean set_model = FALSE;
+  gboolean do_remove = FALSE;
+  gboolean do_set_active = FALSE;
+  gboolean do_set_refresh = FALSE;
+  gboolean show_avatar = FALSE;
+  GdkPixbuf *pixbuf_status;
+  LoadAvatarData *load_avatar_data;
+
+  model = GTK_TREE_MODEL (self);
+
+  iters = individual_store_find_contact (self, individual);
+  if (!iters)
+    {
+      in_list = FALSE;
+    }
+  else
+    {
+      in_list = TRUE;
+    }
+
+  /* Get online state now. */
+  now_online = folks_presence_details_is_online (
+      FOLKS_PRESENCE_DETAILS (individual));
+
+  if (!in_list)
+    {
+      g_debug ("Individual'%s' in list:NO, should be:YES",
+          folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
+
+      games_individual_store_add_individual (self, individual);
+
+      if (self->priv->show_active)
+        {
+          do_set_active = TRUE;
+        }
+    }
+  else
+    {
+      /* Get online state before. */
+      if (iters && g_list_length (iters) > 0)
+        {
+          gtk_tree_model_get (model, iters->data,
+              GAMES_INDIVIDUAL_STORE_COL_IS_ONLINE, &was_online, -1);
+        }
+
+      /* Is this really an update or an online/offline. */
+      if (self->priv->show_active)
+        {
+          if (was_online != now_online)
+            {
+              do_set_active = TRUE;
+              do_set_refresh = TRUE;
+            }
+          else
+            {
+              /* Was TRUE for presence updates. */
+              /* do_set_active = FALSE;  */
+              do_set_refresh = TRUE;
+            }
+        }
+
+      set_model = TRUE;
+    }
+
+  if (self->priv->show_avatars && !self->priv->is_compact)
+    {
+      show_avatar = TRUE;
+    }
+
+  /* Load the avatar asynchronously */
+  load_avatar_data = g_slice_new (LoadAvatarData);
+  load_avatar_data->store = self;
+  g_object_add_weak_pointer (G_OBJECT (self),
+      (gpointer *) &load_avatar_data->store);
+  load_avatar_data->cancellable = g_cancellable_new ();
+
+  self->priv->avatar_cancellables = g_list_prepend (
+      self->priv->avatar_cancellables, load_avatar_data->cancellable);
+
+  games_pixbuf_avatar_from_individual_scaled_async (individual, 32, 32,
+      load_avatar_data->cancellable,
+      (GAsyncReadyCallback) individual_avatar_pixbuf_received_cb,
+      load_avatar_data);
+
+  pixbuf_status =
+      games_individual_store_get_individual_status_icon (self, individual);
+
+  for (l = iters; l && set_model; l = l->next)
+    {
+      GamesCapabilities individual_caps =
+          individual_store_get_individual_gaming_capabilities (individual);
+
+      gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+          GAMES_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf_status,
+          GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+          GAMES_INDIVIDUAL_STORE_COL_NAME,
+            folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
+          GAMES_INDIVIDUAL_STORE_COL_PRESENCE_TYPE,
+            folks_presence_details_get_presence_type (
+                FOLKS_PRESENCE_DETAILS (individual)),
+          GAMES_INDIVIDUAL_STORE_COL_STATUS,
+            folks_presence_details_get_presence_message (
+                FOLKS_PRESENCE_DETAILS (individual)),
+          GAMES_INDIVIDUAL_STORE_COL_COMPACT, self->priv->is_compact,
+          GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, FALSE,
+          GAMES_INDIVIDUAL_STORE_COL_IS_ONLINE, now_online,
+          GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
+          GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL_CAPABILITIES, individual_caps,
+          -1);
+    }
+
+  if (self->priv->show_active && do_set_active)
+    {
+      individual_store_contact_set_active (self, individual, do_set_active,
+          do_set_refresh);
+
+      if (do_set_active)
+        {
+          data =
+              individual_store_contact_active_new (self, individual,
+              do_remove);
+          data->timeout = g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
+              (GSourceFunc) individual_store_contact_active_cb, data);
+        }
+    }
+
+  /* FIXME: when someone goes online then offline quickly, the
+   * first timeout sets the user to be inactive and the second
+   * timeout removes the user from the contact list, really we
+   * should remove the first timeout.
+   */
+  free_iters (iters);
+}
+
+static void
+individual_store_individual_updated_cb (FolksIndividual *individual,
+    GParamSpec *param,
+    GamesIndividualStore *self)
+{
+  individual_store_contact_update (self, individual);
+}
+
+static void
+individual_store_contact_updated_cb (GamesContact *contact,
+    GParamSpec *pspec,
+    GamesIndividualStore *self)
+{
+  FolksIndividual *individual;
+
+  individual = g_object_get_data (G_OBJECT (contact), "individual");
+  if (individual == NULL)
+    return;
+
+  individual_store_contact_update (self, individual);
+}
+
+static void
+individual_personas_changed_cb (FolksIndividual *individual,
+    GeeSet *added,
+    GeeSet *removed,
+    GamesIndividualStore *self)
+{
+  GeeIterator *iter;
+
+  iter = gee_iterable_iterator (GEE_ITERABLE (removed));
+  /* FIXME: libfolks hasn't grown capabilities support yet, so we have to go
+   * through the GamesContacts for them. */
+  while (gee_iterator_next (iter))
+    {
+      TpfPersona *persona = gee_iterator_get (iter);
+      TpContact *tp_contact;
+      GamesContact *contact;
+
+      if (TPF_IS_PERSONA (persona))
+        {
+          tp_contact = tpf_persona_get_contact (persona);
+          if (tp_contact != NULL)
+            {
+              contact = games_contact_dup_from_tp_contact (tp_contact);
+              games_contact_set_persona (contact, FOLKS_PERSONA (persona));
+
+              g_object_set_data (G_OBJECT (contact), "individual", NULL);
+              g_signal_handlers_disconnect_by_func (contact,
+                  (GCallback) individual_store_contact_updated_cb, self);
+
+              g_object_unref (contact);
+            }
+        }
+
+      g_clear_object (&persona);
+    }
+  g_clear_object (&iter);
+
+  iter = gee_iterable_iterator (GEE_ITERABLE (added));
+  while (gee_iterator_next (iter))
+    {
+      TpfPersona *persona = gee_iterator_get (iter);
+      TpContact *tp_contact;
+      GamesContact *contact;
+
+      if (TPF_IS_PERSONA (persona))
+        {
+          tp_contact = tpf_persona_get_contact (persona);
+          if (tp_contact != NULL)
+            {
+              contact = games_contact_dup_from_tp_contact (tp_contact);
+              games_contact_set_persona (contact, FOLKS_PERSONA (persona));
+
+              g_object_set_data (G_OBJECT (contact), "individual", individual);
+              g_signal_connect (contact, "notify::capabilities",
+                  (GCallback) individual_store_contact_updated_cb, self);
+
+              g_object_unref (contact);
+            }
+        }
+
+      g_clear_object (&persona);
+    }
+  g_clear_object (&iter);
+}
+
+static void
+individual_store_favourites_changed_cb (FolksIndividual *individual,
+    GParamSpec *param,
+    GamesIndividualStore *self)
+{
+  g_debug ("Individual %s is %s a favourite",
+      folks_individual_get_id (individual),
+      folks_favourite_details_get_is_favourite (
+        FOLKS_FAVOURITE_DETAILS (individual)) ? "now" : "no longer");
+
+  games_individual_store_remove_individual (self, individual);
+  games_individual_store_add_individual (self, individual);
+}
+
+void
+individual_store_add_individual_and_connect (GamesIndividualStore *self,
+    FolksIndividual *individual)
+{
+  GeeSet *empty_set = gee_set_empty (G_TYPE_NONE, NULL, NULL);
+
+  games_individual_store_add_individual (self, individual);
+
+  g_signal_connect (individual, "notify::avatar",
+      (GCallback) individual_store_individual_updated_cb, self);
+  g_signal_connect (individual, "notify::presence-type",
+      (GCallback) individual_store_individual_updated_cb, self);
+  g_signal_connect (individual, "notify::presence-message",
+      (GCallback) individual_store_individual_updated_cb, self);
+  g_signal_connect (individual, "notify::alias",
+      (GCallback) individual_store_individual_updated_cb, self);
+  g_signal_connect (individual, "personas-changed",
+      (GCallback) individual_personas_changed_cb, self);
+  g_signal_connect (individual, "notify::is-favourite",
+      (GCallback) individual_store_favourites_changed_cb, self);
+
+  /* provide an empty set so the callback can assume non-NULL sets */
+  individual_personas_changed_cb (individual,
+      folks_individual_get_personas (individual), empty_set, self);
+  g_clear_object (&empty_set);
+}
+
+void
+games_individual_store_disconnect_individual (GamesIndividualStore *self,
+    FolksIndividual *individual)
+{
+  GeeSet *empty_set = gee_set_empty (G_TYPE_NONE, NULL, NULL);
+
+  /* provide an empty set so the callback can assume non-NULL sets */
+  individual_personas_changed_cb (individual, empty_set,
+      folks_individual_get_personas (individual), self);
+  g_clear_object (&empty_set);
+
+  g_signal_handlers_disconnect_by_func (individual,
+      (GCallback) individual_store_individual_updated_cb, self);
+  g_signal_handlers_disconnect_by_func (individual,
+      (GCallback) individual_personas_changed_cb, self);
+  g_signal_handlers_disconnect_by_func (individual,
+      (GCallback) individual_store_favourites_changed_cb, self);
+}
+
+void
+individual_store_remove_individual_and_disconnect (
+    GamesIndividualStore *self,
+    FolksIndividual *individual)
+{
+  games_individual_store_disconnect_individual (self, individual);
+  games_individual_store_remove_individual (self, individual);
+}
+
+
diff --git a/libgames-contacts/games-individual-store.h b/libgames-contacts/games-individual-store.h
new file mode 100644
index 0000000..7e75a1a
--- /dev/null
+++ b/libgames-contacts/games-individual-store.h
@@ -0,0 +1,165 @@
+/* games-individual-store.h: Model Folks individuals 
+ *
+ * Copyright  2012 Chandni Verma
+ *
+ * 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 of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+#ifndef GAMES_INDIVIDUAL_STORE_H
+#define GAMES_INDIVIDUAL_STORE_H
+
+#include <gtk/gtk.h>
+#include <folks/folks.h>
+
+G_BEGIN_DECLS
+
+#define GAMES_TYPE_INDIVIDUAL_STORE            (games_individual_store_get_type ())
+#define GAMES_INDIVIDUAL_STORE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAMES_TYPE_INDIVIDUAL_STORE, GamesIndividualStore))
+#define GAMES_INDIVIDUAL_STORE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAMES_TYPE_INDIVIDUAL_STORE, GamesIndividualStoreClass))
+#define GAMES_IS_INDIVIDUAL_STORE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAMES_TYPE_INDIVIDUAL_STORE))
+#define GAMES_IS_INDIVIDUAL_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAMES_TYPE_INDIVIDUAL_STORE))
+#define GAMES_INDIVIDUAL_STORE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GAMES_TYPE_INDIVIDUAL_STORE, GamesIndividualStoreClass))
+
+typedef struct _GamesIndividualStore        GamesIndividualStore;
+typedef struct _GamesIndividualStoreClass   GamesIndividualStoreClass;
+typedef struct _GamesIndividualStorePriv    GamesIndividualStorePriv;
+
+typedef enum
+{
+  GAMES_INDIVIDUAL_STORE_SORT_STATE,
+  GAMES_INDIVIDUAL_STORE_SORT_NAME
+} GamesIndividualStoreSort;
+
+typedef enum
+{
+  GAMES_INDIVIDUAL_STORE_COL_ICON_STATUS,
+  GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR,
+  GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE,
+  GAMES_INDIVIDUAL_STORE_COL_NAME,
+  GAMES_INDIVIDUAL_STORE_COL_PRESENCE_TYPE,
+  GAMES_INDIVIDUAL_STORE_COL_STATUS,
+  GAMES_INDIVIDUAL_STORE_COL_COMPACT,
+  GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL,
+  GAMES_INDIVIDUAL_STORE_COL_IS_GROUP,
+  GAMES_INDIVIDUAL_STORE_COL_IS_ACTIVE,
+  GAMES_INDIVIDUAL_STORE_COL_IS_ONLINE,
+  GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR,
+  GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL_CAPABILITIES,
+  GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP,
+  GAMES_INDIVIDUAL_STORE_COL_COUNT,
+} GamesIndividualStoreCol;
+
+#define GAMES_INDIVIDUAL_STORE_UNGROUPED _("Ungrouped")
+#define GAMES_INDIVIDUAL_STORE_FAVORITE  _("Favorite People")
+#define GAMES_INDIVIDUAL_STORE_PEOPLE_NEARBY _("People Nearby")
+
+struct _GamesIndividualStore{
+  GtkListStore parent;
+
+  /*< private >*/
+  GamesIndividualStorePriv *priv;
+};
+
+struct _GamesIndividualStoreClass{
+  GtkListStoreClass parent_class;
+
+  /* class members */
+  void (*reload_individuals) (GamesIndividualStore *self);
+  gboolean (*initial_loading) (GamesIndividualStore *self);
+};
+
+/* used by GAMES_TYPE_INDIVIDUAL_STORE */
+GType                 games_individual_store_get_type              (void) G_GNUC_CONST;
+
+/* Function prototypes */
+
+gboolean games_individual_store_get_show_avatars (
+    GamesIndividualStore *store);
+
+void games_individual_store_set_show_avatars (GamesIndividualStore *store,
+    gboolean show_avatars);
+
+gboolean games_individual_store_get_show_groups (
+    GamesIndividualStore *store);
+
+void games_individual_store_set_show_groups (GamesIndividualStore *store,
+    gboolean show_groups);
+
+gboolean games_individual_store_get_is_compact (
+    GamesIndividualStore *store);
+
+void games_individual_store_set_is_compact (GamesIndividualStore *store,
+    gboolean is_compact);
+
+gboolean games_individual_store_get_show_protocols (
+    GamesIndividualStore *store);
+
+void games_individual_store_set_show_protocols (
+    GamesIndividualStore *store,
+    gboolean show_protocols);
+
+GamesIndividualStoreSort games_individual_store_get_sort_criterium (
+    GamesIndividualStore *store);
+
+void games_individual_store_set_sort_criterium (
+    GamesIndividualStore *store,
+    GamesIndividualStoreSort sort_criterium);
+
+gboolean games_individual_store_row_separator_func (GtkTreeModel *model,
+    GtkTreeIter *iter,
+    gpointer data);
+
+gchar *games_individual_store_get_parent_group (GtkTreeModel *model,
+    GtkTreePath *path,
+    gboolean *path_is_group,
+    gboolean *is_fake_group);
+
+GdkPixbuf *games_individual_store_get_individual_status_icon (
+    GamesIndividualStore *store,
+    FolksIndividual *individual);
+
+void individual_store_add_individual_and_connect (
+    GamesIndividualStore *self,
+    FolksIndividual *individual);
+
+void individual_store_remove_individual_and_disconnect (
+    GamesIndividualStore *self,
+    FolksIndividual *individual);
+
+/* protected */
+
+void games_individual_store_disconnect_individual (
+    GamesIndividualStore *self,
+    FolksIndividual *individual);
+
+void games_individual_store_remove_individual (
+    GamesIndividualStore *self,
+    FolksIndividual *individual);
+
+void games_individual_store_add_individual (
+    GamesIndividualStore *self,
+    FolksIndividual *individual);
+
+void games_individual_store_refresh_individual (
+    GamesIndividualStore *self,
+    FolksIndividual *individual);
+
+
+G_END_DECLS
+#endif /* GAMES_INDIVIDUAL_STORE_H */
+/* EOF */
diff --git a/libgames-contacts/games-ui-utils.c b/libgames-contacts/games-ui-utils.c
new file mode 100644
index 0000000..668cae7
--- /dev/null
+++ b/libgames-contacts/games-ui-utils.c
@@ -0,0 +1,1091 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* games-ui-utils.c - This file contains generic UI specific code for use in
+ *                    displaying games and contacts to players.
+ *
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
+ * This file has been prepared from utilities found in Empathy
+ * (https://live.gnome.org/Empathy)
+ */
+
+#include <config.h>
+
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include <libxml/uri.h>
+
+#include <telepathy-glib/util.h>
+#include <folks/folks.h>
+
+#include "games-ui-utils.h"
+
+/* Contact presence image names */
+
+#define GAMES_IMAGE_OFFLINE             "user-offline"
+/* user-invisible is not (yet?) in the naming spec but already implemented by
+ * some theme */
+#define GAMES_IMAGE_HIDDEN              "user-invisible"
+#define GAMES_IMAGE_AVAILABLE           "user-available"
+#define GAMES_IMAGE_BUSY                "user-busy"
+#define GAMES_IMAGE_AWAY                "user-away"
+#define GAMES_IMAGE_EXT_AWAY            "user-extended-away"
+#define GAMES_IMAGE_IDLE                "user-idle"
+#define GAMES_IMAGE_PENDING             "games-pending"
+
+
+gboolean
+games_xml_validate (xmlDoc      *doc,
+    const gchar *dtd_filename)
+{
+  gchar *path;
+  xmlChar *escaped;
+  xmlValidCtxt  cvp;
+  xmlDtd *dtd;
+  gboolean ret;
+
+  path = g_build_filename (PKGDATADIR, PACKAGE, dtd_filename, NULL);
+
+  g_debug ("Loading dtd file %s", path);
+
+  /* The list of valid chars is taken from libxml. */
+  escaped = xmlURIEscapeStr ((const xmlChar *) path,
+    (const xmlChar *)":@&=+$,/?;");
+  g_free (path);
+
+  memset (&cvp, 0, sizeof (cvp));
+  dtd = xmlParseDTD (NULL, escaped);
+  ret = xmlValidateDtd (&cvp, doc, dtd);
+
+  xmlFree (escaped);
+  xmlFreeDtd (dtd);
+
+  return ret;
+}
+
+void
+games_builder_connect (GtkBuilder  *gui,
+			 gpointer     user_data,
+			 const gchar *first_object,
+			 ...)
+{
+	va_list      args;
+	const gchar *name;
+	const gchar *sig;
+	GObject     *object;
+	GCallback    callback;
+
+	va_start (args, first_object);
+	for (name = first_object; name; name = va_arg (args, const gchar *)) {
+		sig = va_arg (args, const gchar *);
+		callback = va_arg (args, GCallback);
+
+		object = gtk_builder_get_object (gui, name);
+		if (!object) {
+			g_warning ("File is missing object '%s'.", name);
+			continue;
+		}
+
+		g_signal_connect (object, sig, callback, user_data);
+	}
+
+	va_end (args);
+}
+
+GtkWidget *
+games_builder_unref_and_keep_widget (GtkBuilder *gui,
+				       GtkWidget  *widget)
+{
+	/* On construction gui sinks the initial reference to widget. When gui
+	 * is finalized it will drop its ref to widget. We take our own ref to
+	 * prevent widget being finalised. The widget is forced to have a
+	 * floating reference, like when it was initially unowned so that it can
+	 * be used like any other GtkWidget. */
+
+	g_object_ref (widget);
+	g_object_force_floating (G_OBJECT (widget));
+	g_object_unref (gui);
+
+	return widget;
+}
+
+const gchar *
+games_icon_name_for_presence (TpConnectionPresenceType presence)
+{
+	switch (presence) {
+	case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
+		return GAMES_IMAGE_AVAILABLE;
+	case TP_CONNECTION_PRESENCE_TYPE_BUSY:
+		return GAMES_IMAGE_BUSY;
+	case TP_CONNECTION_PRESENCE_TYPE_AWAY:
+		return GAMES_IMAGE_AWAY;
+	case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
+		if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
+					     GAMES_IMAGE_EXT_AWAY))
+			return GAMES_IMAGE_EXT_AWAY;
+
+		/* The 'extended-away' icon is not an official one so we fallback to idle if
+		 * it's not implemented */
+		return GAMES_IMAGE_IDLE;
+	case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
+		if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
+					     GAMES_IMAGE_HIDDEN))
+			return GAMES_IMAGE_HIDDEN;
+
+		/* The 'hidden' icon is not an official one so we fallback to offline if
+		 * it's not implemented */
+		return GAMES_IMAGE_OFFLINE;
+	case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
+	case TP_CONNECTION_PRESENCE_TYPE_ERROR:
+		return GAMES_IMAGE_OFFLINE;
+	case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
+		return GAMES_IMAGE_PENDING;
+	case TP_CONNECTION_PRESENCE_TYPE_UNSET:
+	default:
+		return NULL;
+	}
+
+	return NULL;
+}
+
+const gchar *
+games_icon_name_for_contact (GamesContact *contact)
+{
+	TpConnectionPresenceType presence;
+
+	g_return_val_if_fail (GAMES_IS_CONTACT (contact),
+			      GAMES_IMAGE_OFFLINE);
+
+	presence = games_contact_get_presence (contact);
+	return games_icon_name_for_presence (presence);
+}
+
+/* Translate Folks' general presence type to the Tp presence type */
+TpConnectionPresenceType
+games_folks_presence_type_to_tp (FolksPresenceType type)
+{
+  return (TpConnectionPresenceType) type;
+}
+
+const gchar *
+games_icon_name_for_individual (FolksIndividual *individual)
+{
+	FolksPresenceType folks_presence;
+	TpConnectionPresenceType presence;
+
+	folks_presence =
+	    folks_presence_details_get_presence_type (
+	        FOLKS_PRESENCE_DETAILS (individual));
+	presence = games_folks_presence_type_to_tp (folks_presence);
+
+	return games_icon_name_for_presence (presence);
+}
+
+const gchar *
+games_protocol_name_for_contact (GamesContact   *contact)
+{
+	TpAccount     *account;
+
+	g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+	account = games_contact_get_account (contact);
+	if (account == NULL) {
+		return NULL;
+	}
+
+	return tp_account_get_icon_name (account);
+}
+
+GdkPixbuf *
+games_pixbuf_from_data (gchar *data,
+			  gsize  data_size)
+{
+	return games_pixbuf_from_data_and_mime (data, data_size, NULL);
+}
+
+GdkPixbuf *
+games_pixbuf_from_data_and_mime (gchar  *data,
+				   gsize   data_size,
+				   gchar **mime_type)
+{
+	GdkPixbufLoader *loader;
+	GdkPixbufFormat *format;
+	GdkPixbuf       *pixbuf = NULL;
+	gchar          **mime_types;
+	GError          *error = NULL;
+
+	if (!data) {
+		return NULL;
+	}
+
+	loader = gdk_pixbuf_loader_new ();
+	if (!gdk_pixbuf_loader_write (loader, (guchar *) data, data_size, &error)) {
+		g_debug ("Failed to write to pixbuf loader: %s",
+			error ? error->message : "No error given");
+		goto out;
+	}
+	if (!gdk_pixbuf_loader_close (loader, &error)) {
+		g_debug ("Failed to close pixbuf loader: %s",
+			error ? error->message : "No error given");
+		goto out;
+	}
+
+	pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+	if (pixbuf) {
+		g_object_ref (pixbuf);
+
+		if (mime_type != NULL) {
+			format = gdk_pixbuf_loader_get_format (loader);
+			mime_types = gdk_pixbuf_format_get_mime_types (format);
+
+			*mime_type = g_strdup (*mime_types);
+			if (mime_types[1] != NULL) {
+				g_debug ("Loader supports more than one mime "
+					"type! Picking the first one, %s",
+					*mime_type);
+			}
+			g_strfreev (mime_types);
+		}
+	}
+
+out:
+	g_clear_error (&error);
+	g_object_unref (loader);
+
+	return pixbuf;
+}
+
+struct SizeData {
+	gint     width;
+	gint     height;
+	gboolean preserve_aspect_ratio;
+};
+
+static void
+pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader *loader,
+				     int              width,
+				     int              height,
+				     struct SizeData *data)
+{
+	g_return_if_fail (width > 0 && height > 0);
+
+	if (data->preserve_aspect_ratio && (data->width > 0 || data->height > 0)) {
+		if (width < data->width && height < data->height) {
+			width = width;
+			height = height;
+		}
+
+		if (data->width < 0) {
+			width = width * (double) data->height / (gdouble) height;
+			height = data->height;
+		} else if (data->height < 0) {
+			height = height * (double) data->width / (double) width;
+			width = data->width;
+		} else if ((double) height * (double) data->width >
+			   (double) width * (double) data->height) {
+			width = 0.5 + (double) width * (double) data->height / (double) height;
+			height = data->height;
+		} else {
+			height = 0.5 + (double) height * (double) data->width / (double) width;
+			width = data->width;
+		}
+	} else {
+		if (data->width > 0) {
+			width = data->width;
+		}
+
+		if (data->height > 0) {
+			height = data->height;
+		}
+	}
+
+	gdk_pixbuf_loader_set_size (loader, width, height);
+}
+
+static void
+games_avatar_pixbuf_roundify (GdkPixbuf *pixbuf)
+{
+	gint width, height, rowstride;
+	guchar *pixels;
+
+	width = gdk_pixbuf_get_width (pixbuf);
+	height = gdk_pixbuf_get_height (pixbuf);
+	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+	pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+	if (width < 6 || height < 6) {
+		return;
+	}
+
+	/* Top left */
+	pixels[3] = 0;
+	pixels[7] = 0x80;
+	pixels[11] = 0xC0;
+	pixels[rowstride + 3] = 0x80;
+	pixels[rowstride * 2 + 3] = 0xC0;
+
+	/* Top right */
+	pixels[width * 4 - 1] = 0;
+	pixels[width * 4 - 5] = 0x80;
+	pixels[width * 4 - 9] = 0xC0;
+	pixels[rowstride + (width * 4) - 1] = 0x80;
+	pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
+
+	/* Bottom left */
+	pixels[(height - 1) * rowstride + 3] = 0;
+	pixels[(height - 1) * rowstride + 7] = 0x80;
+	pixels[(height - 1) * rowstride + 11] = 0xC0;
+	pixels[(height - 2) * rowstride + 3] = 0x80;
+	pixels[(height - 3) * rowstride + 3] = 0xC0;
+
+	/* Bottom right */
+	pixels[height * rowstride - 1] = 0;
+	pixels[(height - 1) * rowstride - 1] = 0x80;
+	pixels[(height - 2) * rowstride - 1] = 0xC0;
+	pixels[height * rowstride - 5] = 0x80;
+	pixels[height * rowstride - 9] = 0xC0;
+}
+
+static gboolean
+games_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
+{
+	gint height, rowstride, i;
+	guchar *pixels;
+	guchar *row;
+
+	height = gdk_pixbuf_get_height (pixbuf);
+	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+	pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+	row = pixels;
+	for (i = 3; i < rowstride; i+=4) {
+		if (row[i] < 0xfe) {
+			return FALSE;
+		}
+	}
+
+	for (i = 1; i < height - 1; i++) {
+		row = pixels + (i*rowstride);
+		if (row[3] < 0xfe || row[rowstride-1] < 0xfe) {
+			return FALSE;
+		}
+	}
+
+	row = pixels + ((height-1) * rowstride);
+	for (i = 3; i < rowstride; i+=4) {
+		if (row[i] < 0xfe) {
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static GdkPixbuf *
+avatar_pixbuf_from_loader (GdkPixbufLoader *loader)
+{
+	GdkPixbuf *pixbuf;
+
+	pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+	if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
+		GdkPixbuf *rounded_pixbuf;
+
+		rounded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+						 gdk_pixbuf_get_width (pixbuf),
+						 gdk_pixbuf_get_height (pixbuf));
+		gdk_pixbuf_copy_area (pixbuf, 0, 0,
+				      gdk_pixbuf_get_width (pixbuf),
+				      gdk_pixbuf_get_height (pixbuf),
+				      rounded_pixbuf,
+				      0, 0);
+		pixbuf = rounded_pixbuf;
+	} else {
+		g_object_ref (pixbuf);
+	}
+
+	if (games_gdk_pixbuf_is_opaque (pixbuf)) {
+		games_avatar_pixbuf_roundify (pixbuf);
+	}
+
+	return pixbuf;
+}
+
+GdkPixbuf *
+games_pixbuf_from_avatar_scaled (GamesAvatar *avatar,
+				  gint          width,
+				  gint          height)
+{
+	GdkPixbuf        *pixbuf;
+	GdkPixbufLoader	 *loader;
+	struct SizeData   data;
+	GError           *error = NULL;
+
+	if (!avatar) {
+		return NULL;
+	}
+
+	data.width = width;
+	data.height = height;
+	data.preserve_aspect_ratio = TRUE;
+
+	loader = gdk_pixbuf_loader_new ();
+
+	g_signal_connect (loader, "size-prepared",
+			  G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
+			  &data);
+
+	if (avatar->len == 0) {
+		g_warning ("Avatar has 0 length");
+		return NULL;
+	} else if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error)) {
+		g_warning ("Couldn't write avatar image:%p with "
+			   "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
+			   avatar->data, avatar->len, error->message);
+		g_error_free (error);
+		return NULL;
+	}
+
+	gdk_pixbuf_loader_close (loader, NULL);
+	pixbuf = avatar_pixbuf_from_loader (loader);
+
+	g_object_unref (loader);
+
+	return pixbuf;
+}
+
+GdkPixbuf *
+games_pixbuf_avatar_from_contact_scaled (GamesContact *contact,
+					  gint           width,
+					  gint           height)
+{
+	GamesAvatar *avatar;
+
+	g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+	avatar = games_contact_get_avatar (contact);
+
+	return games_pixbuf_from_avatar_scaled (avatar, width, height);
+}
+
+typedef struct {
+	FolksIndividual *individual;
+	GSimpleAsyncResult *result;
+	guint width;
+	guint height;
+	struct SizeData size_data;
+	GdkPixbufLoader *loader;
+	GCancellable *cancellable;
+	guint8 data[512];
+} PixbufAvatarFromIndividualClosure;
+
+static PixbufAvatarFromIndividualClosure *
+pixbuf_avatar_from_individual_closure_new (FolksIndividual    *individual,
+					   GSimpleAsyncResult *result,
+					   gint                width,
+					   gint                height,
+					   GCancellable       *cancellable)
+{
+	PixbufAvatarFromIndividualClosure *closure;
+
+	g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+	closure = g_new0 (PixbufAvatarFromIndividualClosure, 1);
+	closure->individual = g_object_ref (individual);
+	closure->result = g_object_ref (result);
+	closure->width = width;
+	closure->height = height;
+	if (cancellable != NULL)
+		closure->cancellable = g_object_ref (cancellable);
+
+	return closure;
+}
+
+static void
+pixbuf_avatar_from_individual_closure_free (
+		PixbufAvatarFromIndividualClosure *closure)
+{
+	g_clear_object (&closure->cancellable);
+	tp_clear_object (&closure->loader);
+	g_object_unref (closure->individual);
+	g_object_unref (closure->result);
+	g_free (closure);
+}
+
+static void
+avatar_icon_load_close_cb (GObject      *object,
+                           GAsyncResult *result,
+                           gpointer      user_data)
+{
+	GError *error = NULL;
+
+	g_input_stream_close_finish (G_INPUT_STREAM (object), result, &error);
+
+	if (error != NULL) {
+		g_debug ("Failed to close pixbuf stream: %s", error->message);
+		g_error_free (error);
+	}
+}
+
+static void
+avatar_icon_load_read_cb (GObject      *object,
+                          GAsyncResult *result,
+                          gpointer      user_data)
+{
+	GInputStream *stream = G_INPUT_STREAM (object);
+	PixbufAvatarFromIndividualClosure *closure = user_data;
+	gssize n_read;
+	GError *error = NULL;
+
+	/* Finish reading this chunk from the stream */
+	n_read = g_input_stream_read_finish (stream, result, &error);
+	if (error != NULL) {
+		g_debug ("Failed to finish read from pixbuf stream: %s",
+			error->message);
+		g_simple_async_result_set_from_error (closure->result, error);
+		goto out_close;
+	}
+
+	/* Write the chunk to the pixbuf loader */
+	if (!gdk_pixbuf_loader_write (closure->loader, (guchar *) closure->data,
+			n_read, &error)) {
+		g_debug ("Failed to write to pixbuf loader: %s",
+			error ? error->message : "No error given");
+		g_simple_async_result_set_from_error (closure->result, error);
+		goto out_close;
+	}
+
+	if (n_read == 0) {
+		/* EOF? */
+		if (!gdk_pixbuf_loader_close (closure->loader, &error)) {
+			g_debug ("Failed to close pixbuf loader: %s",
+				error ? error->message : "No error given");
+			g_simple_async_result_set_from_error (closure->result, error);
+			goto out;
+		}
+
+		/* We're done. */
+		g_simple_async_result_set_op_res_gpointer (closure->result,
+			avatar_pixbuf_from_loader (closure->loader),
+			g_object_unref);
+
+		goto out;
+	} else {
+		/* Loop round and read another chunk. */
+		g_input_stream_read_async (stream, closure->data,
+			G_N_ELEMENTS (closure->data),
+			G_PRIORITY_DEFAULT, closure->cancellable,
+			avatar_icon_load_read_cb, closure);
+
+		return;
+	}
+
+out_close:
+	/* We must close the pixbuf loader before unreffing it. */
+	gdk_pixbuf_loader_close (closure->loader, NULL);
+
+out:
+	/* Close the file for safety (even though it should be
+	 * automatically closed when the stream is finalised). */
+	g_input_stream_close_async (stream, G_PRIORITY_DEFAULT, NULL,
+		(GAsyncReadyCallback) avatar_icon_load_close_cb, NULL);
+
+	g_simple_async_result_complete (closure->result);
+
+	g_clear_error (&error);
+	pixbuf_avatar_from_individual_closure_free (closure);
+}
+
+static void
+avatar_icon_load_cb (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+	GLoadableIcon *icon = G_LOADABLE_ICON (object);
+	PixbufAvatarFromIndividualClosure *closure = user_data;
+	GInputStream *stream;
+	GError *error = NULL;
+
+	stream = g_loadable_icon_load_finish (icon, result, NULL, &error);
+	if (error != NULL) {
+		g_debug ("Failed to open avatar stream: %s", error->message);
+		g_simple_async_result_set_from_error (closure->result, error);
+		goto out;
+	}
+
+	closure->size_data.width = closure->width;
+	closure->size_data.height = closure->height;
+	closure->size_data.preserve_aspect_ratio = TRUE;
+
+	/* Load the data into a pixbuf loader in chunks. */
+	closure->loader = gdk_pixbuf_loader_new ();
+
+	g_signal_connect (closure->loader, "size-prepared",
+			  G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
+	                  &(closure->size_data));
+
+	/* Begin to read the first chunk. */
+	g_input_stream_read_async (stream, closure->data,
+			G_N_ELEMENTS (closure->data),
+			G_PRIORITY_DEFAULT, closure->cancellable,
+			avatar_icon_load_read_cb, closure);
+
+	g_object_unref (stream);
+
+	return;
+
+out:
+	g_simple_async_result_complete (closure->result);
+
+	g_clear_error (&error);
+	tp_clear_object (&stream);
+	pixbuf_avatar_from_individual_closure_free (closure);
+}
+
+void
+games_pixbuf_avatar_from_individual_scaled_async (
+		FolksIndividual     *individual,
+		gint                 width,
+		gint                 height,
+		GCancellable        *cancellable,
+		GAsyncReadyCallback  callback,
+		gpointer             user_data)
+{
+	GLoadableIcon *avatar_icon;
+	GSimpleAsyncResult *result;
+	PixbufAvatarFromIndividualClosure *closure;
+
+	result = g_simple_async_result_new (G_OBJECT (individual),
+			callback, user_data,
+			games_pixbuf_avatar_from_individual_scaled_async);
+
+	avatar_icon =
+		folks_avatar_details_get_avatar (FOLKS_AVATAR_DETAILS (individual));
+	if (avatar_icon == NULL) {
+		g_simple_async_result_set_error (result, G_IO_ERROR,
+			G_IO_ERROR_NOT_FOUND, "no avatar found");
+
+		g_simple_async_result_complete (result);
+		g_object_unref (result);
+		return;
+	}
+
+	closure = pixbuf_avatar_from_individual_closure_new (individual, result,
+							     width, height,
+							     cancellable);
+
+	g_return_if_fail (closure != NULL);
+
+	g_loadable_icon_load_async (avatar_icon, width, cancellable,
+			avatar_icon_load_cb, closure);
+
+	g_object_unref (result);
+}
+
+/* Return a ref on the GdkPixbuf */
+GdkPixbuf *
+games_pixbuf_avatar_from_individual_scaled_finish (
+		FolksIndividual *individual,
+		GAsyncResult *result,
+		GError **error)
+{
+	GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+	gboolean result_valid;
+	GdkPixbuf *pixbuf;
+
+	g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return NULL;
+
+	result_valid = g_simple_async_result_is_valid (result,
+			G_OBJECT (individual),
+			games_pixbuf_avatar_from_individual_scaled_async);
+	g_return_val_if_fail (result_valid, NULL);
+
+	pixbuf = g_simple_async_result_get_op_res_gpointer (simple);
+	return pixbuf != NULL ? g_object_ref (pixbuf) : NULL;
+}
+
+GdkPixbuf *
+games_pixbuf_contact_status_icon (GamesContact *contact,
+				   gboolean       show_protocol)
+{
+	const gchar *icon_name;
+
+	g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+	icon_name = games_icon_name_for_contact (contact);
+
+	if (icon_name == NULL) {
+		return NULL;
+	}
+	return games_pixbuf_contact_status_icon_with_icon_name (contact,
+	    icon_name,
+	    show_protocol);
+}
+
+GdkPixbuf *
+games_pixbuf_contact_status_icon_with_icon_name (GamesContact *contact,
+					  const gchar    *icon_name,
+					  gboolean       show_protocol)
+{
+	GdkPixbuf *pix_status;
+	GdkPixbuf *pix_protocol;
+	gchar     *icon_filename;
+	gint       height, width;
+	gint       numerator, denominator;
+
+	g_return_val_if_fail (GAMES_IS_CONTACT (contact) ||
+			(show_protocol == FALSE), NULL);
+	g_return_val_if_fail (icon_name != NULL, NULL);
+
+	numerator = 3;
+	denominator = 4;
+
+	icon_filename = games_filename_from_icon_name (icon_name,
+							 GTK_ICON_SIZE_MENU);
+	if (icon_filename == NULL) {
+		g_debug ("icon name: %s could not be found\n", icon_name);
+		return NULL;
+	}
+
+	pix_status = gdk_pixbuf_new_from_file (icon_filename, NULL);
+
+	if (pix_status == NULL) {
+		g_debug ("Could not open icon %s\n", icon_filename);
+		g_free (icon_filename);
+		return NULL;
+	}
+
+	g_free (icon_filename);
+
+	if (!show_protocol)
+		return pix_status;
+
+	height = gdk_pixbuf_get_height (pix_status);
+	width = gdk_pixbuf_get_width (pix_status);
+
+	pix_protocol = games_pixbuf_protocol_from_contact_scaled (contact,
+								    width * numerator / denominator,
+								    height * numerator / denominator);
+
+	if (pix_protocol == NULL) {
+		return pix_status;
+	}
+	gdk_pixbuf_composite (pix_protocol, pix_status,
+	    0, height - height * numerator / denominator,
+	    width * numerator / denominator, height * numerator / denominator,
+	    0, height - height * numerator / denominator,
+	    1, 1,
+	    GDK_INTERP_BILINEAR, 255);
+
+	g_object_unref (pix_protocol);
+
+	return pix_status;
+}
+
+GdkPixbuf *
+games_pixbuf_protocol_from_contact_scaled (GamesContact *contact,
+					  gint           width,
+					  gint           height)
+{
+	TpAccount *account;
+	gchar     *filename;
+	GdkPixbuf *pixbuf = NULL;
+
+	g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+	account = games_contact_get_account (contact);
+	filename = games_filename_from_icon_name (tp_account_get_icon_name (account),
+						    GTK_ICON_SIZE_MENU);
+	if (filename != NULL) {
+		pixbuf = gdk_pixbuf_new_from_file_at_size (filename, width, height, NULL);
+		g_free (filename);
+	}
+
+	return pixbuf;
+}
+
+GdkPixbuf *
+games_pixbuf_scale_down_if_necessary (GdkPixbuf *pixbuf, gint max_size)
+{
+	gint      width, height;
+	gdouble   factor;
+
+	width = gdk_pixbuf_get_width (pixbuf);
+	height = gdk_pixbuf_get_height (pixbuf);
+
+	if (width > 0 && (width > max_size || height > max_size)) {
+		factor = (gdouble) max_size / MAX (width, height);
+
+		width = width * factor;
+		height = height * factor;
+
+		return gdk_pixbuf_scale_simple (pixbuf,
+						width, height,
+						GDK_INTERP_HYPER);
+	}
+
+	return g_object_ref (pixbuf);
+}
+
+GdkPixbuf *
+games_pixbuf_from_icon_name_sized (const gchar *icon_name,
+				     gint size)
+{
+	GtkIconTheme *theme;
+	GdkPixbuf *pixbuf;
+	GError *error = NULL;
+
+	if (!icon_name) {
+		return NULL;
+	}
+
+	theme = gtk_icon_theme_get_default ();
+
+	pixbuf = gtk_icon_theme_load_icon (theme,
+					   icon_name,
+					   size,
+					   0,
+					   &error);
+	if (error) {
+		g_debug ("Error loading icon: %s", error->message);
+		g_clear_error (&error);
+	}
+
+	return pixbuf;
+}
+
+GdkPixbuf *
+games_pixbuf_from_icon_name (const gchar *icon_name,
+			       GtkIconSize  icon_size)
+{
+	gint  w, h;
+	gint  size = 48;
+
+	if (!icon_name) {
+		return NULL;
+	}
+
+	if (gtk_icon_size_lookup (icon_size, &w, &h)) {
+		size = (w + h) / 2;
+	}
+
+	return games_pixbuf_from_icon_name_sized (icon_name, size);
+}
+
+gchar *
+games_filename_from_icon_name (const gchar *icon_name,
+				 GtkIconSize  icon_size)
+{
+	GtkIconTheme *icon_theme;
+	GtkIconInfo  *icon_info;
+	gint          w, h;
+	gint          size = 48;
+	gchar        *ret;
+
+	icon_theme = gtk_icon_theme_get_default ();
+
+	if (gtk_icon_size_lookup (icon_size, &w, &h)) {
+		size = (w + h) / 2;
+	}
+
+	icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0);
+	if (icon_info == NULL)
+		return NULL;
+
+	ret = g_strdup (gtk_icon_info_get_filename (icon_info));
+	gtk_icon_info_free (icon_info);
+
+	return ret;
+}
+
+gint64
+games_get_current_action_time (void)
+{
+  return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
+}
+
+void
+games_launch_program (const gchar *dir,
+    const gchar *name,
+    const gchar *args)
+{
+  GdkDisplay *display;
+  GError *error = NULL;
+  gchar *path, *cmd;
+  GAppInfo *app_info;
+  GdkAppLaunchContext *context = NULL;
+
+  path = g_build_filename (dir, name, NULL);
+
+  if (args != NULL)
+    cmd = g_strconcat (path, " ", args, NULL);
+  else
+    cmd = g_strdup (path);
+
+  app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
+  if (app_info == NULL)
+    {
+      g_debug ("Failed to create app info: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  display = gdk_display_get_default ();
+  context = gdk_display_get_app_launch_context (display);
+
+  if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
+      &error))
+    {
+      g_warning ("Failed to launch %s: %s", name, error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+out:
+  tp_clear_object (&app_info);
+  tp_clear_object (&context);
+  g_free (path);
+  g_free (cmd);
+}
+
+/* Most of the workspace manipulation code has been copied from libwnck
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005-2007 Vincent Untz
+ */
+static void
+_wnck_activate_workspace (Screen *screen,
+    int new_active_space,
+    Time timestamp)
+{
+  Display *display;
+  Window   root;
+  XEvent   xev;
+
+  display = DisplayOfScreen (screen);
+  root = RootWindowOfScreen (screen);
+
+  xev.xclient.type = ClientMessage;
+  xev.xclient.serial = 0;
+  xev.xclient.send_event = True;
+  xev.xclient.display = display;
+  xev.xclient.window = root;
+  xev.xclient.message_type = gdk_x11_get_xatom_by_name ("_NET_CURRENT_DESKTOP");
+  xev.xclient.format = 32;
+  xev.xclient.data.l[0] = new_active_space;
+  xev.xclient.data.l[1] = timestamp;
+  xev.xclient.data.l[2] = 0;
+  xev.xclient.data.l[3] = 0;
+  xev.xclient.data.l[4] = 0;
+
+  gdk_error_trap_push ();
+  XSendEvent (display, root, False,
+      SubstructureRedirectMask | SubstructureNotifyMask,
+      &xev);
+  XSync (display, False);
+  gdk_error_trap_pop_ignored ();
+}
+
+static gboolean
+_wnck_get_cardinal (Screen *screen,
+    Window xwindow,
+    Atom atom,
+    int *val)
+{
+  Display *display;
+  Atom type;
+  int format;
+  gulong nitems;
+  gulong bytes_after;
+  gulong *num;
+  int err, result;
+
+  display = DisplayOfScreen (screen);
+
+  *val = 0;
+
+  gdk_error_trap_push ();
+  type = None;
+  result = XGetWindowProperty (display, xwindow, atom,
+      0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
+      &bytes_after, (void *) &num);
+  err = gdk_error_trap_pop ();
+  if (err != Success ||
+      result != Success)
+    return FALSE;
+
+  if (type != XA_CARDINAL)
+    {
+      XFree (num);
+      return FALSE;
+    }
+
+  *val = *num;
+
+  XFree (num);
+
+  return TRUE;
+}
+
+static int
+window_get_workspace (Screen *xscreen,
+    Window win)
+{
+  int number;
+
+  if (!_wnck_get_cardinal (xscreen, win,
+        gdk_x11_get_xatom_by_name ("_NET_WM_DESKTOP"), &number))
+    return -1;
+
+  return number;
+}
+
+/* Ask X to move to the desktop on which @window currently is
+ * and the present @window. */
+void
+games_move_to_window_desktop (GtkWindow *window,
+    guint32 timestamp)
+{
+  GdkScreen *screen;
+  Screen *xscreen;
+  GdkWindow *gdk_window;
+  int workspace;
+
+  screen = gtk_window_get_screen (window);
+  xscreen = gdk_x11_screen_get_xscreen (screen);
+  gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+
+  workspace = window_get_workspace (xscreen,
+      gdk_x11_window_get_xid (gdk_window));
+  if (workspace == -1)
+    goto out;
+
+  _wnck_activate_workspace (xscreen, workspace, timestamp);
+
+out:
+  gtk_window_present_with_time (window, timestamp);
+}
+
diff --git a/libgames-contacts/games-ui-utils.h b/libgames-contacts/games-ui-utils.h
new file mode 100644
index 0000000..9fc7f54
--- /dev/null
+++ b/libgames-contacts/games-ui-utils.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* games-ui-utils.h - This file contains generic UI specific code for use in
+ *                    displaying games and contacts to players.
+ *
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
+ * This file has been prepared from utilities provided in
+ * Empathy (https://live.gnome.org/Empathy).
+ */
+
+#ifndef __GAMES_UI_UTILS_H__
+#define __GAMES_UI_UTILS_H__
+
+#include <gtk/gtk.h>
+#include <libxml/uri.h>
+#include <libxml/tree.h>
+
+#include <folks/folks.h>
+
+#include "games-contact.h"
+
+G_BEGIN_DECLS
+
+#define STR_EMPTY(x) ((x) == NULL || (x)[0] == '\0')
+
+#define GAMES_RECT_IS_ON_SCREEN(x,y,w,h) ((x) + (w) > 0 && \
+					    (y) + (h) > 0 && \
+					    (x) < gdk_screen_width () && \
+					    (y) < gdk_screen_height ())
+
+typedef void (*GamesPixbufAvatarFromIndividualCb) (FolksIndividual *individual,
+		GdkPixbuf *pixbuf,
+		gpointer user_data);
+
+void            games_gtk_init                        (void);
+
+/* Glade */
+GtkBuilder *    games_builder_get_file                (const gchar      *filename,
+							 const gchar      *first_object,
+							 ...);
+void            games_builder_connect                 (GtkBuilder       *gui,
+							 gpointer          user_data,
+							 const gchar      *first_object,
+							 ...);
+GtkWidget     *games_builder_unref_and_keep_widget    (GtkBuilder       *gui,
+							 GtkWidget        *root);
+
+/* Pixbufs */
+const gchar * games_icon_name_for_presence            (TpConnectionPresenceType  presence);
+const gchar * games_icon_name_for_contact             (GamesContact   *contact);
+TpConnectionPresenceType games_folks_presence_type_to_tp (FolksPresenceType type);
+const gchar * games_icon_name_for_individual          (FolksIndividual  *individual);
+const gchar * games_protocol_name_for_contact         (GamesContact   *contact);
+GdkPixbuf *   games_pixbuf_from_data                  (gchar            *data,
+							 gsize             data_size);
+GdkPixbuf *   games_pixbuf_from_data_and_mime         (gchar            *data,
+							 gsize             data_size,
+							 gchar           **mime_type);
+void games_pixbuf_avatar_from_individual_scaled_async (FolksIndividual     *individual,
+							 gint                 width,
+							 gint                 height,
+							 GCancellable        *cancellable,
+							 GAsyncReadyCallback  callback,
+							 gpointer             user_data);
+GdkPixbuf * games_pixbuf_avatar_from_individual_scaled_finish (
+							 FolksIndividual  *individual,
+							 GAsyncResult     *result,
+							 GError          **error);
+GdkPixbuf *   games_pixbuf_from_avatar_scaled         (GamesAvatar    *avatar,
+							 gint              width,
+							 gint              height);
+GdkPixbuf *   games_pixbuf_avatar_from_contact_scaled (GamesContact   *contact,
+							 gint              width,
+							 gint              height);
+GdkPixbuf *   games_pixbuf_protocol_from_contact_scaled (GamesContact   *contact,
+							 gint              width,
+							 gint              height);
+GdkPixbuf *   games_pixbuf_contact_status_icon (GamesContact   *contact,
+							 gboolean          show_protocol);
+GdkPixbuf *   games_pixbuf_contact_status_icon_with_icon_name (GamesContact   *contact,
+							 const gchar       *icon_name,
+							 gboolean          show_protocol);
+GdkPixbuf *   games_pixbuf_scale_down_if_necessary    (GdkPixbuf        *pixbuf,
+							 gint              max_size);
+GdkPixbuf *   games_pixbuf_from_icon_name             (const gchar      *icon_name,
+							 GtkIconSize       icon_size);
+GdkPixbuf *   games_pixbuf_from_icon_name_sized       (const gchar      *icon_name,
+							 gint              size);
+gchar *       games_filename_from_icon_name           (const gchar      *icon_name,
+							 GtkIconSize       icon_size);
+
+/* Windows */
+void        games_window_present                      (GtkWindow *window);
+void        games_window_present_with_time            (GtkWindow        *window,
+							 guint32 timestamp);
+GtkWindow * games_get_toplevel_window                 (GtkWidget        *widget);
+
+void games_move_to_window_desktop (GtkWindow *window,
+    guint32 timestamp);
+
+/* URL */
+gchar *     games_make_absolute_url                   (const gchar      *url);
+
+gchar *     games_make_absolute_url_len               (const gchar      *url,
+							 guint             len);
+void        games_url_show                            (GtkWidget        *parent,
+							 const char       *url);
+
+/* Misc */
+gint64      games_get_current_action_time             (void);
+
+gboolean games_individual_match_string (
+    FolksIndividual *individual,
+    const gchar *text,
+    GPtrArray *words);
+
+void games_launch_program (const gchar *dir,
+    const gchar *name,
+    const gchar *args);
+
+gboolean    games_xml_validate                        (xmlDoc          *doc,
+                                                       const gchar     *dtd_filename);
+
+G_END_DECLS
+
+#endif /*  __GAMES_UI_UTILS_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]