[Telepathy-commits] [telepathy-qt4/master] Add 'callable' example CM from telepathy-glib 0.7.27
Simon McVittie
simon.mcvittie at collabora.co.uk
Tue Mar 17 15:27:13 PDT 2009
---
configure.ac | 2 +-
tests/lib/Makefile.am | 3 +-
tests/lib/callable/Makefile.am | 39 ++
tests/lib/callable/conn.c | 402 ++++++++++++
tests/lib/callable/conn.h | 78 +++
tests/lib/callable/connection-manager.c | 130 ++++
tests/lib/callable/connection-manager.h | 73 +++
tests/lib/callable/media-channel.c | 1024 +++++++++++++++++++++++++++++++
tests/lib/callable/media-channel.h | 74 +++
tests/lib/callable/media-manager.c | 438 +++++++++++++
tests/lib/callable/media-manager.h | 71 +++
tests/lib/callable/media-stream.c | 506 +++++++++++++++
tests/lib/callable/media-stream.h | 83 +++
13 files changed, 2921 insertions(+), 2 deletions(-)
create mode 100644 tests/lib/callable/Makefile.am
create mode 100644 tests/lib/callable/conn.c
create mode 100644 tests/lib/callable/conn.h
create mode 100644 tests/lib/callable/connection-manager.c
create mode 100644 tests/lib/callable/connection-manager.h
create mode 100644 tests/lib/callable/media-channel.c
create mode 100644 tests/lib/callable/media-channel.h
create mode 100644 tests/lib/callable/media-manager.c
create mode 100644 tests/lib/callable/media-manager.h
create mode 100644 tests/lib/callable/media-stream.c
create mode 100644 tests/lib/callable/media-stream.h
diff --git a/configure.ac b/configure.ac
index 59247c7..faf6de1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -182,7 +182,7 @@ AC_SUBST(PROTO_CFLAGS)
dnl Check for telepathy-glib, and for Qt <-> GLib main loop integration:
dnl if we have both, we can run more tests
-PKG_CHECK_MODULES(TP_GLIB, [telepathy-glib >= 0.7.26], [have_tp_glib=yes],
+PKG_CHECK_MODULES(TP_GLIB, [telepathy-glib >= 0.7.27], [have_tp_glib=yes],
[have_tp_glib=no])
AC_SUBST(TP_GLIB_CFLAGS)
AC_SUBST(TP_GLIB_LIBS)
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index 1d9687e..e770a0f 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -28,7 +28,7 @@ libtp_qt4_tests_la_LIBADD = $(top_builddir)/TelepathyQt4/libtelepathy-qt4.la
if ENABLE_TP_GLIB_TESTS
-SUBDIRS += contactlist echo
+SUBDIRS += callable contactlist echo
AM_CFLAGS += $(TP_GLIB_CFLAGS)
AM_CXXFLAGS += $(TP_GLIB_CFLAGS)
@@ -44,6 +44,7 @@ libtp_glib_tests_la_SOURCES = \
simple-manager.h
libtp_glib_tests_la_LIBADD = \
$(TP_GLIB_LIBS) \
+ callable/libexample-cm-callable.la \
echo/libexample-cm-echo.la \
contactlist/libexample-cm-contactlist.la
diff --git a/tests/lib/callable/Makefile.am b/tests/lib/callable/Makefile.am
new file mode 100644
index 0000000..2d7d8a9
--- /dev/null
+++ b/tests/lib/callable/Makefile.am
@@ -0,0 +1,39 @@
+# Taken from telepathy-glib. The only change is to remove main.c and cut down
+# the Makefile.am accordingly.
+#
+# PLEASE DO NOT MODIFY THIS CONNECTION MANAGER. Either subclass it,
+# copy-and-modify (moving it to a better namespace), or make changes in the
+# copy in telepathy-glib first.
+
+noinst_LTLIBRARIES = libexample-cm-callable.la
+
+libexample_cm_callable_la_SOURCES = \
+ conn.c \
+ conn.h \
+ connection-manager.c \
+ connection-manager.h \
+ media-channel.c \
+ media-channel.h \
+ media-manager.c \
+ media-manager.h \
+ media-stream.c \
+ media-stream.h
+
+libexample_cm_callable_la_LIBADD = $(TP_GLIB_LIBS)
+
+AM_CFLAGS = \
+ $(ERROR_CFLAGS) \
+ $(TP_GLIB_CFLAGS)
+
+EXTRA_DIST = manager-file.py
+
+_gen/example_callable.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/example_callable.manager _gen/param-spec-struct.h
+CLEANFILES = $(BUILT_SOURCES)
+
+clean-local:
+ rm -rf _gen
diff --git a/tests/lib/callable/conn.c b/tests/lib/callable/conn.c
new file mode 100644
index 0000000..34a3530
--- /dev/null
+++ b/tests/lib/callable/conn.c
@@ -0,0 +1,402 @@
+/*
+ * conn.c - an example connection
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "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 "media-manager.h"
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallableConnection,
+ example_callable_connection,
+ TP_TYPE_BASE_CONNECTION,
+ 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,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+struct _ExampleCallableConnectionPrivate
+{
+ gchar *account;
+ guint simulation_delay;
+ gboolean away;
+ gchar *presence_message;
+};
+
+static void
+example_callable_connection_init (ExampleCallableConnection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALLABLE_CONNECTION,
+ ExampleCallableConnectionPrivate);
+ self->priv->away = FALSE;
+ self->priv->presence_message = g_strdup ("");
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_value_set_string (value, self->priv->account);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ 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)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_free (self->priv->account);
+ self->priv->account = g_value_dup_string (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (object);
+
+ tp_contacts_mixin_finalize (object);
+ g_free (self->priv->account);
+ g_free (self->priv->presence_message);
+
+ G_OBJECT_CLASS (example_callable_connection_parent_class)->finalize (object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (conn);
+
+ return g_strdup_printf ("%s@%p", self->priv->account, self);
+}
+
+gchar *
+example_callable_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 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_callable_normalize_contact, NULL);
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (conn);
+ GPtrArray *ret = g_ptr_array_sized_new (1);
+
+ g_ptr_array_add (ret,
+ g_object_new (EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER,
+ "connection", conn,
+ "simulation-delay", self->priv->simulation_delay,
+ NULL));
+
+ return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_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
+constructed (GObject *object)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ void (*chain_up) (GObject *) =
+ G_OBJECT_CLASS (example_callable_connection_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ tp_contacts_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleCallableConnection, contacts_mixin));
+ tp_base_connection_register_with_contacts_mixin (base);
+
+ tp_presence_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleCallableConnection, 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)
+{
+ ExampleCallableConnection *self =
+ EXAMPLE_CALLABLE_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);
+ ExampleCallablePresence presence;
+ GHashTable *parameters;
+
+ parameters = g_hash_table_new_full (g_str_hash,
+ g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ /* we know our own status from the connection; for this example CM,
+ * everyone else's status is assumed to be "available" */
+ if (contact == base->self_handle)
+ {
+ presence = (self->priv->away ? EXAMPLE_CALLABLE_PRESENCE_AWAY
+ : EXAMPLE_CALLABLE_PRESENCE_AVAILABLE);
+
+ if (self->priv->presence_message[0] != '\0')
+ g_hash_table_insert (parameters, "message",
+ tp_g_value_slice_new_string (self->priv->presence_message));
+ }
+ else
+ {
+ presence = EXAMPLE_CALLABLE_PRESENCE_AVAILABLE;
+ }
+
+ 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)
+{
+ ExampleCallableConnection *self =
+ EXAMPLE_CALLABLE_CONNECTION (object);
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ GHashTable *presences;
+ const gchar *message = "";
+
+ if (status->optional_arguments != NULL)
+ {
+ GValue *v = g_hash_table_lookup (status->optional_arguments, "message");
+
+ if (v != NULL && G_VALUE_HOLDS_STRING (v))
+ {
+ message = g_value_get_string (v);
+
+ if (message == NULL)
+ message = "";
+ }
+ }
+
+ if (status->index == EXAMPLE_CALLABLE_PRESENCE_AWAY)
+ {
+ if (self->priv->away && !tp_strdiff (message,
+ self->priv->presence_message))
+ return TRUE;
+
+ self->priv->away = TRUE;
+ }
+ else
+ {
+ if (!self->priv->away && !tp_strdiff (message,
+ self->priv->presence_message))
+ return TRUE;
+
+ self->priv->away = FALSE;
+ }
+
+ g_free (self->priv->presence_message);
+ self->priv->presence_message = g_strdup (message);
+
+ 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 const TpPresenceStatusOptionalArgumentSpec can_have_message[] = {
+ { "message", "s", NULL, NULL },
+ { NULL }
+};
+
+/* Must be kept in sync with ExampleCallablePresence enum in header */
+static const TpPresenceStatusSpec presence_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, can_have_message },
+ { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE,
+ can_have_message },
+ { NULL }
+};
+
+static void
+example_callable_connection_class_init (
+ ExampleCallableConnectionClass *klass)
+{
+ static const gchar *interfaces_always_present[] = {
+ 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 (ExampleCallableConnectionPrivate));
+
+ 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_STRINGS);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ tp_contacts_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallableConnectionClass, contacts_mixin));
+ tp_presence_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallableConnectionClass, presence_mixin),
+ status_available, get_contact_statuses, set_own_status,
+ presence_statuses);
+ tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
+}
diff --git a/tests/lib/callable/conn.h b/tests/lib/callable/conn.h
new file mode 100644
index 0000000..f3d4690
--- /dev/null
+++ b/tests/lib/callable/conn.h
@@ -0,0 +1,78 @@
+/*
+ * 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_CALLABLE_CONN_H__
+#define __EXAMPLE_CALLABLE_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 _ExampleCallableConnection ExampleCallableConnection;
+typedef struct _ExampleCallableConnectionPrivate
+ ExampleCallableConnectionPrivate;
+
+typedef struct _ExampleCallableConnectionClass ExampleCallableConnectionClass;
+typedef struct _ExampleCallableConnectionClassPrivate
+ ExampleCallableConnectionClassPrivate;
+
+struct _ExampleCallableConnectionClass {
+ TpBaseConnectionClass parent_class;
+ TpPresenceMixinClass presence_mixin;
+ TpContactsMixinClass contacts_mixin;
+
+ ExampleCallableConnectionClassPrivate *priv;
+};
+
+struct _ExampleCallableConnection {
+ TpBaseConnection parent;
+ TpPresenceMixin presence_mixin;
+ TpContactsMixin contacts_mixin;
+
+ ExampleCallableConnectionPrivate *priv;
+};
+
+GType example_callable_connection_get_type (void);
+
+#define EXAMPLE_TYPE_CALLABLE_CONNECTION \
+ (example_callable_connection_get_type ())
+#define EXAMPLE_CALLABLE_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION, \
+ ExampleCallableConnection))
+#define EXAMPLE_CALLABLE_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALLABLE_CONNECTION, \
+ ExampleCallableConnectionClass))
+#define EXAMPLE_IS_CALLABLE_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION))
+#define EXAMPLE_IS_CALLABLE_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALLABLE_CONNECTION))
+#define EXAMPLE_CALLABLE_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION, \
+ ExampleCallableConnectionClass))
+
+gchar *example_callable_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id, gpointer context, GError **error);
+
+/* Must be kept in sync with the array presence_statuses in conn.c */
+typedef enum {
+ EXAMPLE_CALLABLE_PRESENCE_OFFLINE = 0,
+ EXAMPLE_CALLABLE_PRESENCE_UNKNOWN,
+ EXAMPLE_CALLABLE_PRESENCE_ERROR,
+ EXAMPLE_CALLABLE_PRESENCE_AWAY,
+ EXAMPLE_CALLABLE_PRESENCE_AVAILABLE
+} ExampleCallablePresence;
+
+G_END_DECLS
+
+#endif
diff --git a/tests/lib/callable/connection-manager.c b/tests/lib/callable/connection-manager.c
new file mode 100644
index 0000000..fa8e0f0
--- /dev/null
+++ b/tests/lib/callable/connection-manager.c
@@ -0,0 +1,130 @@
+/*
+ * manager.c - an example connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "connection-manager.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+
+#include "conn.h"
+
+G_DEFINE_TYPE (ExampleCallableConnectionManager,
+ example_callable_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+struct _ExampleCallableConnectionManagerPrivate
+{
+ int dummy;
+};
+
+static void
+example_callable_connection_manager_init (
+ ExampleCallableConnectionManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER,
+ ExampleCallableConnectionManagerPrivate);
+}
+
+typedef struct {
+ gchar *account;
+ guint simulation_delay;
+} 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_callable_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)
+{
+ ExampleParams *params = g_slice_new0 (ExampleParams);
+
+ params->simulation_delay = 1000;
+ return params;
+}
+
+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_callable_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;
+ ExampleCallableConnection *conn;
+
+ conn = EXAMPLE_CALLABLE_CONNECTION
+ (g_object_new (EXAMPLE_TYPE_CALLABLE_CONNECTION,
+ "account", params->account,
+ "simulation-delay", params->simulation_delay,
+ "protocol", proto,
+ NULL));
+
+ return (TpBaseConnection *) conn;
+}
+
+static void
+example_callable_connection_manager_class_init (
+ ExampleCallableConnectionManagerClass *klass)
+{
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallableConnectionManagerPrivate));
+
+ base_class->new_connection = new_connection;
+ base_class->cm_dbus_name = "example_callable";
+ base_class->protocol_params = example_protocols;
+}
diff --git a/tests/lib/callable/connection-manager.h b/tests/lib/callable/connection-manager.h
new file mode 100644
index 0000000..5d854cb
--- /dev/null
+++ b/tests/lib/callable/connection-manager.h
@@ -0,0 +1,73 @@
+/*
+ * manager.h - header for an example connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __EXAMPLE_CALLABLE_CONNECTION_MANAGER_H__
+#define __EXAMPLE_CALLABLE_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallableConnectionManager
+ ExampleCallableConnectionManager;
+typedef struct _ExampleCallableConnectionManagerPrivate
+ ExampleCallableConnectionManagerPrivate;
+
+typedef struct _ExampleCallableConnectionManagerClass
+ ExampleCallableConnectionManagerClass;
+typedef struct _ExampleCallableConnectionManagerClassPrivate
+ ExampleCallableConnectionManagerClassPrivate;
+
+struct _ExampleCallableConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+
+ ExampleCallableConnectionManagerClassPrivate *priv;
+};
+
+struct _ExampleCallableConnectionManager {
+ TpBaseConnectionManager parent;
+
+ ExampleCallableConnectionManagerPrivate *priv;
+};
+
+GType example_callable_connection_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER \
+ (example_callable_connection_manager_get_type ())
+#define EXAMPLE_CALLABLE_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER, \
+ ExampleCallableConnectionManager))
+#define EXAMPLE_CALLABLE_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER, \
+ ExampleCallableConnectionManagerClass))
+#define EXAMPLE_IS_CALLABLE_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER))
+#define EXAMPLE_IS_CALLABLE_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER))
+#define EXAMPLE_CALLABLE_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER, \
+ ExampleCallableConnectionManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/tests/lib/callable/media-channel.c b/tests/lib/callable/media-channel.c
new file mode 100644
index 0000000..5f305cf
--- /dev/null
+++ b/tests/lib/callable/media-channel.c
@@ -0,0 +1,1024 @@
+/*
+ * media-channel.c - an example 1-1 streamed media call.
+ *
+ * For simplicity, this channel emulates a device with its own
+ * audio/video user interface, like a video-equipped form of the phones
+ * manipulated by telepathy-snom or gnome-phone-manager.
+ *
+ * As a result, this channel does not have the MediaSignalling interface, and
+ * clients should not attempt to do their own streaming using
+ * telepathy-farsight, telepathy-stream-engine or maemo-stream-engine.
+ *
+ * In practice, nearly all connection managers also have the MediaSignalling
+ * interface on their streamed media channels. Usage for those CMs is the
+ * same, except that whichever client is the primary handler for the channel
+ * should also hand the channel over to telepathy-farsight or
+ * telepathy-stream-engine to implement the actual streaming.
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "media-channel.h"
+
+#include "media-stream.h"
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+static void media_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallableMediaChannel,
+ example_callable_media_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAMED_MEDIA,
+ media_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, 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_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+enum
+{
+ SIGNAL_CALL_TERMINATED,
+ N_SIGNALS
+};
+
+typedef enum {
+ PROGRESS_NONE,
+ PROGRESS_CALLING,
+ PROGRESS_ACTIVE,
+ PROGRESS_ENDED
+} ExampleCallableCallProgress;
+
+static guint signals[N_SIGNALS] = { 0 };
+
+struct _ExampleCallableMediaChannelPrivate
+{
+ TpBaseConnection *conn;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandle initiator;
+ ExampleCallableCallProgress progress;
+
+ guint simulation_delay;
+
+ guint next_stream_id;
+
+ GHashTable *streams;
+
+ gboolean locally_requested;
+ gboolean disposed;
+};
+
+static const char * example_callable_media_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ NULL
+};
+
+static void
+example_callable_media_channel_init (ExampleCallableMediaChannel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL,
+ ExampleCallableMediaChannelPrivate);
+
+ self->priv->next_stream_id = 1;
+ self->priv->streams = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+}
+
+static void
+constructed (GObject *object)
+{
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_callable_media_channel_parent_class)->constructed;
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ DBusGConnection *bus;
+ TpIntSet *members;
+ TpIntSet *local_pending;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ tp_handle_ref (contact_repo, self->priv->handle);
+ tp_handle_ref (contact_repo, self->priv->initiator);
+
+ bus = tp_get_bus ();
+ dbus_g_connection_register_g_object (bus, self->priv->object_path, object);
+
+ tp_group_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleCallableMediaChannel, group),
+ contact_repo, self->priv->conn->self_handle);
+
+ /* Initially, the channel contains the initiator as a member; they are also
+ * the actor for the change that adds any initial members. */
+
+ members = tp_intset_new_containing (self->priv->initiator);
+
+ if (self->priv->locally_requested)
+ {
+ /* Nobody is locally pending. The remote peer will turn up in
+ * remote-pending state when we actually contact them, which is done
+ * in RequestStreams */
+ self->priv->progress = PROGRESS_NONE;
+ local_pending = NULL;
+ }
+ else
+ {
+ /* This is an incoming call, so the self-handle is locally
+ * pending, to indicate that we need to answer. */
+ self->priv->progress = PROGRESS_CALLING;
+ local_pending = tp_intset_new_containing (self->priv->conn->self_handle);
+ }
+
+ tp_group_mixin_change_members (object, "",
+ members /* added */,
+ NULL /* nobody removed */,
+ local_pending, /* added to local-pending */
+ NULL /* nobody added to remote-pending */,
+ self->priv->initiator /* actor */, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (members);
+
+ if (local_pending != NULL)
+ tp_intset_destroy (local_pending);
+
+ /* We don't need to allow adding or removing members to this Group in ways
+ * that need flags set, so the only flag we set is to say we support the
+ * Properties interface to the Group.
+ *
+ * It doesn't make sense to add anyone to the Group, since we already know
+ * who we're going to call (or were called by). The only call to AddMembers
+ * we need to support is to move ourselves from local-pending to member in
+ * the incoming call case, and that's always allowed anyway.
+ *
+ * (Connection managers that support the various backwards-compatible
+ * ways to make an outgoing StreamedMedia channel have to support adding the
+ * peer to remote-pending, but that has no actual effect other than to
+ * obscure what's going on; in this one, there's no need to support that
+ * usage.)
+ *
+ * Similarly, it doesn't make sense to remove anyone from this Group apart
+ * from ourselves (to hang up), and removing the SelfHandle is always
+ * allowed anyway.
+ */
+ tp_group_mixin_change_flags (object, TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
+
+ if (!self->priv->locally_requested)
+ {
+ /* FIXME: act on any streams that the remote peer has already enabled */
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (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_STREAMED_MEDIA);
+ break;
+
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ break;
+
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ tp_handle_inspect (contact_repo, self->priv->handle));
+ }
+ break;
+
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, self->priv->locally_requested);
+ break;
+
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, self->priv->initiator);
+ break;
+
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ tp_handle_inspect (contact_repo, self->priv->initiator));
+ }
+ break;
+
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, example_callable_media_channel_interfaces);
+ break;
+
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, (self->priv->progress == PROGRESS_ENDED));
+ 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;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ 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)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_assert (self->priv->object_path == NULL);
+ 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
+ * contact repo yet - instead we ref it in the constructor.
+ */
+ self->priv->handle = g_value_get_uint (value);
+ break;
+
+ case PROP_INITIATOR_HANDLE:
+ /* likewise */
+ self->priv->initiator = g_value_get_uint (value);
+ break;
+
+ case PROP_REQUESTED:
+ self->priv->locally_requested = g_value_get_boolean (value);
+ break;
+
+ case PROP_HANDLE_TYPE:
+ case PROP_CHANNEL_TYPE:
+ /* these properties are 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_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+example_callable_media_channel_close (ExampleCallableMediaChannel *self,
+ TpHandle actor,
+ TpChannelGroupChangeReason reason)
+{
+ if (self->priv->progress != PROGRESS_ENDED)
+ {
+ TpIntSet *everyone;
+ const gchar *send_reason;
+
+ self->priv->progress = PROGRESS_ENDED;
+
+ /* In a real protocol these would be some sort of real protocol construct,
+ * like an XMPP error stanza or a SIP error code */
+ switch (reason)
+ {
+ case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY:
+ send_reason = "<user-is-busy/>";
+ break;
+
+ case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER:
+ send_reason = "<no-answer/>";
+ break;
+
+ default:
+ send_reason = "<call-terminated/>";
+ }
+
+ everyone = tp_intset_new_containing (self->priv->handle);
+ tp_intset_add (everyone, self->group.self_handle);
+ tp_group_mixin_change_members ((GObject *) self, "",
+ NULL /* nobody added */,
+ everyone /* removed */,
+ NULL /* nobody locally pending */,
+ NULL /* nobody remotely pending */,
+ actor,
+ reason);
+ tp_intset_destroy (everyone);
+
+ g_message ("SIGNALLING: send: Terminating call: %s", send_reason);
+ g_signal_emit (self, signals[SIGNAL_CALL_TERMINATED], 0);
+ tp_svc_channel_emit_closed (self);
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+
+ if (self->priv->disposed)
+ return;
+
+ self->priv->disposed = TRUE;
+
+ example_callable_media_channel_close (self, 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ ((GObjectClass *) example_callable_media_channel_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_unref (contact_handles, self->priv->handle);
+ tp_handle_unref (contact_handles, self->priv->initiator);
+
+ g_free (self->priv->object_path);
+
+ tp_group_mixin_finalize (object);
+
+ ((GObjectClass *) example_callable_media_channel_parent_class)->finalize (object);
+}
+
+static gboolean
+add_member (GObject *object,
+ TpHandle member,
+ const gchar *message,
+ GError **error)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ /* In connection managers that supported the RequestChannel method for
+ * streamed media channels, it would be necessary to support adding the
+ * called contact to the members of an outgoing call. However, in this
+ * legacy-free example, we don't support that usage, so the only use for
+ * AddMembers is to accept an incoming call.
+ */
+
+ if (member == self->group.self_handle &&
+ tp_handle_set_is_member (self->group.local_pending, member))
+ {
+ /* We're in local-pending, move to members to accept. */
+ TpIntSet *set = tp_intset_new_containing (member);
+
+ g_message ("SIGNALLING: send: Accepting incoming call from %s",
+ tp_handle_inspect (contact_repo, self->priv->handle));
+
+ tp_group_mixin_change_members (object, "",
+ set /* added */,
+ NULL /* nobody removed */,
+ NULL /* nobody added to local pending */,
+ NULL /* nobody added to remote pending */,
+ member /* actor */, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ return TRUE;
+ }
+
+ /* Otherwise it's a meaningless request, so reject it. */
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Cannot add handle %u to channel", member);
+ return FALSE;
+}
+
+static gboolean
+remove_member_with_reason (GObject *object,
+ TpHandle member,
+ const gchar *message,
+ guint reason,
+ GError **error)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+
+ /* The TpGroupMixin won't call this unless removing the member is allowed
+ * by the group flags, which in this case means it must be our own handle
+ * (because the other user never appears in local-pending).
+ */
+
+ g_assert (member == self->group.self_handle);
+
+ example_callable_media_channel_close (self, self->group.self_handle, reason);
+ return TRUE;
+}
+
+static void
+example_callable_media_channel_class_init (ExampleCallableMediaChannelClass *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 (ExampleCallableMediaChannelPrivate));
+
+ 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_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", "Peer's ID",
+ "The string obtained by inspecting the target 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_CONSTRUCT_ONLY | G_PARAM_READWRITE | 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_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ signals[SIGNAL_CALL_TERMINATED] = g_signal_new ("call-terminated",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallableMediaChannelClass,
+ dbus_properties_class));
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallableMediaChannelClass, group_class),
+ add_member,
+ NULL);
+ tp_group_mixin_class_allow_self_removal (object_class);
+ tp_group_mixin_class_set_remove_with_reason_func (object_class,
+ remove_member_with_reason);
+ tp_group_mixin_init_dbus_properties (object_class);
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+
+ example_callable_media_channel_close (self, self->group.self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ 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_STREAMED_MEDIA);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+ self->priv->handle);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ example_callable_media_channel_interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+media_list_streams (TpSvcChannelTypeStreamedMedia *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ GPtrArray *array = g_ptr_array_sized_new (g_hash_table_size (
+ self->priv->streams));
+ GHashTableIter iter;
+ gpointer v;
+
+ g_hash_table_iter_init (&iter, self->priv->streams);
+
+ while (g_hash_table_iter_next (&iter, NULL, &v))
+ {
+ ExampleCallableMediaStream *stream = v;
+ GValueArray *va;
+
+ g_object_get (stream,
+ "stream-info", &va,
+ NULL);
+
+ g_ptr_array_add (array, va);
+ }
+
+ tp_svc_channel_type_streamed_media_return_from_list_streams (context,
+ array);
+ g_ptr_array_foreach (array, (GFunc) g_value_array_free, NULL);
+ g_ptr_array_free (array, TRUE);
+}
+
+static void
+media_remove_streams (TpSvcChannelTypeStreamedMedia *iface,
+ const GArray *stream_ids,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ guint i;
+
+ for (i = 0; i < stream_ids->len; i++)
+ {
+ guint id = g_array_index (stream_ids, guint, i);
+
+ if (g_hash_table_lookup (self->priv->streams,
+ GUINT_TO_POINTER (id)) == NULL)
+ {
+ GError *error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "No stream with ID %u in this channel", id);
+
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+ }
+
+ for (i = 0; i < stream_ids->len; i++)
+ {
+ guint id = g_array_index (stream_ids, guint, i);
+
+ example_callable_media_stream_close (
+ g_hash_table_lookup (self->priv->streams, GUINT_TO_POINTER (id)));
+ }
+
+ tp_svc_channel_type_streamed_media_return_from_remove_streams (context);
+}
+
+static void
+media_request_stream_direction (TpSvcChannelTypeStreamedMedia *iface,
+ guint stream_id,
+ guint stream_direction,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ ExampleCallableMediaStream *stream = g_hash_table_lookup (
+ self->priv->streams, GUINT_TO_POINTER (stream_id));
+ GError *error = NULL;
+
+ if (stream == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "No stream with ID %u in this channel", stream_id);
+ goto error;
+ }
+
+ if (stream_direction > TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Stream direction %u is not valid", stream_direction);
+ goto error;
+ }
+
+ /* In some protocols, streams cannot be neither sending nor receiving, so
+ * if a stream is set to TP_MEDIA_STREAM_DIRECTION_NONE, this is equivalent
+ * to removing it with RemoveStreams. (This is true in XMPP, for instance.)
+ *
+ * If this was the case, there would be code like this here:
+ *
+ * if (stream_direction == TP_MEDIA_STREAM_DIRECTION_NONE)
+ * {
+ * example_callable_media_stream_close (stream);
+ * tp_svc_channel_type_streamed_media_return_from_request_stream_direction (
+ * context);
+ * return;
+ * }
+ *
+ * However, for this example we'll emulate a protocol where streams can be
+ * directionless.
+ */
+
+ if (!example_callable_media_stream_change_direction (stream,
+ stream_direction, &error))
+ goto error;
+
+ tp_svc_channel_type_streamed_media_return_from_request_stream_direction (
+ context);
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+stream_removed_cb (ExampleCallableMediaStream *stream,
+ ExampleCallableMediaChannel *self)
+{
+ guint id;
+
+ g_object_get (stream,
+ "id", &id,
+ NULL);
+
+ g_signal_handlers_disconnect_matched (stream, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+ g_hash_table_remove (self->priv->streams, GUINT_TO_POINTER (id));
+ tp_svc_channel_type_streamed_media_emit_stream_removed (self, id);
+
+ if (g_hash_table_size (self->priv->streams) == 0)
+ {
+ /* no streams left, so the call terminates */
+ example_callable_media_channel_close (self, 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ }
+}
+
+static void
+stream_direction_changed_cb (ExampleCallableMediaStream *stream,
+ ExampleCallableMediaChannel *self)
+{
+ guint id, direction, pending;
+
+ g_object_get (stream,
+ "id", &id,
+ "direction", &direction,
+ "pending-send", &pending,
+ NULL);
+
+ tp_svc_channel_type_streamed_media_emit_stream_direction_changed (self, id,
+ direction, pending);
+}
+
+static void
+stream_state_changed_cb (ExampleCallableMediaStream *stream,
+ GParamSpec *spec G_GNUC_UNUSED,
+ ExampleCallableMediaChannel *self)
+{
+ guint id, state;
+
+ g_object_get (stream,
+ "id", &id,
+ "state", &state,
+ NULL);
+
+ tp_svc_channel_type_streamed_media_emit_stream_state_changed (self, id,
+ state);
+}
+
+static gboolean
+simulate_contact_answered_cb (gpointer p)
+{
+ ExampleCallableMediaChannel *self = p;
+ TpIntSet *peer_set;
+ GHashTableIter iter;
+ gpointer v;
+
+ /* if the call has been cancelled while we were waiting for the
+ * contact to answer, do nothing */
+ if (self->priv->progress == PROGRESS_ENDED)
+ return FALSE;
+
+ /* otherwise, we're waiting for a response from the contact, which now
+ * arrives */
+ g_assert (self->priv->progress == PROGRESS_CALLING);
+
+ g_message ("SIGNALLING: receive: contact answered our call");
+
+ self->priv->progress = PROGRESS_ACTIVE;
+
+ peer_set = tp_intset_new_containing (self->priv->handle);
+ tp_group_mixin_change_members ((GObject *) self, "",
+ peer_set /* added */,
+ NULL /* nobody removed */,
+ NULL /* nobody added to local-pending */,
+ NULL /* nobody added to remote-pending */,
+ self->priv->handle /* actor */,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (peer_set);
+
+ g_hash_table_iter_init (&iter, self->priv->streams);
+
+ while (g_hash_table_iter_next (&iter, NULL, &v))
+ {
+ /* remote contact accepts our proposed stream direction... */
+ example_callable_media_stream_simulate_contact_agreed_to_send (v);
+ /* ... and the stream tries to connect */
+ example_callable_media_stream_connect (v);
+ }
+
+ return FALSE;
+}
+
+static void
+media_request_streams (TpSvcChannelTypeStreamedMedia *iface,
+ guint contact_handle,
+ const GArray *media_types,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ GPtrArray *array;
+ guint i;
+ GError *error = NULL;
+
+ if (!tp_handle_is_valid (contact_repo, contact_handle, &error))
+ goto error;
+
+ if (contact_handle != self->priv->handle)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "This channel is for handle #%u, we can't make a stream to #%u",
+ self->priv->handle, contact_handle);
+ goto error;
+ }
+
+ if (self->priv->progress == PROGRESS_ENDED)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Call has terminated");
+ goto error;
+ }
+
+ for (i = 0; i < media_types->len; i++)
+ {
+ guint media_type = g_array_index (media_types, guint, i);
+
+ switch (media_type)
+ {
+ case TP_MEDIA_STREAM_TYPE_AUDIO:
+ case TP_MEDIA_STREAM_TYPE_VIDEO:
+ break;
+ default:
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "%u is not a valid Media_Stream_Type", media_type);
+ goto error;
+ }
+ }
+
+ array = g_ptr_array_sized_new (media_types->len);
+
+ for (i = 0; i < media_types->len; i++)
+ {
+ guint media_type = g_array_index (media_types, guint, i);
+ ExampleCallableMediaStream *stream;
+ GValueArray *info;
+ guint id = self->priv->next_stream_id++;
+
+ if (self->priv->progress < PROGRESS_CALLING)
+ {
+ TpIntSet *peer_set = tp_intset_new_containing (self->priv->handle);
+
+ g_message ("SIGNALLING: send: new streamed media call");
+ self->priv->progress = PROGRESS_CALLING;
+
+ tp_group_mixin_change_members ((GObject *) self, "",
+ NULL /* nobody added */,
+ NULL /* nobody removed */,
+ NULL /* nobody added to local-pending */,
+ peer_set /* added to remote-pending */,
+ self->group.self_handle /* actor */,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (peer_set);
+
+ /* In this example there is no real contact, so just simulate them
+ * answering after a short time */
+ /* FIXME: define a special contact who never answers, and if it's
+ * that contact, don't add this timeout */
+ g_timeout_add_full (G_PRIORITY_DEFAULT, self->priv->simulation_delay,
+ simulate_contact_answered_cb, g_object_ref (self),
+ g_object_unref);
+ }
+
+ g_message ("SIGNALLING: send: new %s stream",
+ media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
+
+ stream = g_object_new (EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM,
+ "channel", self,
+ "id", id,
+ "handle", self->priv->handle,
+ "type", media_type,
+ NULL);
+
+ g_hash_table_insert (self->priv->streams, GUINT_TO_POINTER (id), stream);
+
+ tp_svc_channel_type_streamed_media_emit_stream_added (self, id,
+ self->priv->handle, media_type);
+
+ g_signal_connect (stream, "removed", G_CALLBACK (stream_removed_cb),
+ self);
+ g_signal_connect (stream, "notify::state",
+ G_CALLBACK (stream_state_changed_cb), self);
+ g_signal_connect (stream, "direction-changed",
+ G_CALLBACK (stream_direction_changed_cb), self);
+
+ /* newly requested streams start off in a "we want to be bidirectional"
+ * state */
+ example_callable_media_stream_change_direction (stream,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, NULL);
+
+ if (self->priv->progress == PROGRESS_ACTIVE)
+ {
+ example_callable_media_stream_connect (stream);
+ }
+
+ g_object_get (stream,
+ "stream-info", &info,
+ NULL);
+
+ g_ptr_array_add (array, info);
+ }
+
+ tp_svc_channel_type_streamed_media_return_from_request_streams (context,
+ array);
+ g_ptr_array_free (array, TRUE);
+
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+media_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelTypeStreamedMediaClass *klass = iface;
+
+#define IMPLEMENT(x) \
+ tp_svc_channel_type_streamed_media_implement_##x (klass, media_##x)
+ IMPLEMENT (list_streams);
+ IMPLEMENT (remove_streams);
+ IMPLEMENT (request_stream_direction);
+ IMPLEMENT (request_streams);
+#undef IMPLEMENT
+}
diff --git a/tests/lib/callable/media-channel.h b/tests/lib/callable/media-channel.h
new file mode 100644
index 0000000..428370d
--- /dev/null
+++ b/tests/lib/callable/media-channel.h
@@ -0,0 +1,74 @@
+/*
+ * media-channel.h - header for an example channel
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __EXAMPLE_CALLABLE_MEDIA_CHANNEL_H__
+#define __EXAMPLE_CALLABLE_MEDIA_CHANNEL_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/group-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallableMediaChannel ExampleCallableMediaChannel;
+typedef struct _ExampleCallableMediaChannelPrivate
+ ExampleCallableMediaChannelPrivate;
+
+typedef struct _ExampleCallableMediaChannelClass
+ ExampleCallableMediaChannelClass;
+typedef struct _ExampleCallableMediaChannelClassPrivate
+ ExampleCallableMediaChannelClassPrivate;
+
+GType example_callable_media_channel_get_type (void);
+
+#define EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL \
+ (example_callable_media_channel_get_type ())
+#define EXAMPLE_CALLABLE_MEDIA_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL, \
+ ExampleCallableMediaChannel))
+#define EXAMPLE_CALLABLE_MEDIA_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL, \
+ ExampleCallableMediaChannelClass))
+#define EXAMPLE_IS_CALLABLE_MEDIA_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL))
+#define EXAMPLE_IS_CALLABLE_MEDIA_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL))
+#define EXAMPLE_CALLABLE_MEDIA_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL, \
+ ExampleCallableMediaChannelClass))
+
+struct _ExampleCallableMediaChannelClass {
+ GObjectClass parent_class;
+ TpGroupMixinClass group_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+
+ ExampleCallableMediaChannelClassPrivate *priv;
+};
+
+struct _ExampleCallableMediaChannel {
+ GObject parent;
+ TpGroupMixin group;
+
+ ExampleCallableMediaChannelPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif
diff --git a/tests/lib/callable/media-manager.c b/tests/lib/callable/media-manager.c
new file mode 100644
index 0000000..7f9372b
--- /dev/null
+++ b/tests/lib/callable/media-manager.c
@@ -0,0 +1,438 @@
+/*
+ * media-manager.c - an example channel manager for StreamedMedia calls.
+ * This channel manager emulates a protocol like XMPP Jingle, where you can
+ * make several simultaneous calls to the same or different contacts.
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "media-manager.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "media-channel.h"
+
+static void channel_manager_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallableMediaManager,
+ example_callable_media_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init))
+
+/* type definition stuff */
+
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+struct _ExampleCallableMediaManagerPrivate
+{
+ TpBaseConnection *conn;
+ guint simulation_delay;
+
+ /* List of ExampleCallableMediaChannel */
+ GList *channels;
+
+ /* Next channel will be ("MediaChannel%u", next_channel_index) */
+ guint next_channel_index;
+
+ gulong status_changed_id;
+};
+
+static void
+example_callable_media_manager_init (ExampleCallableMediaManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER,
+ ExampleCallableMediaManagerPrivate);
+
+ self->priv->conn = NULL;
+ self->priv->channels = NULL;
+ self->priv->status_changed_id = 0;
+}
+
+static void
+example_callable_media_manager_close_all (ExampleCallableMediaManager *self)
+{
+ if (self->priv->channels != NULL)
+ {
+ GList *tmp = self->priv->channels;
+
+ self->priv->channels = NULL;
+
+ g_list_foreach (tmp, (GFunc) g_object_unref, NULL);
+ g_list_free (tmp);
+ }
+
+ 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)
+{
+ ExampleCallableMediaManager *self = EXAMPLE_CALLABLE_MEDIA_MANAGER (object);
+
+ example_callable_media_manager_close_all (self);
+ g_assert (self->priv->channels == NULL);
+
+ ((GObjectClass *) example_callable_media_manager_parent_class)->dispose (
+ object);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallableMediaManager *self = EXAMPLE_CALLABLE_MEDIA_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ 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)
+{
+ ExampleCallableMediaManager *self = EXAMPLE_CALLABLE_MEDIA_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ /* We don't ref the connection, because it owns a reference to the
+ * channel manager, and it guarantees that the manager's lifetime is
+ * less than its lifetime */
+ self->priv->conn = g_value_get_object (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+status_changed_cb (TpBaseConnection *conn,
+ guint status,
+ guint reason,
+ ExampleCallableMediaManager *self)
+{
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ {
+ example_callable_media_manager_close_all (self);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleCallableMediaManager *self = EXAMPLE_CALLABLE_MEDIA_MANAGER (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_callable_media_manager_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+ "status-changed", (GCallback) status_changed_cb, self);
+}
+
+static void
+example_callable_media_manager_class_init (
+ ExampleCallableMediaManagerClass *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_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallableMediaManagerPrivate));
+}
+
+static void
+example_callable_media_manager_foreach_channel (
+ TpChannelManager *iface,
+ TpExportableChannelFunc callback,
+ gpointer user_data)
+{
+ ExampleCallableMediaManager *self = EXAMPLE_CALLABLE_MEDIA_MANAGER (iface);
+
+ g_list_foreach (self->priv->channels, (GFunc) callback, user_data);
+}
+
+static void
+channel_closed_cb (ExampleCallableMediaChannel *chan,
+ ExampleCallableMediaManager *self)
+{
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (self->priv->channels != NULL)
+ {
+ self->priv->channels = g_list_remove_all (self->priv->channels, chan);
+ }
+}
+
+static ExampleCallableMediaChannel *
+new_channel (ExampleCallableMediaManager *self,
+ TpHandle handle,
+ TpHandle initiator,
+ gpointer request_token)
+{
+ ExampleCallableMediaChannel *chan;
+ gchar *object_path;
+ GSList *requests = NULL;
+
+ /* FIXME: This could potentially wrap around, but only after 4 billion
+ * calls, which is probably plenty. */
+ object_path = g_strdup_printf ("%s/MediaChannel%u",
+ self->priv->conn->object_path, self->priv->next_channel_index++);
+
+ chan = g_object_new (EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL,
+ "connection", self->priv->conn,
+ "object-path", object_path,
+ "handle", handle,
+ "initiator-handle", initiator,
+ "requested", (self->priv->conn->self_handle == initiator),
+ "simulation-delay", self->priv->simulation_delay,
+ NULL);
+
+ g_free (object_path);
+
+ g_signal_connect (chan, "closed", G_CALLBACK (channel_closed_cb), self);
+
+ self->priv->channels = g_list_prepend (self->priv->channels, chan);
+
+ 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);
+
+ return chan;
+}
+
+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_callable_media_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);
+
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType",
+ tp_g_value_slice_new_static_string (
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA));
+
+ g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType",
+ tp_g_value_slice_new_uint (TP_HANDLE_TYPE_CONTACT));
+
+ func (manager, table, allowed_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+static gboolean
+example_callable_media_manager_request (ExampleCallableMediaManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ TpHandle handle;
+ GError *error = NULL;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL ".ChannelType"),
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
+ {
+ return FALSE;
+ }
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT)
+ {
+ 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 == self->priv->conn->self_handle)
+ {
+ /* In protocols with a concept of multiple "resources" signed in to
+ * one account (XMPP, and possibly MSN) it is technically possible to
+ * call yourself - e.g. if you're signed in on two PCs, you can call one
+ * from the other. For simplicity, this example simulates a protocol
+ * where this is not the case.
+ */
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "In this protocol, you can't call yourself");
+ goto error;
+ }
+
+ if (!require_new)
+ {
+ /* see if we're already calling that handle */
+ const GList *link;
+
+ for (link = self->priv->channels; link != NULL; link = link->next)
+ {
+ guint its_handle;
+
+ g_object_get (link->data,
+ "handle", &its_handle,
+ NULL);
+
+ if (its_handle == handle)
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (link->data));
+ return TRUE;
+ }
+ }
+ }
+
+ new_channel (self, handle, self->priv->conn->self_handle,
+ request_token);
+ 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_callable_media_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_callable_media_manager_request (
+ EXAMPLE_CALLABLE_MEDIA_MANAGER (manager),
+ request_token, request_properties, TRUE);
+}
+
+static gboolean
+example_callable_media_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_callable_media_manager_request (
+ EXAMPLE_CALLABLE_MEDIA_MANAGER (manager),
+ request_token, request_properties, FALSE);
+}
+
+static void
+channel_manager_iface_init (gpointer g_iface,
+ gpointer iface_data G_GNUC_UNUSED)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = example_callable_media_manager_foreach_channel;
+ iface->foreach_channel_class =
+ example_callable_media_manager_foreach_channel_class;
+ iface->create_channel = example_callable_media_manager_create_channel;
+ iface->ensure_channel = example_callable_media_manager_ensure_channel;
+ /* In this channel manager, RequestChannel is not supported (it's new
+ * code so there's no reason to be backwards compatible). The requirements
+ * for RequestChannel are somewhat complicated for backwards compatibility
+ * reasons: see telepathy-gabble or
+ * http://telepathy.freedesktop.org/wiki/Requesting%20StreamedMedia%20channels
+ * for the gory details. */
+ iface->request_channel = NULL;
+}
diff --git a/tests/lib/callable/media-manager.h b/tests/lib/callable/media-manager.h
new file mode 100644
index 0000000..5be239e
--- /dev/null
+++ b/tests/lib/callable/media-manager.h
@@ -0,0 +1,71 @@
+/*
+ * media-manager.h - header for an example channel manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __EXAMPLE_CALLABLE_MEDIA_MANAGER_H__
+#define __EXAMPLE_CALLABLE_MEDIA_MANAGER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallableMediaManager ExampleCallableMediaManager;
+typedef struct _ExampleCallableMediaManagerPrivate
+ ExampleCallableMediaManagerPrivate;
+
+typedef struct _ExampleCallableMediaManagerClass
+ ExampleCallableMediaManagerClass;
+typedef struct _ExampleCallableMediaManagerClassPrivate
+ ExampleCallableMediaManagerClassPrivate;
+
+struct _ExampleCallableMediaManagerClass {
+ GObjectClass parent_class;
+
+ ExampleCallableMediaManagerClassPrivate *priv;
+};
+
+struct _ExampleCallableMediaManager {
+ GObject parent;
+
+ ExampleCallableMediaManagerPrivate *priv;
+};
+
+GType example_callable_media_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER \
+ (example_callable_media_manager_get_type ())
+#define EXAMPLE_CALLABLE_MEDIA_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER, \
+ ExampleCallableMediaManager))
+#define EXAMPLE_CALLABLE_MEDIA_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER, \
+ ExampleCallableMediaManagerClass))
+#define EXAMPLE_IS_CALLABLE_MEDIA_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER))
+#define EXAMPLE_IS_CALLABLE_MEDIA_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER))
+#define EXAMPLE_CALLABLE_MEDIA_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER, \
+ ExampleCallableMediaManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/tests/lib/callable/media-stream.c b/tests/lib/callable/media-stream.c
new file mode 100644
index 0000000..43f9ccd
--- /dev/null
+++ b/tests/lib/callable/media-stream.c
@@ -0,0 +1,506 @@
+/*
+ * media-stream.c - a stream in a streamed media call.
+ *
+ * In connection managers with MediaSignalling, this object would be a D-Bus
+ * object in its own right. In this CM, MediaSignalling is not used, and this
+ * object just represents internal state of the MediaChannel.
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "media-stream.h"
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/gtypes.h>
+
+#include "media-channel.h"
+
+G_DEFINE_TYPE (ExampleCallableMediaStream,
+ example_callable_media_stream,
+ G_TYPE_OBJECT)
+
+enum
+{
+ PROP_CHANNEL = 1,
+ PROP_ID,
+ PROP_HANDLE,
+ PROP_TYPE,
+ PROP_STATE,
+ PROP_PENDING_SEND,
+ PROP_DIRECTION,
+ PROP_STREAM_INFO,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+enum
+{
+ SIGNAL_REMOVED,
+ SIGNAL_DIRECTION_CHANGED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+struct _ExampleCallableMediaStreamPrivate
+{
+ TpBaseConnection *conn;
+ ExampleCallableMediaChannel *channel;
+ guint id;
+ TpHandle handle;
+ TpMediaStreamType type;
+ TpMediaStreamState state;
+ TpMediaStreamDirection direction;
+ TpMediaStreamPendingSend pending_send;
+
+ guint simulation_delay;
+
+ gulong call_terminated_id;
+
+ guint connected_event_id;
+
+ gboolean removed;
+};
+
+static void
+example_callable_media_stream_init (ExampleCallableMediaStream *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM,
+ ExampleCallableMediaStreamPrivate);
+
+ /* FIXME: no particular "implicit" direction is currently mandated by
+ * telepathy-spec */
+ self->priv->direction = TP_MEDIA_STREAM_DIRECTION_NONE;
+ self->priv->pending_send = 0;
+}
+
+static void
+call_terminated_cb (ExampleCallableMediaChannel *channel,
+ ExampleCallableMediaStream *self)
+{
+ g_signal_handler_disconnect (channel, self->priv->call_terminated_id);
+ self->priv->call_terminated_id = 0;
+ example_callable_media_stream_close (self);
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleCallableMediaStream *self = EXAMPLE_CALLABLE_MEDIA_STREAM (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_callable_media_stream_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ g_object_get (self->priv->channel,
+ "connection", &self->priv->conn,
+ NULL);
+ self->priv->call_terminated_id = g_signal_connect (self->priv->channel,
+ "call-terminated", G_CALLBACK (call_terminated_cb), self);
+
+ if (self->priv->handle != 0)
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_ref (contact_repo, self->priv->handle);
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallableMediaStream *self = EXAMPLE_CALLABLE_MEDIA_STREAM (object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ g_value_set_uint (value, self->priv->id);
+ break;
+
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+
+ case PROP_TYPE:
+ g_value_set_uint (value, self->priv->type);
+ break;
+
+ case PROP_STATE:
+ g_value_set_uint (value, self->priv->state);
+ break;
+
+ case PROP_PENDING_SEND:
+ g_value_set_uint (value, self->priv->pending_send);
+ break;
+
+ case PROP_DIRECTION:
+ g_value_set_uint (value, self->priv->direction);
+ break;
+
+ case PROP_CHANNEL:
+ g_value_set_object (value, self->priv->channel);
+ break;
+
+ case PROP_STREAM_INFO:
+ {
+ GValueArray *va = g_value_array_new (6);
+ guint i;
+
+ for (i = 0; i < 6; i++)
+ {
+ g_value_array_append (va, NULL);
+ g_value_init (va->values + i, G_TYPE_UINT);
+ }
+
+ g_value_set_uint (va->values + 0, self->priv->id);
+ g_value_set_uint (va->values + 1, self->priv->handle);
+ g_value_set_uint (va->values + 2, self->priv->type);
+ g_value_set_uint (va->values + 3, self->priv->state);
+ g_value_set_uint (va->values + 4, self->priv->direction);
+ g_value_set_uint (va->values + 5, self->priv->pending_send);
+
+ g_value_take_boxed (value, va);
+ }
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ 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)
+{
+ ExampleCallableMediaStream *self = EXAMPLE_CALLABLE_MEDIA_STREAM (object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ self->priv->id = g_value_get_uint (value);
+ break;
+
+ case PROP_HANDLE:
+ self->priv->handle = g_value_get_uint (value);
+ break;
+
+ case PROP_TYPE:
+ self->priv->type = g_value_get_uint (value);
+ break;
+
+ case PROP_CHANNEL:
+ g_assert (self->priv->channel == NULL);
+ self->priv->channel = g_value_dup_object (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleCallableMediaStream *self = EXAMPLE_CALLABLE_MEDIA_STREAM (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ example_callable_media_stream_close (self);
+
+ if (self->priv->handle != 0)
+ {
+ tp_handle_unref (contact_repo, self->priv->handle);
+ self->priv->handle = 0;
+ }
+
+ if (self->priv->channel != NULL)
+ {
+ if (self->priv->call_terminated_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->channel,
+ self->priv->call_terminated_id);
+ self->priv->call_terminated_id = 0;
+ }
+
+ g_object_unref (self->priv->channel);
+ self->priv->channel = NULL;
+ }
+
+ if (self->priv->conn != NULL)
+ {
+ g_object_unref (self->priv->conn);
+ self->priv->conn = NULL;
+ }
+
+ ((GObjectClass *) example_callable_media_stream_parent_class)->dispose (object);
+}
+
+static void
+example_callable_media_stream_class_init (ExampleCallableMediaStreamClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallableMediaStreamPrivate));
+
+ object_class->constructed = constructed;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+
+ param_spec = g_param_spec_object ("channel", "ExampleCallableMediaChannel",
+ "Media channel that owns this stream",
+ EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CHANNEL, param_spec);
+
+ param_spec = g_param_spec_uint ("id", "Stream ID",
+ "ID of this stream",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("handle", "Peer's TpHandle",
+ "The handle with which this stream communicates or 0 if not applicable",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HANDLE, param_spec);
+
+ param_spec = g_param_spec_uint ("type", "TpMediaStreamType",
+ "Media stream type",
+ 0, NUM_TP_MEDIA_STREAM_TYPES - 1, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TYPE, param_spec);
+
+ param_spec = g_param_spec_uint ("state", "TpMediaStreamState",
+ "Media stream connection state",
+ 0, NUM_TP_MEDIA_STREAM_STATES - 1, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STATE, param_spec);
+
+ param_spec = g_param_spec_uint ("direction", "TpMediaStreamDirection",
+ "Media stream direction",
+ 0, NUM_TP_MEDIA_STREAM_DIRECTIONS - 1, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DIRECTION, param_spec);
+
+ param_spec = g_param_spec_uint ("pending-send", "TpMediaStreamPendingSend",
+ "Requested media stream directions pending approval",
+ 0,
+ TP_MEDIA_STREAM_PENDING_LOCAL_SEND | TP_MEDIA_STREAM_PENDING_REMOTE_SEND,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PENDING_SEND, param_spec);
+
+ param_spec = g_param_spec_boxed ("stream-info", "Stream info",
+ "6-entry GValueArray as returned by ListStreams and RequestStreams",
+ TP_STRUCT_TYPE_MEDIA_STREAM_INFO,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STREAM_INFO, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ signals[SIGNAL_REMOVED] = g_signal_new ("removed",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[SIGNAL_DIRECTION_CHANGED] = g_signal_new ("direction-changed",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+void
+example_callable_media_stream_close (ExampleCallableMediaStream *self)
+{
+ if (!self->priv->removed)
+ {
+ self->priv->removed = TRUE;
+
+ g_message ("Sending to server: Closing stream %u",
+ self->priv->id);
+
+ g_signal_emit (self, signals[SIGNAL_REMOVED], 0);
+
+ if (self->priv->connected_event_id != 0)
+ {
+ g_source_remove (self->priv->connected_event_id);
+ }
+ }
+}
+
+void
+example_callable_media_stream_simulate_contact_agreed_to_send (
+ ExampleCallableMediaStream *self)
+{
+ if (self->priv->removed ||
+ !(self->priv->pending_send & TP_MEDIA_STREAM_PENDING_REMOTE_SEND))
+ return;
+
+ g_message ("SIGNALLING: receive: OK, I'll send you media on stream %u",
+ self->priv->id);
+
+ self->priv->direction |= TP_MEDIA_STREAM_DIRECTION_RECEIVE;
+ self->priv->pending_send &= ~TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
+
+ g_signal_emit (self, signals[SIGNAL_DIRECTION_CHANGED], 0);
+}
+
+static gboolean
+simulate_contact_agreed_to_send_cb (gpointer p)
+{
+ example_callable_media_stream_simulate_contact_agreed_to_send (p);
+ return FALSE;
+}
+
+gboolean
+example_callable_media_stream_change_direction (
+ ExampleCallableMediaStream *self,
+ TpMediaStreamDirection direction,
+ GError **error)
+{
+ gboolean sending =
+ ((self->priv->direction & TP_MEDIA_STREAM_DIRECTION_SEND) != 0);
+ gboolean receiving =
+ ((self->priv->direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 0);
+ gboolean want_to_send =
+ ((direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 0);
+ gboolean want_to_receive =
+ ((direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 0);
+ gboolean pending_remote_send =
+ ((self->priv->pending_send & TP_MEDIA_STREAM_PENDING_REMOTE_SEND) != 0);
+ gboolean pending_local_send =
+ ((self->priv->pending_send & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0);
+ gboolean changed = FALSE;
+
+ if (want_to_send)
+ {
+ if (!sending)
+ {
+ if (pending_local_send)
+ {
+ g_message ("SIGNALLING: send: I will now send you media on "
+ "stream %u", self->priv->id);
+ }
+
+ g_message ("MEDIA: Sending media to peer for stream %u",
+ self->priv->id);
+ changed = TRUE;
+ self->priv->direction |= TP_MEDIA_STREAM_DIRECTION_SEND;
+ }
+ }
+ else
+ {
+ if (sending)
+ {
+ g_message ("SIGNALLING: send: I will no longer send you media on "
+ "stream %u", self->priv->id);
+ g_message ("MEDIA: No longer sending media to peer for stream %u",
+ self->priv->id);
+ changed = TRUE;
+ self->priv->direction &= ~TP_MEDIA_STREAM_DIRECTION_SEND;
+ }
+ else if (pending_local_send)
+ {
+ g_message ("SIGNALLING: send: No, I refuse to send you media on "
+ "stream %u", self->priv->id);
+ changed = TRUE;
+ self->priv->pending_send &= ~TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
+ }
+ }
+
+ if (want_to_receive)
+ {
+ if (!receiving && !pending_remote_send)
+ {
+ g_message ("SIGNALLING: send: Please start sending me stream %u",
+ self->priv->id);
+ changed = TRUE;
+ self->priv->pending_send |= TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
+ g_timeout_add_full (G_PRIORITY_DEFAULT, self->priv->simulation_delay,
+ simulate_contact_agreed_to_send_cb, g_object_ref (self),
+ g_object_unref);
+ }
+ }
+ else
+ {
+ if (receiving)
+ {
+ g_message ("SIGNALLING: send: Please stop sending me stream %u",
+ self->priv->id);
+ g_message ("MEDIA: Suppressing output of stream %u",
+ self->priv->id);
+ changed = TRUE;
+ self->priv->direction &= ~TP_MEDIA_STREAM_DIRECTION_RECEIVE;
+ }
+ }
+
+ if (changed)
+ g_signal_emit (self, signals[SIGNAL_DIRECTION_CHANGED], 0);
+
+ return TRUE;
+}
+
+static gboolean
+simulate_stream_connected_cb (gpointer p)
+{
+ ExampleCallableMediaStream *self = EXAMPLE_CALLABLE_MEDIA_STREAM (p);
+
+ g_message ("MEDIA: stream connected");
+ self->priv->state = TP_MEDIA_STREAM_STATE_CONNECTED;
+ g_object_notify ((GObject *) self, "state");
+
+ return FALSE;
+}
+
+void
+example_callable_media_stream_connect (ExampleCallableMediaStream *self)
+{
+ /* if already trying to connect, do nothing */
+ if (self->priv->connected_event_id != 0)
+ return;
+
+ /* simulate it taking a short time to connect */
+ self->priv->connected_event_id = g_timeout_add (self->priv->simulation_delay,
+ simulate_stream_connected_cb, self);
+}
diff --git a/tests/lib/callable/media-stream.h b/tests/lib/callable/media-stream.h
new file mode 100644
index 0000000..5f5d916
--- /dev/null
+++ b/tests/lib/callable/media-stream.h
@@ -0,0 +1,83 @@
+/*
+ * media-stream.h - header for an example stream
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __EXAMPLE_CALLABLE_MEDIA_STREAM_H__
+#define __EXAMPLE_CALLABLE_MEDIA_STREAM_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/enums.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallableMediaStream ExampleCallableMediaStream;
+typedef struct _ExampleCallableMediaStreamPrivate
+ ExampleCallableMediaStreamPrivate;
+
+typedef struct _ExampleCallableMediaStreamClass
+ ExampleCallableMediaStreamClass;
+typedef struct _ExampleCallableMediaStreamClassPrivate
+ ExampleCallableMediaStreamClassPrivate;
+
+GType example_callable_media_stream_get_type (void);
+
+#define EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM \
+ (example_callable_media_stream_get_type ())
+#define EXAMPLE_CALLABLE_MEDIA_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM, \
+ ExampleCallableMediaStream))
+#define EXAMPLE_CALLABLE_MEDIA_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM, \
+ ExampleCallableMediaStreamClass))
+#define EXAMPLE_IS_CALLABLE_MEDIA_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM))
+#define EXAMPLE_IS_CALLABLE_MEDIA_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM))
+#define EXAMPLE_CALLABLE_MEDIA_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM, \
+ ExampleCallableMediaStreamClass))
+
+struct _ExampleCallableMediaStreamClass {
+ GObjectClass parent_class;
+
+ ExampleCallableMediaStreamClassPrivate *priv;
+};
+
+struct _ExampleCallableMediaStream {
+ GObject parent;
+
+ ExampleCallableMediaStreamPrivate *priv;
+};
+
+void example_callable_media_stream_close (ExampleCallableMediaStream *self);
+gboolean example_callable_media_stream_change_direction (
+ ExampleCallableMediaStream *self, TpMediaStreamDirection direction,
+ GError **error);
+void example_callable_media_stream_connect (ExampleCallableMediaStream *self);
+
+/* This controls receiving emulated network events, so it wouldn't exist in
+ * a real connection manager */
+void example_callable_media_stream_simulate_contact_agreed_to_send (
+ ExampleCallableMediaStream *self);
+
+G_END_DECLS
+
+#endif
--
1.5.6.5
More information about the telepathy-commits
mailing list