Contacts

It is often the case that a Telepathy client will want to learn as much information about a list of contacts as it can, e.g. their alias, avatar, presence, capabilities, location. Requesting data from each of the interfaces individually results in n D-Bus method calls on the connection. The Contacts interface acts as a sort of proxy to the other interfaces of the connection, allowing the client to request the most common information in a single D-Bus method call (thus saving on D-Bus roundtrips).

Depending on a connection's capabilities, it may not implement the full set of available interfaces (e.g. IRC provides no avatars). Thus the interfaces available for use with the Contacts interface are available through the ContactAttributeInterfaces property.

The lookup is done using the GetContactAttributes method, which takes an array of contact handles (e.g. from a contact list), an array of interfaces you re interested in (from the ContactAttributeInterfaces) and whether or not to hold the handles (as if HoldHandles was called). The return value is a nested mapping of handles to mapped key/value pairs. Example 7-1 gives an example return.

The keys are of the form interface/attribute (e.g. org.freedesktop.Telepathy.Connection/contact-id). They do not map to interface properties. For the moment they are documented in the spec with the Contacts interface.

Example 7-1Example Return Value for GetContactAttributes

GetContactAttributes was called on a list of handles with the Connection, Aliasing and SimplePresence interfaces.

<handle 1> org.freedesktop.Telepathy.Connection/contact-id friend1@jabber.org
org.freedesktop.Telepathy.Connection.Interface.Aliasing/alias Gary
org.freedesktop.Telepathy.Connection.Interface.SimplePresence/presence Presence...
<handle 2> org.freedesktop.Telepathy.Connection/contact-id friend2@jabber.org
org.freedesktop.Telepathy.Connection.Interface.Aliasing/alias Stephanie
org.freedesktop.Telepathy.Connection.Interface.SimplePresence/presence Presence...
<handle 3> org.freedesktop.Telepathy.Connection/contact-id friend3@jabber.org
org.freedesktop.Telepathy.Connection.Interface.Aliasing/alias Danielle
org.freedesktop.Telepathy.Connection.Interface.SimplePresence/presence Presence...

7.1.1. Receiving Updates

While Contacts/GetContactAttributes allows us to bulk-request lots of information about a contact. It does not provide a mechanism to receive signals to notify the client of changes to a contact (e.g., their alias, avatar, location, status, etc.). Instead the client should connect to the various signals provided by the interfaces its interested in.

7.1.2. telepathy-glib — TpContact

telepathy-glib provides an class specifically for managing information relating to a contact, TpContact.

A collection of TpContact instances can be created by calling tp_connection_get_contacts_by_handle (or tp_connection_get_contacts_by_id). telepathy-glib takes care of tracking TpContact objects, so requesting the same contact multiple times is safe and results in the same object being returned while the object exists.

A TpChannel that implements Group provides API for requesting the list of handles associated with the channel: tp_channel_group_get_members, tp_channel_group_get_local_pending and tp_channel_group_get_remote_pending (see Section 6.4.1 ― telepathy-glib). These functions all return a set of handles as a TpIntSet that can be turned into TpContact objects. Example 7-2 shows how to request a set of TpContact objects from a channel.

Unlike in the D-Bus API, the provided list of features does not need to be checked against available features for the connection. telepathy-glib handles this for us. Thus, the list of features should only be the list of features that the client supports.

Example 7-2Requesting TpContact objects from tp_channel_group_get_members()
const TpIntSet *members = tp_channel_group_get_members (channel);
GArray *handles = tp_intset_to_array (members);
g_print ("   channel contains %i members\n", handles->len);

/* we want to create a TpContact for each member of this channel */
static const TpContactFeature features[] = {
        TP_CONTACT_FEATURE_ALIAS,
        TP_CONTACT_FEATURE_PRESENCE
};

tp_connection_get_contacts_by_handle (conn,
                handles->len, (const TpHandle *) handles->data,
                G_N_ELEMENTS (features), features,
                contacts_ready,
                channel, NULL, NULL);

g_array_free (handles, TRUE);

Complete Source Code

TpContact objects will be unreferenced when the callback is finished, thus to hold on to a TpContact you must reference it (i.e. using g_object_ref or using it in a way that implicitly references — e.g. adding it to a GtkListStore). Example 7-3 shows the callback from creating a set of TpContacts.

The contact's parameters (alias, presence, avatar, etc.) are exposed on TpContact as GObject properties. Thus for a referenced object, we can connect the notify signal to track updates to these properties. telepathy-glib takes care of all of the underlying work in Telepathy, so your application doesn't need to connect any of the Telepathy signals.

Example 7-3Handling a Set of Newly Created Contacts
static void
contact_notify_cb (TpContact    *contact,
                   GParamSpec   *pspec,
                   gpointer      user_data)
{
        if (pspec)
        {
                g_print (" %% parameter updated %s\n", pspec->name);
        }

        g_print ("  - %s (%s)\t\t%s - %s\n",
                        tp_contact_get_alias (contact),
                        tp_contact_get_identifier (contact),
                        tp_contact_get_presence_status (contact),
                        tp_contact_get_presence_message (contact));
}

static void
contacts_ready (TpConnection            *conn,
                guint                    n_contacts,
                TpContact * const       *contacts,
                guint                    n_failed,
                const TpHandle          *failed,
                const GError            *in_error,
                gpointer                 user_data,
                GObject                 *weak_obj)
{
        TpChannel *channel = TP_CHANNEL (user_data);

        handle_error (in_error);

        g_print (" > contacts_ready for %s (%i contacts - %i failed)\n",
                        tp_channel_get_identifier (channel),
                        n_contacts, n_failed);

        int i;
        for (i = 0; i < n_contacts; i++)
        {
                TpContact *contact = contacts[i];

                g_object_ref (contact);
                g_signal_connect (contact, "notify",
                                G_CALLBACK (contact_notify_cb), NULL);

                contact_notify_cb (contact, NULL, NULL);
        }
}

Complete Source Code

Handling Your Contact Roster

To handle the contact roster for your application, keep the list of TpContact objects in a data structure appropriate for displaying in your user interface (e.g. GtkListStore). Connect the notify signal to track updates to contact information (e.g. presence, alias, avatar).

If possible, provide a sort function for your view using tp_connection_presence_type_cmp_availability to sort contacts by their availability.

In GTK+, useful functions are gtk_tree_view_column_set_cell_data_func or gtk_cell_layout_set_cell_data_func to pull data from the model for display in the view; and gtk_tree_sortable_set_default_sort_func to sort the view by availability (don't forget gtk_tree_sortable_set_sort_column_id (model, GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);).

A TpChannel's group-members-changed signal can be used to monitor the lifetime of a group. Members that have been added can have their TpContact requested.