summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2010-02-22 18:10:09 +0000
committerSimon McVittie <simon.mcvittie@collabora.co.uk>2010-02-22 18:10:09 +0000
commitbc62ca21f71fc6a84cad4f5c4e105e5c0870e6e3 (patch)
tree1b6c90d0c3a9dc5cadc6a71185a64a49051f2842
parent0fdda7da58232f3bacded364ad6cd9c61db9c2a8 (diff)
parent7bc9ce497b1ad511c90e20ce2672d2df1e4b45e7 (diff)
downloadtelepathy-glib-bc62ca21f71fc6a84cad4f5c4e105e5c0870e6e3.tar.gz
Merge branch 'from-the-future'
-rw-r--r--.gitignore2
-rw-r--r--Makefile.am10
-rw-r--r--configure.ac3
-rw-r--r--examples/Makefile.am2
-rw-r--r--examples/future/Makefile.am1
-rw-r--r--examples/future/call-cm/Makefile.am71
-rw-r--r--examples/future/call-cm/call-channel.c1624
-rw-r--r--examples/future/call-cm/call-channel.h74
-rw-r--r--examples/future/call-cm/call-content.c369
-rw-r--r--examples/future/call-cm/call-content.h81
-rw-r--r--examples/future/call-cm/call-manager.c537
-rw-r--r--examples/future/call-cm/call-manager.h71
-rw-r--r--examples/future/call-cm/call-stream.c705
-rw-r--r--examples/future/call-cm/call-stream.h82
-rw-r--r--examples/future/call-cm/cm.c129
-rw-r--r--examples/future/call-cm/cm.h73
-rw-r--r--examples/future/call-cm/conn.c421
-rw-r--r--examples/future/call-cm/conn.h78
-rw-r--r--examples/future/call-cm/main.c61
-rw-r--r--examples/future/call-cm/manager-file.py23
-rw-r--r--extensions/Call_Content.xml141
-rw-r--r--extensions/Call_Content_Codec_Offer.xml57
-rw-r--r--extensions/Call_Content_Interface_Media.xml229
-rw-r--r--extensions/Call_Stream.xml165
-rw-r--r--extensions/Call_Stream_Endpoint.xml95
-rw-r--r--extensions/Call_Stream_Interface_Media.xml400
-rw-r--r--extensions/Channel_Type_Call.xml931
-rw-r--r--extensions/Makefile.am172
-rw-r--r--extensions/all.xml12
-rw-r--r--extensions/call-content.c161
-rw-r--r--extensions/call-content.h62
-rw-r--r--extensions/call-content.xml10
-rw-r--r--extensions/call-stream.c161
-rw-r--r--extensions/call-stream.h62
-rw-r--r--extensions/call-stream.xml10
-rw-r--r--extensions/channel.xml9
-rw-r--r--extensions/extensions-cli.c35
-rw-r--r--extensions/extensions.c6
-rw-r--r--extensions/extensions.h27
-rw-r--r--extensions/misc.xml10
-rw-r--r--tests/dbus/Makefile.am6
-rw-r--r--tests/dbus/call-example.c1133
42 files changed, 8309 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 81660359e..192b34ae8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,8 @@ docs/reference/xml
examples/client/telepathy-example-*
examples/cm/*/telepathy-example-*
examples/extensions/extensions.html
+/examples/future/*/telepathy-example-*
+/extensions/extensions.html
gtk-doc.make
install-sh
lcov.info
diff --git a/Makefile.am b/Makefile.am
index f9d0158e8..0b6ab8db5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,14 @@
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = m4 tools spec telepathy-glib examples tests docs
+SUBDIRS = \
+ m4 \
+ tools \
+ spec \
+ telepathy-glib \
+ extensions \
+ examples \
+ tests \
+ docs
DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc --disable-debug
diff --git a/configure.ac b/configure.ac
index 809331a79..3e772d05f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -210,6 +210,9 @@ AC_OUTPUT( Makefile \
examples/cm/extended/Makefile \
examples/cm/no-protocols/Makefile \
examples/extensions/Makefile \
+ examples/future/Makefile \
+ examples/future/call-cm/Makefile \
+ extensions/Makefile \
spec/Makefile \
telepathy-glib/Makefile \
telepathy-glib/telepathy-glib.pc \
diff --git a/examples/Makefile.am b/examples/Makefile.am
index e8fe94091..fde86c064 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -1,4 +1,4 @@
# extensions has to be compiled first, because other examples need it.
-SUBDIRS = extensions client cm
+SUBDIRS = extensions client cm future
EXTRA_DIST = README
diff --git a/examples/future/Makefile.am b/examples/future/Makefile.am
new file mode 100644
index 000000000..c7ae472ce
--- /dev/null
+++ b/examples/future/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = call-cm
diff --git a/examples/future/call-cm/Makefile.am b/examples/future/call-cm/Makefile.am
new file mode 100644
index 000000000..c5ee64513
--- /dev/null
+++ b/examples/future/call-cm/Makefile.am
@@ -0,0 +1,71 @@
+# Example connection manager with audio/video calls.
+
+EXAMPLES = telepathy-example-cm-call
+noinst_LTLIBRARIES = libexample-cm-call.la
+
+if INSTALL_EXAMPLES
+libexec_PROGRAMS = $(EXAMPLES)
+else
+noinst_PROGRAMS = $(EXAMPLES)
+endif
+
+libexample_cm_call_la_SOURCES = \
+ cm.c \
+ cm.h \
+ conn.c \
+ conn.h \
+ call-channel.c \
+ call-channel.h \
+ call-manager.c \
+ call-manager.h \
+ call-content.c \
+ call-content.h \
+ call-stream.c \
+ call-stream.h
+
+# In an external project you'd use $(TP_GLIB_LIBS) (obtained from
+# pkg-config via autoconf) instead of the .la path
+libexample_cm_call_la_LIBADD = \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib.la \
+ $(top_builddir)/extensions/libfuture-extensions.la
+
+telepathy_example_cm_call_SOURCES = \
+ main.c
+
+telepathy_example_cm_call_LDADD = \
+ $(noinst_LTLIBRARIES)
+
+AM_CFLAGS = \
+ $(ERROR_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(TP_GLIB_CFLAGS)
+
+EXTRA_DIST = manager-file.py
+
+servicedir = ${datadir}/dbus-1/services
+
+if INSTALL_EXAMPLES
+service_DATA = _gen/org.freedesktop.Telepathy.ConnectionManager.example_call.service
+$(service_DATA): %: Makefile
+ $(mkdir_p) _gen
+ { echo "[D-BUS Service]" && \
+ echo "Name=org.freedesktop.Telepathy.ConnectionManager.example_call" && \
+ echo "Exec=${libexecdir}/telepathy-example-cm-call"; } > $@
+
+managerdir = ${datadir}/telepathy/managers
+manager_DATA = _gen/example_call.manager
+endif
+
+_gen/example_call.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/future/call-cm/call-channel.c b/examples/future/call-cm/call-channel.c
new file mode 100644
index 000000000..e46dd07dc
--- /dev/null
+++ b/examples/future/call-cm/call-channel.c
@@ -0,0 +1,1624 @@
+/*
+ * call-channel.c - an example 1-1 audio/video 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 has the HardwareStreaming flag, its contents
+ * and streams do not have the Media 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 do not have HardwareStreaming,
+ * and do have the Media interface on their contents/streams. 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 "call-channel.h"
+
+#include <string.h>
+
+#include <gobject/gvaluecollector.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "extensions/extensions.h"
+
+#include "call-content.h"
+#include "call-stream.h"
+
+static void call_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+static void hold_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallChannel,
+ example_call_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 (FUTURE_TYPE_SVC_CHANNEL_TYPE_CALL,
+ call_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_HOLD,
+ hold_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,
+ PROP_INITIAL_AUDIO,
+ PROP_INITIAL_VIDEO,
+ PROP_CONTENT_PATHS,
+ PROP_CALL_STATE,
+ PROP_CALL_FLAGS,
+ PROP_CALL_STATE_REASON,
+ PROP_CALL_STATE_DETAILS,
+ PROP_HARDWARE_STREAMING,
+ PROP_CALL_MEMBERS,
+ PROP_INITIAL_TRANSPORT,
+ PROP_MUTABLE_CONTENTS,
+ N_PROPS
+};
+
+struct _ExampleCallChannelPrivate
+{
+ TpBaseConnection *conn;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandle initiator;
+
+ FutureCallState call_state;
+ FutureCallFlags call_flags;
+ GValueArray *call_state_reason;
+ GHashTable *call_state_details;
+ FutureCallMemberFlags peer_flags;
+
+ guint simulation_delay;
+
+ guint next_stream_id;
+
+ /* strdup'd name => referenced ExampleCallContent */
+ GHashTable *contents;
+
+ guint hold_state;
+ guint hold_state_reason;
+
+ gboolean locally_requested;
+ gboolean initial_audio;
+ gboolean initial_video;
+ gboolean disposed;
+ gboolean closed;
+};
+
+static const char * example_call_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_HOLD,
+ NULL
+};
+
+/* In practice you need one for audio, plus one per video (e.g. a
+ * presentation might have separate video contents for the slides
+ * and a camera pointed at the presenter), so having more than three
+ * would be highly unusual */
+#define MAX_CONTENTS_PER_CALL 100
+
+G_GNUC_NULL_TERMINATED static void
+example_call_channel_set_state (ExampleCallChannel *self,
+ FutureCallState state,
+ FutureCallFlags flags,
+ TpHandle actor,
+ FutureCallStateChangeReason reason,
+ const gchar *error,
+ ...)
+{
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandle old_actor;
+ const gchar *key;
+ va_list va;
+
+ self->priv->call_state = state;
+ self->priv->call_flags = flags;
+
+ old_actor = g_value_get_uint (self->priv->call_state_reason->values + 0);
+
+ if (actor != 0)
+ tp_handle_ref (contact_handles, actor);
+
+ if (old_actor != 0)
+ tp_handle_unref (contact_handles, old_actor);
+
+ g_value_set_uint (self->priv->call_state_reason->values + 0, actor);
+ g_value_set_uint (self->priv->call_state_reason->values + 1, reason);
+ g_value_set_string (self->priv->call_state_reason->values + 2, error);
+
+ g_hash_table_remove_all (self->priv->call_state_details);
+
+ va_start (va, error);
+
+ /* This is basically tp_asv_new_va(), but that doesn't exist yet
+ * (and when it does, we still won't want to use it in this example
+ * just yet, because examples shouldn't use unreleased API) */
+ for (key = va_arg (va, const gchar *);
+ key != NULL;
+ key = va_arg (va, const gchar *))
+ {
+ GType type = va_arg (va, GType);
+ GValue *value = tp_g_value_slice_new (type);
+ gchar *collect_error = NULL;
+
+ G_VALUE_COLLECT (value, va, 0, &collect_error);
+
+ if (collect_error != NULL)
+ {
+ g_critical ("key %s: %s", key, collect_error);
+ g_free (collect_error);
+ collect_error = NULL;
+ tp_g_value_slice_free (value);
+ continue;
+ }
+
+ g_hash_table_insert (self->priv->call_state_details,
+ (gchar *) key, value);
+ }
+
+ va_end (va);
+
+ future_svc_channel_type_call_emit_call_state_changed (self,
+ self->priv->call_state, self->priv->call_flags,
+ self->priv->call_state_reason, self->priv->call_state_details);
+}
+
+static void
+example_call_channel_init (ExampleCallChannel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALL_CHANNEL,
+ ExampleCallChannelPrivate);
+
+ self->priv->next_stream_id = 1;
+ self->priv->contents = g_hash_table_new_full (g_str_hash,
+ g_str_equal, g_free, g_object_unref);
+
+ self->priv->call_state = FUTURE_CALL_STATE_UNKNOWN; /* set in constructed */
+ self->priv->call_flags = 0;
+ self->priv->call_state_reason = tp_value_array_build (3,
+ G_TYPE_UINT, 0, /* actor */
+ G_TYPE_UINT, FUTURE_CALL_STATE_CHANGE_REASON_UNKNOWN,
+ G_TYPE_STRING, "",
+ G_TYPE_INVALID);
+ self->priv->call_state_details = tp_asv_new (
+ NULL, NULL);
+
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_UNHELD;
+ self->priv->hold_state_reason = TP_LOCAL_HOLD_STATE_REASON_NONE;
+}
+
+static ExampleCallContent *example_call_channel_add_content (
+ ExampleCallChannel *self, TpMediaStreamType media_type,
+ gboolean locally_requested, gboolean initial,
+ const gchar *requested_name, GError **error);
+
+static void example_call_channel_initiate_outgoing (ExampleCallChannel *self);
+
+static void
+constructed (GObject *object)
+{
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_call_channel_parent_class)->constructed;
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpDBusDaemon *dbus_daemon;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ dbus_daemon = tp_dbus_daemon_dup (NULL);
+ g_return_if_fail (dbus_daemon != NULL);
+
+ tp_handle_ref (contact_repo, self->priv->handle);
+ tp_handle_ref (contact_repo, self->priv->initiator);
+
+ dbus_g_connection_register_g_object (
+ tp_proxy_get_dbus_connection (dbus_daemon),
+ self->priv->object_path, object);
+
+ g_object_unref (dbus_daemon);
+ dbus_daemon = NULL;
+
+ 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 example_call_channel_initiate_outgoing. */
+ example_call_channel_set_state (self,
+ FUTURE_CALL_STATE_PENDING_INITIATOR, 0, 0,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ NULL);
+ }
+ else
+ {
+ /* This is an incoming call, so the self-handle is locally
+ * pending, to indicate that we need to answer. */
+ example_call_channel_set_state (self,
+ FUTURE_CALL_STATE_PENDING_RECEIVER, 0, self->priv->handle,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ NULL);
+ }
+
+ if (self->priv->locally_requested)
+ {
+ if (self->priv->initial_audio)
+ {
+ g_message ("Channel initially has an audio stream");
+ example_call_channel_add_content (self,
+ TP_MEDIA_STREAM_TYPE_AUDIO, TRUE, TRUE, NULL, NULL);
+ }
+
+ if (self->priv->initial_video)
+ {
+ g_message ("Channel initially has a video stream");
+ example_call_channel_add_content (self,
+ TP_MEDIA_STREAM_TYPE_VIDEO, TRUE, TRUE, NULL, NULL);
+ }
+ }
+ else
+ {
+ /* the caller has almost certainly asked us for some streams - there's
+ * not much point in having a call otherwise */
+
+ if (self->priv->initial_audio)
+ {
+ g_message ("Channel initially has an audio stream");
+ example_call_channel_add_content (self,
+ TP_MEDIA_STREAM_TYPE_AUDIO, FALSE, TRUE, NULL, NULL);
+ }
+
+ if (self->priv->initial_video)
+ {
+ g_message ("Channel initially has a video stream");
+ example_call_channel_add_content (self,
+ TP_MEDIA_STREAM_TYPE_VIDEO, FALSE, TRUE, NULL, NULL);
+ }
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_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, FUTURE_IFACE_CHANNEL_TYPE_CALL);
+ 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_call_channel_interfaces);
+ break;
+
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value,
+ (self->priv->call_state == FUTURE_CALL_STATE_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;
+
+ case PROP_INITIAL_AUDIO:
+ g_value_set_boolean (value, self->priv->initial_audio);
+ break;
+
+ case PROP_INITIAL_VIDEO:
+ g_value_set_boolean (value, self->priv->initial_video);
+ break;
+
+ case PROP_CONTENT_PATHS:
+ {
+ GPtrArray *paths = g_ptr_array_sized_new (g_hash_table_size (
+ self->priv->contents));
+ GHashTableIter iter;
+ gpointer v;
+
+ g_hash_table_iter_init (&iter, self->priv->contents);
+
+ while (g_hash_table_iter_next (&iter, NULL, &v))
+ {
+ gchar *path;
+
+ g_object_get (v,
+ "object-path", &path,
+ NULL);
+
+ g_ptr_array_add (paths, path);
+ }
+
+ g_value_take_boxed (value, paths);
+ }
+ break;
+
+ case PROP_CALL_STATE:
+ g_value_set_uint (value, self->priv->call_state);
+ break;
+
+ case PROP_CALL_FLAGS:
+ g_value_set_uint (value, self->priv->call_flags);
+ break;
+
+ case PROP_CALL_STATE_REASON:
+ g_value_set_boxed (value, self->priv->call_state_reason);
+ break;
+
+ case PROP_CALL_STATE_DETAILS:
+ g_value_set_boxed (value, self->priv->call_state_details);
+ break;
+
+ case PROP_HARDWARE_STREAMING:
+ /* yes, this implementation has hardware streaming */
+ g_value_set_boolean (value, TRUE);
+ break;
+
+ case PROP_MUTABLE_CONTENTS:
+ /* yes, this implementation can add contents */
+ g_value_set_boolean (value, TRUE);
+ break;
+
+ case PROP_INITIAL_TRANSPORT:
+ /* this implementation has hardware_streaming, so the initial
+ * transport is rather meaningless */
+ g_value_set_static_string (value, "");
+ break;
+
+ case PROP_CALL_MEMBERS:
+ {
+ GHashTable *uu_map = g_hash_table_new (NULL, NULL);
+
+ /* There is one contact, other than the self-handle. */
+ g_hash_table_insert (uu_map, GUINT_TO_POINTER (self->priv->handle),
+ GUINT_TO_POINTER (self->priv->peer_flags));
+ g_value_take_boxed (value, uu_map);
+ }
+ 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)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_assert (self->priv->object_path == NULL); /* construct-only */
+ 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;
+
+ case PROP_INITIAL_AUDIO:
+ self->priv->initial_audio = g_value_get_boolean (value);
+ break;
+
+ case PROP_INITIAL_VIDEO:
+ self->priv->initial_video = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+example_call_channel_terminate (ExampleCallChannel *self,
+ TpHandle actor,
+ TpChannelGroupChangeReason reason,
+ FutureCallStateChangeReason call_reason,
+ const gchar *error_name)
+{
+ if (self->priv->call_state != FUTURE_CALL_STATE_ENDED)
+ {
+ GList *values;
+ GHashTable *empty_uu_map = g_hash_table_new (NULL, NULL);
+ GArray *au = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+
+ example_call_channel_set_state (self,
+ FUTURE_CALL_STATE_ENDED, 0, actor,
+ call_reason, error_name,
+ NULL);
+
+ /* FIXME: fd.o #24936 #c20: it's unclear in the spec whether we should
+ * remove peers on call termination or not. For now this example does. */
+ g_array_append_val (au, self->priv->handle);
+ future_svc_channel_type_call_emit_call_members_changed (self,
+ empty_uu_map, au);
+ g_hash_table_unref (empty_uu_map);
+ g_array_free (au, TRUE);
+
+ if (actor == tp_base_connection_get_self_handle (self->priv->conn))
+ {
+ const gchar *send_reason;
+
+ /* 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/>";
+ }
+
+ g_message ("SIGNALLING: send: Terminating call: %s", send_reason);
+ }
+
+ /* terminate all streams: to avoid modifying the hash table (in the
+ * stream-removed handler) while iterating over it, we have to copy the
+ * keys and iterate over those */
+ values = g_hash_table_get_values (self->priv->contents);
+ g_list_foreach (values, (GFunc) g_object_ref, NULL);
+
+ for (; values != NULL; values = g_list_delete_link (values, values))
+ {
+ ExampleCallStream *stream =
+ example_call_content_get_stream (values->data);
+
+ if (stream != NULL)
+ example_call_stream_close (stream);
+
+ g_object_unref (values->data);
+ }
+ }
+}
+
+void
+example_call_channel_disconnected (ExampleCallChannel *self)
+{
+ example_call_channel_terminate (self, 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+ FUTURE_CALL_STATE_CHANGE_REASON_UNKNOWN,
+ TP_ERROR_STR_DISCONNECTED);
+
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object);
+
+ if (self->priv->disposed)
+ return;
+
+ self->priv->disposed = TRUE;
+
+ g_hash_table_destroy (self->priv->contents);
+ self->priv->contents = NULL;
+
+ /* FIXME: right error code? arguably this should always be a no-op */
+ example_call_channel_terminate (self,
+ tp_base_connection_get_self_handle (self->priv->conn),
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+ FUTURE_CALL_STATE_CHANGE_REASON_UNKNOWN, "");
+
+ /* the manager is meant to hold a ref to us until we've closed */
+ g_assert (self->priv->closed);
+
+ ((GObjectClass *) example_call_channel_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object);
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_array_free (self->priv->call_state_reason);
+ g_hash_table_destroy (self->priv->call_state_details);
+
+ tp_handle_unref (contact_handles, self->priv->handle);
+ tp_handle_unref (contact_handles, self->priv->initiator);
+
+ g_free (self->priv->object_path);
+
+ ((GObjectClass *) example_call_channel_parent_class)->finalize (object);
+}
+
+static void
+example_call_channel_class_init (ExampleCallChannelClass *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 TpDBusPropertiesMixinPropImpl call_props[] = {
+ { "Contents", "content-paths", NULL },
+ { "CallState", "call-state", NULL },
+ { "CallFlags", "call-flags", NULL },
+ { "CallStateReason", "call-state-reason", NULL },
+ { "CallStateDetails", "call-state-details", NULL },
+ { "HardwareStreaming", "hardware-streaming", NULL },
+ { "CallMembers", "call-members", NULL },
+ { "InitialTransport", "initial-transport", NULL },
+ { "InitialAudio", "initial-audio", NULL },
+ { "InitialVideo", "initial-video", NULL },
+ { "MutableContents", "mutable-contents", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { FUTURE_IFACE_CHANNEL_TYPE_CALL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ call_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallChannelPrivate));
+
+ 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);
+
+ param_spec = g_param_spec_boolean ("initial-audio", "Initial audio?",
+ "True if this channel had an audio stream when first announced",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIAL_AUDIO,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("initial-video", "Initial video?",
+ "True if this channel had a video stream when first announced",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIAL_VIDEO,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("content-paths", "Content paths",
+ "A list of the object paths of contents",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTENT_PATHS,
+ param_spec);
+
+ param_spec = g_param_spec_uint ("call-state", "Call state",
+ "High-level state of the call",
+ 0, NUM_FUTURE_CALL_STATES - 1, FUTURE_CALL_STATE_PENDING_INITIATOR,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CALL_STATE,
+ param_spec);
+
+ param_spec = g_param_spec_uint ("call-flags", "Call flags",
+ "Flags for additional sub-states",
+ 0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CALL_FLAGS,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("call-state-reason", "Call state reason",
+ "Reason for call-state and call-flags",
+ FUTURE_STRUCT_TYPE_CALL_STATE_REASON,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CALL_STATE_REASON,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("call-state-details", "Call state details",
+ "Additional details of the call state/flags/reason",
+ TP_HASH_TYPE_STRING_VARIANT_MAP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CALL_STATE_DETAILS,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("hardware-streaming",
+ "Hardware streaming?",
+ "True if this channel does all of its own streaming (it does)",
+ TRUE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HARDWARE_STREAMING,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("call-members", "Call members",
+ "A map from call members (only one in this example) to their states",
+ FUTURE_HASH_TYPE_CALL_MEMBER_MAP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CALL_MEMBERS,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initial-transport", "Initial transport",
+ "The initial transport for this channel (there is none)",
+ "",
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIAL_TRANSPORT,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("mutable-contents", "Mutable contents?",
+ "True if contents can be added to this channel (they can)",
+ TRUE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MUTABLE_CONTENTS,
+ param_spec);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallChannelClass,
+ dbus_properties_class));
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface);
+
+ example_call_channel_terminate (self,
+ tp_base_connection_get_self_handle (self->priv->conn),
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "");
+
+ if (!self->priv->closed)
+ {
+ self->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,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_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_call_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
+}
+
+#if 0
+/* FIXME: there's no equivalent of this in Call (yet?) */
+
+/* This is expressed in terms of streams because it's the old API, but it
+ * really means removing contents. */
+static void
+media_remove_streams (TpSvcChannelTypeStreamedMedia *iface,
+ const GArray *stream_ids,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_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->contents,
+ 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);
+ ExampleCallContent *content =
+ g_hash_table_lookup (self->priv->contents, GUINT_TO_POINTER (id));
+ ExampleCallStream *stream = example_call_content_get_stream (content);
+
+ if (stream != NULL)
+ example_call_stream_close (stream);
+ }
+
+ tp_svc_channel_type_streamed_media_return_from_remove_streams (context);
+}
+#endif
+
+static void
+stream_removed_cb (ExampleCallContent *content,
+ const gchar *stream_path G_GNUC_UNUSED,
+ ExampleCallChannel *self)
+{
+ gchar *path, *name;
+
+ /* Contents in this example CM can only have one stream, so if their
+ * stream disappears, the content has to be removed too. */
+
+ g_object_get (content,
+ "object-path", &path,
+ "name", &name,
+ NULL);
+
+ g_hash_table_remove (self->priv->contents, name);
+
+ future_svc_channel_type_call_emit_content_removed (self, path);
+ g_free (path);
+ g_free (name);
+
+ if (g_hash_table_size (self->priv->contents) == 0)
+ {
+ /* no contents left, so the call terminates */
+ example_call_channel_terminate (self, 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+ FUTURE_CALL_STATE_CHANGE_REASON_UNKNOWN, "");
+ /* FIXME: is there an appropriate error? */
+ }
+}
+
+static gboolean
+simulate_contact_ended_cb (gpointer p)
+{
+ ExampleCallChannel *self = p;
+
+ /* if the call has been cancelled while we were waiting for the
+ * contact to do so, do nothing! */
+ if (self->priv->call_state == FUTURE_CALL_STATE_ENDED)
+ return FALSE;
+
+ g_message ("SIGNALLING: receive: call terminated: <call-terminated/>");
+
+ example_call_channel_terminate (self, self->priv->handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "");
+
+ return FALSE;
+}
+
+static gboolean
+simulate_contact_answered_cb (gpointer p)
+{
+ ExampleCallChannel *self = p;
+ GHashTableIter iter;
+ gpointer v;
+ TpHandleRepoIface *contact_repo;
+ const gchar *peer;
+
+ /* if the call has been cancelled while we were waiting for the
+ * contact to answer, do nothing! */
+ if (self->priv->call_state == FUTURE_CALL_STATE_ENDED)
+ return FALSE;
+
+ /* otherwise, we're waiting for a response from the contact, which now
+ * arrives */
+ g_assert (self->priv->call_state == FUTURE_CALL_STATE_PENDING_RECEIVER);
+
+ g_message ("SIGNALLING: receive: contact answered our call");
+
+ example_call_channel_set_state (self,
+ FUTURE_CALL_STATE_ACCEPTED, 0, self->priv->handle,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ NULL);
+
+ g_hash_table_iter_init (&iter, self->priv->contents);
+
+ while (g_hash_table_iter_next (&iter, NULL, &v))
+ {
+ ExampleCallStream *stream = example_call_content_get_stream (v);
+
+ if (stream == NULL)
+ continue;
+
+ /* remote contact accepts our proposed stream direction */
+ example_call_stream_simulate_contact_agreed_to_send (stream);
+ }
+
+ contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ peer = tp_handle_inspect (contact_repo, self->priv->handle);
+
+ /* If the contact's ID contains the magic string "(terminate)", simulate
+ * them hanging up after a moment. */
+ if (strstr (peer, "(terminate)") != NULL)
+ {
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ simulate_contact_ended_cb, g_object_ref (self),
+ g_object_unref);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+simulate_contact_busy_cb (gpointer p)
+{
+ ExampleCallChannel *self = p;
+
+ /* if the call has been cancelled while we were waiting for the
+ * contact to answer, do nothing */
+ if (self->priv->call_state == FUTURE_CALL_STATE_ENDED)
+ return FALSE;
+
+ /* otherwise, we're waiting for a response from the contact, which now
+ * arrives */
+ g_assert (self->priv->call_state == FUTURE_CALL_STATE_PENDING_RECEIVER);
+
+ g_message ("SIGNALLING: receive: call terminated: <user-is-busy/>");
+
+ example_call_channel_terminate (self, self->priv->handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_BUSY,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
+ TP_ERROR_STR_BUSY);
+
+ return FALSE;
+}
+
+static ExampleCallContent *
+example_call_channel_add_content (ExampleCallChannel *self,
+ TpMediaStreamType media_type,
+ gboolean locally_requested,
+ gboolean initial,
+ const gchar *requested_name,
+ GError **error)
+{
+ ExampleCallContent *content;
+ ExampleCallStream *stream;
+ guint id = self->priv->next_stream_id++;
+ const gchar *type_str;
+ TpHandle creator;
+ gchar *name;
+ gchar *path;
+ FutureCallContentDisposition disposition =
+ FUTURE_CALL_CONTENT_DISPOSITION_NONE;
+ guint i;
+
+ type_str = (media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
+ creator = self->priv->handle;
+
+ /* an arbitrary limit much less than 2**32 means we don't use ridiculous
+ * amounts of memory, and also means @i can't wrap around when we use it to
+ * uniquify content names. */
+ if (g_hash_table_size (self->priv->contents) > MAX_CONTENTS_PER_CALL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_PERMISSION_DENIED,
+ "What are you doing with all those contents anyway?!");
+ return NULL;
+ }
+
+ if (requested_name == NULL || requested_name[0] == '\0')
+ {
+ requested_name = type_str;
+ }
+
+ for (i = 0; ; i++)
+ {
+ if (i == 0)
+ name = g_strdup (requested_name);
+ else
+ name = g_strdup_printf ("%s (%u)", requested_name, i);
+
+ if (!g_hash_table_lookup_extended (self->priv->contents, name,
+ NULL, NULL))
+ {
+ /* this name hasn't been used - good enough */
+ break;
+ }
+
+ g_free (name);
+ name = NULL;
+ }
+
+ if (initial)
+ disposition = FUTURE_CALL_CONTENT_DISPOSITION_INITIAL;
+
+ if (locally_requested)
+ {
+ g_message ("SIGNALLING: send: new %s stream %s", type_str, name);
+ creator = self->priv->conn->self_handle;
+ }
+
+ path = g_strdup_printf ("%s/Content%u", self->priv->object_path, id);
+ content = g_object_new (EXAMPLE_TYPE_CALL_CONTENT,
+ "connection", self->priv->conn,
+ "creator", creator,
+ "type", media_type,
+ "name", name,
+ "disposition", disposition,
+ "object-path", path,
+ NULL);
+
+ g_hash_table_insert (self->priv->contents, name, content);
+ future_svc_channel_type_call_emit_content_added (self, path, media_type);
+ g_free (path);
+
+ path = g_strdup_printf ("%s/Stream%u", self->priv->object_path, id);
+ stream = g_object_new (EXAMPLE_TYPE_CALL_STREAM,
+ "connection", self->priv->conn,
+ "handle", self->priv->handle,
+ "locally-requested", locally_requested,
+ "object-path", path,
+ NULL);
+
+ example_call_content_add_stream (content, stream);
+ g_free (path);
+
+ tp_g_signal_connect_object (content, "stream-removed",
+ G_CALLBACK (stream_removed_cb), self, 0);
+
+ return content;
+}
+
+static gboolean
+simulate_contact_ringing_cb (gpointer p)
+{
+ ExampleCallChannel *self = p;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ const gchar *peer;
+ GHashTable *uu_map = g_hash_table_new (NULL, NULL);
+ GArray *empty_au = g_array_sized_new (FALSE, FALSE, sizeof (guint), 0);
+
+ /* ring, ring! */
+ self->priv->peer_flags = FUTURE_CALL_MEMBER_FLAG_RINGING;
+ g_hash_table_insert (uu_map, GUINT_TO_POINTER (self->priv->handle),
+ GUINT_TO_POINTER (self->priv->peer_flags));
+ future_svc_channel_type_call_emit_call_members_changed (self,
+ uu_map, empty_au);
+ g_hash_table_unref (uu_map);
+ g_array_free (empty_au, TRUE);
+
+
+ /* In this example there is no real contact, so just simulate them
+ * answering after a short time - unless the contact's name
+ * contains "(no answer)" or "(busy)" */
+
+ peer = tp_handle_inspect (contact_repo, self->priv->handle);
+
+ if (strstr (peer, "(busy)") != NULL)
+ {
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ simulate_contact_busy_cb, g_object_ref (self),
+ g_object_unref);
+ }
+ else if (strstr (peer, "(no answer)") != NULL)
+ {
+ /* do nothing - the call just rings forever */
+ }
+ else
+ {
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ simulate_contact_answered_cb, g_object_ref (self),
+ g_object_unref);
+ }
+
+ return FALSE;
+}
+
+static void
+example_call_channel_initiate_outgoing (ExampleCallChannel *self)
+{
+ g_message ("SIGNALLING: send: new streamed media call");
+ example_call_channel_set_state (self,
+ FUTURE_CALL_STATE_PENDING_RECEIVER, 0,
+ tp_base_connection_get_self_handle (self->priv->conn),
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ NULL);
+
+ /* After a moment, we're sent an informational message saying it's ringing */
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ simulate_contact_ringing_cb, g_object_ref (self),
+ g_object_unref);
+}
+
+static void
+call_ringing (FutureSvcChannelTypeCall *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface);
+ GError *error = NULL;
+
+ if (self->priv->locally_requested)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Ringing() makes no sense on an outgoing call");
+ goto finally;
+ }
+
+ if (self->priv->call_state != FUTURE_CALL_STATE_PENDING_RECEIVER)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Ringing() makes no sense now that we're not pending receiver");
+ goto finally;
+ }
+
+ g_message ("SIGNALLING: send: ring, ring!");
+
+ example_call_channel_set_state (self, FUTURE_CALL_STATE_PENDING_RECEIVER,
+ self->priv->call_flags | FUTURE_CALL_FLAG_LOCALLY_RINGING,
+ tp_base_connection_get_self_handle (self->priv->conn),
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", NULL);
+
+finally:
+ if (error == NULL)
+ {
+ future_svc_channel_type_call_return_from_ringing (context);
+ }
+ else
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+static void
+accept_incoming_call (ExampleCallChannel *self)
+{
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ GHashTableIter iter;
+ gpointer v;
+
+ g_assert (self->priv->call_state == FUTURE_CALL_STATE_PENDING_RECEIVER);
+
+ g_message ("SIGNALLING: send: Accepting incoming call from %s",
+ tp_handle_inspect (contact_repo, self->priv->handle));
+
+ example_call_channel_set_state (self,
+ FUTURE_CALL_STATE_ACCEPTED, 0,
+ tp_base_connection_get_self_handle (self->priv->conn),
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ NULL);
+
+ g_hash_table_iter_init (&iter, self->priv->contents);
+
+ while (g_hash_table_iter_next (&iter, NULL, &v))
+ {
+ ExampleCallStream *stream = example_call_content_get_stream (v);
+ guint disposition;
+
+ g_object_get (v,
+ "disposition", &disposition,
+ NULL);
+
+ if (stream == NULL ||
+ disposition != FUTURE_CALL_CONTENT_DISPOSITION_INITIAL)
+ continue;
+
+ /* we accept the proposed stream direction */
+ example_call_stream_accept_proposed_direction (stream);
+ }
+}
+
+static void
+call_accept (FutureSvcChannelTypeCall *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface);
+
+ if (self->priv->locally_requested)
+ {
+ if (self->priv->call_state == FUTURE_CALL_STATE_PENDING_INITIATOR)
+ {
+ /* Take the contents we've already added, and make them happen */
+ example_call_channel_initiate_outgoing (self);
+
+ future_svc_channel_type_call_return_from_accept (context);
+ }
+ else if (self->priv->call_state == FUTURE_CALL_STATE_ENDED)
+ {
+ GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "This call has already ended" };
+
+ dbus_g_method_return_error (context, &na);
+ }
+ else
+ {
+ GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "This outgoing call has already been started" };
+
+ dbus_g_method_return_error (context, &na);
+ }
+ }
+ else
+ {
+ if (self->priv->call_state == FUTURE_CALL_STATE_PENDING_RECEIVER)
+ {
+ accept_incoming_call (self);
+ future_svc_channel_type_call_return_from_accept (context);
+ }
+ else if (self->priv->call_state == FUTURE_CALL_STATE_ENDED)
+ {
+ GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "This call has already ended" };
+
+ dbus_g_method_return_error (context, &na);
+ }
+ else
+ {
+ GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "This incoming call has already been accepted" };
+
+ dbus_g_method_return_error (context, &na);
+ }
+ }
+}
+
+static void
+call_hangup (FutureSvcChannelTypeCall *iface,
+ guint reason,
+ const gchar *detailed_reason,
+ const gchar *message G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface);
+
+ if (self->priv->call_state == FUTURE_CALL_STATE_ENDED)
+ {
+ GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "This call has already ended" };
+
+ dbus_g_method_return_error (context, &na);
+ return;
+ }
+ else
+ {
+ example_call_channel_terminate (self,
+ tp_base_connection_get_self_handle (self->priv->conn),
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE, reason, detailed_reason);
+ future_svc_channel_type_call_return_from_hangup (context);
+ }
+}
+
+static void
+call_add_content (FutureSvcChannelTypeCall *iface,
+ const gchar *content_name,
+ guint content_type,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface);
+ GError *error = NULL;
+ gchar *content_path;
+ ExampleCallContent *content;
+
+ switch (content_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 supported Media_Stream_Type", content_type);
+ goto error;
+ }
+
+ content = example_call_channel_add_content (self, content_type, TRUE, FALSE,
+ content_name, &error);
+
+ if (content == NULL)
+ goto error;
+
+ g_object_get (content,
+ "object-path", &content_path,
+ NULL);
+ future_svc_channel_type_call_return_from_add_content (context,
+ content_path);
+ g_free (content_path);
+
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+call_iface_init (gpointer iface,
+ gpointer data)
+{
+ FutureSvcChannelTypeCallClass *klass = iface;
+
+#define IMPLEMENT(x) \
+ future_svc_channel_type_call_implement_##x (klass, call_##x)
+ IMPLEMENT (ringing);
+ IMPLEMENT (hangup);
+ IMPLEMENT (accept);
+ IMPLEMENT (add_content);
+#undef IMPLEMENT
+}
+
+static gboolean
+simulate_hold (gpointer p)
+{
+ ExampleCallChannel *self = p;
+
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_HELD;
+ g_message ("SIGNALLING: hold state changed to held");
+ tp_svc_channel_interface_hold_emit_hold_state_changed (self,
+ self->priv->hold_state, self->priv->hold_state_reason);
+
+ example_call_channel_set_state (self, self->priv->call_state,
+ self->priv->call_flags | FUTURE_CALL_FLAG_LOCALLY_HELD,
+ tp_base_connection_get_self_handle (self->priv->conn),
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", NULL);
+
+ return FALSE;
+}
+
+static gboolean
+simulate_unhold (gpointer p)
+{
+ ExampleCallChannel *self = p;
+
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_UNHELD;
+ g_message ("SIGNALLING: hold state changed to unheld");
+ tp_svc_channel_interface_hold_emit_hold_state_changed (self,
+ self->priv->hold_state, self->priv->hold_state_reason);
+
+ example_call_channel_set_state (self, self->priv->call_state,
+ self->priv->call_flags & ~FUTURE_CALL_FLAG_LOCALLY_HELD,
+ tp_base_connection_get_self_handle (self->priv->conn),
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", NULL);
+
+ return FALSE;
+}
+
+static gboolean
+simulate_inability_to_unhold (gpointer p)
+{
+ ExampleCallChannel *self = p;
+
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_PENDING_HOLD;
+ g_message ("SIGNALLING: unable to unhold - hold state changed to "
+ "pending hold");
+ tp_svc_channel_interface_hold_emit_hold_state_changed (self,
+ self->priv->hold_state, self->priv->hold_state_reason);
+ /* hold again */
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ simulate_hold, g_object_ref (self),
+ g_object_unref);
+ return FALSE;
+}
+
+static void
+hold_get_hold_state (TpSvcChannelInterfaceHold *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface);
+
+ tp_svc_channel_interface_hold_return_from_get_hold_state (context,
+ self->priv->hold_state, self->priv->hold_state_reason);
+}
+
+static void
+hold_request_hold (TpSvcChannelInterfaceHold *iface,
+ gboolean hold,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+ const gchar *peer;
+ GSourceFunc callback;
+
+ if ((hold && self->priv->hold_state == TP_LOCAL_HOLD_STATE_HELD) ||
+ (!hold && self->priv->hold_state == TP_LOCAL_HOLD_STATE_UNHELD))
+ {
+ tp_svc_channel_interface_hold_return_from_request_hold (context);
+ return;
+ }
+
+ peer = tp_handle_inspect (contact_repo, self->priv->handle);
+
+ if (!hold && strstr (peer, "(no unhold)") != NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "unable to unhold");
+ goto error;
+ }
+
+ self->priv->hold_state_reason = TP_LOCAL_HOLD_STATE_REASON_REQUESTED;
+
+ if (hold)
+ {
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_PENDING_HOLD;
+ callback = simulate_hold;
+ }
+ else
+ {
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_PENDING_UNHOLD;
+
+ peer = tp_handle_inspect (contact_repo, self->priv->handle);
+
+ if (strstr (peer, "(inability to unhold)") != NULL)
+ {
+ callback = simulate_inability_to_unhold;
+ }
+ else
+ {
+ callback = simulate_unhold;
+ }
+ }
+
+ g_message ("SIGNALLING: hold state changed to pending %s",
+ (hold ? "hold" : "unhold"));
+ tp_svc_channel_interface_hold_emit_hold_state_changed (iface,
+ self->priv->hold_state, self->priv->hold_state_reason);
+ /* No need to change the call flags - we never change the actual hold state
+ * here, only the pending hold state */
+
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ callback, g_object_ref (self),
+ g_object_unref);
+
+ tp_svc_channel_interface_hold_return_from_request_hold (context);
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+
+void
+hold_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelInterfaceHoldClass *klass = iface;
+
+#define IMPLEMENT(x) \
+ tp_svc_channel_interface_hold_implement_##x (klass, hold_##x)
+ IMPLEMENT (get_hold_state);
+ IMPLEMENT (request_hold);
+#undef IMPLEMENT
+}
diff --git a/examples/future/call-cm/call-channel.h b/examples/future/call-cm/call-channel.h
new file mode 100644
index 000000000..26b9c85a5
--- /dev/null
+++ b/examples/future/call-cm/call-channel.h
@@ -0,0 +1,74 @@
+/*
+ * call-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_CALL_CHANNEL_H
+#define EXAMPLE_CALL_CHANNEL_H
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallChannel ExampleCallChannel;
+typedef struct _ExampleCallChannelPrivate
+ ExampleCallChannelPrivate;
+
+typedef struct _ExampleCallChannelClass
+ ExampleCallChannelClass;
+typedef struct _ExampleCallChannelClassPrivate
+ ExampleCallChannelClassPrivate;
+
+GType example_call_channel_get_type (void);
+
+#define EXAMPLE_TYPE_CALL_CHANNEL \
+ (example_call_channel_get_type ())
+#define EXAMPLE_CALL_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CALL_CHANNEL, \
+ ExampleCallChannel))
+#define EXAMPLE_CALL_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CALL_CHANNEL, \
+ ExampleCallChannelClass))
+#define EXAMPLE_IS_CALL_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CALL_CHANNEL))
+#define EXAMPLE_IS_CALL_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CALL_CHANNEL))
+#define EXAMPLE_CALL_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_CHANNEL, \
+ ExampleCallChannelClass))
+
+struct _ExampleCallChannelClass {
+ GObjectClass parent_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+
+ ExampleCallChannelClassPrivate *priv;
+};
+
+struct _ExampleCallChannel {
+ GObject parent;
+
+ ExampleCallChannelPrivate *priv;
+};
+
+void example_call_channel_disconnected (ExampleCallChannel *self);
+
+G_END_DECLS
+
+#endif
diff --git a/examples/future/call-cm/call-content.c b/examples/future/call-cm/call-content.c
new file mode 100644
index 000000000..b6097acef
--- /dev/null
+++ b/examples/future/call-cm/call-content.c
@@ -0,0 +1,369 @@
+/*
+ * call-content.c - a content in a call.
+ *
+ * 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 "call-content.h"
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "extensions/extensions.h"
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallContent,
+ example_call_content,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ /* no methods, so no vtable needed */
+ G_IMPLEMENT_INTERFACE (FUTURE_TYPE_SVC_CALL_CONTENT, NULL))
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CONNECTION,
+ PROP_NAME,
+ PROP_TYPE,
+ PROP_CREATOR,
+ PROP_DISPOSITION,
+ PROP_STREAM_PATHS,
+ N_PROPS
+};
+
+struct _ExampleCallContentPrivate
+{
+ gchar *object_path;
+ TpBaseConnection *conn;
+ gchar *name;
+ TpMediaStreamType type;
+ TpHandle creator;
+ FutureCallContentDisposition disposition;
+ ExampleCallStream *stream;
+};
+
+static void
+example_call_content_init (ExampleCallContent *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALL_CONTENT,
+ ExampleCallContentPrivate);
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleCallContent *self = EXAMPLE_CALL_CONTENT (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_call_content_parent_class)->constructed;
+ TpHandleRepoIface *contact_repo;
+ TpDBusDaemon *dbus_daemon;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ dbus_daemon = tp_dbus_daemon_dup (NULL);
+ g_return_if_fail (dbus_daemon != NULL);
+
+ dbus_g_connection_register_g_object (
+ tp_proxy_get_dbus_connection (dbus_daemon),
+ self->priv->object_path, object);
+
+ g_object_unref (dbus_daemon);
+ dbus_daemon = NULL;
+
+ contact_repo = tp_base_connection_get_handles (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT);
+ tp_handle_ref (contact_repo, self->priv->creator);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallContent *self = EXAMPLE_CALL_CONTENT (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, self->priv->name);
+ break;
+
+ case PROP_CREATOR:
+ g_value_set_uint (value, self->priv->creator);
+ break;
+
+ case PROP_TYPE:
+ g_value_set_uint (value, self->priv->type);
+ break;
+
+ case PROP_DISPOSITION:
+ g_value_set_uint (value, self->priv->disposition);
+ break;
+
+ case PROP_STREAM_PATHS:
+ {
+ GPtrArray *paths = g_ptr_array_sized_new (1);
+
+ if (self->priv->stream != NULL)
+ {
+ gchar *path;
+
+ g_object_get (self->priv->stream,
+ "object-path", &path,
+ NULL);
+
+ g_ptr_array_add (paths, path);
+ }
+
+ g_value_take_boxed (value, paths);
+ }
+ 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)
+{
+ ExampleCallContent *self = EXAMPLE_CALL_CONTENT (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_assert (self->priv->object_path == NULL); /* construct-only */
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+
+ case PROP_CONNECTION:
+ g_assert (self->priv->conn == NULL);
+ self->priv->conn = g_value_dup_object (value);
+ break;
+
+ case PROP_CREATOR:
+ /* 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.
+ */
+ g_assert (self->priv->creator == 0); /* construct-only */
+ self->priv->creator = g_value_get_uint (value);
+ break;
+
+ case PROP_NAME:
+ g_assert (self->priv->name == NULL); /* construct-only */
+ self->priv->name = g_value_dup_string (value);
+ break;
+
+ case PROP_TYPE:
+ self->priv->type = g_value_get_uint (value);
+ break;
+
+ case PROP_DISPOSITION:
+ self->priv->disposition = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleCallContent *self = EXAMPLE_CALL_CONTENT (object);
+
+ if (self->priv->stream != NULL)
+ {
+ g_object_unref (self->priv->stream);
+ self->priv->stream = NULL;
+ }
+
+ if (self->priv->conn != NULL)
+ {
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_unref (contact_handles, self->priv->creator);
+ self->priv->creator = 0;
+
+ g_object_unref (self->priv->conn);
+ self->priv->conn = NULL;
+ }
+
+ ((GObjectClass *) example_call_content_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleCallContent *self = EXAMPLE_CALL_CONTENT (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_call_content_parent_class)->finalize;
+
+ g_free (self->priv->object_path);
+ g_free (self->priv->name);
+
+ if (chain_up != NULL)
+ chain_up (object);
+}
+
+static void
+example_call_content_class_init (ExampleCallContentClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl content_props[] = {
+ { "Name", "name", NULL },
+ { "Type", "type", NULL },
+ { "Creator", "creator", NULL },
+ { "Disposition", "disposition", NULL },
+ { "Streams", "stream-paths", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { FUTURE_IFACE_CALL_CONTENT,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ content_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallContentPrivate));
+
+ object_class->constructed = constructed;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ param_spec = g_param_spec_string ("object-path", "D-Bus object path",
+ "The D-Bus object path used for this object on the bus.", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection",
+ "Connection that owns this content",
+ 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 ("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 ("disposition",
+ "FutureCallContentDisposition",
+ "Disposition of the content",
+ 0, NUM_FUTURE_CALL_CONTENT_DISPOSITIONS - 1,
+ FUTURE_CALL_CONTENT_DISPOSITION_NONE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DISPOSITION, param_spec);
+
+ param_spec = g_param_spec_uint ("creator", "Creator's handle",
+ "The contact who initiated this content",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CREATOR, param_spec);
+
+ param_spec = g_param_spec_string ("name", "Content name",
+ "The name of the content",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NAME, param_spec);
+
+ param_spec = g_param_spec_boxed ("stream-paths", "Stream paths",
+ "Streams' object paths",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STREAM_PATHS,
+ param_spec);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallContentClass,
+ dbus_properties_class));
+}
+
+ExampleCallStream *
+example_call_content_get_stream (ExampleCallContent *self)
+{
+ g_return_val_if_fail (EXAMPLE_IS_CALL_CONTENT (self), NULL);
+ return self->priv->stream;
+}
+
+static void
+example_call_content_stream_removed_cb (ExampleCallContent *self,
+ ExampleCallStream *stream)
+{
+ gchar *path;
+
+ g_return_if_fail (EXAMPLE_IS_CALL_CONTENT (self));
+ g_return_if_fail (EXAMPLE_IS_CALL_STREAM (stream));
+ g_return_if_fail (self->priv->stream == stream);
+
+ g_object_get (stream,
+ "object-path", &path,
+ NULL);
+ future_svc_call_content_emit_stream_removed (self, path);
+ g_free (path);
+
+ g_object_unref (self->priv->stream);
+ self->priv->stream = NULL;
+}
+
+void
+example_call_content_add_stream (ExampleCallContent *self,
+ ExampleCallStream *stream)
+{
+ gchar *path;
+
+ g_return_if_fail (EXAMPLE_IS_CALL_CONTENT (self));
+ g_return_if_fail (EXAMPLE_IS_CALL_STREAM (stream));
+ g_return_if_fail (self->priv->stream == NULL);
+
+ self->priv->stream = g_object_ref (stream);
+ g_object_get (stream,
+ "object-path", &path,
+ NULL);
+ future_svc_call_content_emit_stream_added (self, path);
+ g_free (path);
+
+ tp_g_signal_connect_object (stream, "removed",
+ G_CALLBACK (example_call_content_stream_removed_cb), self,
+ G_CONNECT_SWAPPED);
+}
diff --git a/examples/future/call-cm/call-content.h b/examples/future/call-cm/call-content.h
new file mode 100644
index 000000000..4425dfa23
--- /dev/null
+++ b/examples/future/call-cm/call-content.h
@@ -0,0 +1,81 @@
+/*
+ * call-content.h - header for an example content
+ *
+ * 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_CALL_CONTENT_H
+#define EXAMPLE_CALL_CONTENT_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "call-stream.h"
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallContent ExampleCallContent;
+typedef struct _ExampleCallContentPrivate
+ ExampleCallContentPrivate;
+
+typedef struct _ExampleCallContentClass
+ ExampleCallContentClass;
+typedef struct _ExampleCallContentClassPrivate
+ ExampleCallContentClassPrivate;
+
+GType example_call_content_get_type (void);
+
+#define EXAMPLE_TYPE_CALL_CONTENT \
+ (example_call_content_get_type ())
+#define EXAMPLE_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CALL_CONTENT, \
+ ExampleCallContent))
+#define EXAMPLE_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CALL_CONTENT, \
+ ExampleCallContentClass))
+#define EXAMPLE_IS_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CALL_CONTENT))
+#define EXAMPLE_IS_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CALL_CONTENT))
+#define EXAMPLE_CALL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_CONTENT, \
+ ExampleCallContentClass))
+
+struct _ExampleCallContentClass {
+ GObjectClass parent_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+
+ ExampleCallContentClassPrivate *priv;
+};
+
+struct _ExampleCallContent {
+ GObject parent;
+
+ ExampleCallContentPrivate *priv;
+};
+
+/* In this example, each content can only have one stream. */
+ExampleCallStream *example_call_content_get_stream (ExampleCallContent *self);
+
+void example_call_content_add_stream (ExampleCallContent *self,
+ ExampleCallStream *stream);
+
+G_END_DECLS
+
+#endif
diff --git a/examples/future/call-cm/call-manager.c b/examples/future/call-cm/call-manager.c
new file mode 100644
index 000000000..9bb34a5a2
--- /dev/null
+++ b/examples/future/call-cm/call-manager.c
@@ -0,0 +1,537 @@
+/*
+ * call-manager.c - an example channel manager for Call channels.
+ *
+ * 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 "call-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 "extensions/extensions.h"
+
+#include "call-channel.h"
+
+static void channel_manager_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallManager,
+ example_call_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 _ExampleCallManagerPrivate
+{
+ TpBaseConnection *conn;
+ guint simulation_delay;
+
+ /* Map from reffed ExampleCallChannel to the same pointer; used as a
+ * set.
+ */
+ GHashTable *channels;
+
+ /* Next channel will be ("CallChannel%u", next_channel_index) */
+ guint next_channel_index;
+
+ gulong status_changed_id;
+ gulong available_id;
+};
+
+static void
+example_call_manager_init (ExampleCallManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALL_MANAGER,
+ ExampleCallManagerPrivate);
+
+ self->priv->conn = NULL;
+ self->priv->channels = g_hash_table_new_full (NULL, NULL, g_object_unref,
+ NULL);
+ self->priv->status_changed_id = 0;
+ self->priv->available_id = 0;
+}
+
+static void
+example_call_manager_close_all (ExampleCallManager *self)
+{
+ if (self->priv->channels != NULL)
+ {
+ GHashTable *tmp = self->priv->channels;
+ GHashTableIter iter;
+ gpointer v;
+
+ self->priv->channels = NULL;
+
+ g_hash_table_iter_init (&iter, tmp);
+
+ while (g_hash_table_iter_next (&iter, NULL, &v))
+ example_call_channel_disconnected (v);
+
+ g_hash_table_unref (tmp);
+ }
+
+ if (self->priv->available_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->conn,
+ self->priv->available_id);
+ self->priv->available_id = 0;
+ }
+
+ 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)
+{
+ ExampleCallManager *self = EXAMPLE_CALL_MANAGER (object);
+
+ example_call_manager_close_all (self);
+ g_assert (self->priv->channels == NULL);
+
+ ((GObjectClass *) example_call_manager_parent_class)->dispose (
+ object);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallManager *self = EXAMPLE_CALL_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)
+{
+ ExampleCallManager *self = EXAMPLE_CALL_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,
+ ExampleCallManager *self)
+{
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ {
+ example_call_manager_close_all (self);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static ExampleCallChannel *new_channel (ExampleCallManager *self,
+ TpHandle handle, TpHandle initiator, gpointer request_token,
+ gboolean initial_audio, gboolean initial_video);
+
+static gboolean
+simulate_incoming_call_cb (gpointer p)
+{
+ ExampleCallManager *self = p;
+ TpHandleRepoIface *contact_repo;
+ TpHandle caller;
+
+ /* do nothing if we've been disconnected while waiting for the contact to
+ * call us */
+ if (self->priv->available_id == 0)
+ return FALSE;
+
+ /* We're called by someone whose ID on the IM service is "caller" */
+ contact_repo = tp_base_connection_get_handles (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT);
+ caller = tp_handle_ensure (contact_repo, "caller", NULL, NULL);
+
+ new_channel (self, caller, caller, NULL, TRUE, FALSE);
+
+ return FALSE;
+}
+
+/* Whenever our presence changes from away to available, and whenever our
+ * presence message changes while remaining available, simulate a call from
+ * a contact */
+static void
+available_cb (GObject *conn G_GNUC_UNUSED,
+ const gchar *message,
+ ExampleCallManager *self)
+{
+ g_timeout_add_full (G_PRIORITY_DEFAULT, self->priv->simulation_delay,
+ simulate_incoming_call_cb, g_object_ref (self), g_object_unref);
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleCallManager *self = EXAMPLE_CALL_MANAGER (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_call_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);
+
+ self->priv->available_id = g_signal_connect (self->priv->conn,
+ "available", (GCallback) available_cb, self);
+}
+
+static void
+example_call_manager_class_init (ExampleCallManagerClass *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 (ExampleCallManagerPrivate));
+}
+
+static void
+example_call_manager_foreach_channel (TpChannelManager *iface,
+ TpExportableChannelFunc callback,
+ gpointer user_data)
+{
+ ExampleCallManager *self = EXAMPLE_CALL_MANAGER (iface);
+ GHashTableIter iter;
+ gpointer chan;
+
+ g_hash_table_iter_init (&iter, self->priv->channels);
+
+ while (g_hash_table_iter_next (&iter, &chan, NULL))
+ callback (chan, user_data);
+}
+
+static void
+channel_closed_cb (ExampleCallChannel *chan,
+ ExampleCallManager *self)
+{
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (self->priv->channels != NULL)
+ g_hash_table_remove (self->priv->channels, chan);
+}
+
+static ExampleCallChannel *
+new_channel (ExampleCallManager *self,
+ TpHandle handle,
+ TpHandle initiator,
+ gpointer request_token,
+ gboolean initial_audio,
+ gboolean initial_video)
+{
+ ExampleCallChannel *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/CallChannel%u",
+ self->priv->conn->object_path, self->priv->next_channel_index++);
+
+ chan = g_object_new (EXAMPLE_TYPE_CALL_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,
+ "initial-audio", initial_audio,
+ "initial-video", initial_video,
+ NULL);
+
+ g_free (object_path);
+
+ g_signal_connect (chan, "closed", G_CALLBACK (channel_closed_cb), self);
+
+ g_hash_table_insert (self->priv->channels, chan, 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 audio_fixed_properties[] = {
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+ FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO,
+ NULL
+};
+
+static const gchar * const video_fixed_properties[] = {
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+ FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO,
+ NULL
+};
+
+static const gchar * const audio_allowed_properties[] = {
+ TP_PROP_CHANNEL_TARGET_HANDLE,
+ TP_PROP_CHANNEL_TARGET_ID,
+ FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO,
+ NULL
+};
+
+static const gchar * const video_allowed_properties[] = {
+ TP_PROP_CHANNEL_TARGET_HANDLE,
+ TP_PROP_CHANNEL_TARGET_ID,
+ FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO,
+ NULL
+};
+
+static void
+example_call_manager_foreach_channel_class (
+ TpChannelManager *manager,
+ TpChannelManagerChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ G_TYPE_STRING, FUTURE_IFACE_CHANNEL_TYPE_CALL,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
+ FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, G_TYPE_BOOLEAN, TRUE,
+ NULL);
+
+ func (manager, table, audio_allowed_properties, user_data);
+
+ g_hash_table_remove (table, FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO);
+ tp_asv_set_boolean (table, FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO,
+ TRUE);
+
+ func (manager, table, video_allowed_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+static gboolean
+example_call_manager_request (ExampleCallManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ TpHandle handle;
+ GError *error = NULL;
+ gboolean initial_audio, initial_video;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_PROP_CHANNEL_CHANNEL_TYPE),
+ FUTURE_IFACE_CHANNEL_TYPE_CALL))
+ {
+ return FALSE;
+ }
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) != TP_HANDLE_TYPE_CONTACT)
+ {
+ return FALSE;
+ }
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
+ g_assert (handle != 0);
+
+ initial_audio = tp_asv_get_boolean (request_properties,
+ FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, NULL);
+ initial_video = tp_asv_get_boolean (request_properties,
+ FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, NULL);
+
+ if (!initial_audio && !initial_video)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Call channels must initially have either audio or video content");
+ goto error;
+ }
+
+ /* the set of (fixed | allowed) properties is the same for audio and video,
+ * so we only need to check with one set */
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ audio_fixed_properties, audio_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 */
+ GHashTableIter iter;
+ gpointer chan;
+
+ g_hash_table_iter_init (&iter, self->priv->channels);
+
+ while (g_hash_table_iter_next (&iter, &chan, NULL))
+ {
+ guint its_handle;
+
+ g_object_get (chan,
+ "handle", &its_handle,
+ NULL);
+
+ if (its_handle == handle)
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (chan));
+ return TRUE;
+ }
+ }
+ }
+
+ new_channel (self, handle, self->priv->conn->self_handle, request_token,
+ initial_audio, initial_video);
+ 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_call_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_call_manager_request (
+ EXAMPLE_CALL_MANAGER (manager),
+ request_token, request_properties, TRUE);
+}
+
+static gboolean
+example_call_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_call_manager_request (
+ EXAMPLE_CALL_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_call_manager_foreach_channel;
+ iface->foreach_channel_class = example_call_manager_foreach_channel_class;
+ iface->create_channel = example_call_manager_create_channel;
+ iface->ensure_channel = example_call_manager_ensure_channel;
+ /* In this channel manager, RequestChannel is not supported; Call is not
+ * designed to work with the old RequestChannel API. */
+ iface->request_channel = NULL;
+}
diff --git a/examples/future/call-cm/call-manager.h b/examples/future/call-cm/call-manager.h
new file mode 100644
index 000000000..96f5aee39
--- /dev/null
+++ b/examples/future/call-cm/call-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_CALL_MANAGER_H
+#define EXAMPLE_CALL_MANAGER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallManager ExampleCallManager;
+typedef struct _ExampleCallManagerPrivate
+ ExampleCallManagerPrivate;
+
+typedef struct _ExampleCallManagerClass
+ ExampleCallManagerClass;
+typedef struct _ExampleCallManagerClassPrivate
+ ExampleCallManagerClassPrivate;
+
+struct _ExampleCallManagerClass {
+ GObjectClass parent_class;
+
+ ExampleCallManagerClassPrivate *priv;
+};
+
+struct _ExampleCallManager {
+ GObject parent;
+
+ ExampleCallManagerPrivate *priv;
+};
+
+GType example_call_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CALL_MANAGER \
+ (example_call_manager_get_type ())
+#define EXAMPLE_CALL_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALL_MANAGER, \
+ ExampleCallManager))
+#define EXAMPLE_CALL_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALL_MANAGER, \
+ ExampleCallManagerClass))
+#define EXAMPLE_IS_CALL_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALL_MANAGER))
+#define EXAMPLE_IS_CALL_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALL_MANAGER))
+#define EXAMPLE_CALL_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_MANAGER, \
+ ExampleCallManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/examples/future/call-cm/call-stream.c b/examples/future/call-cm/call-stream.c
new file mode 100644
index 000000000..cf7e49c02
--- /dev/null
+++ b/examples/future/call-cm/call-stream.c
@@ -0,0 +1,705 @@
+/*
+ * call-stream.c - a stream in a call.
+ *
+ * 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 "call-stream.h"
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/gtypes.h>
+
+#include "extensions/extensions.h"
+
+static void stream_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallStream,
+ example_call_stream,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (FUTURE_TYPE_SVC_CALL_STREAM, stream_iface_init))
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CONNECTION,
+ PROP_HANDLE,
+ PROP_SIMULATION_DELAY,
+ PROP_LOCALLY_REQUESTED,
+ PROP_SENDERS,
+ N_PROPS
+};
+
+enum
+{
+ SIGNAL_REMOVED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+struct _ExampleCallStreamPrivate
+{
+ gchar *object_path;
+ TpBaseConnection *conn;
+ TpHandle handle;
+ FutureSendingState local_sending_state;
+ FutureSendingState remote_sending_state;
+
+ guint simulation_delay;
+
+ guint connected_event_id;
+
+ gboolean locally_requested;
+ gboolean removed;
+};
+
+static void
+example_call_stream_init (ExampleCallStream *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALL_STREAM,
+ ExampleCallStreamPrivate);
+
+ /* start off directionless */
+ self->priv->local_sending_state = FUTURE_SENDING_STATE_NONE;
+ self->priv->remote_sending_state = FUTURE_SENDING_STATE_NONE;
+}
+
+static void example_call_stream_receive_direction_request (
+ ExampleCallStream *self, gboolean local_send, gboolean remote_send);
+static void example_call_stream_change_direction (ExampleCallStream *self,
+ gboolean want_to_send, gboolean want_to_receive);
+
+static void
+constructed (GObject *object)
+{
+ ExampleCallStream *self = EXAMPLE_CALL_STREAM (object);
+ TpDBusDaemon *dbus_daemon;
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_call_stream_parent_class)->constructed;
+
+ dbus_daemon = tp_dbus_daemon_dup (NULL);
+ g_return_if_fail (dbus_daemon != NULL);
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ dbus_daemon = tp_dbus_daemon_dup (NULL);
+ g_return_if_fail (dbus_daemon != NULL);
+
+ dbus_g_connection_register_g_object (
+ tp_proxy_get_dbus_connection (dbus_daemon),
+ self->priv->object_path, object);
+
+ g_object_unref (dbus_daemon);
+ dbus_daemon = NULL;
+
+ if (self->priv->locally_requested)
+ {
+ example_call_stream_change_direction (self, TRUE, TRUE);
+ }
+ else
+ {
+ example_call_stream_receive_direction_request (self, TRUE, TRUE);
+ }
+
+ 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)
+{
+ ExampleCallStream *self = EXAMPLE_CALL_STREAM (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+
+ 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;
+
+ case PROP_LOCALLY_REQUESTED:
+ g_value_set_boolean (value, self->priv->locally_requested);
+ break;
+
+ case PROP_SENDERS:
+ {
+ GHashTable *senders = g_hash_table_new (NULL, NULL);
+
+ g_hash_table_insert (senders, GUINT_TO_POINTER (self->priv->handle),
+ GUINT_TO_POINTER (self->priv->remote_sending_state));
+
+ g_hash_table_insert (senders,
+ GUINT_TO_POINTER (tp_base_connection_get_self_handle (
+ self->priv->conn)),
+ GUINT_TO_POINTER (self->priv->local_sending_state));
+
+ g_value_take_boxed (value, senders);
+ }
+ 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)
+{
+ ExampleCallStream *self = EXAMPLE_CALL_STREAM (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_assert (self->priv->object_path == NULL); /* construct-only */
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+
+ case PROP_HANDLE:
+ self->priv->handle = g_value_get_uint (value);
+ break;
+
+ case PROP_CONNECTION:
+ g_assert (self->priv->conn == NULL);
+ self->priv->conn = g_value_dup_object (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ case PROP_LOCALLY_REQUESTED:
+ self->priv->locally_requested = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleCallStream *self = EXAMPLE_CALL_STREAM (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ example_call_stream_close (self);
+
+ if (self->priv->handle != 0)
+ {
+ tp_handle_unref (contact_repo, self->priv->handle);
+ self->priv->handle = 0;
+ }
+
+ if (self->priv->conn != NULL)
+ {
+ g_object_unref (self->priv->conn);
+ self->priv->conn = NULL;
+ }
+
+ ((GObjectClass *) example_call_stream_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleCallStream *self = EXAMPLE_CALL_STREAM (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_call_stream_parent_class)->finalize;
+
+ g_free (self->priv->object_path);
+
+ if (chain_up != NULL)
+ chain_up (object);
+}
+
+static void
+example_call_stream_class_init (ExampleCallStreamClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl stream_props[] = {
+ { "Senders", "senders", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { FUTURE_IFACE_CALL_STREAM,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ stream_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallStreamPrivate));
+
+ object_class->constructed = constructed;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ param_spec = g_param_spec_string ("object-path", "D-Bus object path",
+ "The D-Bus object path used for this object on the bus.", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection",
+ "Connection that (indirectly) owns this stream",
+ 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 ("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 ("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);
+
+ param_spec = g_param_spec_boolean ("locally-requested", "Locally 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_LOCALLY_REQUESTED,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("senders", "Senders",
+ "Map from contact handles to their sending states",
+ FUTURE_HASH_TYPE_CONTACT_SENDING_STATE_MAP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SENDERS, 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);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallStreamClass,
+ dbus_properties_class));
+}
+
+void
+example_call_stream_close (ExampleCallStream *self)
+{
+ if (!self->priv->removed)
+ {
+ self->priv->removed = TRUE;
+
+ g_message ("%s: Sending to server: Closing stream",
+ self->priv->object_path);
+
+ if (self->priv->connected_event_id != 0)
+ {
+ g_source_remove (self->priv->connected_event_id);
+ }
+
+ /* this has to come last, because the MediaChannel may unref us in
+ * response to the removed signal */
+ g_signal_emit (self, signals[SIGNAL_REMOVED], 0);
+ }
+}
+
+void
+example_call_stream_accept_proposed_direction (ExampleCallStream *self)
+{
+ GHashTable *updated_senders;
+ GArray *removed_senders;
+
+ if (self->priv->removed ||
+ self->priv->local_sending_state != FUTURE_SENDING_STATE_PENDING_SEND)
+ return;
+
+ g_message ("%s: SIGNALLING: Sending to server: OK, I'll send you media",
+ self->priv->object_path);
+
+ self->priv->local_sending_state = FUTURE_SENDING_STATE_SENDING;
+
+ updated_senders = g_hash_table_new (NULL, NULL);
+ removed_senders = g_array_sized_new (FALSE, FALSE, sizeof (guint), 0);
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (tp_base_connection_get_self_handle (self->priv->conn)),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_SENDING));
+ future_svc_call_stream_emit_senders_changed (self, updated_senders,
+ removed_senders);
+ g_hash_table_unref (updated_senders);
+ g_array_free (removed_senders, TRUE);
+}
+
+void
+example_call_stream_simulate_contact_agreed_to_send (ExampleCallStream *self)
+{
+ GHashTable *updated_senders;
+ GArray *removed_senders;
+
+ if (self->priv->removed ||
+ self->priv->remote_sending_state != FUTURE_SENDING_STATE_PENDING_SEND)
+ return;
+
+ g_message ("%s: SIGNALLING: received: OK, I'll send you media",
+ self->priv->object_path);
+
+ self->priv->remote_sending_state = FUTURE_SENDING_STATE_SENDING;
+
+ updated_senders = g_hash_table_new (NULL, NULL);
+ removed_senders = g_array_sized_new (FALSE, FALSE, sizeof (guint), 0);
+ g_hash_table_insert (updated_senders, GUINT_TO_POINTER (self->priv->handle),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_SENDING));
+ future_svc_call_stream_emit_senders_changed (self, updated_senders,
+ removed_senders);
+ g_hash_table_unref (updated_senders);
+ g_array_free (removed_senders, TRUE);
+}
+
+static gboolean
+simulate_contact_agreed_to_send_cb (gpointer p)
+{
+ example_call_stream_simulate_contact_agreed_to_send (p);
+ return FALSE;
+}
+
+static void
+example_call_stream_change_direction (ExampleCallStream *self,
+ gboolean want_to_send, gboolean want_to_receive)
+{
+ GHashTable *updated_senders = g_hash_table_new (NULL, NULL);
+
+ if (want_to_send)
+ {
+ if (self->priv->local_sending_state != FUTURE_SENDING_STATE_SENDING)
+ {
+ if (self->priv->local_sending_state ==
+ FUTURE_SENDING_STATE_PENDING_SEND)
+ {
+ g_message ("%s: SIGNALLING: send: I will now send you media",
+ self->priv->object_path);
+ }
+
+ g_message ("%s: MEDIA: sending media to peer",
+ self->priv->object_path);
+ self->priv->local_sending_state = FUTURE_SENDING_STATE_SENDING;
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (tp_base_connection_get_self_handle (
+ self->priv->conn)),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_SENDING));
+ }
+ }
+ else
+ {
+ if (self->priv->local_sending_state == FUTURE_SENDING_STATE_SENDING)
+ {
+ g_message ("%s: SIGNALLING: send: I will no longer send you media",
+ self->priv->object_path);
+ g_message ("%s: MEDIA: no longer sending media to peer",
+ self->priv->object_path);
+ self->priv->local_sending_state = FUTURE_SENDING_STATE_NONE;
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (tp_base_connection_get_self_handle (
+ self->priv->conn)),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE));
+ }
+ else if (self->priv->local_sending_state ==
+ FUTURE_SENDING_STATE_PENDING_SEND)
+ {
+ g_message ("%s: SIGNALLING: send: refusing to send you media",
+ self->priv->object_path);
+ self->priv->local_sending_state = FUTURE_SENDING_STATE_NONE;
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (tp_base_connection_get_self_handle (
+ self->priv->conn)),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE));
+ }
+ }
+
+ if (want_to_receive)
+ {
+ if (self->priv->remote_sending_state == FUTURE_SENDING_STATE_NONE)
+ {
+ g_message ("%s: SIGNALLING: send: send me media, please?",
+ self->priv->object_path);
+ self->priv->remote_sending_state = FUTURE_SENDING_STATE_PENDING_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);
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (self->priv->handle),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_PENDING_SEND));
+ }
+ }
+ else
+ {
+ if (self->priv->remote_sending_state != FUTURE_SENDING_STATE_NONE)
+ {
+ g_message ("%s: SIGNALLING: send: Please stop sending me media",
+ self->priv->object_path);
+ g_message ("%s: MEDIA: suppressing output of stream",
+ self->priv->object_path);
+ self->priv->remote_sending_state = FUTURE_SENDING_STATE_NONE;
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (self->priv->handle),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE));
+ }
+ }
+
+ if (g_hash_table_size (updated_senders) != 0)
+ {
+ GArray *removed_senders = g_array_sized_new (FALSE, FALSE,
+ sizeof (guint), 0);
+
+ future_svc_call_stream_emit_senders_changed (self, updated_senders,
+ removed_senders);
+
+ g_array_free (removed_senders, TRUE);
+ }
+
+ g_hash_table_unref (updated_senders);
+}
+
+/* The remote user wants to change the direction of this stream according
+ * to @local_send and @remote_send. Shall we let him? */
+static void
+example_call_stream_receive_direction_request (ExampleCallStream *self,
+ gboolean local_send,
+ gboolean remote_send)
+{
+ GHashTable *updated_senders = g_hash_table_new (NULL, NULL);
+
+ /* 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. (This is true in XMPP, for instance.)
+ *
+ * However, for this example we'll emulate a protocol where streams can be
+ * directionless.
+ */
+
+ if (local_send)
+ {
+ g_message ("%s: SIGNALLING: send: Please start sending me media",
+ self->priv->object_path);
+
+ if (self->priv->local_sending_state == FUTURE_SENDING_STATE_NONE)
+ {
+ /* ask the user for permission */
+ self->priv->local_sending_state = FUTURE_SENDING_STATE_PENDING_SEND;
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (tp_base_connection_get_self_handle (
+ self->priv->conn)),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_PENDING_SEND));
+ }
+ else
+ {
+ /* nothing to do, we're already sending (or asking the user for
+ * permission to do so) on that stream */
+ }
+ }
+ else
+ {
+ g_message ("%s: SIGNALLING: receive: Please stop sending me media",
+ self->priv->object_path);
+ g_message ("%s: SIGNALLING: reply: OK!",
+ self->priv->object_path);
+
+ if (self->priv->local_sending_state == FUTURE_SENDING_STATE_SENDING)
+ {
+ g_message ("%s: MEDIA: no longer sending media to peer",
+ self->priv->object_path);
+ self->priv->local_sending_state = FUTURE_SENDING_STATE_NONE;
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (tp_base_connection_get_self_handle (
+ self->priv->conn)),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE));
+ }
+ else if (self->priv->local_sending_state ==
+ FUTURE_SENDING_STATE_PENDING_SEND)
+ {
+ self->priv->local_sending_state = FUTURE_SENDING_STATE_NONE;
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (tp_base_connection_get_self_handle (
+ self->priv->conn)),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE));
+ }
+ else
+ {
+ /* nothing to do, we're not sending on that stream anyway */
+ }
+ }
+
+ if (remote_send)
+ {
+ g_message ("%s: SIGNALLING: receive: I will now send you media",
+ self->priv->object_path);
+
+ if (self->priv->remote_sending_state != FUTURE_SENDING_STATE_SENDING)
+ {
+ self->priv->remote_sending_state = FUTURE_SENDING_STATE_SENDING;
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (self->priv->handle),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_SENDING));
+ }
+ }
+ else
+ {
+ if (self->priv->remote_sending_state ==
+ FUTURE_SENDING_STATE_PENDING_SEND)
+ {
+ g_message ("%s: SIGNALLING: receive: No, I refuse to send you media",
+ self->priv->object_path);
+ self->priv->remote_sending_state = FUTURE_SENDING_STATE_NONE;
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (self->priv->handle),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE));
+ }
+ else if (self->priv->remote_sending_state ==
+ FUTURE_SENDING_STATE_SENDING)
+ {
+ g_message ("%s: SIGNALLING: receive: I will no longer send media",
+ self->priv->object_path);
+ self->priv->remote_sending_state = FUTURE_SENDING_STATE_NONE;
+
+ g_hash_table_insert (updated_senders,
+ GUINT_TO_POINTER (self->priv->handle),
+ GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE));
+ }
+ }
+
+ if (g_hash_table_size (updated_senders) != 0)
+ {
+ GArray *removed_senders = g_array_sized_new (FALSE, FALSE,
+ sizeof (guint), 0);
+
+ future_svc_call_stream_emit_senders_changed (self, updated_senders,
+ removed_senders);
+
+ g_array_free (removed_senders, TRUE);
+ }
+
+ g_hash_table_unref (updated_senders);
+}
+
+static void
+stream_set_sending (FutureSvcCallStream *iface G_GNUC_UNUSED,
+ gboolean sending,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallStream *self = EXAMPLE_CALL_STREAM (iface);
+
+ example_call_stream_change_direction (self, sending,
+ (self->priv->remote_sending_state == FUTURE_SENDING_STATE_SENDING));
+
+ future_svc_call_stream_return_from_set_sending (context);
+}
+
+static void
+stream_request_receiving (FutureSvcCallStream *iface,
+ TpHandle contact,
+ gboolean receive,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallStream *self = EXAMPLE_CALL_STREAM (iface);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+
+ if (!tp_handle_is_valid (contact_repo, contact, &error))
+ {
+ goto finally;
+ }
+
+ if (contact != self->priv->handle)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Can't receive from contact #%u: this stream only contains #%u",
+ contact, self->priv->handle);
+ goto finally;
+ }
+
+ example_call_stream_change_direction (self,
+ (self->priv->local_sending_state == FUTURE_SENDING_STATE_SENDING),
+ receive);
+
+finally:
+ if (error == NULL)
+ {
+ future_svc_call_stream_return_from_request_receiving (context);
+ }
+ else
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+static void
+stream_iface_init (gpointer iface,
+ gpointer data)
+{
+ FutureSvcCallStreamClass *klass = iface;
+
+#define IMPLEMENT(x) \
+ future_svc_call_stream_implement_##x (klass, stream_##x)
+ IMPLEMENT (set_sending);
+ IMPLEMENT (request_receiving);
+#undef IMPLEMENT
+}
diff --git a/examples/future/call-cm/call-stream.h b/examples/future/call-cm/call-stream.h
new file mode 100644
index 000000000..e3093202e
--- /dev/null
+++ b/examples/future/call-cm/call-stream.h
@@ -0,0 +1,82 @@
+/*
+ * call-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_CALL_STREAM_H
+#define EXAMPLE_CALL_STREAM_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallStream ExampleCallStream;
+typedef struct _ExampleCallStreamPrivate
+ ExampleCallStreamPrivate;
+
+typedef struct _ExampleCallStreamClass
+ ExampleCallStreamClass;
+typedef struct _ExampleCallStreamClassPrivate
+ ExampleCallStreamClassPrivate;
+
+GType example_call_stream_get_type (void);
+
+#define EXAMPLE_TYPE_CALL_STREAM \
+ (example_call_stream_get_type ())
+#define EXAMPLE_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CALL_STREAM, \
+ ExampleCallStream))
+#define EXAMPLE_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CALL_STREAM, \
+ ExampleCallStreamClass))
+#define EXAMPLE_IS_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CALL_STREAM))
+#define EXAMPLE_IS_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CALL_STREAM))
+#define EXAMPLE_CALL_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_STREAM, \
+ ExampleCallStreamClass))
+
+struct _ExampleCallStreamClass {
+ GObjectClass parent_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+
+ ExampleCallStreamClassPrivate *priv;
+};
+
+struct _ExampleCallStream {
+ GObject parent;
+
+ ExampleCallStreamPrivate *priv;
+};
+
+void example_call_stream_close (ExampleCallStream *self);
+void example_call_stream_accept_proposed_direction (ExampleCallStream *self);
+void example_call_stream_connect (ExampleCallStream *self);
+
+/* This controls receiving emulated network events, so it wouldn't exist in
+ * a real connection manager */
+void example_call_stream_simulate_contact_agreed_to_send (
+ ExampleCallStream *self);
+
+G_END_DECLS
+
+#endif
diff --git a/examples/future/call-cm/cm.c b/examples/future/call-cm/cm.c
new file mode 100644
index 000000000..4d06ad2a9
--- /dev/null
+++ b/examples/future/call-cm/cm.c
@@ -0,0 +1,129 @@
+/*
+ * 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 "cm.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+
+#include "conn.h"
+
+G_DEFINE_TYPE (ExampleCallConnectionManager,
+ example_call_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+struct _ExampleCallConnectionManagerPrivate
+{
+ int dummy;
+};
+
+static void
+example_call_connection_manager_init (ExampleCallConnectionManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALL_CONNECTION_MANAGER,
+ ExampleCallConnectionManagerPrivate);
+}
+
+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_call_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_call_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;
+ ExampleCallConnection *conn;
+
+ conn = EXAMPLE_CALL_CONNECTION
+ (g_object_new (EXAMPLE_TYPE_CALL_CONNECTION,
+ "account", params->account,
+ "simulation-delay", params->simulation_delay,
+ "protocol", proto,
+ NULL));
+
+ return (TpBaseConnection *) conn;
+}
+
+static void
+example_call_connection_manager_class_init (
+ ExampleCallConnectionManagerClass *klass)
+{
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallConnectionManagerPrivate));
+
+ base_class->new_connection = new_connection;
+ base_class->cm_dbus_name = "example_call";
+ base_class->protocol_params = example_protocols;
+}
diff --git a/examples/future/call-cm/cm.h b/examples/future/call-cm/cm.h
new file mode 100644
index 000000000..275abb229
--- /dev/null
+++ b/examples/future/call-cm/cm.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_CALL_CM_H
+#define EXAMPLE_CALL_CM_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallConnectionManager
+ ExampleCallConnectionManager;
+typedef struct _ExampleCallConnectionManagerPrivate
+ ExampleCallConnectionManagerPrivate;
+
+typedef struct _ExampleCallConnectionManagerClass
+ ExampleCallConnectionManagerClass;
+typedef struct _ExampleCallConnectionManagerClassPrivate
+ ExampleCallConnectionManagerClassPrivate;
+
+struct _ExampleCallConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+
+ ExampleCallConnectionManagerClassPrivate *priv;
+};
+
+struct _ExampleCallConnectionManager {
+ TpBaseConnectionManager parent;
+
+ ExampleCallConnectionManagerPrivate *priv;
+};
+
+GType example_call_connection_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CALL_CONNECTION_MANAGER \
+ (example_call_connection_manager_get_type ())
+#define EXAMPLE_CALL_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALL_CONNECTION_MANAGER, \
+ ExampleCallConnectionManager))
+#define EXAMPLE_CALL_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALL_CONNECTION_MANAGER, \
+ ExampleCallConnectionManagerClass))
+#define EXAMPLE_IS_CALL_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALL_CONNECTION_MANAGER))
+#define EXAMPLE_IS_CALL_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALL_CONNECTION_MANAGER))
+#define EXAMPLE_CALL_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_CONNECTION_MANAGER, \
+ ExampleCallConnectionManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/examples/future/call-cm/conn.c b/examples/future/call-cm/conn.c
new file mode 100644
index 000000000..321e5e0a7
--- /dev/null
+++ b/examples/future/call-cm/conn.c
@@ -0,0 +1,421 @@
+/*
+ * 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/telepathy-glib.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/handle-repo-static.h>
+
+#include "call-manager.h"
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallConnection,
+ example_call_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
+};
+
+enum
+{
+ SIGNAL_AVAILABLE,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+struct _ExampleCallConnectionPrivate
+{
+ gchar *account;
+ guint simulation_delay;
+ gboolean away;
+ gchar *presence_message;
+};
+
+static void
+example_call_connection_init (ExampleCallConnection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALL_CONNECTION,
+ ExampleCallConnectionPrivate);
+ self->priv->away = FALSE;
+ self->priv->presence_message = g_strdup ("");
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ ExampleCallConnection *self = EXAMPLE_CALL_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)
+{
+ ExampleCallConnection *self = EXAMPLE_CALL_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)
+{
+ ExampleCallConnection *self = EXAMPLE_CALL_CONNECTION (object);
+
+ tp_contacts_mixin_finalize (object);
+ g_free (self->priv->account);
+ g_free (self->priv->presence_message);
+
+ G_OBJECT_CLASS (example_call_connection_parent_class)->finalize (object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+ ExampleCallConnection *self = EXAMPLE_CALL_CONNECTION (conn);
+
+ return g_strdup_printf ("%s@%p", self->priv->account, self);
+}
+
+gchar *
+example_call_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_call_normalize_contact, NULL);
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+ ExampleCallConnection *self = EXAMPLE_CALL_CONNECTION (conn);
+ GPtrArray *ret = g_ptr_array_sized_new (1);
+
+ g_ptr_array_add (ret,
+ g_object_new (EXAMPLE_TYPE_CALL_MANAGER,
+ "connection", conn,
+ "simulation-delay", self->priv->simulation_delay,
+ NULL));
+
+ return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ ExampleCallConnection *self = EXAMPLE_CALL_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_call_connection_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ tp_contacts_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleCallConnection, contacts_mixin));
+ tp_base_connection_register_with_contacts_mixin (base);
+
+ tp_presence_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleCallConnection, 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)
+{
+ ExampleCallConnection *self =
+ EXAMPLE_CALL_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);
+ ExampleCallPresence 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_CALL_PRESENCE_AWAY
+ : EXAMPLE_CALL_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_CALL_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)
+{
+ ExampleCallConnection *self =
+ EXAMPLE_CALL_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_CALL_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);
+
+ if (!self->priv->away)
+ {
+ g_signal_emit (self, signals[SIGNAL_AVAILABLE], 0, message);
+ }
+
+ return TRUE;
+}
+
+static const TpPresenceStatusOptionalArgumentSpec can_have_message[] = {
+ { "message", "s", NULL, NULL },
+ { NULL }
+};
+
+/* Must be kept in sync with ExampleCallPresence 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_call_connection_class_init (
+ ExampleCallConnectionClass *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 (ExampleCallConnectionPrivate));
+
+ 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);
+
+ /* Used in the call manager, to simulate an incoming call when we become
+ * available */
+ signals[SIGNAL_AVAILABLE] = g_signal_new ("available",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ tp_contacts_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallConnectionClass, contacts_mixin));
+ tp_presence_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallConnectionClass, 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/examples/future/call-cm/conn.h b/examples/future/call-cm/conn.h
new file mode 100644
index 000000000..3e3fb8ca9
--- /dev/null
+++ b/examples/future/call-cm/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_CALL_CONN_H
+#define EXAMPLE_CALL_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 _ExampleCallConnection ExampleCallConnection;
+typedef struct _ExampleCallConnectionPrivate
+ ExampleCallConnectionPrivate;
+
+typedef struct _ExampleCallConnectionClass ExampleCallConnectionClass;
+typedef struct _ExampleCallConnectionClassPrivate
+ ExampleCallConnectionClassPrivate;
+
+struct _ExampleCallConnectionClass {
+ TpBaseConnectionClass parent_class;
+ TpPresenceMixinClass presence_mixin;
+ TpContactsMixinClass contacts_mixin;
+
+ ExampleCallConnectionClassPrivate *priv;
+};
+
+struct _ExampleCallConnection {
+ TpBaseConnection parent;
+ TpPresenceMixin presence_mixin;
+ TpContactsMixin contacts_mixin;
+
+ ExampleCallConnectionPrivate *priv;
+};
+
+GType example_call_connection_get_type (void);
+
+#define EXAMPLE_TYPE_CALL_CONNECTION \
+ (example_call_connection_get_type ())
+#define EXAMPLE_CALL_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALL_CONNECTION, \
+ ExampleCallConnection))
+#define EXAMPLE_CALL_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALL_CONNECTION, \
+ ExampleCallConnectionClass))
+#define EXAMPLE_IS_CALL_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALL_CONNECTION))
+#define EXAMPLE_IS_CALL_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALL_CONNECTION))
+#define EXAMPLE_CALL_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_CONNECTION, \
+ ExampleCallConnectionClass))
+
+gchar *example_call_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_CALL_PRESENCE_OFFLINE = 0,
+ EXAMPLE_CALL_PRESENCE_UNKNOWN,
+ EXAMPLE_CALL_PRESENCE_ERROR,
+ EXAMPLE_CALL_PRESENCE_AWAY,
+ EXAMPLE_CALL_PRESENCE_AVAILABLE
+} ExampleCallPresence;
+
+G_END_DECLS
+
+#endif
diff --git a/examples/future/call-cm/main.c b/examples/future/call-cm/main.c
new file mode 100644
index 000000000..f3db411ca
--- /dev/null
+++ b/examples/future/call-cm/main.c
@@ -0,0 +1,61 @@
+/*
+ * 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
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <telepathy-glib/debug.h>
+#include <telepathy-glib/run.h>
+
+#include "extensions/extensions.h"
+
+#include "cm.h"
+
+static TpBaseConnectionManager *
+construct_cm (void)
+{
+ return (TpBaseConnectionManager *) g_object_new (
+ EXAMPLE_TYPE_CALL_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
+
+ /* strictly speaking, this is only necessary for client code, but it's
+ * harmless here */
+ g_type_init ();
+ future_cli_init ();
+
+ return tp_run_connection_manager ("telepathy-example-cm-call",
+ VERSION, construct_cm, argc, argv);
+}
diff --git a/examples/future/call-cm/manager-file.py b/examples/future/call-cm/manager-file.py
new file mode 100644
index 000000000..137751ad1
--- /dev/null
+++ b/examples/future/call-cm/manager-file.py
@@ -0,0 +1,23 @@
+# Input for tools/manager-file.py
+
+MANAGER = 'example_call'
+PARAMS = {
+ 'example' : {
+ 'account': {
+ 'dtype': 's',
+ 'flags': 'required register',
+ 'filter': 'account_param_filter',
+ # 'filter_data': 'NULL',
+ # 'default': ...,
+ # 'struct_field': '...',
+ # 'setter_data': 'NULL',
+ },
+ 'simulation-delay': {
+ 'dtype': 'u',
+ 'default': 1000,
+ },
+ },
+ }
+STRUCTS = {
+ 'example': 'ExampleParams'
+ }
diff --git a/extensions/Call_Content.xml b/extensions/Call_Content.xml
new file mode 100644
index 000000000..1d5e891df
--- /dev/null
+++ b/extensions/Call_Content.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This object represents one Content inside a Call. For example in an
+ audio/video call there would be one audio and one video content. Each
+ content has one or more <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call">Stream.DRAFT</tp:dbus-ref>
+ objects which represent the actual transport to one or more contacts.
+
+ </tp:docstring>
+
+ <property name="Name" tp:name-for-bindings="Name" type="s" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of the content.
+ [FIXME: rationale?]</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Type" tp:name-for-bindings="Type"
+ type="u" tp:type="Media_Stream_Type" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The media type of this content</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Creator" tp:name-for-bindings="Creator"
+ type="u" tp:type="Contact_Handle" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The creator of this content</p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="Call_Content_Disposition" type="u">
+ <tp:docstring>
+ [FIXME]
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The content has no specific disposition
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Early_Media" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ [FIXME: what does this mean?]
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Initial" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The content was initially part of the call. When <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT"
+ >Accept</tp:dbus-ref> is called on the channel, all streams of
+ this content where the self-handle's sending state in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Stream.DRAFT"
+ >Senders</tp:dbus-ref> is Sending_State_Pending_Send
+ will be moved to Sending_State_Sending as if <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Stream.DRAFT"
+ >SetSending</tp:dbus-ref>(TRUE) had been called.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="Disposition" tp:name-for-bindings="Disposition"
+ type="u" tp:type="Call_Content_Disposition" access="read">
+ <tp:docstring>
+ The disposition of this content. This property cannot change.
+ </tp:docstring>
+ </property>
+
+ <signal name="StreamAdded" tp:name-for-bindings="Stream_Added">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a stream is added to a call</p>
+ </tp:docstring>
+ <arg name="Stream" type="o">
+ <tp:docstring>
+ The stream which was added
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="StreamRemoved" tp:name-for-bindings="Stream_Removed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a stream is added to a call</p>
+ </tp:docstring>
+ <arg name="Stream" type="o">
+ <tp:docstring>
+ The stream which was removed
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Streams" tp:name-for-bindings="Streams"
+ type="ao" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The list of
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call">Stream.DRAFT</tp:dbus-ref>
+ objects that exist in this content.</p>
+
+ <tp:rationale>
+ <p>In a conference call multiple parties can share one media content
+ (say, audio), but the streaming of that media can either be shared
+ or separate. For example, in a multicast conference all contacts
+ would share one stream, while in a Muji conference there would be
+ a stream for each participant.</p>
+ </tp:rationale>
+
+ <p>Change notification is via
+ <tp:member-ref>StreamAdded</tp:member-ref> and
+ <tp:member-ref>StreamRemoved</tp:member-ref>.</p>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Call_Content_Codec_Offer.xml b/extensions/Call_Content_Codec_Offer.xml
new file mode 100644
index 000000000..31ff0b3c9
--- /dev/null
+++ b/extensions/Call_Content_Codec_Offer.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content_Codec_Offer"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This object represents an offer of a Codec payload mapping.
+ FIXME add Accept and Reject signals ?
+ </tp:docstring>
+
+ <method name="Accept" tp:name-for-bindings="Accept">
+ <arg name="Codecs" direction="in"
+ type="a(usuua{ss})" tp:type="Codec[]" />
+ <tp:docstring>
+ Accept the updated Codec mapping and update the local mapping
+ </tp:docstring>
+ </method>
+
+ <method name="Reject" tp:name-for-bindings="Reject">
+ <tp:docstring>
+ Reject the proposed update to the codecs
+ FIXME add error codes and strings here
+ </tp:docstring>
+ </method>
+
+ <property name="RemoteContactCodecMap"
+ tp:name-for-bindings="Remote_Contact_Codec_Map"
+ type="a{ua(usuua{ss})}" tp:type="Contact_Codec_Map" access="read">
+ <tp:docstring>
+ A map from remote contacts to the lists of codecs they support.
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Call_Content_Interface_Media.xml b/extensions/Call_Content_Interface_Media.xml
new file mode 100644
index 000000000..2b3eb65d5
--- /dev/null
+++ b/extensions/Call_Content_Interface_Media.xml
@@ -0,0 +1,229 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content_Interface_Media"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call.Content"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Interface to use by a software implementation of media streaming.
+
+ FIXME: How should the streaming implementation know when it is its turn
+ to set the codecs.
+ </tp:docstring>
+
+ <tp:struct name="Codec" array-name="Codec_List">
+ <tp:docstring>
+ A description of a codec.
+ </tp:docstring>
+
+ <tp:member name="Identifier" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Numeric identifier for the codec. This will be used as the PT in the
+ SDP or content description.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Name" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The name of the codec.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Clockrate" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The clockrate of the codec
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Channels" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Number of channels of the codec if applicable, otherwise 0
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Parameters" type="a{ss}">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Extra parameters for this codec
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:mapping name="Contact_Codec_Map">
+ <tp:docstring>
+ A map from contacts to the lists of codecs they support.
+ </tp:docstring>
+
+ <tp:member name="Handle" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ A contact
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Codecs" type="a(usuua{ss})" tp:type="Codec[]">
+ <tp:docstring>
+ The codecs that the contact supports
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:struct name="Codec_Offering">
+ <tp:docstring>
+ A codec offer and its corresponding remote contact codec map.
+ </tp:docstring>
+
+ <tp:member name="Codec_Offer" type="o">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The object path to the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Call.Content"
+ >CodecOffer.DRAFT</tp:dbus-ref>
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Remote_Contact_Codec_Map" type="a{ua(usuua{ss})}"
+ tp:type="Contact_Codec_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The <tp:dbus-ref namespace="org.freedesktop.Telepathy.Call.Content"
+ >CodecOffer.DRAFT.RemoteContactCodecMap</tp:dbus-ref> property
+ of the codec offer.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <signal name="CodecsChanged" tp:name-for-bindings="Codecs_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the codecs in use change.</p>
+
+ <p>As well as acting as change notification for the
+ <tp:member-ref>ContactCodecMap</tp:member-ref>, emission of this
+ signal implies that the <tp:member-ref>CodecOffer</tp:member-ref>
+ property has changed to <code>('/', {})</code>.</p>
+ </tp:docstring>
+
+ <arg name="Updated_Codecs" type="a{ua(usuua{ss})}"
+ tp:type="Contact_Codec_Map">
+ <tp:docstring>
+ A map from contacts to their codecs. Each pair in this map is added
+ to the <tp:member-ref>ContactCodecMap</tp:member-ref> property,
+ replacing any previous pair with that key.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Removed_Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of keys which were removed from the
+ <tp:member-ref>ContactCodecMap</tp:member-ref>, probably because
+ those contacts left the call.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="SetCodecs" tp:name-for-bindings="Set_Codecs">
+ <tp:docstring>
+ Set or update the local codec mapping.
+ </tp:docstring>
+
+ <arg name="Codecs" direction="in"
+ type="a(usuua{ss})" tp:type="Codec[]">
+ <tp:docstring>
+ The codecs now supported by the local user.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <property name="ContactCodecMap" tp:name-for-bindings="Contact_Codec_Map"
+ type="a{ua(usuua{ss})}" tp:type="Contact_Codec_Map" access="read">
+ <tp:docstring>
+ <p>A map from contact handles (including the local user's own handle)
+ to the codecs supported by that contact.</p>
+
+ <p>Change notification is via
+ <tp:member-ref>CodecsChanged</tp:member-ref>.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="NewCodecOffer" tp:name-for-bindings="New_Codec_Offer">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a new <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Content"
+ >CodecOffer.DRAFT</tp:dbus-ref> appears. The streaming
+ implementation MUST respond by calling the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT"
+ >Accept</tp:dbus-ref> or <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT"
+ >Reject</tp:dbus-ref> method on the codec offer object.</p>
+
+ <p>Emission of this signal indicates that the
+ <tp:member-ref>CodecOffer</tp:member-ref> property has changed to
+ <code>(Offer, Codecs)</code>.</p>
+ </tp:docstring>
+
+ <arg name="Offer" type="o">
+ <tp:docstring>
+ The object path of the new codec offer. This replaces any previous
+ codec offer.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Codecs" type="a{ua(usuua{ss})}" tp:type="Contact_Codec_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The <tp:dbus-ref namespace="org.freedesktop.Telepathy.Call.Content"
+ >CodecOffer.DRAFT.RemoteContactCodecMap</tp:dbus-ref> property
+ of the codec offer.</p>
+
+ <tp:rationale>
+ <p>Having the RemoteContactCodecMap property here saves a D-Bus
+ round-trip - it shouldn't be necessary to get the property
+ from the CodecOffer object, in practice.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="CodecOffer" tp:name-for-bindings="Codec_Offer"
+ type="(oa{ua(usuua{ss})})" tp:type="Codec_Offering" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The object path to the current
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Content"
+ >CodecOffer.DRAFT</tp:dbus-ref> object, and its
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Call.Content"
+ >CodecOffer.DRAFT.RemoteContactCodecMap</tp:dbus-ref> property.
+ If the object path is "/" then there isn't an outstanding
+ codec offer, and the mapping MUST be empty.</p>
+
+ <tp:rationale>
+ <p>Having the RemoteContactCodecMap property here saves a D-Bus
+ round-trip - it shouldn't be necessary to get the property
+ from the CodecOffer object, in practice.</p>
+ </tp:rationale>
+
+ <p>Change notification is via
+ <tp:member-ref>NewCodecOffer</tp:member-ref> (which replaces the
+ value of this property with a new codec offer), and
+ <tp:member-ref>CodecsChanged</tp:member-ref> (which implies that
+ there is no longer any active codec offer).</p>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Call_Stream.xml b/extensions/Call_Stream.xml
new file mode 100644
index 000000000..302bd5e9b
--- /dev/null
+++ b/extensions/Call_Stream.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" ?>
+<node name="/Call_Stream"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Stream.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ One stream inside a content
+ FIXME, direction should be a mapping of contact -> (bool)sending ?
+ </tp:docstring>
+
+ <method name="SetSending" tp:name-for-bindings="Set_Sending">
+ <p>Set the stream to start or stop sending media from the local
+ user to other contacts.</p>
+
+ <arg name="Send" type="b" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, the local user's sending state should change
+ to Sending, if it isn't already.</p>
+
+ <p>If false, the local user's sending state should change to None,
+ if it isn't already.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ [FIXME: when?]
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestReceiving" tp:name-for-bindings="Request_Receiving">
+ <tp:docstring>
+ Request that a remote contact stops or starts sending on this stream.
+ </tp:docstring>
+
+ <arg name="Contact" type="u" tp:type="Contact_Handle" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Contact from which sending is requested</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Receive" type="b" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, request that the given contact starts to send media.
+ If false, request that the given contact stops sending media.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ [FIXME: when?]
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="SendersChanged"
+ tp:name-for-bindings="Senders_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Emitted when <tp:member-ref>Senders</tp:member-ref> changes.
+ </tp:docstring>
+
+ <arg name="Updates" type="a{uu}" tp:type="Contact_Sending_State_Map">
+ <tp:docstring>
+ A mapping from channel-specific handles to their updated sending
+ state, whose keys include at least the senders who were added,
+ and the senders whose states changed.
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The channel-specific handles that were removed from
+ the keys of the Senders property, as a result of the
+ contact leaving this stream
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:enum name="Sending_State" type="u">
+ <tp:docstring>
+ Tristate indicating whether a contact is sending media.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>
+ The contact is not sending media and has not been asked to do so.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Pending_Send" value="1">
+ <tp:docstring>
+ The contact has been asked to start sending media.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Sending" value="2">
+ <tp:docstring>
+ The contact is sending media.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:mapping name="Contact_Sending_State_Map">
+ <tp:docstring>
+ A map from contacts to their sending state.
+ </tp:docstring>
+ <tp:member name="Contact" type="u" tp:type="Contact_Handle">
+ </tp:member>
+ <tp:member name="Sending" type="u" tp:type="Sending_State">
+ <tp:docstring>
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <property name="Senders" tp:name-for-bindings="Senders"
+ type="a{uu}" access="read" tp:type="Contact_Sending_State_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map from contacts to their sending state.</p>
+
+ <p>The local user's handle in this map (the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Group.SelfHandle</tp:dbus-ref> if the channel implements
+ Group, or the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.SelfHandle</tp:dbus-ref> otherwise) indicates
+ whether the local user is sending media. Media sent on this stream
+ should be assumed to be received, directly or indirectly, by every
+ other contact in the Senders mapping. Sending_State_Pending_Send
+ indicates that another contact has asked the local user to send
+ media.</p>
+
+ <p>Other contacts' handles in this map indicate whether they are
+ sending media to the contacts in this stream.
+ Sending_State_Pending_Send indicates contacts who are not sending but
+ have been asked to do so.</p>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Call_Stream_Endpoint.xml b/extensions/Call_Stream_Endpoint.xml
new file mode 100644
index 000000000..fbab2cfa3
--- /dev/null
+++ b/extensions/Call_Stream_Endpoint.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" ?>
+<node name="/Call_Stream_Endpoint"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This object represents a set of candidates of one end-point.
+ </tp:docstring>
+
+ <property name="RemoteCredentials"
+ tp:name-for-bindings="Remote_Credentials"
+ type="(ss)" tp:type="Stream_Credentials" access="read">
+ </property>
+
+ <signal name="RemoteCredentialsSet"
+ tp:name-for-bindings="Remote_Credentials_Set">
+ <arg name="Username" type="s" />
+ <arg name="Password" type="s" />
+ </signal>
+
+ <property name="RemoteCandidates" tp:name-for-bindings="Remote_Candidates"
+ type="a(usqa{sv})" tp:type="Candidate[]" access="read">
+ </property>
+
+ <signal name="RemoteCandidatesAdded"
+ tp:name-for-bindings="Remote_Candidates_Added">
+ <arg name="Candidates"
+ type="a(usqa{sv})" tp:type="Candidate[]"/>
+ </signal>
+
+ <signal name="CandidateSelected"
+ tp:name-for-bindings="Candidate_Selected">
+ <arg name="Candidate"
+ type="(usqa{sv})" tp:type="Candidate"/>
+ </signal>
+
+ <property name="SelectedCandidate"
+ tp:name-for-bindings="Selected_Candidate"
+ type="(usqa{sv})" tp:type="Candidate" access="read">
+ </property>
+
+ <method name="SetSelectedCandidate"
+ tp:name-for-bindings="Set_Selected_Candidate">
+ <arg name="Candidate"
+ type="(usqa{sv})" tp:type="Candidate" direction="in">
+ <tp:docstring>
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <property name="StreamState" tp:name-for-bindings="Stream_State"
+ type="u" tp:type="Media_Stream_State"
+ access="read">
+ </property>
+
+ <signal name="StreamStateChanged"
+ tp:name-for-bindings="Stream_State_Changed">
+ <arg name="state"
+ type="u" tp:type="Media_Stream_State"/>
+ </signal>
+
+ <method name="SetStreamState"
+ tp:name-for-bindings="Set_Stream_State">
+ <arg name="State" type="u" tp:type="Media_Stream_State"
+ direction="in" />
+ </method>
+
+ <property name="Transport" tp:name-for-bindings="Transport"
+ type="u" tp:type="Stream_Transport_Type" access="read">
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Call_Stream_Interface_Media.xml b/extensions/Call_Stream_Interface_Media.xml
new file mode 100644
index 000000000..ccf903374
--- /dev/null
+++ b/extensions/Call_Stream_Interface_Media.xml
@@ -0,0 +1,400 @@
+<?xml version="1.0" ?>
+<node name="/Call_Stream_Interface_Media"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call.Stream"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ [FIXME]
+ </tp:docstring>
+
+ <tp:method name="SetCredentials" tp:name-for-bindings="Set_Credentials">
+ <tp:docstring>
+ Used to set the username fragment and password for streams that have
+ global credentials.
+
+ <tp:rationale>
+ [FIXME: rationale?]
+ </tp:rationale>
+ </tp:docstring>
+ <arg name="Username" type="s" direction="in"/>
+ <arg name="Password" type="s" direction="in" />
+ </tp:method>
+
+ <tp:mapping name="Candidate_Info">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Extra information about the candidate. Allowed and mandatory keys
+ depend on the transport protocol used. The following keys are commenly
+ used:
+ <dl>
+ <dt>Type (u)</dt>
+ <dd>type of candidate (host, srflx, prflx, relay)</dd>
+
+ <dt>Foundation (s)</dt>
+ <dd>the foundation of this candiate</dd>
+
+ <dt>Protocol (u) </dt>
+ <dd>Underlying protocol of the candidate (udp, tcp) </dd>
+
+ <dt>Priority (u) </dt>
+ <dd>Priority of the candidate </dd>
+
+ <dt>BaseIP (u) </dt>
+ <dd>Base IP of this candidate </dd>
+
+ <dt>Username (s) </dt>
+ <dd>Username of this candidate
+ (only if credentials are per candidate)</dd>
+
+ <dt>Password (s) </dt>
+ <dd>Password of this candidate
+ (only if credentials are per candidate)</dd>
+
+ <dt>RawUDPFallback (b) </dt>
+ <dd>Indicate whether this candidate may be used to provide a UDP
+ fallback</dd>
+ </dl>
+ </tp:docstring>
+ <tp:member name="Key" type="s">
+ <tp:docstring>One of the well-known keys documented here, or an
+ implementation-specific key</tp:docstring>
+ </tp:member>
+ <tp:member name="Value" type="v">
+ <tp:docstring>The value corresponding to that key</tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:struct name="Candidate" array-name="Candidate_List">
+ <tp:docstring>A Stream Candidate</tp:docstring>
+
+ <tp:member name="Component" type="u">
+ <tp:docstring>The component number</tp:docstring>
+ </tp:member>
+ <tp:member name="IP" type="s">
+ <tp:docstring>The IP address to use</tp:docstring>
+ </tp:member>
+ <tp:member name="Port" type="q">
+ <tp:docstring>The port number to use</tp:docstring>
+ </tp:member>
+ <tp:member name="Info" type="a{sv}" tp:type="Candidate_Info">
+ <tp:docstring>Additional information about the candidate</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <method name="AddCandidates" tp:name-for-bindings="Add_Candidates">
+ <tp:docstring>
+ Add candidates to <tp:member-ref>LocalCandidates</tp:member-ref>
+ and signal them to the remote contact(s).
+ </tp:docstring>
+
+ <arg name="candidates" direction="in"
+ type="a(usqa{sv})" tp:type="Candidate[]">
+ <tp:docstring>
+ Candidates to be appended to
+ <tp:member-ref>LocalCandidates</tp:member-ref>
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="CandidatesPrepared"
+ tp:name-for-bindings="Candidates_Prepared">
+ <tp:docstring>
+ This indicates to the CM that the initial batch of candidates has been
+ added.
+
+ <tp:rationale>
+ [FIXME: rationale]
+ </tp:rationale>
+ </tp:docstring>
+ </method>
+
+ <tp:enum type="u" name="Stream_Transport_Type">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ A transport that can be used for streaming.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Raw_UDP" value="0">
+ <tp:docstring>
+ Raw UDP, with or without STUN. All streaming clients are assumed to
+ support this transport, so there is no handler capability token for
+ it in the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type"
+ >Call.DRAFT</tp:dbus-ref> interface.
+ [This corresponds to "none" or "stun" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="ICE" value="1">
+ <tp:docstring>
+ Interactive Connectivity Establishment, as defined by the IETF MMUSIC
+ working group.
+ [FIXME: do we want this to cover both ICE-UDP and ICE-TCP, or split
+ them?]
+ [This corresponds to "ice-udp" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="GTalk_P2P" value="2">
+ <tp:docstring>
+ Google Talk peer-to-peer connectivity establishment, as implemented
+ by libjingle 0.3.
+ [This corresponds to "gtalk-p2p" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="WLM_8_5" value="3">
+ <tp:docstring>
+ The transport used by Windows Live Messenger 8.5 or later, which
+ resembles ICE draft 6.
+ [This corresponds to "wlm-8.5" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="WLM_2009" value="4">
+ <tp:docstring>
+ The transport used by Windows Live Messenger 2009 or later, which
+ resembles ICE draft 19.
+ [This corresponds to "wlm-2009" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="Transport" tp:name-for-bindings="Transport"
+ type="u" tp:type="Stream_Transport_Type" access="read">
+ <tp:docstring>
+ The transport for this stream. This property is immutable.
+ </tp:docstring>
+ </property>
+
+ <property name="LocalCandidates" tp:name-for-bindings="Local_Candidates"
+ type="a(usqa{sv})" tp:type="Candidate[]" access="read">
+ <tp:docstring>
+ [FIXME]. Change notification is via
+ <tp:member-ref>LocalCandidatesAdded</tp:member-ref>.
+ </tp:docstring>
+ </property>
+
+ <signal name="LocalCandidatesAdded"
+ tp:name-for-bindings="Local_Candidates_Added">
+ <tp:docstring>
+ Emitted when local candidates are added to
+ <tp:member-ref>LocalCandidates</tp:member-ref>.
+ </tp:docstring>
+
+ <arg name="Candidates"
+ type="a(usqa{sv})" tp:type="Candidate[]">
+ <tp:docstring>
+ Candidates that have been appended to
+ <tp:member-ref>LocalCandidates</tp:member-ref>
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:struct name="Stream_Credentials">
+ <tp:docstring>A username/password pair.</tp:docstring>
+
+ <tp:member name="Username" type="s">
+ <tp:docstring>The username</tp:docstring>
+ </tp:member>
+
+ <tp:member name="Password" type="s">
+ <tp:docstring>The password</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="LocalCredentials" tp:name-for-bindings="Local_Credentials"
+ type="(ss)" tp:type="Stream_Credentials" access="read">
+ <tp:docstring>
+ [FIXME]. Change notification is via
+ <tp:member-ref>LocalCredentialsSet</tp:member-ref>.
+ </tp:docstring>
+
+ </property>
+
+ <signal name="LocalCredentialsSet"
+ tp:name-for-bindings="Local_Credentials_Set">
+ <tp:docstring>
+ Emitted when the value of
+ <tp:member-ref>LocalCredentials</tp:member-ref> changes.
+ </tp:docstring>
+
+ <arg name="Username" type="s" />
+ <arg name="Password" type="s" />
+ </signal>
+
+ <property name="STUNServers" tp:name-for-bindings="STUN_Servers"
+ type="a(sq)" tp:type="Socket_Address_IP[]" access="read">
+ <tp:docstring>
+ The IP addresses of possible STUN servers to use for NAT traversal, as
+ dotted-quad IPv4 address literals or RFC2373 IPv6 address literals.
+ This property cannot change once the stream has been created, so there
+ is no change notification. The IP addresses MUST NOT be given as DNS
+ hostnames.
+
+ <tp:rationale>
+ High-quality connection managers already need an asynchronous
+ DNS resolver, so they might as well resolve this name to an IP
+ to make life easier for streaming implementations.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="RelayInfo" type="aa{sv}" access="read"
+ tp:type="String_Variant_Map[]" tp:name-for-bindings="Relay_Info">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of mappings describing TURN or Google relay servers
+ available for the client to use in its candidate gathering, as
+ determined from the protocol. Map keys are:</p>
+
+ <dl>
+ <dt><code>ip</code> - s</dt>
+ <dd>The IP address of the relay server as a dotted-quad IPv4
+ address literal or an RFC2373 IPv6 address literal. This MUST NOT
+ be a DNS hostname.
+
+ <tp:rationale>
+ High-quality connection managers already need an asynchronous
+ DNS resolver, so they might as well resolve this name to an IP
+ and make life easier for streaming implementations.
+ </tp:rationale>
+ </dd>
+
+ <dt><code>type</code> - s</dt>
+ <dd>
+ <p>Either <code>udp</code> for UDP (UDP MUST be assumed if this
+ key is omitted), <code>tcp</code> for TCP, or
+ <code>tls</code>.</p>
+
+ <p>The precise meaning of this key depends on the
+ <tp:member-ref>Transport</tp:member-ref> property: if
+ Transport is ICE, <code>tls</code> means
+ TLS over TCP as referenced by ICE draft 19, and if
+ Transport is GTalk_P2P, <code>tls</code> means
+ a fake SSL session over TCP as implemented by libjingle.</p>
+ </dd>
+
+ <dt><code>port</code> - q</dt>
+ <dd>The UDP or TCP port of the relay server as an ASCII unsigned
+ integer</dd>
+
+ <dt><code>username</code> - s</dt>
+ <dd>The username to use</dd>
+
+ <dt><code>password</code> - s</dt>
+ <dd>The password to use</dd>
+
+ <dt><code>component</code> - u</dt>
+ <dd>The component number to use this relay server for, as an
+ ASCII unsigned integer; if not included, this relay server
+ may be used for any or all components.
+
+ <tp:rationale>
+ In ICE draft 6, as used by Google Talk, credentials are only
+ valid once, so each component needs relaying separately.
+ </tp:rationale>
+ </dd>
+ </dl>
+
+ <tp:rationale>
+ <p>An equivalent of the gtalk-p2p-relay-token property on
+ MediaSignalling channels is not included here. The connection
+ manager should be responsible for making the necessary HTTP
+ requests to turn the token into a username and password.</p>
+ </tp:rationale>
+
+ <p>The type of relay server that this represents depends on
+ the value of the <tp:member-ref>Transport</tp:member-ref>
+ property. If Transport is ICE, this is a TURN server;
+ if Transport is GTalk_P2P, this is a Google relay server;
+ otherwise, the meaning of RelayInfo is undefined.</p>
+
+ <p>If relaying is not possible for this stream, the list is empty.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="ServerInfoRetrieved"
+ tp:name-for-bindings="Server_Info_Retrieved">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Signals that the initial information about STUN and Relay servers
+ has been retrieved, i.e. the
+ <tp:member-ref>RetrievedServerInfo</tp:member-ref> property is now
+ true.</p>
+ </tp:docstring>
+ </signal>
+
+ <property name="RetrievedServerInfo" type="b"
+ tp:name-for-bindings="Retrieved_Server_Info" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>True if the initial information about STUN servers and Relay servers
+ has been retrieved. Change notification is via the
+ <tp:member-ref>ServerInfoRetrieved</tp:member-ref> signal.</p>
+
+ <tp:rationale>
+ <p>Streaming implementations that can't cope with STUN and relay
+ servers being added later SHOULD wait for this property
+ to become true before proceeding.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <signal name="EndpointsChanged"
+ tp:name-for-bindings="Endpoints_Changed">
+ <tp:docstring>
+ Emitted when the <tp:member-ref>Endpoints</tp:member-ref> property
+ changes.
+ </tp:docstring>
+
+ <arg name="EndpointsAdded" type="ao">
+ <tp:docstring>
+ Endpoints that were added.
+ </tp:docstring>
+ </arg>
+
+ <arg name="EndpointsRemoved" type="ao">
+ <tp:docstring>
+ Endpoints that no longer exist.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Endpoints" tp:name-for-bindings="Endpoints"
+ type="ao" access="read">
+ <p> The list of endpoints
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Stream"
+ >Endpoint.DRAFT</tp:dbus-ref>
+ that exist for this stream.
+ </p>
+
+ <p>Change notification is via the
+ <tp:member-ref>EndpointsChanged</tp:member-ref> signal.</p>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Channel_Type_Call.xml b/extensions/Channel_Type_Call.xml
new file mode 100644
index 000000000..702eb1759
--- /dev/null
+++ b/extensions/Channel_Type_Call.xml
@@ -0,0 +1,931 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Call" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license>
+ 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel type for making audio and video calls.</p>
+
+ <p>A Call channel can have one or more <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call">Content.DRAFT</tp:dbus-ref>
+ objects, which represent the actual Media that forms the Call (e.g. an
+ audio content and a video content).</p>
+ </tp:docstring>
+
+ <method name="Ringing" tp:name-for-bindings="Ringing">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Indicate that the local user has been alerted about the incoming
+ call.</p>
+
+ <p>This method is only useful if the channel's
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >Requested</tp:dbus-ref> property is false, and the
+ <tp:member-ref>CallState</tp:member-ref> is
+ Call_State_Pending_Initiator. While this is the case,
+ this method SHOULD change the
+ <tp:member-ref>CallFlags</tp:member-ref> to include
+ Call_Flag_Ringing, and notify the remote contact that the local
+ user has been alerted (if the protocol implements this); repeated
+ calls to this method SHOULD succeed, but have no further effect.</p>
+
+ <p>In all other states, this method SHOULD fail with the error
+ NotAvailable.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The call was <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel"
+ >Requested</tp:dbus-ref>, so ringing does not make sense.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The call is no longer in state Call_State_Pending_Initiator.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Accept" tp:name-for-bindings="Accept">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>For incoming calls in state Call_State_Pending_Receiver, accept the
+ incoming call; this changes the
+ <tp:member-ref>CallState</tp:member-ref> to Call_State_Accepted.</p>
+
+ <p>For outgoing calls in state Call_State_Pending_Initiator, actually
+ call the remote contact; this changes the
+ <tp:member-ref>CallState</tp:member-ref> to
+ Call_State_Pending_Receiver.</p>
+
+ <p>Otherwise, this method SHOULD fail with the error NotAvailable.</p>
+
+ <p>This method should be called exactly once per Call, by whatever
+ client (user interface) is handling the channel.</p>
+
+ <p>When this method is called, for each <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call"
+ >Content.DRAFT</tp:dbus-ref> whose <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Content.DRAFT"
+ >Disposition</tp:dbus-ref> is Call_Content_Disposition_Initial,
+ any streams where the self-handle's sending state in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Stream.DRAFT"
+ >Senders</tp:dbus-ref> is Sending_State_Pending_Send
+ will be moved to Sending_State_Sending as if <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Stream.DRAFT"
+ >SetSending</tp:dbus-ref>(TRUE) had been called.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The call is not in one of the states where this method makes sense.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Hangup" tp:name-for-bindings="Hangup">
+ <tp:docstring>
+ Request that the call is ended.
+ </tp:docstring>
+
+ <arg direction="in" name="Reason"
+ type="u" tp:type="Call_State_Change_Reason">
+ <tp:docstring>
+ A generic hangup reason.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Detailed_Hangup_Reason"
+ type="s" tp:type="DBus_Error_Name">
+ <tp:docstring>
+ A more specific reason for the call hangup, if one is available, or
+ an empty string otherwise.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Message" type="s">
+ <tp:docstring>
+ A human-readable message to be sent to the remote contact(s).
+
+ <tp:rationale>
+ XMPP Jingle allows calls to be terminated with a human-readable
+ message.
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The call has already been ended.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="AddContent" tp:name-for-bindings="Add_Content">
+ <tp:docstring>
+ [FIXME]
+ </tp:docstring>
+ <arg direction="in" name="Content_Name" type="s">
+ <tp:docstring>
+ The suggested name of the content to add
+
+ <tp:rationale>
+ [FIXME: rationale]
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Content_Type" type="u"
+ tp:type="Media_Stream_Type">
+ <tp:docstring>
+ The media type of the content to add
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Content" type="o">
+ <tp:docstring>
+ Path to the newly-created <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Call.Content.DRAFT</tp:dbus-ref> object.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ [FIXME: when?]
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ [FIXME: when?]
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="ContentAdded"
+ tp:name-for-bindings="Content_Added">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a new <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call"
+ >Content.DRAFT</tp:dbus-ref> is added to the call.</p>
+ </tp:docstring>
+ <arg name="Content" type="o">
+ <tp:docstring>
+ Path to the newly-created <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call"
+ >Content.DRAFT</tp:dbus-ref> object.
+ </tp:docstring>
+ </arg>
+ <arg name="Content_Type" type="u" tp:type="Media_Stream_Type">
+ <tp:docstring>
+ The media type of the content which was added
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="ContentRemoved" tp:name-for-bindings="Content_Removed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call"
+ >Content.DRAFT</tp:dbus-ref> is removed from the call.</p>
+ </tp:docstring>
+ <arg name="Content" type="o">
+ <tp:docstring>
+ The <tp:dbus-ref namespace="org.freedesktop.Telepathy.Call"
+ >Content.DRAFT</tp:dbus-ref> which was removed.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Contents" type="ao" access="read"
+ tp:name-for-bindings="Contents">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The list of
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call">Content.DRAFT</tp:dbus-ref>
+ objects that are part of this call. Change notification
+ is via the <tp:member-ref>ContentAdded</tp:member-ref> and
+ <tp:member-ref>ContentRemoved</tp:member-ref> signals.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum type="u" name="Call_State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The state of a call, as a whole.</p>
+
+ <p>The allowed transitions are:</p>
+
+ <ul>
+ <li>Pending_Initiator → Pending_Receiver (for outgoing calls,
+ when <tp:member-ref>Accept</tp:member-ref> is called)</li>
+ <li>Pending_Receiver → Accepted (for incoming calls, when
+ <tp:member-ref>Accept</tp:member-ref> is called; for outgoing
+ calls to a contact, when the remote contact accepts the call;
+ for joining a conference call, when the local user successfully
+ joins the conference)</li>
+ <li>Accepted → Pending_Receiver (when transferred to another
+ contact)</li>
+ <li>any state → Ended (when the call is terminated normally, or
+ when an error occurs)</li>
+ </ul>
+
+ <p>Clients MAY consider unknown values from this enum to be an
+ error - additional values will not be defined after the Call
+ specification is declared to be stable.</p>
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value = "0">
+ <tp:docstring>
+ The call state is not known. This call state MUST NOT appear as a
+ value of the <tp:member-ref>CallState</tp:member-ref> property, but
+ MAY be used by client code to represent calls whose state is as yet
+ unknown.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Pending_Initiator" value = "1">
+ <tp:docstring>
+ The initiator of the call hasn't accepted the call yet. This state
+ only makes sense for outgoing calls, where it means that the local
+ user has not yet sent any signalling messages to the remote user(s),
+ and will not do so until <tp:member-ref>Accept</tp:member-ref> is
+ called.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Pending_Receiver" value = "2">
+ <tp:docstring>
+ The receiver (the contact being called) hasn't accepted the call yet.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Accepted" value = "3">
+ <tp:docstring>
+ The contact being called has accepted the call.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Ended" value = "4">
+ <tp:docstring>
+ The call has ended, either via normal termination or an error.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:flags name="Call_Flags" value-prefix="Call_Flag" type="u">
+ <tp:docstring>
+ A set of flags representing the status of the call as a whole,
+ providing more specific information than the
+ <tp:member-ref>CallState</tp:member-ref>. Many of these flags only make
+ sense in a particular state.
+ </tp:docstring>
+
+ <tp:flag suffix="Locally_Ringing" value="1">
+ <tp:docstring>
+ The local contact has been alerted about the call but has not
+ responded; if possible, the remote contact(s) have been informed of
+ this fact. This flag only makes sense on incoming calls in
+ state Call_State_Pending_Receiver. It SHOULD be set when
+ <tp:member-ref>Ringing</tp:member-ref> is called successfully, and
+ unset when the state changes.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Queued" value="2">
+ <tp:docstring>
+ The contact is temporarily unavailable, and the call has been placed
+ in a queue (e.g. 182 Queued in SIP, or call-waiting in telephony).
+ This flag only makes sense on outgoing 1-1 calls in
+ state Call_State_Pending_Receiver. It SHOULD be set or unset
+ according to informational messages from other contacts.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Locally_Held" value="4">
+ <tp:docstring>
+ The call has been put on hold by the local user, e.g. using the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Hold</tp:dbus-ref> interface. This flag SHOULD only be set if
+ there is at least one Content, and all Contents are locally held;
+ it makes sense on calls in state Call_State_Pending_Receiver or
+ Call_State_Accepted.
+
+ <tp:rationale>
+ Otherwise, in transient situations where some but not all contents
+ are on hold, UIs would falsely indicate that the call as a whole
+ is on hold, which could lead to the user saying something they'll
+ regret, while under the impression that the other contacts can't
+ hear them!
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Forwarded" value="8">
+ <tp:docstring>
+ The initiator of the call originally called a contact other than the
+ current recipient of the call, but the call was then forwarded or
+ diverted. This flag only makes sense on outgoing calls, in state
+ Call_State_Pending_Receiver or Call_State_Accepted. It SHOULD be
+ set or unset according to informational messages from other contacts.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="In_Progress" value="16">
+ <tp:docstring>
+ Progress has been made in placing the outgoing call, but the
+ contact may not have been made aware of the call yet
+ (so the Ringing state is not appropriate). This corresponds to SIP's
+ status code 183 Session Progress, and could be used when the
+ outgoing call has reached a gateway, for instance.
+ This flag only makes sense on outgoing calls in state
+ Call_State_Pending_Receiver, and SHOULD be set or unset according to
+ informational messages from servers, gateways and other
+ infrastructure.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Clearing" value="32">
+ <tp:docstring>
+ This flag only occurs when the CallState is Ended. The call with
+ this flag set has ended, but not all resources corresponding to the
+ call have been freed yet.
+
+ Depending on the protocol there might be some audible feedback while
+ the clearing flag is set.
+
+ <tp:rationale>
+ In calls following the ITU-T Q.931 standard there is a period of
+ time between the call ending and the underlying channel being
+ completely free for re-use.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <property name="CallStateDetails"
+ tp:name-for-bindings="Call_State_Details" type="a{sv}" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map used to provide optional extensible details for the
+ <tp:member-ref>CallState</tp:member-ref>,
+ <tp:member-ref>CallFlags</tp:member-ref> and/or
+ <tp:member-ref>CallStateReason</tp:member-ref>.</p>
+
+ <p>Well-known keys and their corresponding value types include:</p>
+
+ <dl>
+ <dt>hangup-message - s</dt>
+ <dd>An optional human-readable message sent when the call was ended,
+ corresponding to the Message argument to the
+ <tp:member-ref>Hangup</tp:member-ref> method. This is only
+ applicable when the call state is Call_State_Ended.
+ <tp:rationale>
+ XMPP Jingle can send such messages.
+ </tp:rationale>
+ </dd>
+
+ <dt>queue-message - s</dt>
+ <dd>An optional human-readable message sent when the local contact
+ is being held in a queue. This is only applicable when
+ Call_Flag_Queued is in the call flags.
+ <tp:rationale>
+ SIP 182 notifications can have human-readable messages attached.
+ </tp:rationale>
+ </dd>
+
+ <dt>debug-message - s</dt>
+ <dd>A message giving further details of any error indicated by the
+ <tp:member-ref>CallStateReason</tp:member-ref>. This will not
+ normally be localized or suitable for display to users, and is only
+ applicable when the call state is Call_State_Ended.</dd>
+ </dl>
+ </tp:docstring>
+ </property>
+
+ <property name="CallState" type="u" access="read"
+ tp:name-for-bindings="Call_State" tp:type="Call_State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current high-level state of this call. The
+ <tp:member-ref>CallFlags</tp:member-ref> provide additional
+ information, and the <tp:member-ref>CallStateReason</tp:member-ref>
+ and <tp:member-ref>CallStateDetails</tp:member-ref> explain the
+ reason for the current values for those properties.</p>
+
+ <p>Clients MAY consider unknown values in this property to be an
+ error.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="CallFlags" type="u" access="read"
+ tp:name-for-bindings="Call_Flags" tp:type="Call_Flags">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Flags representing the status of the call as a whole,
+ providing more specific information than the
+ <tp:member-ref>CallState</tp:member-ref>.</p>
+
+ <p>Clients are expected to ignore unknown flags in this property,
+ without error.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="Call_State_Change_Reason" type="u">
+ <tp:docstring>
+ A simple representation of the reason for a change in the call's
+ state, which may be used by simple clients, or used as a fallback
+ when the DBus_Reason member of a <tp:type>Call_State_Reason</tp:type>
+ struct is not understood.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ We just don't know. Unknown values of this enum SHOULD also be
+ treated like this.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="User_Requested" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change was requested by the contact indicated by the Actor
+ member of a <tp:type>Call_State_Reason</tp:type> struct.</p>
+
+ <p>If the Actor is the local user, the DBus_Reason SHOULD be the
+ empty string.</p>
+
+ <p>If the Actor is a remote user, the DBus_Reason SHOULD be the empty
+ string if the call was terminated normally, but MAY be a non-empty
+ error name to indicate error-like call termination reasons (call
+ rejected as busy, kicked from a conference by a moderator, etc.).</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:struct name="Call_State_Reason">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A description of the reason for a change to the
+ <tp:member-ref>CallState</tp:member-ref> and/or
+ <tp:member-ref>CallFlags</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <tp:member type="u" tp:type="Contact_Handle" name="Actor">
+ <tp:docstring>
+ The contact responsible for the change, or 0 if no contact was
+ responsible.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="u" tp:type="Call_State_Change_Reason" name="Reason">
+ <tp:docstring>
+ The reason, chosen from a limited set of possibilities defined by
+ the Telepathy specification.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="s" tp:type="DBus_Error_Name" name="DBus_Reason">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A specific reason for the change, which may be a D-Bus error in
+ the Telepathy namespace, a D-Bus error in any other namespace
+ (for implementation-specific errors), or the empty string to
+ indicate that the state change was not an error.</p>
+
+ <p>This SHOULD be an empty string for changes to any state other
+ than Ended.</p>
+
+ <p>The errors Cancelled and Terminated SHOULD NOT be used here;
+ an empty string SHOULD be used instead.</p>
+
+ <tp:rationale>
+ <p>Those error names are used to indicate normal call
+ termination by the local user or another user, respectively,
+ in contexts where a D-Bus error name must appear.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="CallStateReason" tp:name-for-bindings="Call_State_Reason"
+ type="(uus)" access="read" tp:type="Call_State_Reason">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The reason for the last change to the
+ <tp:member-ref>CallState</tp:member-ref> and/or
+ <tp:member-ref>CallFlags</tp:member-ref>. The
+ <tp:member-ref>CallStateDetails</tp:member-ref> MAY provide additional
+ information.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="CallStateChanged"
+ tp:name-for-bindings="Call_State_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the state of the call as a whole changes.</p>
+
+ <p>This signal is emitted for any change in the properties
+ corresponding to its arguments, even if the other properties
+ referenced remain unchanged.</p>
+ </tp:docstring>
+
+ <arg name="Call_State" type="u" tp:type="Call_State">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallState</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Call_Flags" type="u" tp:type="Call_Flags">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallFlags</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Call_State_Reason" type="(uus)">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallStateReason</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Call_State_Details" type="a{sv}">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallStateDetails</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="HardwareStreaming" tp:name-for-bindings="Hardware_Streaming"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If this property is TRUE, all of the media streaming is done by some
+ mechanism outside the scope of Telepathy.</p>
+
+ <tp:rationale>
+ <p>A connection manager might be intended for a specialized hardware
+ device, which will take care of the audio streaming (e.g.
+ telepathy-yafono, which uses GSM hardware which does the actual
+ audio streaming for the call).</p>
+ </tp:rationale>
+
+ <p>If this is FALSE, the handler is responsible for doing the actual
+ media streaming for at least some contents itself. Those contents
+ will have the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Content.Interface"
+ >Media.DRAFT</tp:dbus-ref> interface, to communicate the necessary
+ information to a streaming implementation. Connection managers SHOULD
+ operate like this, if possible.</p>
+
+ <tp:rationale>
+ <p>Many connection managers (such as telepathy-gabble) only do the
+ call signalling, and expect the client to do the actual streaming
+ using something like
+ <a href="http://farsight.freedesktop.org/">Farsight</a>, to improve
+ latency and allow better UI integration.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <tp:flags type="u" name="Call_Member_Flags" value-prefix="Call_Member_Flag">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A set of flags representing the status of a remote contact in a
+ call.</p>
+
+ <p>It is protocol- and client-specific whether a particular contact
+ will ever have a particular flag set on them, and Telepathy clients
+ SHOULD NOT assume that a flag will ever be set.</p>
+
+ <tp:rationale>
+ <p>180 Ringing in SIP, and its equivalent in XMPP, are optional
+ informational messages, and implementations are not required
+ to send them. The same applies to the messages used to indicate
+ hold state.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:flag suffix="Ringing" value = "1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The remote contact's client has told us that the contact has been
+ alerted about the call but has not responded.</p>
+
+ <tp:rationale>
+ <p>This is a flag per member, not a flag for the call as a whole,
+ because in Muji conference calls, you could invite someone and
+ have their state be "ringing" for a while.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Held" value = "2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The call member has put this call on hold.</p>
+
+ <tp:rationale>
+ <p>This is a flag per member, not a flag for the call as a whole,
+ because in conference calls, any member could put the conference
+ on hold.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <tp:mapping name="Call_Member_Map" array-name="Call_Member_Map_List">
+ <tp:docstring>A mapping from handles to their current state in the call.
+ </tp:docstring>
+ <tp:member type="u" tp:type="Handle" name="key"/>
+ <tp:member type="u" tp:type="Call_Member_Flags" name="Flag"/>
+ </tp:mapping>
+
+ <signal name="CallMembersChanged"
+ tp:name-for-bindings="Call_Members_Changed">
+ <tp:docstring>
+ Emitted when the <tp:member-ref>CallMembers</tp:member-ref> property
+ changes in any way, either because contacts have been added to the
+ call, contacts have been removed from the call, or contacts' flags
+ have changed.
+ </tp:docstring>
+
+ <arg name="Flags_Changed" type="a{uu}" tp:type="Call_Member_Map">
+ <tp:docstring>
+ A map from members of the call to their new call member flags,
+ including at least the members who have been added to
+ <tp:member-ref>CallMembers</tp:member-ref>, and the members whose
+ flags have changed.
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members who have left the call, i.e. keys to be removed
+ from <tp:member-ref>CallMembers</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="CallMembers" tp:name-for-bindings="Call_Members"
+ type="a{uu}" access="read" tp:type="Call_Member_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ A mapping from the remote contacts that are part of this call to flags
+ discribing their status. This mapping never has the local user's handle
+ as a key.
+ </tp:docstring>
+ </property>
+
+ <property name="InitialTransport" tp:name-for-bindings="Initial_Transport"
+ type="s" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ If set on a requested channel this indicates the transport that
+ should be used for this call.
+ <tp:rationale>
+ When implementing a voip gateway one wants the outgoing leg of the
+ gatewayed to have the same transport as the incoming leg. This
+ property allows the gateway to request a Call with the right
+ transport from the CM.
+ </tp:rationale>
+ </p>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialAudio" tp:name-for-bindings="Initial_Audio"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If set to true in a channel request that will create a new channel,
+ the connection manager should immediately attempt to establish an
+ audio stream to the remote contact, making it unnecessary for the
+ client to call
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT">AddContent</tp:dbus-ref>.
+ </p>
+
+ <p>If this property, or InitialVideo, is passed to EnsureChannel
+ (as opposed to CreateChannel), the connection manager SHOULD ignore
+ these properties when checking whether it can return an existing
+ channel as suitable; these properties only become significant when
+ the connection manager has decided to create a new channel.</p>
+
+ <p>If true on a requested channel, this indicates that the audio
+ stream has already been requested and the client does not need to
+ call RequestStreams, although it MAY still do so.</p>
+
+ <p>If true on an unrequested (incoming) channel, this indicates that
+ the remote contact initially requested an audio stream; this does
+ not imply that that audio stream is still active (as indicated by
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT">Contents</tp:dbus-ref>).</p>
+
+ <p>This property is immutable (cannot change), and therefore SHOULD
+ appear wherever immutable properties are reported, e.g. <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref>
+ signals.</p>
+
+ <tp:rationale><p>This reduces D-Bus round trips.</p></tp:rationale>
+
+ <p>Connection managers capable of signalling audio calls to contacts
+ SHOULD include a channel class in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">RequestableChannelClasses</tp:dbus-ref>
+ with <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">ChannelType</tp:dbus-ref>
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Call.DRAFT</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ = Contact in the fixed properties dictionary, and InitialAudio
+ (and also InitialVideo, if applicable) in the allowed properties
+ list. Clients wishing to discover whether a connection manager
+ can signal audio and/or video calls SHOULD use this information.</p>
+
+ <tp:rationale>
+ <p>Not all protocols support signalling video calls, and it would be
+ possible (although unlikely) to have a protocol where only video,
+ and not audio, could be signalled.</p>
+ </tp:rationale>
+
+ <p>Connection managers that support the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">ContactCapabilities</tp:dbus-ref>
+ interface SHOULD represent the capabilities of receiving audio
+ and/or video calls by including a channel class in
+ a contact's capabilities with ChannelType = Call
+ in the fixed properties dictionary, and InitialAudio and/or
+ InitialVideo in the allowed properties list. Clients wishing to
+ discover whether a particular contact is likely to be able to
+ receive audio and/or video calls SHOULD use this information.</p>
+
+ <tp:rationale>
+ <p>Not all clients support video calls, and it would also be
+ possible (although unlikely) to have a client which could only
+ stream video, not audio.</p>
+ </tp:rationale>
+
+ <p>Clients that are willing to receive audio and/or video calls
+ SHOULD include the following among their channel classes if
+ calling <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities">UpdateCapabilities</tp:dbus-ref>
+ (clients of a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref>
+ SHOULD instead arrange for the ChannelDispatcher to do this,
+ by including the filters in their <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandlerChannelFilter</tp:dbus-ref>
+ properties):</p>
+
+ <ul>
+ <li>{ ChannelType = Call }</li>
+ <li>{ ChannelType = Call, InitialAudio = true }
+ if receiving calls with audio is supported</li>
+ <li>{ ChannelType = Call, InitialVideo = true }
+ if receiving calls with video is supported</li>
+ </ul>
+
+ <tp:rationale>
+ <p>Connection managers for protocols with capability discovery,
+ like XMPP, need this information to advertise the appropriate
+ capabilities for their protocol.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialVideo" tp:name-for-bindings="Initial_Video"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same as <tp:member-ref>InitialAudio</tp:member-ref>, but for
+ a video stream. This property is immutable (cannot change).</p>
+
+ <p>In particular, note that if this property is false, this does not
+ imply that an active video stream has not been added, only that no
+ video stream was active at the time the channel appeared.</p>
+
+ <p>This property is the correct way to discover whether connection
+ managers, contacts etc. support video calls; it appears in
+ capabilities structures in the same way as InitialAudio.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="MutableContents" tp:name-for-bindings="Mutable_Contents"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If <tt>True</tt>, a stream of a different content type can be added
+ after the Channel has been requested </p>
+
+ <p>If this property is missing, clients SHOULD assume that it is false,
+ and thus that the channel's streams cannot be changed once the call
+ has started.</p>
+
+ <p>If this property isn't present in the "allowed" set in any of the
+ Call entries contact capabilities, then user interfaces MAY choose to
+ show a separate "call" option for each class of call.</p>
+
+ <tp:rationale>
+ <p>For example, once an audio-only Google Talk call has started,
+ it is not possible to add a video stream; both audio and video
+ must be requested at the start of the call if video is desired.
+ User interfaces may use this pseudo-capability as a hint to
+ display separate "Audio call" and "Video call" buttons, rather
+ than a single "Call" button with the option to add and remove
+ video once the call has started for contacts without this flag.
+ </p>
+ </tp:rationale>
+
+ <p>This property is immutable, and therefore SHOULD be announced
+ in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref>,
+ etc.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:handler-capability-token name="gtalk-p2p">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is Stream_Transport_Type_GTalk_P2P.</p>
+ </tp:docstring>
+ </tp:handler-capability-token>
+
+ <tp:handler-capability-token name="ice">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is Stream_Transport_Type_ICE.</p>
+ </tp:docstring>
+ </tp:handler-capability-token>
+
+ <tp:handler-capability-token name="wlm-8.5">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is Stream_Transport_Type_WLM_8_5.</p>
+ </tp:docstring>
+ </tp:handler-capability-token>
+
+ <tp:handler-capability-token name="wlm-2009">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is Stream_Transport_Type_WLM_2009.</p>
+ </tp:docstring>
+ </tp:handler-capability-token>
+
+ <tp:handler-capability-token name="video/h264" is-family="yes">
+ <tp:docstring>
+ <p>The client supports media streaming with H264 (etc.).</p>
+
+ <p>This handler capability token is a one of a family
+ of similar tokens: for any other audio or video codec whose MIME
+ type is audio/<em>subtype</em> or video/<em>subtype</em>, a handler
+ capability token of this form may exist (the subtype MUST appear
+ in lower case in this context). Clients MAY support more
+ codecs than they explicitly advertise support for; clients SHOULD
+ explicitly advertise support for their preferred codec(s), and
+ for codecs like H264 that are, in practice, significant in codec
+ negotiation.</p>
+
+ <tp:rationale>
+ <p>For instance, the XMPP capability used by the Google Video
+ Chat web client to determine whether a client is compatible
+ with it requires support for H264 video, so an XMPP
+ connection manager that supports this version of Jingle should
+ not advertise the Google Video Chat capability unless there
+ is at least one installed client that declares that it supports
+ <code>video/h264</code> on Call channels.</p>
+ </tp:rationale>
+
+ <p>For example, a client could advertise support for
+ Speex, Theora and H264 by having three
+ handler capability tokens,
+ <code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/audio/speex</code>,
+ <code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/theora</code> and
+ <code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/h264</code>,
+ in its <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">Capabilities</tp:dbus-ref>
+ property.</p>
+
+ <p>Clients MAY have media signalling abilities without explicitly
+ supporting any particular codec, and connection managers SHOULD
+ support this usage.</p>
+
+ <tp:rationale>
+ <p>This is necessary to support gatewaying between two Telepathy
+ connections, in which case the available codecs might not be
+ known to the gatewaying process.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:handler-capability-token>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
new file mode 100644
index 000000000..9e30a81f2
--- /dev/null
+++ b/extensions/Makefile.am
@@ -0,0 +1,172 @@
+# This directory re-uses telepathy-glib's code generation mechanisms to
+# generate code for interfaces that aren't stable enough for telepathy-glib
+# yet, so we can start to adapt example code to use them.
+
+tools_dir = $(top_srcdir)/tools
+
+AM_CFLAGS = \
+ $(ERROR_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(TP_GLIB_CFLAGS)
+
+EXTRA_DIST = \
+ all.xml \
+ call-content.xml \
+ call-stream.xml \
+ channel.xml \
+ misc.xml
+
+noinst_LTLIBRARIES = libfuture-extensions.la
+
+# In an external project you'd use $(TP_GLIB_LIBS) (obtained from
+# pkg-config via autoconf) instead of the .la path
+libfuture_extensions_la_LIBADD = \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(top_builddir)/telepathy-glib/libtelepathy-glib.la
+
+libfuture_extensions_la_SOURCES = \
+ call-content.c \
+ call-content.h \
+ call-stream.c \
+ call-stream.h \
+ extensions.c \
+ extensions-cli.c \
+ extensions.h
+
+nodist_libfuture_extensions_la_SOURCES = \
+ _gen/signals-marshal.c \
+ _gen/signals-marshal.h \
+ _gen/signals-marshal.list \
+ _gen/register-dbus-glib-marshallers-body.h \
+ _gen/enums.h \
+ _gen/gtypes.h \
+ _gen/gtypes-body.h \
+ _gen/interfaces.h \
+ _gen/interfaces-body.h \
+ _gen/cli-call-content.h \
+ _gen/cli-call-content-body.h \
+ _gen/cli-call-stream.h \
+ _gen/cli-call-stream-body.h \
+ _gen/cli-channel.h \
+ _gen/cli-channel-body.h \
+ _gen/cli-misc.h \
+ _gen/cli-misc-body.h \
+ _gen/svc-call-content.h \
+ _gen/svc-call-content.c \
+ _gen/svc-call-stream.h \
+ _gen/svc-call-stream.c \
+ _gen/svc-channel.h \
+ _gen/svc-channel.c \
+ _gen/svc-misc.h \
+ _gen/svc-misc.c
+
+BUILT_SOURCES = \
+ _gen/all.xml \
+ _gen/call-content.xml \
+ _gen/call-stream.xml \
+ _gen/channel.xml \
+ $(nodist_libfuture_extensions_la_SOURCES)
+
+CLEANFILES = $(BUILT_SOURCES)
+
+XSLTPROCFLAGS = --nonet --novalid
+
+# Generated files which can be generated for all categories simultaneously
+
+_gen/all.xml: all.xml $(wildcard *.xml) $(tools_dir)/xincludator.py
+ $(mkdir_p) _gen
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/xincludator.py $< > $@
+
+_gen/gtypes.h _gen/gtypes-body.h: _gen/all.xml \
+ $(top_srcdir)/tools/glib-gtypes-generator.py
+ $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glib-gtypes-generator.py \
+ $< _gen/gtypes Future
+
+_gen/signals-marshal.list: _gen/all.xml \
+ $(tools_dir)/glib-signals-marshal-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-signals-marshal-gen.py $< > $@
+
+_gen/signals-marshal.h: _gen/signals-marshal.list Makefile.am
+ $(AM_V_GEN)$(GLIB_GENMARSHAL) --header --prefix=_future_ext_marshal $< > $@
+
+_gen/signals-marshal.c: _gen/signals-marshal.list Makefile.am
+ $(AM_V_GEN){ echo '#include "_gen/signals-marshal.h"' && \
+ $(GLIB_GENMARSHAL) --body --prefix=_future_ext_marshal $< ; } > $@
+
+_gen/register-dbus-glib-marshallers-body.h: _gen/all.xml \
+ $(tools_dir)/glib-client-marshaller-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-marshaller-gen.py $< \
+ _future_ext > $@
+
+_gen/enums.h: _gen/all.xml \
+ $(tools_dir)/c-constants-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/c-constants-gen.py \
+ Future \
+ $< > $@
+
+_gen/interfaces-body.h _gen/interfaces.h: _gen/all.xml \
+ $(tools_dir)/glib-interfaces-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-interfaces-gen.py \
+ Future _gen/interfaces-body.h _gen/interfaces.h $<
+
+# Generated files which must be generated per "category". Each TpProxy
+# subclass you want to use with --subclass will need to have its own category,
+# although you can subdivide further if you want.
+
+_gen/%.xml: %.xml $(wildcard *.xml) $(tools_dir)/xincludator.py
+ $(mkdir_p) _gen
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/xincludator.py $< > $@
+
+_gen/svc-%.c _gen/svc-%.h: _gen/%.xml \
+ $(tools_dir)/glib-ginterface-gen.py
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-ginterface-gen.py \
+ --filename=_gen/svc-$* \
+ --signal-marshal-prefix=_future_ext \
+ --include='<telepathy-glib/dbus.h>' \
+ --include='"_gen/signals-marshal.h"' \
+ --not-implemented-func='tp_dbus_g_method_return_not_implemented' \
+ --allow-unstable \
+ $< Future_Svc_
+
+_gen/cli-channel-body.h _gen/cli-channel.h: _gen/channel.xml \
+ $(tools_dir)/glib-client-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \
+ --group=channel \
+ --subclass=TpChannel \
+ --subclass-assert=TP_IS_CHANNEL \
+ --iface-quark-prefix=FUTURE_IFACE_QUARK \
+ --tp-proxy-api=0.7.6 \
+ $< Future_Cli _gen/cli-channel
+
+_gen/cli-call-content-body.h _gen/cli-call-content.h: _gen/call-content.xml \
+ $(tools_dir)/glib-client-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \
+ --group=call_content \
+ --subclass=FutureCallContent \
+ --subclass-assert=FUTURE_IS_CALL_CONTENT \
+ --iface-quark-prefix=FUTURE_IFACE_QUARK \
+ --tp-proxy-api=0.7.6 \
+ $< Future_Cli _gen/cli-call-content
+
+_gen/cli-call-stream-body.h _gen/cli-call-stream.h: _gen/call-stream.xml \
+ $(tools_dir)/glib-client-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \
+ --group=call_stream \
+ --subclass=FutureCallStream \
+ --subclass-assert=FUTURE_IS_CALL_STREAM \
+ --iface-quark-prefix=FUTURE_IFACE_QUARK \
+ --tp-proxy-api=0.7.6 \
+ $< Future_Cli _gen/cli-call-stream
+
+# for now the Endpoint etc. interfaces are on every TpProxy - when we
+# have a TpCallEndpoint etc., they should appear on that
+
+_gen/cli-misc-body.h _gen/cli-misc.h: _gen/misc.xml \
+ $(tools_dir)/glib-client-gen.py Makefile.am
+ $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \
+ --group=misc \
+ --iface-quark-prefix=FUTURE_IFACE_QUARK \
+ --tp-proxy-api=0.7.6 \
+ $< Future_Cli _gen/cli-misc
diff --git a/extensions/all.xml b/extensions/all.xml
new file mode 100644
index 000000000..618cef7b0
--- /dev/null
+++ b/extensions/all.xml
@@ -0,0 +1,12 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Extensions from the future</tp:title>
+
+<xi:include href="call-content.xml"/>
+<xi:include href="call-stream.xml"/>
+<xi:include href="channel.xml"/>
+<xi:include href="misc.xml"/>
+
+</tp:spec>
diff --git a/extensions/call-content.c b/extensions/call-content.c
new file mode 100644
index 000000000..29d10cec4
--- /dev/null
+++ b/extensions/call-content.c
@@ -0,0 +1,161 @@
+/*
+ * call-content.c - proxy for a Content in a Call channel
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 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 "extensions/call-content.h"
+
+#include <telepathy-glib/proxy-subclass.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "extensions/extensions.h"
+
+/* Generated code */
+#include "_gen/cli-call-content-body.h"
+
+/**
+ * SECTION:call-content
+ * @title: FutureCallContent
+ * @short_description: proxy for a Content in a Call channel
+ * @see_also: #TpChannel
+ *
+ * FIXME
+ *
+ * Since: FIXME
+ */
+
+/**
+ * FutureCallContentClass:
+ *
+ * The class of a #FutureCallContent.
+ *
+ * Since: FIXME
+ */
+struct _FutureCallContentClass {
+ TpProxyClass parent_class;
+ /*<private>*/
+ gpointer priv;
+};
+
+/**
+ * FutureCallContent:
+ *
+ * A proxy object for a Telepathy connection manager.
+ *
+ * Since: FIXME
+ */
+struct _FutureCallContent {
+ TpProxy parent;
+ /*<private>*/
+ FutureCallContentPrivate *priv;
+};
+
+struct _FutureCallContentPrivate {
+ int dummy;
+};
+
+G_DEFINE_TYPE (FutureCallContent,
+ future_call_content,
+ TP_TYPE_PROXY);
+
+static void
+future_call_content_init (FutureCallContent *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, FUTURE_TYPE_CALL_CONTENT,
+ FutureCallContentPrivate);
+}
+
+static void
+future_call_content_class_init (FutureCallContentClass *klass)
+{
+ TpProxyClass *proxy_class = (TpProxyClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (FutureCallContentPrivate));
+
+ proxy_class->must_have_unique_name = TRUE;
+ proxy_class->interface = FUTURE_IFACE_QUARK_CALL_CONTENT;
+ future_call_content_init_known_interfaces ();
+}
+
+/**
+ * future_call_content_new:
+ * @channel: the Call channel
+ * @object_path: the object path of the content; may not be %NULL
+ * @error: used to indicate the error if %NULL is returned
+ *
+ * <!-- -->
+ *
+ * Returns: a new content proxy, or %NULL on invalid arguments
+ *
+ * Since: FIXME
+ */
+FutureCallContent *
+future_call_content_new (TpChannel *channel,
+ const gchar *object_path,
+ GError **error)
+{
+ FutureCallContent *ret = NULL;
+
+ g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
+ g_return_val_if_fail (object_path != NULL, NULL);
+
+ if (!tp_dbus_check_valid_object_path (object_path, error))
+ goto finally;
+
+ ret = FUTURE_CALL_CONTENT (g_object_new (FUTURE_TYPE_CALL_CONTENT,
+ /* FIXME: pass in the Channel as a property? */
+ "dbus-daemon", tp_proxy_get_dbus_daemon (channel),
+ "bus-name", tp_proxy_get_bus_name (channel),
+ "object-path", object_path,
+ NULL));
+
+finally:
+ return ret;
+}
+
+/**
+ * future_call_content_init_known_interfaces:
+ *
+ * Ensure that the known interfaces for FutureCallContent have been set up.
+ * This is done automatically when necessary, but for correct
+ * overriding of library interfaces by local extensions, you should
+ * call this function before calling
+ * tp_proxy_or_subclass_hook_on_interface_add() with first argument
+ * %FUTURE_TYPE_CALL_CONTENT.
+ *
+ * Since: 0.7.32
+ */
+void
+future_call_content_init_known_interfaces (void)
+{
+ static gsize once = 0;
+
+ if (g_once_init_enter (&once))
+ {
+ GType tp_type = FUTURE_TYPE_CALL_CONTENT;
+
+ tp_proxy_init_known_interfaces ();
+ tp_proxy_or_subclass_hook_on_interface_add (tp_type,
+ future_cli_call_content_add_signals);
+ tp_proxy_subclass_add_error_mapping (tp_type,
+ TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR);
+
+ g_once_init_leave (&once, 1);
+ }
+}
diff --git a/extensions/call-content.h b/extensions/call-content.h
new file mode 100644
index 000000000..5f674f2e1
--- /dev/null
+++ b/extensions/call-content.h
@@ -0,0 +1,62 @@
+/*
+ * call-content.h - proxy for a Content in a Call channel
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 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 FUTURE_CALL_CONTENT_H
+#define FUTURE_CALL_CONTENT_H
+
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/proxy.h>
+
+G_BEGIN_DECLS
+
+typedef struct _FutureCallContent FutureCallContent;
+typedef struct _FutureCallContentPrivate FutureCallContentPrivate;
+typedef struct _FutureCallContentClass FutureCallContentClass;
+
+GType future_call_content_get_type (void);
+
+/* TYPE MACROS */
+#define FUTURE_TYPE_CALL_CONTENT \
+ (future_call_content_get_type ())
+#define FUTURE_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), FUTURE_TYPE_CALL_CONTENT, \
+ FutureCallContent))
+#define FUTURE_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), FUTURE_TYPE_CALL_CONTENT, \
+ FutureCallContentClass))
+#define FUTURE_IS_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), FUTURE_TYPE_CALL_CONTENT))
+#define FUTURE_IS_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), FUTURE_TYPE_CALL_CONTENT))
+#define FUTURE_CALL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FUTURE_TYPE_CALL_CONTENT, \
+ FutureCallContentClass))
+
+FutureCallContent *future_call_content_new (TpChannel *channel,
+ const gchar *object_path, GError **error);
+
+void future_call_content_init_known_interfaces (void);
+
+G_END_DECLS
+
+#include "extensions/_gen/cli-call-content.h"
+
+#endif
diff --git a/extensions/call-content.xml b/extensions/call-content.xml
new file mode 100644
index 000000000..cfb100035
--- /dev/null
+++ b/extensions/call-content.xml
@@ -0,0 +1,10 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Call Content</tp:title>
+
+<xi:include href="Call_Content.xml"/>
+<xi:include href="Call_Content_Interface_Media.xml"/>
+
+</tp:spec>
diff --git a/extensions/call-stream.c b/extensions/call-stream.c
new file mode 100644
index 000000000..b99539896
--- /dev/null
+++ b/extensions/call-stream.c
@@ -0,0 +1,161 @@
+/*
+ * call-stream.c - proxy for a Stream in a Call channel
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 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 "extensions/call-stream.h"
+
+#include <telepathy-glib/proxy-subclass.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "extensions/extensions.h"
+
+/* Generated code */
+#include "_gen/cli-call-stream-body.h"
+
+/**
+ * SECTION:call-stream
+ * @title: FutureCallStream
+ * @short_description: proxy for a Stream in a Call channel
+ * @see_also: #TpChannel
+ *
+ * FIXME
+ *
+ * Since: FIXME
+ */
+
+/**
+ * FutureCallStreamClass:
+ *
+ * The class of a #FutureCallStream.
+ *
+ * Since: FIXME
+ */
+struct _FutureCallStreamClass {
+ TpProxyClass parent_class;
+ /*<private>*/
+ gpointer priv;
+};
+
+/**
+ * FutureCallStream:
+ *
+ * A proxy object for a Telepathy connection manager.
+ *
+ * Since: FIXME
+ */
+struct _FutureCallStream {
+ TpProxy parent;
+ /*<private>*/
+ FutureCallStreamPrivate *priv;
+};
+
+struct _FutureCallStreamPrivate {
+ int dummy;
+};
+
+G_DEFINE_TYPE (FutureCallStream,
+ future_call_stream,
+ TP_TYPE_PROXY);
+
+static void
+future_call_stream_init (FutureCallStream *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, FUTURE_TYPE_CALL_STREAM,
+ FutureCallStreamPrivate);
+}
+
+static void
+future_call_stream_class_init (FutureCallStreamClass *klass)
+{
+ TpProxyClass *proxy_class = (TpProxyClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (FutureCallStreamPrivate));
+
+ proxy_class->must_have_unique_name = TRUE;
+ proxy_class->interface = FUTURE_IFACE_QUARK_CALL_STREAM;
+ future_call_stream_init_known_interfaces ();
+}
+
+/**
+ * future_call_stream_new:
+ * @channel: the Call channel
+ * @object_path: the object path of the stream; may not be %NULL
+ * @error: used to indicate the error if %NULL is returned
+ *
+ * <!-- -->
+ *
+ * Returns: a new stream proxy, or %NULL on invalid arguments
+ *
+ * Since: FIXME
+ */
+FutureCallStream *
+future_call_stream_new (TpChannel *channel,
+ const gchar *object_path,
+ GError **error)
+{
+ FutureCallStream *ret = NULL;
+
+ g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
+ g_return_val_if_fail (object_path != NULL, NULL);
+
+ if (!tp_dbus_check_valid_object_path (object_path, error))
+ goto finally;
+
+ ret = FUTURE_CALL_STREAM (g_object_new (FUTURE_TYPE_CALL_STREAM,
+ /* FIXME: pass in the Channel as a property? */
+ "dbus-daemon", tp_proxy_get_dbus_daemon (channel),
+ "bus-name", tp_proxy_get_bus_name (channel),
+ "object-path", object_path,
+ NULL));
+
+finally:
+ return ret;
+}
+
+/**
+ * future_call_stream_init_known_interfaces:
+ *
+ * Ensure that the known interfaces for FutureCallStream have been set up.
+ * This is done automatically when necessary, but for correct
+ * overriding of library interfaces by local extensions, you should
+ * call this function before calling
+ * tp_proxy_or_subclass_hook_on_interface_add() with first argument
+ * %FUTURE_TYPE_CALL_STREAM.
+ *
+ * Since: 0.7.32
+ */
+void
+future_call_stream_init_known_interfaces (void)
+{
+ static gsize once = 0;
+
+ if (g_once_init_enter (&once))
+ {
+ GType tp_type = FUTURE_TYPE_CALL_STREAM;
+
+ tp_proxy_init_known_interfaces ();
+ tp_proxy_or_subclass_hook_on_interface_add (tp_type,
+ future_cli_call_stream_add_signals);
+ tp_proxy_subclass_add_error_mapping (tp_type,
+ TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR);
+
+ g_once_init_leave (&once, 1);
+ }
+}
diff --git a/extensions/call-stream.h b/extensions/call-stream.h
new file mode 100644
index 000000000..0a195bde0
--- /dev/null
+++ b/extensions/call-stream.h
@@ -0,0 +1,62 @@
+/*
+ * call-stream.h - proxy for a Stream in a Call channel
+ *
+ * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 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 FUTURE_CALL_STREAM_H
+#define FUTURE_CALL_STREAM_H
+
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/proxy.h>
+
+G_BEGIN_DECLS
+
+typedef struct _FutureCallStream FutureCallStream;
+typedef struct _FutureCallStreamPrivate FutureCallStreamPrivate;
+typedef struct _FutureCallStreamClass FutureCallStreamClass;
+
+GType future_call_stream_get_type (void);
+
+/* TYPE MACROS */
+#define FUTURE_TYPE_CALL_STREAM \
+ (future_call_stream_get_type ())
+#define FUTURE_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), FUTURE_TYPE_CALL_STREAM, \
+ FutureCallStream))
+#define FUTURE_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), FUTURE_TYPE_CALL_STREAM, \
+ FutureCallStreamClass))
+#define FUTURE_IS_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), FUTURE_TYPE_CALL_STREAM))
+#define FUTURE_IS_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), FUTURE_TYPE_CALL_STREAM))
+#define FUTURE_CALL_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), FUTURE_TYPE_CALL_STREAM, \
+ FutureCallStreamClass))
+
+FutureCallStream *future_call_stream_new (TpChannel *channel,
+ const gchar *object_path, GError **error);
+
+void future_call_stream_init_known_interfaces (void);
+
+G_END_DECLS
+
+#include "extensions/_gen/cli-call-stream.h"
+
+#endif
diff --git a/extensions/call-stream.xml b/extensions/call-stream.xml
new file mode 100644
index 000000000..3e85b3791
--- /dev/null
+++ b/extensions/call-stream.xml
@@ -0,0 +1,10 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Call Stream</tp:title>
+
+<xi:include href="Call_Stream.xml"/>
+<xi:include href="Call_Stream_Interface_Media.xml"/>
+
+</tp:spec>
diff --git a/extensions/channel.xml b/extensions/channel.xml
new file mode 100644
index 000000000..b4e0a469c
--- /dev/null
+++ b/extensions/channel.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Channel extensions from the future</tp:title>
+
+<xi:include href="Channel_Type_Call.xml"/>
+
+</tp:spec>
diff --git a/extensions/extensions-cli.c b/extensions/extensions-cli.c
new file mode 100644
index 000000000..d29c5ecbd
--- /dev/null
+++ b/extensions/extensions-cli.c
@@ -0,0 +1,35 @@
+#include "extensions.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/proxy-subclass.h>
+
+static void _future_ext_register_dbus_glib_marshallers (void);
+
+/* include auto-generated stubs for client-specific code */
+#include "_gen/signals-marshal.h"
+#include "_gen/cli-channel-body.h"
+#include "_gen/cli-misc-body.h"
+#include "_gen/register-dbus-glib-marshallers-body.h"
+
+static gpointer
+future_cli_once (gpointer data)
+{
+ _future_ext_register_dbus_glib_marshallers ();
+
+ tp_channel_init_known_interfaces ();
+
+ tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_PROXY,
+ future_cli_misc_add_signals);
+ tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_CHANNEL,
+ future_cli_channel_add_signals);
+
+ return NULL;
+}
+
+void
+future_cli_init (void)
+{
+ static GOnce once = G_ONCE_INIT;
+
+ g_once (&once, future_cli_once, NULL);
+}
diff --git a/extensions/extensions.c b/extensions/extensions.c
new file mode 100644
index 000000000..eeda4623c
--- /dev/null
+++ b/extensions/extensions.c
@@ -0,0 +1,6 @@
+#include "extensions.h"
+
+/* include auto-generated stubs for things common to service and client */
+#include "_gen/gtypes-body.h"
+#include "_gen/interfaces-body.h"
+#include "_gen/signals-marshal.h"
diff --git a/extensions/extensions.h b/extensions/extensions.h
new file mode 100644
index 000000000..24d978748
--- /dev/null
+++ b/extensions/extensions.h
@@ -0,0 +1,27 @@
+#ifndef FUTURE_EXTENSIONS_H
+#define FUTURE_EXTENSIONS_H
+
+#include <glib-object.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "extensions/_gen/enums.h"
+#include "extensions/_gen/cli-channel.h"
+#include "extensions/_gen/cli-misc.h"
+#include "extensions/_gen/svc-call-content.h"
+#include "extensions/_gen/svc-call-stream.h"
+#include "extensions/_gen/svc-channel.h"
+#include "extensions/_gen/svc-misc.h"
+
+#include "extensions/call-content.h"
+#include "extensions/call-stream.h"
+
+G_BEGIN_DECLS
+
+#include "extensions/_gen/gtypes.h"
+#include "extensions/_gen/interfaces.h"
+
+void future_cli_init (void);
+
+G_END_DECLS
+
+#endif
diff --git a/extensions/misc.xml b/extensions/misc.xml
new file mode 100644
index 000000000..0384410f6
--- /dev/null
+++ b/extensions/misc.xml
@@ -0,0 +1,10 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Miscellaneous extensions from the future</tp:title>
+
+<xi:include href="Call_Content_Codec_Offer.xml"/>
+<xi:include href="Call_Stream_Endpoint.xml"/>
+
+</tp:spec>
diff --git a/tests/dbus/Makefile.am b/tests/dbus/Makefile.am
index 0ecc9a49c..067abd1ae 100644
--- a/tests/dbus/Makefile.am
+++ b/tests/dbus/Makefile.am
@@ -2,6 +2,7 @@ noinst_PROGRAMS = \
test-account \
test-account-manager \
test-call-cancellation \
+ test-call-example \
test-callable-example \
test-channel-dispatcher \
test-channel-dispatch-operation \
@@ -61,6 +62,11 @@ test_channel_introspect_SOURCES = channel-introspect.c
test_channel_request_SOURCES = channel-request.c
+test_call_example_SOURCES = call-example.c
+test_call_example_LDADD = \
+ $(LDADD) \
+ $(top_builddir)/examples/future/call-cm/libexample-cm-call.la
+
test_callable_example_SOURCES = callable-example.c
test_callable_example_LDADD = \
$(LDADD) \
diff --git a/tests/dbus/call-example.c b/tests/dbus/call-example.c
new file mode 100644
index 000000000..7a400253c
--- /dev/null
+++ b/tests/dbus/call-example.c
@@ -0,0 +1,1133 @@
+/* Feature test for example StreamedMedia CM code.
+ *
+ * Copyright © 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 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 <telepathy-glib/telepathy-glib.h>
+
+#include "examples/future/call-cm/cm.h"
+#include "examples/future/call-cm/conn.h"
+#include "examples/future/call-cm/call-channel.h"
+#include "examples/future/call-cm/call-stream.h"
+#include "extensions/extensions.h"
+
+#include "tests/lib/util.h"
+
+#define CLEAR_OBJECT(o) \
+ G_STMT_START { \
+ if (*(o) != NULL) \
+ { \
+ g_object_unref (*(o)); \
+ *(o) = NULL; \
+ } \
+ } G_STMT_END
+
+#define CLEAR_BOXED(g, o) \
+ G_STMT_START { \
+ if (*(o) != NULL) \
+ { \
+ g_boxed_free ((g), *(o)); \
+ *(o) = NULL; \
+ } \
+ } G_STMT_END
+
+#define CLEAR_HASH(h) \
+ G_STMT_START { \
+ if (*(h) != NULL) \
+ { \
+ g_hash_table_unref (*(h)); \
+ *(h) = NULL; \
+ } \
+ } G_STMT_END
+
+/* FIXME: if this isn't needed for Senders, remove it */
+G_GNUC_UNUSED static void
+test_assert_uu_hash_contains (GHashTable *hash,
+ guint key,
+ guint expected)
+{
+ gpointer v;
+
+ if (!g_hash_table_lookup_extended (hash, GUINT_TO_POINTER (key), NULL, &v))
+ g_error ("Expected %u => %u in hash table, but key was absent", key,
+ expected);
+
+ g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, expected);
+}
+
+typedef struct
+{
+ GMainLoop *mainloop;
+ TpDBusDaemon *dbus;
+ GError *error /* statically initialized to NULL */ ;
+
+ ExampleCallConnectionManager *service_cm;
+
+ TpConnectionManager *cm;
+ TpConnection *conn;
+ TpChannel *chan;
+ TpHandle self_handle;
+ TpHandle peer_handle;
+
+ GHashTable *get_all_return;
+
+ GArray *audio_request;
+ GArray *video_request;
+ GArray *invalid_request;
+
+ GArray *stream_ids;
+ GArray *contacts;
+ GPtrArray *get_contents_return;
+ GHashTable *get_senders_return;
+
+ gulong members_changed_detailed_id;
+
+ FutureCallContent *added_content;
+ FutureCallContent *audio_content;
+ FutureCallContent *video_content;
+ FutureCallStream *audio_stream;
+ FutureCallStream *video_stream;
+} Test;
+
+static void
+cm_ready_cb (TpConnectionManager *cm G_GNUC_UNUSED,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ Test *test = user_data;
+
+ test_assert_no_error (error);
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+conn_ready_cb (TpConnection *conn G_GNUC_UNUSED,
+ const GError *error,
+ gpointer user_data)
+{
+ Test *test = user_data;
+
+ test_assert_no_error (error);
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+setup (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ TpBaseConnectionManager *service_cm_as_base;
+ gboolean ok;
+ gchar *bus_name;
+ gchar *object_path;
+ GHashTable *parameters;
+ guint audio = TP_MEDIA_STREAM_TYPE_AUDIO;
+ guint video = TP_MEDIA_STREAM_TYPE_VIDEO;
+ guint not_a_media_type = 31337;
+
+ g_type_init ();
+ tp_debug_set_flags ("all");
+
+ test->mainloop = g_main_loop_new (NULL, FALSE);
+ test->dbus = tp_dbus_daemon_dup (NULL);
+ g_assert (test->dbus != NULL);
+
+ test->service_cm = EXAMPLE_CALL_CONNECTION_MANAGER (g_object_new (
+ EXAMPLE_TYPE_CALL_CONNECTION_MANAGER,
+ NULL));
+ g_assert (test->service_cm != NULL);
+ service_cm_as_base = TP_BASE_CONNECTION_MANAGER (test->service_cm);
+ g_assert (service_cm_as_base != NULL);
+
+ ok = tp_base_connection_manager_register (service_cm_as_base);
+ g_assert (ok);
+
+ test->cm = tp_connection_manager_new (test->dbus, "example_call",
+ NULL, &test->error);
+ g_assert (test->cm != NULL);
+ tp_connection_manager_call_when_ready (test->cm, cm_ready_cb, test, NULL,
+ NULL);
+ g_main_loop_run (test->mainloop);
+
+ parameters = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+ g_hash_table_insert (parameters, "account",
+ tp_g_value_slice_new_static_string ("me"));
+ g_hash_table_insert (parameters, "simulation-delay",
+ tp_g_value_slice_new_uint (0));
+
+ tp_cli_connection_manager_run_request_connection (test->cm, -1,
+ "example", parameters, &bus_name, &object_path, &test->error, NULL);
+ test_assert_no_error (test->error);
+
+ test->conn = tp_connection_new (test->dbus, bus_name, object_path,
+ &test->error);
+ test_assert_no_error (test->error);
+ g_assert (test->conn != NULL);
+ tp_cli_connection_call_connect (test->conn, -1, NULL, NULL, NULL, NULL);
+ tp_connection_call_when_ready (test->conn, conn_ready_cb, test);
+ g_main_loop_run (test->mainloop);
+
+ test->self_handle = tp_connection_get_self_handle (test->conn);
+ g_assert (test->self_handle != 0);
+
+ test->audio_request = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ g_array_append_val (test->audio_request, audio);
+
+ test->video_request = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ g_array_append_val (test->video_request, video);
+
+ test->invalid_request = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ g_array_append_val (test->invalid_request, not_a_media_type);
+
+ test->stream_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2);
+
+ g_hash_table_destroy (parameters);
+ g_free (bus_name);
+ g_free (object_path);
+}
+
+static void
+channel_created_cb (TpConnection *connection,
+ const gchar *object_path,
+ GHashTable *immutable_properties,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ Test *test = user_data;
+ GError *new_error = NULL;
+
+ test_assert_no_error (error);
+
+ test->chan = tp_channel_new_from_properties (connection, object_path,
+ immutable_properties, &new_error);
+ test_assert_no_error (new_error);
+
+ test->peer_handle = tp_channel_get_handle (test->chan, NULL);
+
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+channel_ready_cb (TpChannel *channel G_GNUC_UNUSED,
+ const GError *error,
+ gpointer user_data)
+{
+ Test *test = user_data;
+
+ test_assert_no_error (error);
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+added_content_cb (TpChannel *chan G_GNUC_UNUSED,
+ const gchar *object_path,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ Test *test = user_data;
+
+ CLEAR_OBJECT (&test->added_content);
+
+ if (error != NULL)
+ {
+ test->error = g_error_copy (error);
+ }
+ else
+ {
+ test->added_content = future_call_content_new (test->chan, object_path,
+ NULL);
+ g_assert (test->added_content != NULL);
+ }
+
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+got_all_cb (TpProxy *proxy,
+ GHashTable *properties,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ Test *test = user_data;
+
+ test_assert_no_error (error);
+
+ CLEAR_HASH (&test->get_all_return);
+ test->get_all_return = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) tp_g_value_slice_free);
+ tp_g_hash_table_update (test->get_all_return, properties,
+ (GBoxedCopyFunc) g_strdup, (GBoxedCopyFunc) tp_g_value_slice_dup);
+
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+got_contents_cb (TpProxy *proxy,
+ const GValue *value,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ Test *test = user_data;
+
+ test_assert_no_error (error);
+
+ CLEAR_BOXED (TP_ARRAY_TYPE_OBJECT_PATH_LIST, &test->get_contents_return);
+ g_assert (G_VALUE_HOLDS (value, TP_ARRAY_TYPE_OBJECT_PATH_LIST));
+ test->get_contents_return = g_value_dup_boxed (value);
+
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+got_senders_cb (TpProxy *proxy,
+ const GValue *value,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ Test *test = user_data;
+
+ CLEAR_HASH (&test->get_senders_return);
+
+ if (test->error != NULL)
+ g_clear_error (&test->error);
+
+ test_assert_no_error (error);
+
+ g_assert (G_VALUE_HOLDS (value, FUTURE_HASH_TYPE_CONTACT_SENDING_STATE_MAP));
+ test->get_senders_return = g_value_dup_boxed (value);
+
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+void_cb (TpChannel *chan G_GNUC_UNUSED,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ Test *test = user_data;
+
+ if (error != NULL)
+ {
+ test->error = g_error_copy (error);
+ }
+
+ g_main_loop_quit (test->mainloop);
+}
+
+static void
+test_connect_channel_signals (Test *test)
+{
+}
+
+static void
+outgoing_call (Test *test,
+ const gchar *id,
+ gboolean initial_audio,
+ gboolean initial_video)
+{
+ GHashTable *request = tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ G_TYPE_STRING, FUTURE_IFACE_CHANNEL_TYPE_CALL,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
+ TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, id,
+ FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO,
+ G_TYPE_BOOLEAN, initial_audio,
+ FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO,
+ G_TYPE_BOOLEAN, initial_video,
+ NULL);
+
+ tp_cli_connection_interface_requests_call_create_channel (test->conn, -1,
+ request, channel_created_cb, test, NULL, NULL);
+ g_hash_table_destroy (request);
+ request = NULL;
+ g_main_loop_run (test->mainloop);
+
+ /* Do this before waiting for it to become ready - we knew its channel type
+ * and interfaces anyway */
+ test_connect_channel_signals (test);
+
+ tp_channel_call_when_ready (test->chan, channel_ready_cb, test);
+ g_main_loop_run (test->mainloop);
+}
+
+static void
+assert_call_properties (GHashTable *get_all_return,
+ FutureCallState call_state,
+ TpHandle actor,
+ FutureCallStateChangeReason reason,
+ const gchar *dbus_reason,
+ gboolean check_call_flags, FutureCallFlags call_flags,
+ gboolean check_initials, gboolean initial_audio, gboolean initial_video)
+{
+ gboolean valid;
+ GValueArray *state_reason;
+
+ g_assert_cmpuint (tp_asv_get_uint32 (get_all_return, "CallState",
+ &valid), ==, call_state);
+ g_assert (valid);
+ state_reason = tp_asv_get_boxed (get_all_return, "CallStateReason",
+ FUTURE_STRUCT_TYPE_CALL_STATE_REASON);
+ g_assert (state_reason != NULL);
+ g_assert_cmpuint (g_value_get_uint (state_reason->values + 0), ==,
+ actor);
+ g_assert_cmpuint (g_value_get_uint (state_reason->values + 1), ==,
+ reason);
+ g_assert_cmpstr (g_value_get_string (state_reason->values + 2), ==,
+ dbus_reason);
+
+ /* Hard-coded properties */
+ g_assert_cmpint (tp_asv_get_boolean (get_all_return,
+ "HardwareStreaming", &valid), ==, TRUE);
+ g_assert (valid);
+ g_assert_cmpint (tp_asv_get_boolean (get_all_return,
+ "MutableContents", &valid), ==, TRUE);
+ g_assert (valid);
+ g_assert_cmpstr (tp_asv_get_string (get_all_return,
+ "InitialTransport"), ==, "");
+
+ if (check_call_flags)
+ {
+ g_assert_cmpuint (tp_asv_get_uint32 (get_all_return,
+ "CallFlags", &valid), ==, 0);
+ g_assert (valid);
+ }
+
+ if (check_initials)
+ {
+ g_assert_cmpint (tp_asv_get_boolean (get_all_return,
+ "InitialAudio", &valid), ==, initial_audio);
+ g_assert (valid);
+
+ g_assert_cmpint (tp_asv_get_boolean (get_all_return,
+ "InitialVideo", &valid), ==, initial_video);
+ g_assert (valid);
+ }
+
+ /* FIXME: CallStateDetails */
+}
+
+static void
+assert_content_properties (GHashTable *get_all_return,
+ TpMediaStreamType type,
+ TpHandle creator,
+ FutureCallContentDisposition disposition)
+{
+ gboolean valid;
+
+ g_assert_cmpstr (tp_asv_get_string (get_all_return, "Name"), !=, NULL);
+ g_assert_cmpuint (tp_asv_get_uint32 (get_all_return, "Type", &valid),
+ ==, type);
+ g_assert_cmpint (valid, ==, TRUE);
+ g_assert_cmpuint (tp_asv_get_uint32 (get_all_return, "Creator",
+ &valid), ==, creator);
+ g_assert_cmpint (valid, ==, TRUE);
+ g_assert_cmpuint (tp_asv_get_uint32 (get_all_return, "Disposition",
+ &valid), ==, disposition);
+ g_assert_cmpint (valid, ==, TRUE);
+}
+
+static void
+loop_until_ended (Test *test)
+{
+ while (1)
+ {
+ tp_cli_dbus_properties_call_get_all (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ if (tp_asv_get_uint32 (test->get_all_return, "CallState",
+ NULL) == FUTURE_CALL_STATE_ENDED)
+ return;
+ }
+}
+
+static void
+loop_until_answered (Test *test)
+{
+ while (1)
+ {
+ tp_cli_dbus_properties_call_get_all (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ if (tp_asv_get_uint32 (test->get_all_return, "CallState",
+ NULL) != FUTURE_CALL_STATE_PENDING_RECEIVER)
+ return;
+ }
+}
+
+static void
+assert_ended_and_run_close (Test *test,
+ TpHandle expected_actor,
+ FutureCallStateChangeReason expected_reason,
+ const gchar *expected_error)
+{
+ /* In response to whatever we just did, the call ends... */
+ tp_cli_dbus_properties_call_get_all (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+ assert_call_properties (test->get_all_return,
+ FUTURE_CALL_STATE_ENDED,
+ expected_actor,
+ expected_reason,
+ expected_error,
+ FALSE, 0, /* ignore call flags */
+ FALSE, FALSE, FALSE); /* ignore initial audio/video */
+
+ /* ... which means there are no contents ... */
+ tp_cli_dbus_properties_call_get (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents",
+ got_contents_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+ g_assert_cmpuint (test->get_contents_return->len, ==, 0);
+
+ /* ... but the channel doesn't close */
+ test_connection_run_until_dbus_queue_processed (test->conn);
+ g_assert (tp_proxy_get_invalidated (test->chan) == NULL);
+
+ /* When we call Close it finally closes */
+ tp_cli_channel_call_close (test->chan, -1, void_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+ test_connection_run_until_dbus_queue_processed (test->conn);
+ g_assert (tp_proxy_get_invalidated (test->chan) != NULL);
+}
+
+static void
+test_basics (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ const GPtrArray *stream_paths;
+ gpointer v;
+
+ outgoing_call (test, "basic-test", TRUE, FALSE);
+
+ /* Get initial state */
+ tp_cli_dbus_properties_call_get_all (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ assert_call_properties (test->get_all_return,
+ FUTURE_CALL_STATE_PENDING_INITIATOR, 0,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ TRUE, 0, /* call flags */
+ TRUE, TRUE, FALSE); /* initial audio/video must be what we said */
+
+ /* We have one audio content but it's not active just yet */
+
+ tp_cli_dbus_properties_call_get (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents",
+ got_contents_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ g_assert_cmpuint (test->get_contents_return->len, ==, 1);
+
+ g_assert (test->audio_content == NULL);
+ test->audio_content = future_call_content_new (test->chan,
+ g_ptr_array_index (test->get_contents_return, 0), NULL);
+ g_assert (test->audio_content != NULL);
+
+ tp_cli_dbus_properties_call_get_all (test->audio_content, -1,
+ FUTURE_IFACE_CALL_CONTENT, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+ assert_content_properties (test->get_all_return,
+ TP_MEDIA_STREAM_TYPE_AUDIO, test->self_handle,
+ FUTURE_CALL_CONTENT_DISPOSITION_INITIAL);
+
+ stream_paths = tp_asv_get_boxed (test->get_all_return, "Streams",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST);
+ g_assert (stream_paths != NULL);
+ g_assert_cmpuint (stream_paths->len, ==, 1);
+
+ g_assert (test->audio_stream == NULL);
+ test->audio_stream = future_call_stream_new (test->chan,
+ g_ptr_array_index (stream_paths, 0), NULL);
+ g_assert (test->audio_stream != NULL);
+
+ tp_cli_dbus_properties_call_get (test->audio_stream, -1,
+ FUTURE_IFACE_CALL_STREAM, "Senders", got_senders_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+
+ test_assert_no_error (test->error);
+
+ g_assert_cmpuint (g_hash_table_size (test->get_senders_return), ==, 2);
+ g_assert (!g_hash_table_lookup_extended (test->get_senders_return,
+ GUINT_TO_POINTER (0), NULL, NULL));
+ g_assert (g_hash_table_lookup_extended (test->get_senders_return,
+ GUINT_TO_POINTER (test->self_handle), NULL, &v));
+ g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, FUTURE_SENDING_STATE_SENDING);
+ g_assert (g_hash_table_lookup_extended (test->get_senders_return,
+ GUINT_TO_POINTER (tp_channel_get_handle (test->chan, NULL)),
+ NULL, &v));
+ g_assert_cmpuint (GPOINTER_TO_UINT (v), ==,
+ FUTURE_SENDING_STATE_PENDING_SEND);
+
+ /* OK, that looks good. Actually make the call */
+ future_cli_channel_type_call_call_accept (test->chan, -1, void_cb,
+ test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ /* Calling Accept again makes no sense, but mustn't crash */
+ future_cli_channel_type_call_call_accept (test->chan, -1, void_cb,
+ test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ g_assert_error (test->error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE);
+ g_clear_error (&test->error);
+
+ /* Wait for the remote contact to answer, if they haven't already */
+
+ loop_until_answered (test);
+
+ /* Calling Accept again makes no sense, but mustn't crash */
+ future_cli_channel_type_call_call_accept (test->chan, -1, void_cb,
+ test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ g_assert_error (test->error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE);
+ g_clear_error (&test->error);
+
+ /* Check the call state */
+
+ tp_cli_dbus_properties_call_get_all (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ assert_call_properties (test->get_all_return,
+ FUTURE_CALL_STATE_ACCEPTED, tp_channel_get_handle (test->chan, NULL),
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ TRUE, 0, /* call flags */
+ FALSE, FALSE, FALSE); /* don't care about initial audio/video */
+
+ /* There's still one content */
+ CLEAR_BOXED (TP_ARRAY_TYPE_OBJECT_PATH_LIST, &test->get_contents_return);
+ test->get_contents_return = g_boxed_copy (TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ tp_asv_get_boxed (test->get_all_return,
+ "Contents", TP_ARRAY_TYPE_OBJECT_PATH_LIST));
+ g_assert_cmpuint (test->get_contents_return->len, ==, 1);
+ g_assert_cmpstr (g_ptr_array_index (test->get_contents_return, 0),
+ ==, tp_proxy_get_object_path (test->audio_content));
+
+ /* Other contact is sending now */
+ CLEAR_HASH (&test->get_senders_return);
+ tp_cli_dbus_properties_call_get (test->audio_stream, -1,
+ FUTURE_IFACE_CALL_STREAM, "Senders", got_senders_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ g_assert_cmpuint (g_hash_table_size (test->get_senders_return), ==, 2);
+ g_assert (!g_hash_table_lookup_extended (test->get_senders_return,
+ GUINT_TO_POINTER (0), NULL, NULL));
+ g_assert (g_hash_table_lookup_extended (test->get_senders_return,
+ GUINT_TO_POINTER (test->self_handle), NULL, &v));
+ g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, FUTURE_SENDING_STATE_SENDING);
+ g_assert (g_hash_table_lookup_extended (test->get_senders_return,
+ GUINT_TO_POINTER (tp_channel_get_handle (test->chan, NULL)),
+ NULL, &v));
+ g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, FUTURE_SENDING_STATE_SENDING);
+
+ /* AddContent with bad content-type must fail */
+
+ future_cli_channel_type_call_call_add_content (test->chan, -1,
+ "", 31337, added_content_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ g_assert (test->error != NULL);
+ g_clear_error (&test->error);
+
+ /* AddContent again, to add a video stream */
+
+ future_cli_channel_type_call_call_add_content (test->chan, -1,
+ "", TP_MEDIA_STREAM_TYPE_VIDEO, added_content_cb,
+ test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ g_assert (test->added_content != NULL);
+ CLEAR_OBJECT (&test->video_content);
+ test->video_content = g_object_ref (test->added_content);
+
+ /* There are two Contents, because now we have the video content too */
+
+ tp_cli_dbus_properties_call_get (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents",
+ got_contents_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ g_assert_cmpuint (test->get_contents_return->len, ==, 2);
+
+ /* they could be either way round */
+ if (!tp_strdiff (g_ptr_array_index (test->get_contents_return, 0),
+ tp_proxy_get_object_path (test->audio_content)))
+ {
+ g_assert_cmpstr (g_ptr_array_index (test->get_contents_return, 1),
+ ==, tp_proxy_get_object_path (test->video_content));
+ }
+ else
+ {
+ g_assert_cmpstr (g_ptr_array_index (test->get_contents_return, 0),
+ ==, tp_proxy_get_object_path (test->video_content));
+ g_assert_cmpstr (g_ptr_array_index (test->get_contents_return, 1),
+ ==, tp_proxy_get_object_path (test->audio_content));
+ }
+
+ tp_cli_dbus_properties_call_get_all (test->video_content, -1,
+ FUTURE_IFACE_CALL_CONTENT, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+ assert_content_properties (test->get_all_return,
+ TP_MEDIA_STREAM_TYPE_VIDEO, test->self_handle,
+ FUTURE_CALL_CONTENT_DISPOSITION_NONE);
+
+ stream_paths = tp_asv_get_boxed (test->get_all_return, "Streams",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST);
+ g_assert (stream_paths != NULL);
+ g_assert_cmpuint (stream_paths->len, ==, 1);
+
+ g_assert (test->video_stream == NULL);
+ test->video_stream = future_call_stream_new (test->chan,
+ g_ptr_array_index (stream_paths, 0), NULL);
+ g_assert (test->video_stream != NULL);
+
+ tp_cli_dbus_properties_call_get (test->video_stream, -1,
+ FUTURE_IFACE_CALL_STREAM, "Senders", got_senders_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ g_assert_cmpuint (g_hash_table_size (test->get_senders_return), ==, 2);
+ g_assert (!g_hash_table_lookup_extended (test->get_senders_return,
+ GUINT_TO_POINTER (0), NULL, NULL));
+ g_assert (g_hash_table_lookup_extended (test->get_senders_return,
+ GUINT_TO_POINTER (test->self_handle), NULL, &v));
+ g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, FUTURE_SENDING_STATE_SENDING);
+ g_assert (g_hash_table_lookup_extended (test->get_senders_return,
+ GUINT_TO_POINTER (tp_channel_get_handle (test->chan, NULL)),
+ NULL, &v));
+
+ /* After a moment, the video stream becomes connected, and the remote user
+ * accepts our proposed direction change. These might happen in either
+ * order, at least in this implementation. */
+
+ if (GPOINTER_TO_UINT (v) != FUTURE_SENDING_STATE_SENDING)
+ g_assert_cmpuint (GPOINTER_TO_UINT (v), ==,
+ FUTURE_SENDING_STATE_PENDING_SEND);
+
+#if 0
+ /* FIXME: Call has no equivalent of RemoveStreams yet, afaics... */
+
+ /* RemoveStreams with a bad stream ID must fail */
+
+ g_array_set_size (test->stream_ids, 0);
+ g_array_append_val (test->stream_ids, not_a_stream_id);
+ tp_cli_channel_type_streamed_media_call_remove_streams (test->chan, -1,
+ test->stream_ids,
+ void_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ g_assert (test->error != NULL);
+ g_clear_error (&test->error);
+
+ /* Drop the video stream with RemoveStreams */
+
+ g_array_set_size (test->stream_ids, 0);
+ g_array_append_val (test->stream_ids, video_stream_id);
+ tp_cli_channel_type_streamed_media_call_remove_streams (test->chan, -1,
+ test->stream_ids,
+ void_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ /* Get contents again: now there's only the audio */
+
+ tp_cli_dbus_properties_call_get (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents",
+ got_contents_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ g_assert_cmpuint (test->get_contents_return->len, ==, 1);
+ g_assert_cmpstr (g_ptr_array_index (test->get_contents_return, 0), ==,
+ tp_proxy_get_object_path (test->audio_content));
+#endif
+
+ /* Hang up the call in the recommended way */
+
+ future_cli_channel_type_call_call_hangup (test->chan,
+ -1, FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "",
+ void_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+ assert_ended_and_run_close (test, test->self_handle,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
+ "");
+
+ /* FIXME: untested things include:
+ *
+ * RequestStreamDirection
+ * StreamDirectionChanged being emitted correctly (part of RSD)
+ * RequestStreamDirection failing (invalid direction, stream ID)
+ */
+}
+
+static void
+test_no_answer (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ /* This identifier contains the magic string (no answer), which means the
+ * example will never answer. */
+ outgoing_call (test, "smcv (no answer)", TRUE, FALSE);
+
+ future_cli_channel_type_call_call_accept (test->chan, -1, void_cb,
+ test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ /* After the initial flurry of D-Bus messages, smcv still hasn't answered */
+ test_connection_run_until_dbus_queue_processed (test->conn);
+
+ tp_cli_dbus_properties_call_get_all (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ assert_call_properties (test->get_all_return,
+ FUTURE_CALL_STATE_PENDING_RECEIVER, test->self_handle,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ TRUE, 0, /* call flags */
+ TRUE, TRUE, FALSE); /* initial audio/video must be TRUE, FALSE */
+
+ /* assume we're never going to get an answer, and hang up */
+ future_cli_channel_type_call_call_hangup (test->chan,
+ -1, FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "",
+ void_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ assert_ended_and_run_close (test, test->self_handle,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
+ "");
+}
+
+static void
+test_busy (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ /* This identifier contains the magic string (busy), which means the example
+ * will simulate rejection of the call as busy rather than accepting it. */
+ outgoing_call (test, "Robot101 (busy)", TRUE, FALSE);
+
+ future_cli_channel_type_call_call_accept (test->chan, -1, void_cb,
+ test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ /* Wait for the remote contact to end the call as busy */
+ loop_until_ended (test);
+ assert_ended_and_run_close (test, tp_channel_get_handle (test->chan, NULL),
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
+ TP_ERROR_STR_BUSY);
+}
+
+static void
+test_terminated_by_peer (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ /* This contact contains the magic string "(terminate)", meaning the example
+ * simulates answering the call but then terminating it */
+ outgoing_call (test, "The Governator (terminate)", TRUE, TRUE);
+
+ future_cli_channel_type_call_call_accept (test->chan, -1, void_cb,
+ test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ /* Wait for the remote contact to answer, if they haven't already */
+
+ loop_until_answered (test);
+
+ /* After that, the remote contact immediately ends the call */
+ loop_until_ended (test);
+ assert_ended_and_run_close (test, tp_channel_get_handle (test->chan, NULL),
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
+ "");
+}
+
+static void
+test_terminate_via_close (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ outgoing_call (test, "basic-test", FALSE, TRUE);
+
+ future_cli_channel_type_call_call_accept (test->chan, -1, void_cb,
+ test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ /* Wait for the remote contact to answer, if they haven't already */
+
+ loop_until_answered (test);
+
+ tp_cli_dbus_properties_call_get_all (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ assert_call_properties (test->get_all_return,
+ FUTURE_CALL_STATE_ACCEPTED, test->peer_handle,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ TRUE, 0, /* call flags */
+ TRUE, FALSE, TRUE); /* initial audio/video must be FALSE, TRUE */
+
+ /* Terminate the call unceremoniously, by calling Close. This is not a
+ * graceful hangup; rather, it's what the ChannelDispatcher would do to
+ * signal a client crash, undispatchability, or whatever */
+
+ tp_cli_channel_call_close (test->chan, -1, void_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ /* In response to termination, the channel does genuinely close */
+ test_connection_run_until_dbus_queue_processed (test->conn);
+ g_assert (tp_proxy_get_invalidated (test->chan) != NULL);
+
+ /* FIXME: when we hook up signals, check for expected call state
+ * transition before invalidation */
+}
+
+/* FIXME: try removing the last stream. In StreamedMedia that terminated the
+ * call, but in Call it's meant to just fail */
+
+/* FIXME: add a special contact who refuses to have video */
+
+/* FIXME: add a special contact who asks us for video */
+
+/* FIXME: add a special contact whose stream errors */
+
+static void
+expect_incoming_call_cb (TpConnection *conn,
+ const GPtrArray *channels,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ Test *test = user_data;
+ guint i;
+
+ for (i = 0; i < channels->len; i++)
+ {
+ GValueArray *va = g_ptr_array_index (channels, i);
+ const gchar *object_path = g_value_get_boxed (va->values + 0);
+ GHashTable *properties = g_value_get_boxed (va->values + 1);
+ const gchar *channel_type;
+
+ channel_type = tp_asv_get_string (properties,
+ TP_PROP_CHANNEL_CHANNEL_TYPE);
+ if (tp_strdiff (channel_type, FUTURE_IFACE_CHANNEL_TYPE_CALL))
+ {
+ /* don't care about this channel */
+ continue;
+ }
+
+ g_assert_cmpuint (tp_asv_get_uint32 (properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL),
+ ==, TP_HANDLE_TYPE_CONTACT);
+ g_assert_cmpint (tp_asv_get_boolean (properties,
+ TP_PROP_CHANNEL_REQUESTED, NULL), ==, FALSE);
+
+ /* we only expect to receive one call */
+ g_assert (test->chan == NULL);
+
+ /* save the channel */
+ test->chan = tp_channel_new_from_properties (conn, object_path,
+ properties, &test->error);
+ test_assert_no_error (test->error);
+ }
+}
+
+/* In this example connection manager, every time the presence status changes
+ * to available or the message changes, an incoming call is simulated. */
+static void
+trigger_incoming_call (Test *test,
+ const gchar *message,
+ const gchar *expected_caller)
+{
+ TpProxySignalConnection *new_channels_sig;
+
+ tp_cli_connection_interface_simple_presence_run_set_presence (test->conn, -1,
+ "away", "preparing for a test", &test->error, NULL);
+ test_assert_no_error (test->error);
+
+ new_channels_sig =
+ tp_cli_connection_interface_requests_connect_to_new_channels (test->conn,
+ expect_incoming_call_cb, test, NULL, NULL, &test->error);
+ test_assert_no_error (test->error);
+
+ tp_cli_connection_interface_simple_presence_run_set_presence (test->conn, -1,
+ "available", message, &test->error, NULL);
+ test_assert_no_error (test->error);
+
+ /* wait for the call to happen if it hasn't already */
+ while (test->chan == NULL)
+ {
+ g_main_context_iteration (NULL, TRUE);
+ }
+
+ g_assert_cmpstr (tp_channel_get_identifier (test->chan), ==,
+ expected_caller);
+ test->peer_handle = tp_channel_get_handle (test->chan, NULL);
+
+ tp_proxy_signal_connection_disconnect (new_channels_sig);
+
+ tp_channel_call_when_ready (test->chan, channel_ready_cb, test);
+ g_main_loop_run (test->mainloop);
+ test_connect_channel_signals (test);
+}
+
+static void
+test_incoming (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ trigger_incoming_call (test, "call me?", "caller");
+
+ /* ring, ring! */
+ tp_cli_dbus_properties_call_get_all (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+ assert_call_properties (test->get_all_return,
+ FUTURE_CALL_STATE_PENDING_RECEIVER, test->peer_handle,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ TRUE, 0, /* call flags */
+ TRUE, TRUE, FALSE); /* initial audio/video must be TRUE, FALSE */
+
+ /* Get Contents: we have an audio content (FIXME: assert that) */
+
+ tp_cli_dbus_properties_call_get (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents",
+ got_contents_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ g_assert_cmpuint (test->get_contents_return->len, ==, 1);
+
+ /* FIXME: assert about the properties of the content and the stream */
+
+ /* Accept the call */
+ future_cli_channel_type_call_call_accept (test->chan, -1, void_cb,
+ test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ tp_cli_dbus_properties_call_get_all (test->chan, -1,
+ FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+ assert_call_properties (test->get_all_return,
+ FUTURE_CALL_STATE_ACCEPTED, test->self_handle,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ TRUE, 0, /* call flags */
+ TRUE, TRUE, FALSE); /* initial audio/video are still TRUE, FALSE */
+
+ /* FIXME: check for stream directionality changes */
+
+ /* Hang up the call */
+ future_cli_channel_type_call_call_hangup (test->chan,
+ -1, FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "",
+ void_cb, test, NULL, NULL);
+ g_main_loop_run (test->mainloop);
+ test_assert_no_error (test->error);
+
+ assert_ended_and_run_close (test, test->self_handle,
+ FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "");
+}
+
+static void
+teardown (Test *test,
+ gconstpointer data G_GNUC_UNUSED)
+{
+ tp_cli_connection_run_disconnect (test->conn, -1, &test->error, NULL);
+ test_assert_no_error (test->error);
+
+ if (test->members_changed_detailed_id != 0)
+ {
+ g_signal_handler_disconnect (test->chan,
+ test->members_changed_detailed_id);
+ }
+
+ g_array_free (test->audio_request, TRUE);
+ g_array_free (test->video_request, TRUE);
+ g_array_free (test->invalid_request, TRUE);
+ g_array_free (test->stream_ids, TRUE);
+ CLEAR_HASH (&test->get_all_return);
+
+ CLEAR_BOXED (TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ &test->get_contents_return);
+ CLEAR_HASH (&test->get_senders_return);
+
+ CLEAR_OBJECT (&test->audio_stream);
+ CLEAR_OBJECT (&test->video_stream);
+ CLEAR_OBJECT (&test->added_content);
+ CLEAR_OBJECT (&test->audio_content);
+ CLEAR_OBJECT (&test->video_content);
+ CLEAR_OBJECT (&test->chan);
+ CLEAR_OBJECT (&test->conn);
+ CLEAR_OBJECT (&test->cm);
+
+ CLEAR_OBJECT (&test->service_cm);
+
+ CLEAR_OBJECT (&test->dbus);
+ g_main_loop_unref (test->mainloop);
+ test->mainloop = NULL;
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_set_prgname ("call-example");
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id=");
+
+ g_type_init ();
+ future_cli_init ();
+
+ g_test_add ("/call/basics", Test, NULL, setup, test_basics, teardown);
+ g_test_add ("/call/busy", Test, NULL, setup, test_busy, teardown);
+ g_test_add ("/call/no-answer", Test, NULL, setup, test_no_answer,
+ teardown);
+ g_test_add ("/call/terminated-by-peer", Test, NULL, setup,
+ test_terminated_by_peer, teardown);
+ g_test_add ("/call/terminate-via-close", Test, NULL, setup,
+ test_terminate_via_close, teardown);
+ g_test_add ("/call/incoming", Test, NULL, setup, test_incoming,
+ teardown);
+
+ return g_test_run ();
+}