[Telepathy-commits] [telepathy-glib/master] Add a contact-list example CM
Simon McVittie
simon.mcvittie at collabora.co.uk
Mon Mar 2 08:04:41 PST 2009
---
configure.ac | 1 +
examples/cm/Makefile.am | 1 +
examples/cm/contactlist/Makefile.am | 65 +
examples/cm/contactlist/conn.c | 591 +++++++++
examples/cm/contactlist/conn.h | 65 +
examples/cm/contactlist/connection-manager.c | 116 ++
examples/cm/contactlist/connection-manager.h | 62 +
examples/cm/contactlist/contact-list-manager.c | 1578 ++++++++++++++++++++++++
examples/cm/contactlist/contact-list-manager.h | 107 ++
examples/cm/contactlist/contact-list.c | 632 ++++++++++
examples/cm/contactlist/contact-list.h | 118 ++
examples/cm/contactlist/main.c | 44 +
examples/cm/contactlist/manager-file.py | 19 +
13 files changed, 3399 insertions(+), 0 deletions(-)
create mode 100644 examples/cm/contactlist/Makefile.am
create mode 100644 examples/cm/contactlist/conn.c
create mode 100644 examples/cm/contactlist/conn.h
create mode 100644 examples/cm/contactlist/connection-manager.c
create mode 100644 examples/cm/contactlist/connection-manager.h
create mode 100644 examples/cm/contactlist/contact-list-manager.c
create mode 100644 examples/cm/contactlist/contact-list-manager.h
create mode 100644 examples/cm/contactlist/contact-list.c
create mode 100644 examples/cm/contactlist/contact-list.h
create mode 100644 examples/cm/contactlist/main.c
create mode 100644 examples/cm/contactlist/manager-file.py
diff --git a/configure.ac b/configure.ac
index a565425..9ea551f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -205,6 +205,7 @@ AC_OUTPUT( Makefile \
examples/client/Makefile \
examples/cm/Makefile \
examples/cm/channelspecific/Makefile \
+ examples/cm/contactlist/Makefile \
examples/cm/echo/Makefile \
examples/cm/echo-message-parts/Makefile \
examples/cm/extended/Makefile \
diff --git a/examples/cm/Makefile.am b/examples/cm/Makefile.am
index 0d84b06..2da3990 100644
--- a/examples/cm/Makefile.am
+++ b/examples/cm/Makefile.am
@@ -1,5 +1,6 @@
SUBDIRS = \
channelspecific \
+ contactlist \
echo \
echo-message-parts \
extended \
diff --git a/examples/cm/contactlist/Makefile.am b/examples/cm/contactlist/Makefile.am
new file mode 100644
index 0000000..3d4d441
--- /dev/null
+++ b/examples/cm/contactlist/Makefile.am
@@ -0,0 +1,65 @@
+# Example connection manager with ContactList channels.
+
+EXAMPLES = telepathy-example-cm-contactlist
+noinst_LTLIBRARIES = libexample-cm-contactlist.la
+
+if INSTALL_EXAMPLES
+libexec_PROGRAMS = $(EXAMPLES)
+else
+noinst_PROGRAMS = $(EXAMPLES)
+endif
+
+libexample_cm_contactlist_la_SOURCES = \
+ conn.c \
+ conn.h \
+ connection-manager.c \
+ connection-manager.h \
+ contact-list.c \
+ contact-list.h \
+ contact-list-manager.c \
+ contact-list-manager.h
+
+# In an external project you'd use $(TP_GLIB_LIBS) (obtained from
+# pkg-config via autoconf) instead of the .la path
+libexample_cm_contactlist_la_LIBADD = \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib.la
+
+telepathy_example_cm_contactlist_SOURCES = \
+ main.c
+
+telepathy_example_cm_contactlist_LDADD = \
+ $(noinst_LTLIBRARIES)
+
+AM_CFLAGS = \
+ $(ERROR_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(TP_GLIB_CFLAGS)
+
+EXTRA_DIST = manager-file.py
+
+if INSTALL_EXAMPLES
+servicedir = ${datadir}/dbus-1/services
+service_DATA = _gen/org.freedesktop.Telepathy.ConnectionManager.example_contact_list.service
+$(service_DATA): %: Makefile
+ $(mkdir_p) _gen
+ { echo "[D-BUS Service]" && \
+ echo "Name=org.freedesktop.Telepathy.ConnectionManager.example_contact_list" && \
+ echo "Exec=${libexecdir}/telepathy-example-cm-contactlist"; } > $@
+
+managerdir = ${datadir}/telepathy/managers
+manager_DATA = _gen/example_contact_list.manager
+endif
+
+_gen/example_contact_list.manager _gen/param-spec-struct.h: \
+ manager-file.py $(top_srcdir)/tools/manager-file.py
+ $(mkdir_p) _gen
+ $(PYTHON) $(top_srcdir)/tools/manager-file.py $(srcdir)/manager-file.py _gen
+
+BUILT_SOURCES = _gen/param-spec-struct.h
+CLEANFILES = $(BUILT_SOURCES)
+
+clean-local:
+ rm -rf _gen
diff --git a/examples/cm/contactlist/conn.c b/examples/cm/contactlist/conn.c
new file mode 100644
index 0000000..335520e
--- /dev/null
+++ b/examples/cm/contactlist/conn.c
@@ -0,0 +1,591 @@
+/*
+ * conn.c - an example connection
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "conn.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/handle-repo-static.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "contact-list-manager.h"
+
+static void init_aliasing (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleContactListConnection,
+ example_contact_list_connection,
+ TP_TYPE_BASE_CONNECTION,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING,
+ init_aliasing);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
+ tp_contacts_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
+ tp_presence_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ tp_presence_mixin_simple_presence_iface_init))
+
+enum
+{
+ PROP_ACCOUNT = 1,
+ N_PROPS
+};
+
+struct _ExampleContactListConnectionPrivate
+{
+ gchar *account;
+ ExampleContactListManager *list_manager;
+ gboolean away;
+};
+
+static void
+example_contact_list_connection_init (ExampleContactListConnection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ ExampleContactListConnectionPrivate);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_value_set_string (value, self->priv->account);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *spec)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_free (self->priv->account);
+ self->priv->account = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+
+ tp_contacts_mixin_finalize (object);
+ g_free (self->priv->account);
+
+ G_OBJECT_CLASS (example_contact_list_connection_parent_class)->finalize (
+ object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+ ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
+
+ return g_strdup_printf ("%s@%p", self->priv->account, self);
+}
+
+gchar *
+example_contact_list_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id,
+ gpointer context,
+ GError **error)
+{
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "Contact ID must not be empty");
+ return NULL;
+ }
+
+ return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
+}
+
+static gchar *
+example_contact_list_normalize_group (TpHandleRepoIface *repo,
+ const gchar *id,
+ gpointer context,
+ GError **error)
+{
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "Contact group name cannot be empty");
+ return NULL;
+ }
+
+ return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
+}
+
+static void
+create_handle_repos (TpBaseConnection *conn,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+ repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_CONTACT, example_contact_list_normalize_contact, NULL);
+
+ repos[TP_HANDLE_TYPE_LIST] = tp_static_handle_repo_new
+ (TP_HANDLE_TYPE_LIST, example_contact_lists ());
+
+ repos[TP_HANDLE_TYPE_GROUP] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_GROUP, example_contact_list_normalize_group, NULL);
+}
+
+static void
+alias_updated_cb (ExampleContactListManager *manager,
+ TpHandle contact,
+ ExampleContactListConnection *self)
+{
+ GPtrArray *aliases;
+ GValueArray *pair;
+
+ pair = g_value_array_new (2);
+ g_value_array_append (pair, NULL);
+ g_value_array_append (pair, NULL);
+ g_value_init (pair->values + 0, G_TYPE_UINT);
+ g_value_init (pair->values + 1, G_TYPE_STRING);
+ g_value_set_uint (pair->values + 0, contact);
+ g_value_set_string (pair->values + 1,
+ example_contact_list_manager_get_alias (manager, contact));
+
+ aliases = g_ptr_array_sized_new (1);
+ g_ptr_array_add (aliases, pair);
+
+ tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases);
+
+ g_ptr_array_free (aliases, TRUE);
+ g_value_array_free (pair);
+}
+
+static void
+presence_updated_cb (ExampleContactListManager *manager,
+ TpHandle contact,
+ ExampleContactListConnection *self)
+{
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpPresenceStatus *status;
+
+ /* we ignore the presence indicated by the contact list for our own handle */
+ if (contact == base->self_handle)
+ return;
+
+ status = tp_presence_status_new (
+ example_contact_list_manager_get_presence (manager, contact),
+ NULL);
+ tp_presence_mixin_emit_one_presence_update ((GObject *) self,
+ contact, status);
+ tp_presence_status_free (status);
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (conn);
+ GPtrArray *ret = g_ptr_array_sized_new (1);
+
+ self->priv->list_manager =
+ EXAMPLE_CONTACT_LIST_MANAGER (g_object_new (
+ EXAMPLE_TYPE_CONTACT_LIST_MANAGER,
+ "connection", conn,
+ NULL));
+
+ g_signal_connect (self->priv->list_manager, "alias-updated",
+ G_CALLBACK (alias_updated_cb), self);
+ g_signal_connect (self->priv->list_manager, "presence-updated",
+ G_CALLBACK (presence_updated_cb), self);
+
+ g_ptr_array_add (ret, self->priv->list_manager);
+
+ return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start connecting, then go to state CONNECTED when finished, but here
+ * we can do it immediately. */
+
+ conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
+ NULL, error);
+
+ if (conn->self_handle == 0)
+ return FALSE;
+
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ return TRUE;
+}
+
+static void
+shut_down (TpBaseConnection *conn)
+{
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start shutting down, then call this function when finished, but here
+ * we can do it immediately. */
+ tp_base_connection_finish_shutdown (conn);
+}
+
+static void
+aliasing_fill_contact_attributes (GObject *object,
+ const GArray *contacts,
+ GHashTable *attributes)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+ guint i;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, guint, i);
+ GValue *value = tp_g_value_slice_new (G_TYPE_STRING);
+
+ g_value_set_string (value,
+ example_contact_list_manager_get_alias (self->priv->list_manager,
+ contact));
+ tp_contacts_mixin_set_contact_attribute (attributes, contact,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING "/alias", value);
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ void (*chain_up) (GObject *) =
+ G_OBJECT_CLASS (example_contact_list_connection_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ tp_contacts_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleContactListConnection, contacts_mixin));
+ tp_base_connection_register_with_contacts_mixin (base);
+ tp_contacts_mixin_add_contact_attributes_iface (object,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ aliasing_fill_contact_attributes);
+
+ tp_presence_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleContactListConnection, presence_mixin));
+ tp_presence_mixin_simple_presence_register_with_contacts_mixin (object);
+}
+
+static gboolean
+status_available (GObject *object,
+ guint index_)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+
+ if (base->status != TP_CONNECTION_STATUS_CONNECTED)
+ return FALSE;
+
+ return TRUE;
+}
+
+static GHashTable *
+get_contact_statuses (GObject *object,
+ const GArray *contacts,
+ GError **error)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ guint i;
+ GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) tp_presence_status_free);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, guint, i);
+ ExampleContactListPresence presence;
+ GHashTable *parameters;
+
+ /* we get our own status from the connection, and everyone else's status
+ * from the contact lists */
+ if (contact == base->self_handle)
+ {
+ presence = (self->priv->away ? EXAMPLE_CONTACT_LIST_PRESENCE_AWAY
+ : EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE);
+ }
+ else
+ {
+ presence = example_contact_list_manager_get_presence (
+ self->priv->list_manager, contact);
+ }
+
+ parameters = g_hash_table_new_full (g_str_hash,
+ g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
+ g_hash_table_insert (result, GUINT_TO_POINTER (contact),
+ tp_presence_status_new (presence, parameters));
+ g_hash_table_destroy (parameters);
+ }
+
+ return result;
+}
+
+static gboolean
+set_own_status (GObject *object,
+ const TpPresenceStatus *status,
+ GError **error)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ GHashTable *presences;
+
+ if (status->index == EXAMPLE_CONTACT_LIST_PRESENCE_AWAY)
+ {
+ if (self->priv->away)
+ return TRUE;
+
+ self->priv->away = TRUE;
+ }
+ else
+ {
+ if (!self->priv->away)
+ return TRUE;
+
+ self->priv->away = FALSE;
+ }
+
+ presences = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+ g_hash_table_insert (presences, GUINT_TO_POINTER (base->self_handle),
+ (gpointer) status);
+ tp_presence_mixin_emit_presence_update (object, presences);
+ g_hash_table_destroy (presences);
+ return TRUE;
+}
+
+static void
+example_contact_list_connection_class_init (
+ ExampleContactListConnectionClass *klass)
+{
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+ TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ NULL };
+ TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->constructed = constructed;
+ object_class->finalize = finalize;
+ g_type_class_add_private (klass,
+ sizeof (ExampleContactListConnectionPrivate));
+
+ base_class->create_handle_repos = create_handle_repos;
+ base_class->get_unique_connection_name = get_unique_connection_name;
+ base_class->create_channel_managers = create_channel_managers;
+ base_class->start_connecting = start_connecting;
+ base_class->shut_down = shut_down;
+ base_class->interfaces_always_present = interfaces_always_present;
+
+ param_spec = g_param_spec_string ("account", "Account name",
+ "The username of this user", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ tp_contacts_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListConnectionClass, contacts_mixin));
+ tp_presence_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListConnectionClass, presence_mixin),
+ status_available, get_contact_statuses, set_own_status,
+ example_contact_list_presence_statuses ());
+ tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
+}
+
+static void
+get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing,
+ DBusGMethodInvocation *context)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+ tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context,
+ TP_CONNECTION_ALIAS_FLAG_USER_SET);
+}
+
+static void
+get_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GHashTable *result;
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, TpHandle, i);
+ const gchar *alias = example_contact_list_manager_get_alias (
+ self->priv->list_manager, contact);
+
+ g_hash_table_insert (result, GUINT_TO_POINTER (contact),
+ (gchar *) alias);
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
+ result);
+ g_hash_table_destroy (result);
+}
+
+static void
+request_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GPtrArray *result;
+ gchar **strings;
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_ptr_array_sized_new (contacts->len + 1);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, TpHandle, i);
+ const gchar *alias = example_contact_list_manager_get_alias (
+ self->priv->list_manager, contact);
+
+ g_ptr_array_add (result, (gchar *) alias);
+ }
+
+ g_ptr_array_add (result, NULL);
+ strings = (gchar **) g_ptr_array_free (result, FALSE);
+ tp_svc_connection_interface_aliasing_return_from_request_aliases (context,
+ (const gchar **) strings);
+ g_free (strings);
+}
+
+static void
+set_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ GHashTable *aliases,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, aliases);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GError *error = NULL;
+
+ if (!tp_handle_is_valid (contact_repo, GPOINTER_TO_UINT (key),
+ &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+ }
+
+ g_hash_table_iter_init (&iter, aliases);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ example_contact_list_manager_set_alias (self->priv->list_manager,
+ GPOINTER_TO_UINT (key), value);
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_set_aliases (context);
+}
+
+static void
+init_aliasing (gpointer iface,
+ gpointer iface_data G_GNUC_UNUSED)
+{
+ TpSvcConnectionInterfaceAliasingClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\
+ klass, x)
+ IMPLEMENT(get_alias_flags);
+ IMPLEMENT(request_aliases);
+ IMPLEMENT(get_aliases);
+ IMPLEMENT(set_aliases);
+#undef IMPLEMENT
+}
diff --git a/examples/cm/contactlist/conn.h b/examples/cm/contactlist/conn.h
new file mode 100644
index 0000000..cb460f6
--- /dev/null
+++ b/examples/cm/contactlist/conn.h
@@ -0,0 +1,65 @@
+/*
+ * conn.h - header for an example connection
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CONTACT_LIST_CONN_H__
+#define __EXAMPLE_CONTACT_LIST_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/contacts-mixin.h>
+#include <telepathy-glib/presence-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListConnection ExampleContactListConnection;
+typedef struct _ExampleContactListConnectionClass
+ ExampleContactListConnectionClass;
+typedef struct _ExampleContactListConnectionPrivate
+ ExampleContactListConnectionPrivate;
+
+struct _ExampleContactListConnectionClass {
+ TpBaseConnectionClass parent_class;
+ TpPresenceMixinClass presence_mixin;
+ TpContactsMixinClass contacts_mixin;
+};
+
+struct _ExampleContactListConnection {
+ TpBaseConnection parent;
+ TpPresenceMixin presence_mixin;
+ TpContactsMixin contacts_mixin;
+
+ ExampleContactListConnectionPrivate *priv;
+};
+
+GType example_contact_list_connection_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_CONNECTION \
+ (example_contact_list_connection_get_type ())
+#define EXAMPLE_CONTACT_LIST_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \
+ ExampleContactListConnection))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \
+ ExampleContactListConnectionClass))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \
+ ExampleContactListConnectionClass))
+
+gchar *example_contact_list_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id, gpointer context, GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/examples/cm/contactlist/connection-manager.c b/examples/cm/contactlist/connection-manager.c
new file mode 100644
index 0000000..691ad93
--- /dev/null
+++ b/examples/cm/contactlist/connection-manager.c
@@ -0,0 +1,116 @@
+/*
+ * manager.c - an example connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "connection-manager.h"
+
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+
+#include "conn.h"
+
+G_DEFINE_TYPE (ExampleContactListConnectionManager,
+ example_contact_list_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+struct _ExampleContactListConnectionManagerPrivate
+{
+ int dummy;
+};
+
+static void
+example_contact_list_connection_manager_init (
+ ExampleContactListConnectionManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER,
+ ExampleContactListConnectionManagerPrivate);
+}
+
+typedef struct {
+ gchar *account;
+} ExampleParams;
+
+static gboolean
+account_param_filter (const TpCMParamSpec *paramspec,
+ GValue *value,
+ GError **error)
+{
+ const gchar *id = g_value_get_string (value);
+
+ g_value_take_string (value,
+ example_contact_list_normalize_contact (NULL, id, NULL, error));
+
+ if (g_value_get_string (value) == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+#include "_gen/param-spec-struct.h"
+
+static gpointer
+alloc_params (void)
+{
+ return g_slice_new0 (ExampleParams);
+}
+
+static void
+free_params (gpointer p)
+{
+ ExampleParams *params = p;
+
+ g_free (params->account);
+
+ g_slice_free (ExampleParams, params);
+}
+
+static const TpCMProtocolSpec example_protocols[] = {
+ { "example", example_contact_list_example_params,
+ alloc_params, free_params },
+ { NULL, NULL }
+};
+
+static TpBaseConnection *
+new_connection (TpBaseConnectionManager *self,
+ const gchar *proto,
+ TpIntSet *params_present,
+ gpointer parsed_params,
+ GError **error)
+{
+ ExampleParams *params = parsed_params;
+ ExampleContactListConnection *conn;
+
+ conn = EXAMPLE_CONTACT_LIST_CONNECTION
+ (g_object_new (EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ "account", params->account,
+ "protocol", proto,
+ NULL));
+
+ return (TpBaseConnection *) conn;
+}
+
+static void
+example_contact_list_connection_manager_class_init (
+ ExampleContactListConnectionManagerClass *klass)
+{
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleContactListConnectionManagerPrivate));
+
+ base_class->new_connection = new_connection;
+ base_class->cm_dbus_name = "example_contact_list";
+ base_class->protocol_params = example_protocols;
+}
diff --git a/examples/cm/contactlist/connection-manager.h b/examples/cm/contactlist/connection-manager.h
new file mode 100644
index 0000000..b99d159
--- /dev/null
+++ b/examples/cm/contactlist/connection-manager.h
@@ -0,0 +1,62 @@
+/*
+ * manager.h - header for an example connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_H__
+#define __EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListConnectionManager
+ ExampleContactListConnectionManager;
+typedef struct _ExampleContactListConnectionManagerClass
+ ExampleContactListConnectionManagerClass;
+typedef struct _ExampleContactListConnectionManagerPrivate
+ ExampleContactListConnectionManagerPrivate;
+
+struct _ExampleContactListConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+};
+
+struct _ExampleContactListConnectionManager {
+ TpBaseConnectionManager parent;
+
+ ExampleContactListConnectionManagerPrivate *priv;
+};
+
+GType example_contact_list_connection_manager_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER \
+ (example_contact_list_connection_manager_get_type ())
+#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \
+ ExampleContactListConnectionManager))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \
+ ExampleContactListConnectionManagerClass))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \
+ ExampleContactListConnectionManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/examples/cm/contactlist/contact-list-manager.c b/examples/cm/contactlist/contact-list-manager.c
new file mode 100644
index 0000000..5a4e380
--- /dev/null
+++ b/examples/cm/contactlist/contact-list-manager.c
@@ -0,0 +1,1578 @@
+/*
+ * Example channel manager for contact lists
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "contact-list-manager.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "contact-list.h"
+
+/* elements 0, 1... of this array must be kept in sync with elements 1, 2...
+ * of the enum ExampleContactList in contact-list-manager.h */
+static const gchar *_contact_lists[NUM_EXAMPLE_CONTACT_LISTS + 1] = {
+ "subscribe",
+ "publish",
+ "stored",
+ NULL
+};
+
+const gchar **
+example_contact_lists (void)
+{
+ return _contact_lists;
+}
+
+/* this array must be kept in sync with the enum
+ * ExampleContactListPresence in contact-list-manager.h */
+static const TpPresenceStatusSpec _statuses[] = {
+ { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL },
+ { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL },
+ { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL },
+ { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, NULL },
+ { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE, NULL },
+ { NULL }
+};
+
+const TpPresenceStatusSpec *
+example_contact_list_presence_statuses (void)
+{
+ return _statuses;
+}
+
+typedef struct {
+ gchar *alias;
+
+ guint subscribe:1;
+ guint publish:1;
+ guint subscribe_requested:1;
+ guint publish_requested:1;
+
+ TpHandleSet *tags;
+
+} ExampleContactDetails;
+
+static ExampleContactDetails *
+example_contact_details_new (void)
+{
+ return g_slice_new0 (ExampleContactDetails);
+}
+
+static void
+example_contact_details_destroy (gpointer p)
+{
+ ExampleContactDetails *d = p;
+
+ if (d->tags != NULL)
+ tp_handle_set_destroy (d->tags);
+
+ g_free (d->alias);
+ g_slice_free (ExampleContactDetails, d);
+}
+
+static void channel_manager_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleContactListManager,
+ example_contact_list_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init))
+
+enum
+{
+ ALIAS_UPDATED,
+ PRESENCE_UPDATED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+enum
+{
+ PROP_CONNECTION = 1,
+ N_PROPS
+};
+
+struct _ExampleContactListManagerPrivate
+{
+ TpBaseConnection *conn;
+ TpHandleRepoIface *contact_repo;
+ TpHandleRepoIface *group_repo;
+
+ TpHandleSet *contacts;
+ /* GUINT_TO_POINTER (handle borrowed from contacts)
+ * => ExampleContactDetails */
+ GHashTable *contact_details;
+
+ ExampleContactList *lists[NUM_EXAMPLE_CONTACT_LISTS];
+
+ /* GUINT_TO_POINTER (handle borrowed from channel) => ExampleContactGroup */
+ GHashTable *groups;
+
+ /* borrowed TpExportableChannel => GSList of gpointer (request tokens) that
+ * will be satisfied by that channel when the contact list has been
+ * downloaded. The requests are in reverse chronological order */
+ GHashTable *queued_requests;
+
+ gulong status_changed_id;
+};
+
+static void
+example_contact_list_manager_init (ExampleContactListManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST_MANAGER, ExampleContactListManagerPrivate);
+
+ self->priv->contact_details = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, example_contact_details_destroy);
+ self->priv->groups = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+ self->priv->queued_requests = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, NULL);
+
+ /* initialized properly in constructed() */
+ self->priv->contact_repo = NULL;
+ self->priv->group_repo = NULL;
+ self->priv->contacts = NULL;
+}
+
+static void
+example_contact_list_manager_close_all (ExampleContactListManager *self)
+{
+ guint i;
+
+ if (self->priv->queued_requests != NULL)
+ {
+ GHashTable *tmp = self->priv->queued_requests;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ self->priv->queued_requests = NULL;
+ g_hash_table_iter_init (&iter, tmp);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GSList *requests = value;
+ GSList *link;
+
+ requests = g_slist_reverse (requests);
+
+ for (link = requests; link != NULL; link = link->next)
+ {
+ tp_channel_manager_emit_request_failed (self,
+ link->data, TP_ERRORS, TP_ERROR_DISCONNECTED,
+ "Unable to complete channel request due to disconnection");
+ }
+
+ g_slist_free (requests);
+ g_hash_table_iter_steal (&iter);
+ }
+
+ g_hash_table_destroy (tmp);
+ }
+
+ if (self->priv->contacts != NULL)
+ {
+ tp_handle_set_destroy (self->priv->contacts);
+ self->priv->contacts = NULL;
+ }
+
+ if (self->priv->contact_details != NULL)
+ {
+ GHashTable *tmp = self->priv->contact_details;
+
+ self->priv->contact_details = NULL;
+ g_hash_table_destroy (tmp);
+ }
+
+ if (self->priv->groups != NULL)
+ {
+ GHashTable *tmp = self->priv->groups;
+
+ self->priv->groups = NULL;
+ g_hash_table_destroy (tmp);
+ }
+
+ for (i = 0; i < NUM_EXAMPLE_CONTACT_LISTS; i++)
+ {
+ if (self->priv->lists[i] != NULL)
+ {
+ g_object_unref (self->priv->lists[i]);
+ self->priv->lists[i] = NULL;
+ }
+ }
+
+ if (self->priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->conn,
+ self->priv->status_changed_id);
+ self->priv->status_changed_id = 0;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (object);
+
+ example_contact_list_manager_close_all (self);
+ g_assert (self->priv->groups == NULL);
+ g_assert (self->priv->lists[0] == NULL);
+ g_assert (self->priv->queued_requests == NULL);
+
+ ((GObjectClass *) example_contact_list_manager_parent_class)->dispose (
+ object);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ /* We don't ref the connection, because it owns a reference to the
+ * manager, and it guarantees that the manager's lifetime is
+ * less than its lifetime */
+ self->priv->conn = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+satisfy_queued_requests (TpExportableChannel *channel,
+ gpointer user_data)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (user_data);
+ GSList *requests = g_hash_table_lookup (self->priv->queued_requests,
+ channel);
+
+ /* this is all fine even if requests is NULL */
+ g_hash_table_steal (self->priv->queued_requests, channel);
+ requests = g_slist_reverse (requests);
+ tp_channel_manager_emit_new_channel (self, channel, requests);
+ g_slist_free (requests);
+}
+
+static ExampleContactDetails *
+lookup_contact (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ return g_hash_table_lookup (self->priv->contact_details,
+ GUINT_TO_POINTER (contact));
+}
+
+static ExampleContactDetails *
+ensure_contact (ExampleContactListManager *self,
+ TpHandle contact,
+ gboolean *created)
+{
+ ExampleContactDetails *ret = lookup_contact (self, contact);
+
+ if (ret == NULL)
+ {
+ tp_handle_set_add (self->priv->contacts, contact);
+
+ ret = example_contact_details_new ();
+ ret->alias = g_strdup (tp_handle_inspect (self->priv->contact_repo,
+ contact));
+
+ g_hash_table_insert (self->priv->contact_details,
+ GUINT_TO_POINTER (contact), ret);
+ }
+
+ return ret;
+}
+
+static void
+example_contact_list_manager_foreach_channel (TpChannelManager *manager,
+ TpExportableChannelFunc callback,
+ gpointer user_data)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (manager);
+ GHashTableIter iter;
+ gpointer handle, channel;
+ guint i;
+
+ for (i = 0; i < NUM_EXAMPLE_CONTACT_LISTS; i++)
+ {
+ if (self->priv->lists[i] != NULL)
+ callback (TP_EXPORTABLE_CHANNEL (self->priv->lists[i]), user_data);
+ }
+
+ g_hash_table_iter_init (&iter, self->priv->groups);
+
+ while (g_hash_table_iter_next (&iter, &handle, &channel))
+ {
+ callback (TP_EXPORTABLE_CHANNEL (channel), user_data);
+ }
+}
+
+static ExampleContactGroup *ensure_group (ExampleContactListManager *self,
+ TpHandle handle);
+
+static ExampleContactList *ensure_list (ExampleContactListManager *self,
+ ExampleContactListHandle handle);
+
+static gboolean
+receive_contact_lists (gpointer p)
+{
+ ExampleContactListManager *self = p;
+ TpHandle handle, cambridge, montreal, francophones;
+ ExampleContactDetails *d;
+ TpIntSet *set, *cam_set, *mtl_set, *fr_set;
+ TpIntSetIter iter;
+ ExampleContactList *subscribe, *publish, *stored;
+ ExampleContactGroup *cambridge_group, *montreal_group,
+ *francophones_group;
+
+ if (self->priv->groups == NULL)
+ {
+ /* connection already disconnected, so don't process the
+ * "data from the server" */
+ return FALSE;
+ }
+
+ /* In a real CM we'd have received a contact list from the server at this
+ * point. But this isn't a real CM, so we have to make one up... */
+
+ g_message ("Receiving roster from server");
+
+ subscribe = ensure_list (self, EXAMPLE_CONTACT_LIST_SUBSCRIBE);
+ publish = ensure_list (self, EXAMPLE_CONTACT_LIST_PUBLISH);
+ stored = ensure_list (self, EXAMPLE_CONTACT_LIST_STORED);
+
+ cambridge = tp_handle_ensure (self->priv->group_repo, "Cambridge", NULL,
+ NULL);
+ montreal = tp_handle_ensure (self->priv->group_repo, "Montreal", NULL,
+ NULL);
+ francophones = tp_handle_ensure (self->priv->group_repo, "Francophones",
+ NULL, NULL);
+
+ cambridge_group = ensure_group (self, cambridge);
+ montreal_group = ensure_group (self, montreal);
+ francophones_group = ensure_group (self, francophones);
+
+ /* Add various people who are already subscribing and publishing */
+
+ set = tp_intset_new ();
+ cam_set = tp_intset_new ();
+ mtl_set = tp_intset_new ();
+ fr_set = tp_intset_new ();
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "sjoerd at example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ tp_intset_add (cam_set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Sjoerd");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+ tp_handle_set_add (d->tags, cambridge);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "guillaume at example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ tp_intset_add (cam_set, handle);
+ tp_intset_add (fr_set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Guillaume");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+ tp_handle_set_add (d->tags, cambridge);
+ tp_handle_set_add (d->tags, francophones);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "olivier at example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ tp_intset_add (mtl_set, handle);
+ tp_intset_add (fr_set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Olivier");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+ tp_handle_set_add (d->tags, montreal);
+ tp_handle_set_add (d->tags, francophones);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "travis at example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Travis");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ tp_group_mixin_change_members ((GObject *) subscribe, "",
+ set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) publish, "",
+ set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_iter_init (&iter, set);
+
+ while (tp_intset_iter_next (&iter))
+ {
+ g_signal_emit (self, signals[ALIAS_UPDATED], 0, iter.element);
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, iter.element);
+ }
+
+ tp_intset_destroy (set);
+
+ /* Add a couple of people whose presence we've requested. They are
+ * remote-pending in subscribe */
+
+ set = tp_intset_new ();
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "geraldine at example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ tp_intset_add (cam_set, handle);
+ tp_intset_add (fr_set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Géraldine");
+ d->subscribe_requested = TRUE;
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+ tp_handle_set_add (d->tags, cambridge);
+ tp_handle_set_add (d->tags, francophones);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "helen at example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ tp_intset_add (cam_set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Helen");
+ d->subscribe_requested = TRUE;
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+ tp_handle_set_add (d->tags, cambridge);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ tp_group_mixin_change_members ((GObject *) subscribe, "",
+ NULL, NULL, NULL, set,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ NULL, NULL, NULL, set,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_iter_init (&iter, set);
+
+ while (tp_intset_iter_next (&iter))
+ {
+ g_signal_emit (self, signals[ALIAS_UPDATED], 0, iter.element);
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, iter.element);
+ }
+
+ tp_intset_destroy (set);
+
+ /* Receive a couple of authorization requests too. These people are
+ * local-pending in publish */
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "wim at example.com",
+ NULL, NULL);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Wim");
+ d->publish_requested = TRUE;
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ set = tp_intset_new_containing (handle);
+ tp_group_mixin_change_members ((GObject *) publish,
+ "I'm more metal than you!",
+ NULL, NULL, set, NULL,
+ handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "christian at example.com",
+ NULL, NULL);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Christian");
+ d->publish_requested = TRUE;
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ set = tp_intset_new_containing (handle);
+ tp_group_mixin_change_members ((GObject *) publish,
+ "I have some fermented herring for you",
+ NULL, NULL, set, NULL,
+ handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
+
+ tp_group_mixin_change_members ((GObject *) cambridge_group, "",
+ cam_set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) montreal_group, "",
+ mtl_set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) francophones_group, "",
+ fr_set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (fr_set);
+ tp_intset_destroy (cam_set);
+ tp_intset_destroy (mtl_set);
+
+ tp_handle_unref (self->priv->group_repo, cambridge);
+ tp_handle_unref (self->priv->group_repo, montreal);
+ tp_handle_unref (self->priv->group_repo, francophones);
+
+ /* Now we've received the roster, we can satisfy all the queued requests */
+
+ example_contact_list_manager_foreach_channel ((TpChannelManager *) self,
+ satisfy_queued_requests, self);
+
+ g_assert (g_hash_table_size (self->priv->queued_requests) == 0);
+ g_hash_table_destroy (self->priv->queued_requests);
+ self->priv->queued_requests = NULL;
+
+ return FALSE;
+}
+
+static void
+status_changed_cb (TpBaseConnection *conn,
+ guint status,
+ guint reason,
+ ExampleContactListManager *self)
+{
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_CONNECTED:
+ {
+ /* Do network I/O to get the contact list. This connection manager
+ * doesn't really have a server, so simulate a small network delay
+ * then invent a contact list */
+ g_timeout_add_full (G_PRIORITY_DEFAULT, 1000, receive_contact_lists,
+ g_object_ref (self), g_object_unref);
+ }
+ break;
+
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ {
+ example_contact_list_manager_close_all (self);
+ }
+ break;
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_contact_list_manager_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ self->priv->contact_repo = tp_base_connection_get_handles (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT);
+ self->priv->group_repo = tp_base_connection_get_handles (self->priv->conn,
+ TP_HANDLE_TYPE_GROUP);
+ self->priv->contacts = tp_handle_set_new (self->priv->contact_repo);
+
+ self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+ "status-changed", (GCallback) status_changed_cb, self);
+}
+
+static void
+example_contact_list_manager_class_init (ExampleContactListManagerClass *klass)
+{
+ GParamSpec *param_spec;
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->constructed = constructed;
+ object_class->dispose = dispose;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ param_spec = g_param_spec_object ("connection", "Connection object",
+ "The connection that owns this channel manager",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ g_type_class_add_private (klass, sizeof (ExampleContactListManagerPrivate));
+
+ signals[ALIAS_UPDATED] = g_signal_new ("alias-updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ signals[PRESENCE_UPDATED] = g_signal_new ("presence-updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+}
+
+static void
+list_closed_cb (ExampleContactList *chan,
+ ExampleContactListManager *self)
+{
+ TpHandle handle;
+
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ g_object_get (chan,
+ "handle", &handle,
+ NULL);
+
+ if (self->priv->lists[handle] == NULL)
+ return;
+
+ g_assert (chan == self->priv->lists[handle]);
+ g_object_unref (self->priv->lists[handle]);
+ self->priv->lists[handle] = NULL;
+}
+
+static void
+group_closed_cb (ExampleContactGroup *chan,
+ ExampleContactListManager *self)
+{
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (self->priv->groups != NULL)
+ {
+ TpHandle handle;
+
+ g_object_get (chan,
+ "handle", &handle,
+ NULL);
+
+ g_hash_table_remove (self->priv->groups, GUINT_TO_POINTER (handle));
+ }
+}
+
+static ExampleContactListBase *
+new_channel (ExampleContactListManager *self,
+ TpHandleType handle_type,
+ TpHandle handle,
+ gpointer request_token)
+{
+ ExampleContactListBase *chan;
+ gchar *object_path;
+ GType type;
+ GSList *requests = NULL;
+
+ if (handle_type == TP_HANDLE_TYPE_LIST)
+ {
+ /* Some Telepathy clients wrongly assume that contact lists of type LIST
+ * have object paths ending with "/subscribe", "/publish" etc. -
+ * telepathy-spec has no such guarantee, so in this example we break
+ * those clients. Please read the spec when implementing it :-) */
+ object_path = g_strdup_printf ("%s/%sContactList",
+ self->priv->conn->object_path, _contact_lists[handle - 1]);
+ type = EXAMPLE_TYPE_CONTACT_LIST;
+ }
+ else
+ {
+ /* Using Group%u (with handle as the value of %u) would be OK here too,
+ * but we'll encode the group name into the object path to be kind
+ * to people reading debug logs. */
+ gchar *id = tp_escape_as_identifier (tp_handle_inspect (
+ self->priv->group_repo, handle));
+
+ g_assert (handle_type == TP_HANDLE_TYPE_GROUP);
+ object_path = g_strdup_printf ("%s/Group/%s",
+ self->priv->conn->object_path, id);
+ type = EXAMPLE_TYPE_CONTACT_GROUP;
+
+ g_free (id);
+ }
+
+ chan = g_object_new (type,
+ "connection", self->priv->conn,
+ "manager", self,
+ "object-path", object_path,
+ "handle-type", handle_type,
+ "handle", handle,
+ NULL);
+
+ g_free (object_path);
+
+ if (handle_type == TP_HANDLE_TYPE_LIST)
+ {
+ g_signal_connect (chan, "closed", (GCallback) list_closed_cb, self);
+ g_assert (self->priv->lists[handle] == NULL);
+ self->priv->lists[handle] = EXAMPLE_CONTACT_LIST (chan);
+ }
+ else
+ {
+ g_signal_connect (chan, "closed", (GCallback) group_closed_cb, self);
+
+ g_assert (g_hash_table_lookup (self->priv->groups,
+ GUINT_TO_POINTER (handle)) == NULL);
+ g_hash_table_insert (self->priv->groups, GUINT_TO_POINTER (handle),
+ EXAMPLE_CONTACT_GROUP (chan));
+ }
+
+ if (self->priv->queued_requests == NULL)
+ {
+ if (request_token != NULL)
+ requests = g_slist_prepend (requests, request_token);
+
+ tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
+ requests);
+ g_slist_free (requests);
+ }
+ else if (request_token != NULL)
+ {
+ /* initial contact list not received yet, so we have to wait for it */
+ requests = g_hash_table_lookup (self->priv->queued_requests, chan);
+ g_hash_table_steal (self->priv->queued_requests, chan);
+ requests = g_slist_prepend (requests, request_token);
+ g_hash_table_insert (self->priv->queued_requests, chan, requests);
+ }
+
+ return chan;
+}
+
+static ExampleContactList *
+ensure_list (ExampleContactListManager *self,
+ ExampleContactListHandle handle)
+{
+ if (self->priv->lists[handle] == NULL)
+ {
+ new_channel (self, TP_HANDLE_TYPE_LIST, handle, NULL);
+ g_assert (self->priv->lists[handle] != NULL);
+ }
+
+ return self->priv->lists[handle];
+}
+
+static ExampleContactGroup *
+ensure_group (ExampleContactListManager *self,
+ TpHandle handle)
+{
+ ExampleContactGroup *group = g_hash_table_lookup (self->priv->groups,
+ GUINT_TO_POINTER (handle));
+
+ if (group == NULL)
+ {
+ group = EXAMPLE_CONTACT_GROUP (new_channel (self, TP_HANDLE_TYPE_GROUP,
+ handle, NULL));
+ }
+
+ return group;
+}
+
+static const gchar * const fixed_properties[] = {
+ TP_IFACE_CHANNEL ".ChannelType",
+ TP_IFACE_CHANNEL ".TargetHandleType",
+ NULL
+};
+
+static const gchar * const allowed_properties[] = {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ TP_IFACE_CHANNEL ".TargetID",
+ NULL
+};
+
+static void
+example_contact_list_manager_foreach_channel_class (TpChannelManager *manager,
+ TpChannelManagerChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+ GValue *ctype, *htype;
+
+ ctype = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (ctype, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST);
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType", ctype);
+
+ htype = tp_g_value_slice_new (G_TYPE_UINT); /* initialized later */
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType", htype);
+
+ g_value_set_uint (htype, TP_HANDLE_TYPE_LIST);
+ func (manager, table, allowed_properties, user_data);
+ g_value_set_uint (htype, TP_HANDLE_TYPE_GROUP);
+ func (manager, table, allowed_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+static gboolean
+example_contact_list_manager_request (ExampleContactListManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ TpHandleType handle_type;
+ TpHandle handle;
+ ExampleContactListBase *chan;
+ GError *error = NULL;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL ".ChannelType"),
+ TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
+ {
+ return FALSE;
+ }
+
+ handle_type = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL);
+
+ if (handle_type != TP_HANDLE_TYPE_LIST &&
+ handle_type != TP_HANDLE_TYPE_GROUP)
+ {
+ return FALSE;
+ }
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandle", NULL);
+ g_assert (handle != 0);
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ fixed_properties, allowed_properties, &error))
+ {
+ goto error;
+ }
+
+ if (handle_type == TP_HANDLE_TYPE_LIST)
+ {
+ /* telepathy-glib has already checked that the handle is valid */
+ g_assert (handle < NUM_EXAMPLE_CONTACT_LISTS);
+
+ chan = EXAMPLE_CONTACT_LIST_BASE (self->priv->lists[handle]);
+ }
+ else
+ {
+ chan = g_hash_table_lookup (self->priv->groups,
+ GUINT_TO_POINTER (handle));
+ }
+
+ if (chan == NULL)
+ {
+ chan = new_channel (self, handle_type, handle, request_token);
+ }
+ else if (require_new)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "A ContactList channel for type #%u, handle #%u already exists",
+ handle_type, handle);
+ goto error;
+ }
+ else
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (chan));
+ }
+
+ return TRUE;
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+static gboolean
+example_contact_list_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_contact_list_manager_request (
+ EXAMPLE_CONTACT_LIST_MANAGER (manager), request_token,
+ request_properties, TRUE);
+}
+
+static gboolean
+example_contact_list_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_contact_list_manager_request (
+ EXAMPLE_CONTACT_LIST_MANAGER (manager), request_token,
+ request_properties, FALSE);
+}
+
+static void
+channel_manager_iface_init (gpointer g_iface,
+ gpointer data G_GNUC_UNUSED)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = example_contact_list_manager_foreach_channel;
+ iface->foreach_channel_class =
+ example_contact_list_manager_foreach_channel_class;
+ iface->create_channel = example_contact_list_manager_create_channel;
+ iface->ensure_channel = example_contact_list_manager_ensure_channel;
+ /* In this channel manager, Request has the same semantics as Ensure */
+ iface->request_channel = example_contact_list_manager_ensure_channel;
+}
+
+static void
+send_updated_roster (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d = g_hash_table_lookup (self->priv->contact_details,
+ GUINT_TO_POINTER (contact));
+ const gchar *identifier = tp_handle_inspect (self->priv->contact_repo,
+ contact);
+
+ /* In a real connection manager, we'd transmit these new details to the
+ * server, rather than just printing messages. */
+
+ if (d == NULL)
+ {
+ g_message ("Deleting contact %s from server", identifier);
+ }
+ else
+ {
+ g_message ("Transmitting new state of contact %s to server", identifier);
+ g_message ("\talias = %s", d->alias);
+ g_message ("\tcan see our presence = %s",
+ d->publish ? "yes" :
+ (d->publish_requested ? "no, but has requested it" : "no"));
+ g_message ("\tsends us presence = %s",
+ d->subscribe ? "yes" :
+ (d->subscribe_requested ? "no, but we have requested it" : "no"));
+
+ if (d->tags == NULL || tp_handle_set_size (d->tags) == 0)
+ {
+ g_message ("\tnot in any groups");
+ }
+ else
+ {
+ TpIntSet *set = tp_handle_set_peek (d->tags);
+ TpIntSetIter iter = TP_INTSET_ITER_INIT (set);
+
+ while (tp_intset_iter_next (&iter))
+ {
+ g_message ("\tin group: %s",
+ tp_handle_inspect (self->priv->group_repo, iter.element));
+ }
+ }
+ }
+}
+
+gboolean
+example_contact_list_manager_add_to_group (ExampleContactListManager *self,
+ GObject *channel,
+ TpHandle group,
+ TpHandle member,
+ const gchar *message,
+ GError **error)
+{
+ gboolean updated;
+ ExampleContactDetails *d = ensure_contact (self, member, &updated);
+ ExampleContactList *stored = self->priv->lists[
+ EXAMPLE_CONTACT_LIST_STORED];
+
+ if (d->tags == NULL)
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+
+ if (!tp_handle_set_is_member (d->tags, group))
+ {
+ tp_handle_set_add (d->tags, group);
+ updated = TRUE;
+ }
+
+ if (updated)
+ {
+ TpIntSet *added = tp_intset_new_containing (member);
+
+ send_updated_roster (self, member);
+ tp_group_mixin_change_members (channel, "", added, NULL, NULL, NULL,
+ self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ added, NULL, NULL, NULL,
+ self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (added);
+ }
+
+ return TRUE;
+}
+
+gboolean
+example_contact_list_manager_remove_from_group (
+ ExampleContactListManager *self,
+ GObject *channel,
+ TpHandle group,
+ TpHandle member,
+ const gchar *message,
+ GError **error)
+{
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ /* If not on the roster or not in any groups, we have nothing to do */
+ if (d == NULL || d->tags == NULL)
+ return TRUE;
+
+ if (tp_handle_set_remove (d->tags, group))
+ {
+ TpIntSet *removed = tp_intset_new_containing (member);
+
+ send_updated_roster (self, member);
+ tp_group_mixin_change_members (channel, "", NULL, removed, NULL, NULL,
+ self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (removed);
+ }
+
+ return TRUE;
+}
+
+typedef struct {
+ ExampleContactListManager *self;
+ TpHandle contact;
+} SelfAndContact;
+
+static SelfAndContact *
+self_and_contact_new (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ SelfAndContact *ret = g_slice_new0 (SelfAndContact);
+
+ ret->self = g_object_ref (self);
+ ret->contact = contact;
+ tp_handle_ref (self->priv->contact_repo, contact);
+ return ret;
+}
+
+static void
+self_and_contact_destroy (gpointer p)
+{
+ SelfAndContact *s = p;
+
+ tp_handle_unref (s->self->priv->contact_repo, s->contact);
+ g_object_unref (s->self);
+ g_slice_free (SelfAndContact, s);
+}
+
+static void
+receive_auth_request (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d;
+ TpIntSet *set;
+ ExampleContactList *publish = self->priv->lists[
+ EXAMPLE_CONTACT_LIST_PUBLISH];
+
+ /* if shutting down, do nothing */
+ if (publish == NULL)
+ return;
+
+ /* A remote contact has asked to see our presence.
+ *
+ * In a real connection manager this would be the result of incoming
+ * data from the server. */
+
+ g_message ("From server: %s has sent us a publish request",
+ tp_handle_inspect (self->priv->contact_repo, contact));
+
+ d = ensure_contact (self, contact, NULL);
+
+ if (d->publish)
+ return;
+
+ d->publish_requested = TRUE;
+
+ set = tp_intset_new_containing (contact);
+ tp_group_mixin_change_members ((GObject *) publish,
+ "May I see your presence, please?",
+ NULL, NULL, set, NULL,
+ contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+}
+
+static gboolean
+receive_authorized (gpointer p)
+{
+ SelfAndContact *s = p;
+ ExampleContactDetails *d;
+ TpIntSet *set;
+ ExampleContactList *subscribe = s->self->priv->lists[
+ EXAMPLE_CONTACT_LIST_STORED];
+
+ /* A remote contact has accepted our request to see their presence.
+ *
+ * In a real connection manager this would be the result of incoming
+ * data from the server. */
+
+ g_message ("From server: %s has accepted our subscribe request",
+ tp_handle_inspect (s->self->priv->contact_repo, s->contact));
+
+ d = ensure_contact (s->self, s->contact, NULL);
+
+ /* if we were already subscribed to them, then nothing really happened */
+ if (d->subscribe)
+ return FALSE;
+
+ d->subscribe_requested = FALSE;
+ d->subscribe = TRUE;
+
+ set = tp_intset_new_containing (s->contact);
+ tp_group_mixin_change_members ((GObject *) subscribe, "",
+ set, NULL, NULL, NULL,
+ s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ /* their presence changes to something other than UNKNOWN */
+ g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact);
+
+ /* if we're not publishing to them, also pretend they have asked us to
+ * do so */
+ if (!d->publish)
+ {
+ receive_auth_request (s->self, s->contact);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+receive_unauthorized (gpointer p)
+{
+ SelfAndContact *s = p;
+ ExampleContactDetails *d;
+ TpIntSet *set;
+ ExampleContactList *subscribe = s->self->priv->lists[
+ EXAMPLE_CONTACT_LIST_SUBSCRIBE];
+
+ /* if shutting down, do nothing */
+ if (subscribe == NULL)
+ return FALSE;
+
+ /* A remote contact has rejected our request to see their presence.
+ *
+ * In a real connection manager this would be the result of incoming
+ * data from the server. */
+
+ g_message ("From server: %s has rejected our subscribe request",
+ tp_handle_inspect (s->self->priv->contact_repo, s->contact));
+
+ d = ensure_contact (s->self, s->contact, NULL);
+
+ if (!d->subscribe && !d->subscribe_requested)
+ return FALSE;
+
+ d->subscribe_requested = FALSE;
+ d->subscribe = FALSE;
+
+ set = tp_intset_new_containing (s->contact);
+ tp_group_mixin_change_members ((GObject *) subscribe, "Say 'please'!",
+ NULL, set, NULL, NULL,
+ s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ /* their presence changes to UNKNOWN */
+ g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact);
+
+ return FALSE;
+}
+
+gboolean
+example_contact_list_manager_add_to_list (ExampleContactListManager *self,
+ GObject *channel,
+ ExampleContactListHandle list,
+ TpHandle member,
+ const gchar *message,
+ GError **error)
+{
+ TpIntSet *set;
+
+ switch (list)
+ {
+ case EXAMPLE_CONTACT_LIST_SUBSCRIBE:
+ /* we would like to see member's presence */
+ {
+ gboolean created;
+ ExampleContactDetails *d = ensure_contact (self, member, &created);
+ gchar *message_lc;
+
+ /* if they already authorized us, it's a no-op */
+ if (d->subscribe)
+ return TRUE;
+
+ /* In a real connection manager we'd start a network request here */
+ g_message ("Transmitting authorization request to %s: %s",
+ tp_handle_inspect (self->priv->contact_repo, member),
+ message);
+
+ if (created || !d->subscribe_requested)
+ {
+ d->subscribe_requested = TRUE;
+ send_updated_roster (self, member);
+ }
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, message,
+ NULL, NULL, NULL, set,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ /* Pretend that after 500ms, the contact notices the request
+ * and allows or rejects it. In this example connection manager,
+ * empty requests are allowed, as are requests that contain "please"
+ * case-insensitively. All other requests are denied. */
+ message_lc = g_ascii_strdown (message, -1);
+
+ if (message[0] == '\0' || strstr (message_lc, "please") != NULL)
+ {
+ g_timeout_add_full (G_PRIORITY_DEFAULT, 500, receive_authorized,
+ self_and_contact_new (self, member),
+ self_and_contact_destroy);
+ }
+ else
+ {
+ g_timeout_add_full (G_PRIORITY_DEFAULT, 500,
+ receive_unauthorized,
+ self_and_contact_new (self, member),
+ self_and_contact_destroy);
+ }
+
+ g_free (message_lc);
+ }
+ return TRUE;
+
+ case EXAMPLE_CONTACT_LIST_PUBLISH:
+ /* We would like member to see our presence. This is meaningless,
+ * unless they have asked for it. */
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ if (d == NULL || !d->publish_requested)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Can't unilaterally send presence to %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ return FALSE;
+ }
+
+ if (!d->publish)
+ {
+ d->publish = TRUE;
+ d->publish_requested = FALSE;
+ send_updated_roster (self, member);
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ set, NULL, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ }
+ }
+ return TRUE;
+
+ case EXAMPLE_CONTACT_LIST_STORED:
+ /* we would like member to be on the roster */
+ {
+ gboolean created;
+
+ ensure_contact (self, member, &created);
+
+ if (created)
+ send_updated_roster (self, member);
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ set, NULL, NULL, NULL, self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ }
+ return TRUE;
+
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+}
+
+static gboolean
+auth_request_cb (gpointer p)
+{
+ SelfAndContact *s = p;
+
+ receive_auth_request (s->self, s->contact);
+
+ return FALSE;
+}
+
+gboolean
+example_contact_list_manager_remove_from_list (ExampleContactListManager *self,
+ GObject *channel,
+ ExampleContactListHandle list,
+ TpHandle member,
+ const gchar *message,
+ GError **error)
+{
+ TpIntSet *set;
+
+ switch (list)
+ {
+ case EXAMPLE_CONTACT_LIST_PUBLISH:
+ /* we would like member not to see our presence any more, or we
+ * would like to reject a request from them to see our presence */
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ if (d != NULL)
+ {
+ if (d->publish_requested)
+ {
+ g_message ("Rejecting authorization request from %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->publish_requested = FALSE;
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ }
+ else if (d->publish)
+ {
+ g_message ("Removing authorization from %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->publish = FALSE;
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ /* Pretend that after 500ms, the contact notices the change
+ * and asks for our presence again */
+ g_timeout_add_full (G_PRIORITY_DEFAULT, 500, auth_request_cb,
+ self_and_contact_new (self, member),
+ self_and_contact_destroy);
+ }
+ else
+ {
+ /* nothing to do, avoid "updating the roster" */
+ return TRUE;
+ }
+
+ send_updated_roster (self, member);
+ }
+ }
+ return TRUE;
+
+ case EXAMPLE_CONTACT_LIST_SUBSCRIBE:
+ /* we would like to avoid receiving member's presence any more,
+ * or we would like to cancel an outstanding request for their
+ * presence */
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ if (d != NULL)
+ {
+ if (d->subscribe_requested)
+ {
+ g_message ("Cancelling our authorization request to %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->subscribe_requested = FALSE;
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ }
+ else if (d->subscribe)
+ {
+ g_message ("We no longer want presence from %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->subscribe = FALSE;
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ /* since they're no longer on the subscribe list, we can't
+ * see their presence, so emit a signal changing it to
+ * UNKNOWN */
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member);
+ }
+ else
+ {
+ /* nothing to do, avoid "updating the roster" */
+ return TRUE;
+ }
+
+ send_updated_roster (self, member);
+ }
+ }
+ return TRUE;
+
+ case EXAMPLE_CONTACT_LIST_STORED:
+ /* we would like to remove member from the roster altogether */
+ {
+ if (lookup_contact (self, member) != NULL)
+ {
+ g_hash_table_remove (self->priv->contact_details,
+ GUINT_TO_POINTER (member));
+ send_updated_roster (self, member);
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ tp_handle_set_remove (self->priv->contacts, member);
+
+ /* since they're no longer on the subscribe list, we can't
+ * see their presence, so emit a signal changing it to
+ * UNKNOWN */
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member);
+ }
+ }
+ return TRUE;
+
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+}
+
+ExampleContactListPresence
+example_contact_list_manager_get_presence (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d = lookup_contact (self, contact);
+ const gchar *id;
+
+ if (d == NULL || !d->subscribe)
+ {
+ /* we don't know the presence of people not on the subscribe list,
+ * by definition */
+ return EXAMPLE_CONTACT_LIST_PRESENCE_UNKNOWN;
+ }
+
+ id = tp_handle_inspect (self->priv->contact_repo, contact);
+
+ /* In this example CM, we fake contacts' presence based on their name:
+ * contacts in the first half of the alphabet are available, the rest
+ * (including non-alphabetic and non-ASCII initial letters) are away. */
+ if ((id[0] >= 'A' && id[0] <= 'M') || (id[0] >= 'a' && id[0] <= 'm'))
+ {
+ return EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE;
+ }
+
+ return EXAMPLE_CONTACT_LIST_PRESENCE_AWAY;
+}
+
+const gchar *
+example_contact_list_manager_get_alias (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d = lookup_contact (self, contact);
+
+ if (d == NULL)
+ {
+ /* we don't have a user-defined alias for people not on the roster */
+ return tp_handle_inspect (self->priv->contact_repo, contact);
+ }
+
+ return d->alias;
+}
+
+void
+example_contact_list_manager_set_alias (ExampleContactListManager *self,
+ TpHandle contact,
+ const gchar *alias)
+{
+ gboolean created;
+ ExampleContactDetails *d = ensure_contact (self, contact, &created);
+ ExampleContactList *stored = self->priv->lists[
+ EXAMPLE_CONTACT_LIST_STORED];
+ gchar *old = d->alias;
+ TpIntSet *set;
+
+ /* FIXME: if stored list hasn't been retrieved yet, queue the change for
+ * later */
+
+ /* if shutting down, do nothing */
+ if (stored == NULL)
+ return;
+
+ d->alias = g_strdup (alias);
+
+ if (created || tp_strdiff (old, alias))
+ send_updated_roster (self, contact);
+
+ g_free (old);
+
+ set = tp_intset_new_containing (contact);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL, self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+}
diff --git a/examples/cm/contactlist/contact-list-manager.h b/examples/cm/contactlist/contact-list-manager.h
new file mode 100644
index 0000000..5811a69
--- /dev/null
+++ b/examples/cm/contactlist/contact-list-manager.h
@@ -0,0 +1,107 @@
+/*
+ * Example channel manager for contact lists
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CONTACT_LIST_MANAGER_H__
+#define __EXAMPLE_CONTACT_LIST_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/handle.h>
+#include <telepathy-glib/presence-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListManager ExampleContactListManager;
+typedef struct _ExampleContactListManagerClass ExampleContactListManagerClass;
+typedef struct _ExampleContactListManagerPrivate ExampleContactListManagerPrivate;
+
+struct _ExampleContactListManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _ExampleContactListManager {
+ GObject parent;
+
+ ExampleContactListManagerPrivate *priv;
+};
+
+GType example_contact_list_manager_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_MANAGER \
+ (example_contact_list_manager_get_type ())
+#define EXAMPLE_CONTACT_LIST_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CONTACT_LIST_MANAGER, \
+ ExampleContactListManager))
+#define EXAMPLE_CONTACT_LIST_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CONTACT_LIST_MANAGER, \
+ ExampleContactListManagerClass))
+#define EXAMPLE_IS_CONTACT_LIST_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CONTACT_LIST_MANAGER))
+#define EXAMPLE_IS_CONTACT_LIST_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CONTACT_LIST_MANAGER))
+#define EXAMPLE_CONTACT_LIST_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST_MANAGER, \
+ ExampleContactListManagerClass))
+
+gboolean example_contact_list_manager_add_to_group (
+ ExampleContactListManager *self, GObject *channel,
+ TpHandle group, TpHandle member, const gchar *message, GError **error);
+
+gboolean example_contact_list_manager_remove_from_group (
+ ExampleContactListManager *self, GObject *channel,
+ TpHandle group, TpHandle member, const gchar *message, GError **error);
+
+/* elements 1, 2... of this enum must be kept in sync with elements 0, 1...
+ * of the array _contact_lists in contact-list-manager.h */
+typedef enum {
+ INVALID_EXAMPLE_CONTACT_LIST,
+ EXAMPLE_CONTACT_LIST_SUBSCRIBE = 1,
+ EXAMPLE_CONTACT_LIST_PUBLISH,
+ EXAMPLE_CONTACT_LIST_STORED,
+ NUM_EXAMPLE_CONTACT_LISTS
+} ExampleContactListHandle;
+
+/* this enum must be kept in sync with the array _statuses in
+ * contact-list-manager.c */
+typedef enum {
+ EXAMPLE_CONTACT_LIST_PRESENCE_OFFLINE = 0,
+ EXAMPLE_CONTACT_LIST_PRESENCE_UNKNOWN,
+ EXAMPLE_CONTACT_LIST_PRESENCE_ERROR,
+ EXAMPLE_CONTACT_LIST_PRESENCE_AWAY,
+ EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE
+} ExampleContactListPresence;
+
+const TpPresenceStatusSpec *example_contact_list_presence_statuses (
+ void);
+
+gboolean example_contact_list_manager_add_to_list (
+ ExampleContactListManager *self, GObject *channel,
+ ExampleContactListHandle list, TpHandle member, const gchar *message,
+ GError **error);
+
+gboolean example_contact_list_manager_remove_from_list (
+ ExampleContactListManager *self, GObject *channel,
+ ExampleContactListHandle list, TpHandle member, const gchar *message,
+ GError **error);
+
+const gchar **example_contact_lists (void);
+
+ExampleContactListPresence example_contact_list_manager_get_presence (
+ ExampleContactListManager *self, TpHandle contact);
+const gchar *example_contact_list_manager_get_alias (
+ ExampleContactListManager *self, TpHandle contact);
+void example_contact_list_manager_set_alias (
+ ExampleContactListManager *self, TpHandle contact, const gchar *alias);
+
+G_END_DECLS
+
+#endif
diff --git a/examples/cm/contactlist/contact-list.c b/examples/cm/contactlist/contact-list.c
new file mode 100644
index 0000000..381d405
--- /dev/null
+++ b/examples/cm/contactlist/contact-list.c
@@ -0,0 +1,632 @@
+/*
+ * An example ContactList channel with handle type LIST
+ *
+ * Copyright © 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "contact-list.h"
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/exportable-channel.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+#include "contact-list-manager.h"
+
+static void channel_iface_init (gpointer iface, gpointer data);
+static void list_channel_iface_init (gpointer iface, gpointer data);
+static void group_channel_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleContactListBase, example_contact_list_base,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_LIST, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL))
+
+G_DEFINE_TYPE_WITH_CODE (ExampleContactList, example_contact_list,
+ EXAMPLE_TYPE_CONTACT_LIST_BASE,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, list_channel_iface_init))
+
+G_DEFINE_TYPE_WITH_CODE (ExampleContactGroup, example_contact_group,
+ EXAMPLE_TYPE_CONTACT_LIST_BASE,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, group_channel_iface_init))
+
+static const gchar *contact_list_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ NULL
+};
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_CONNECTION,
+ PROP_MANAGER,
+ PROP_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ N_PROPS
+};
+
+struct _ExampleContactListBasePrivate
+{
+ TpBaseConnection *conn;
+ ExampleContactListManager *manager;
+ gchar *object_path;
+ TpHandleType handle_type;
+ TpHandle handle;
+
+ /* These are really booleans, but gboolean is signed. Thanks, GLib */
+ unsigned closed:1;
+ unsigned disposed:1;
+};
+
+struct _ExampleContactListPrivate
+{
+ int dummy:1;
+};
+
+struct _ExampleContactGroupPrivate
+{
+ int dummy:1;
+};
+
+static void
+example_contact_list_base_init (ExampleContactListBase *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST_BASE, ExampleContactListBasePrivate);
+}
+
+static void
+example_contact_list_init (ExampleContactList *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_CONTACT_LIST,
+ ExampleContactListPrivate);
+}
+
+static void
+example_contact_group_init (ExampleContactGroup *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_CONTACT_GROUP,
+ ExampleContactGroupPrivate);
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_contact_list_base_parent_class)->constructed;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandle self_handle = self->priv->conn->self_handle;
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles
+ (self->priv->conn, self->priv->handle_type);
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ g_assert (TP_IS_BASE_CONNECTION (self->priv->conn));
+ g_assert (EXAMPLE_IS_CONTACT_LIST_MANAGER (self->priv->manager));
+
+ dbus_g_connection_register_g_object (tp_get_bus (), self->priv->object_path,
+ object);
+
+ tp_handle_ref (handle_repo, self->priv->handle);
+ tp_group_mixin_init (object, G_STRUCT_OFFSET (ExampleContactListBase, group),
+ contact_repo, self_handle);
+ /* Both the subclasses have full support for telepathy-spec 0.17.6. */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
+}
+
+static void
+list_constructed (GObject *object)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_contact_list_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ g_assert (self->parent.priv->handle_type == TP_HANDLE_TYPE_LIST);
+
+ switch (self->parent.priv->handle)
+ {
+ case EXAMPLE_CONTACT_LIST_PUBLISH:
+ /* We can stop publishing presence to people, but we can't
+ * start sending people our presence unless they ask for it.
+ *
+ * (We can accept people's requests to see our presence - but that's
+ * always allowed, so there's no flag.)
+ */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0);
+ break;
+ case EXAMPLE_CONTACT_LIST_STORED:
+ /* We can add people to our roster (not that that's very useful without
+ * also adding them to subscribe), and we can remove them altogether
+ * (which implicitly removes them from subscribe, publish, and all
+ * user-defined groups).
+ */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0);
+ break;
+ case EXAMPLE_CONTACT_LIST_SUBSCRIBE:
+ /* We can ask people to show us their presence, attaching a message.
+ * We can also cancel (rescind) requests that they haven't replied to,
+ * and stop receiving their presence after they allow it.
+ */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD |
+ TP_CHANNEL_GROUP_FLAG_CAN_REMOVE |
+ TP_CHANNEL_GROUP_FLAG_CAN_RESCIND,
+ 0);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+group_constructed (GObject *object)
+{
+ ExampleContactGroup *self = EXAMPLE_CONTACT_GROUP (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_contact_group_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ g_assert (self->parent.priv->handle_type == TP_HANDLE_TYPE_GROUP);
+
+ /* We can add people to user-defined groups, and also remove them. */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0);
+}
+
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, self->priv->handle_type);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (
+ self->priv->conn, self->priv->handle_type);
+
+ g_value_set_string (value,
+ tp_handle_inspect (handle_repo, self->priv->handle));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, FALSE);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, 0);
+ break;
+ case PROP_INITIATOR_ID:
+ g_value_set_static_string (value, "");
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+ case PROP_MANAGER:
+ g_value_set_object (value, self->priv->manager);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, contact_list_interfaces);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, self->priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ NULL));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (self->priv->object_path);
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ /* we don't ref it here because we don't necessarily have access to the
+ * repository (or even type) yet - instead we ref it in the constructor.
+ */
+ self->priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ self->priv->handle_type = g_value_get_uint (value);
+ break;
+ case PROP_CHANNEL_TYPE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+ case PROP_CONNECTION:
+ self->priv->conn = g_value_get_object (value);
+ break;
+ case PROP_MANAGER:
+ self->priv->manager = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ if (self->priv->disposed)
+ return;
+
+ self->priv->disposed = TRUE;
+
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ ((GObjectClass *) example_contact_list_base_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles
+ (self->priv->conn, self->priv->handle_type);
+
+ tp_handle_unref (handle_repo, self->priv->handle);
+ g_free (self->priv->object_path);
+ tp_group_mixin_finalize (object);
+
+ ((GObjectClass *) example_contact_list_base_parent_class)->finalize (object);
+}
+
+static gboolean
+group_add_member (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ return example_contact_list_manager_add_to_group (self->priv->manager,
+ object, self->priv->handle, handle, message, error);
+}
+
+static gboolean
+group_remove_member (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ return example_contact_list_manager_remove_from_group (self->priv->manager,
+ object, self->priv->handle, handle, message, error);
+}
+
+static gboolean
+list_add_member (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ return example_contact_list_manager_add_to_list (self->priv->manager,
+ object, self->priv->handle, handle, message, error);
+}
+
+static gboolean
+list_remove_member (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ return example_contact_list_manager_remove_from_list (self->priv->manager,
+ object, self->priv->handle, handle, message, error);
+}
+
+static void
+example_contact_list_base_class_init (ExampleContactListBaseClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (ExampleContactListBasePrivate));
+
+ object_class->constructed = constructed;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Connection object that owns this channel",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_object ("manager", "ExampleContactListManager",
+ "ExampleContactListManager object that owns this channel",
+ EXAMPLE_TYPE_CONTACT_LIST_MANAGER,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MANAGER, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Chatroom's ID",
+ "The string obtained by inspecting the MUC's handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListBaseClass, dbus_properties_class));
+
+ /* Group mixin is initialized separately for each subclass - they have
+ * different callbacks */
+}
+
+static void
+example_contact_list_class_init (ExampleContactListClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (ExampleContactListPrivate));
+
+ object_class->constructed = list_constructed;
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListBaseClass, group_class),
+ list_add_member,
+ list_remove_member);
+ tp_group_mixin_init_dbus_properties (object_class);
+}
+
+static void
+example_contact_group_class_init (ExampleContactGroupClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (ExampleContactGroupPrivate));
+
+ object_class->constructed = group_constructed;
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListBaseClass, group_class),
+ group_add_member,
+ group_remove_member);
+ tp_group_mixin_init_dbus_properties (object_class);
+}
+
+static void
+list_channel_close (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ GError e = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "ContactList channels with handle type LIST may not be closed" };
+
+ dbus_g_method_return_error (context, &e);
+}
+
+static void
+group_channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactGroup *self = EXAMPLE_CONTACT_GROUP (iface);
+ ExampleContactListBase *base = EXAMPLE_CONTACT_LIST_BASE (iface);
+
+ if (tp_handle_set_size (base->group.members) > 0)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Non-empty groups may not be deleted (closed)" };
+
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ if (!base->priv->closed)
+ {
+ /* If this was a real connection manager we'd delete the group here,
+ * if such a concept existed in the protocol (in XMPP, it doesn't).
+ *
+ * Afterwards, close the channel:
+ */
+ base->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_get_channel_type (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_CONTACT_LIST);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (iface);
+
+ tp_svc_channel_return_from_get_handle (context, self->priv->handle_type,
+ self->priv->handle);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ contact_list_interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ /* close is implemented in subclasses, so don't IMPLEMENT (close); */
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+list_channel_iface_init (gpointer iface,
+ gpointer data G_GNUC_UNUSED)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, list_channel_##x)
+ IMPLEMENT (close);
+#undef IMPLEMENT
+}
+
+static void
+group_channel_iface_init (gpointer iface,
+ gpointer data G_GNUC_UNUSED)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, group_channel_##x)
+ IMPLEMENT (close);
+#undef IMPLEMENT
+}
diff --git a/examples/cm/contactlist/contact-list.h b/examples/cm/contactlist/contact-list.h
new file mode 100644
index 0000000..e7d2fa3
--- /dev/null
+++ b/examples/cm/contactlist/contact-list.h
@@ -0,0 +1,118 @@
+/*
+ * Example ContactList channels with handle type LIST or GROUP
+ *
+ * Copyright © 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef EXAMPLE_CONTACT_LIST_H
+#define EXAMPLE_CONTACT_LIST_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/group-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListBase ExampleContactListBase;
+typedef struct _ExampleContactListBaseClass ExampleContactListBaseClass;
+typedef struct _ExampleContactListBasePrivate ExampleContactListBasePrivate;
+
+typedef struct _ExampleContactList ExampleContactList;
+typedef struct _ExampleContactListClass ExampleContactListClass;
+typedef struct _ExampleContactListPrivate ExampleContactListPrivate;
+
+typedef struct _ExampleContactGroup ExampleContactGroup;
+typedef struct _ExampleContactGroupClass ExampleContactGroupClass;
+typedef struct _ExampleContactGroupPrivate ExampleContactGroupPrivate;
+
+GType example_contact_list_base_get_type (void);
+GType example_contact_list_get_type (void);
+GType example_contact_group_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_BASE \
+ (example_contact_list_base_get_type ())
+#define EXAMPLE_CONTACT_LIST_BASE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CONTACT_LIST_BASE, \
+ ExampleContactListBase))
+#define EXAMPLE_CONTACT_LIST_BASE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CONTACT_LIST_BASE, \
+ ExampleContactListBaseClass))
+#define EXAMPLE_IS_CONTACT_LIST_BASE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CONTACT_LIST_BASE))
+#define EXAMPLE_IS_CONTACT_LIST_BASE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CONTACT_LIST_BASE))
+#define EXAMPLE_CONTACT_LIST_BASE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST_BASE, \
+ ExampleContactListBaseClass))
+
+#define EXAMPLE_TYPE_CONTACT_LIST \
+ (example_contact_list_get_type ())
+#define EXAMPLE_CONTACT_LIST(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CONTACT_LIST, \
+ ExampleContactList))
+#define EXAMPLE_CONTACT_LIST_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CONTACT_LIST, \
+ ExampleContactListClass))
+#define EXAMPLE_IS_CONTACT_LIST(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CONTACT_LIST))
+#define EXAMPLE_IS_CONTACT_LIST_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CONTACT_LIST))
+#define EXAMPLE_CONTACT_LIST_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST, \
+ ExampleContactListClass))
+
+#define EXAMPLE_TYPE_CONTACT_GROUP \
+ (example_contact_group_get_type ())
+#define EXAMPLE_CONTACT_GROUP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CONTACT_GROUP, \
+ ExampleContactGroup))
+#define EXAMPLE_CONTACT_GROUP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CONTACT_GROUP, \
+ ExampleContactGroupClass))
+#define EXAMPLE_IS_CONTACT_GROUP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CONTACT_GROUP))
+#define EXAMPLE_IS_CONTACT_GROUP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CONTACT_GROUP))
+#define EXAMPLE_CONTACT_GROUP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_GROUP, \
+ ExampleContactGroupClass))
+
+struct _ExampleContactListBaseClass {
+ GObjectClass parent_class;
+ TpGroupMixinClass group_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+struct _ExampleContactListClass {
+ ExampleContactListBaseClass parent_class;
+};
+
+struct _ExampleContactGroupClass {
+ ExampleContactListBaseClass parent_class;
+};
+
+struct _ExampleContactListBase {
+ GObject parent;
+ TpGroupMixin group;
+ ExampleContactListBasePrivate *priv;
+};
+
+struct _ExampleContactList {
+ ExampleContactListBase parent;
+ ExampleContactListPrivate *priv;
+};
+
+struct _ExampleContactGroup {
+ ExampleContactListBase parent;
+ ExampleContactGroupPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif
diff --git a/examples/cm/contactlist/main.c b/examples/cm/contactlist/main.c
new file mode 100644
index 0000000..c49622e
--- /dev/null
+++ b/examples/cm/contactlist/main.c
@@ -0,0 +1,44 @@
+/*
+ * main.c - entry point for an example Telepathy connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "config.h"
+
+#include <telepathy-glib/debug.h>
+#include <telepathy-glib/run.h>
+
+#include "connection-manager.h"
+
+static TpBaseConnectionManager *
+construct_cm (void)
+{
+ return (TpBaseConnectionManager *) g_object_new (
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER,
+ NULL);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+#ifdef ENABLE_DEBUG
+ tp_debug_divert_messages (g_getenv ("EXAMPLE_CM_LOGFILE"));
+ tp_debug_set_flags (g_getenv ("EXAMPLE_DEBUG"));
+
+ if (g_getenv ("EXAMPLE_TIMING") != NULL)
+ g_log_set_default_handler (tp_debug_timestamped_log_handler, NULL);
+
+ if (g_getenv ("EXAMPLE_PERSIST") != NULL)
+ tp_debug_set_persistent (TRUE);
+#endif
+
+ return tp_run_connection_manager ("telepathy-example-cm-contactlist",
+ VERSION, construct_cm, argc, argv);
+}
diff --git a/examples/cm/contactlist/manager-file.py b/examples/cm/contactlist/manager-file.py
new file mode 100644
index 0000000..2e668a8
--- /dev/null
+++ b/examples/cm/contactlist/manager-file.py
@@ -0,0 +1,19 @@
+# Input for tools/manager-file.py
+
+MANAGER = 'example_contact_list'
+PARAMS = {
+ 'example' : {
+ 'account': {
+ 'dtype': 's',
+ 'flags': 'required register',
+ 'filter': 'account_param_filter',
+ # 'filter_data': 'NULL',
+ # 'default': ...,
+ # 'struct_field': '...',
+ # 'setter_data': 'NULL',
+ },
+ },
+ }
+STRUCTS = {
+ 'example': 'ExampleParams'
+ }
--
1.5.6.5
More information about the telepathy-commits
mailing list