summaryrefslogtreecommitdiff
path: root/rakia/media-channel.c
diff options
context:
space:
mode:
Diffstat (limited to 'rakia/media-channel.c')
-rw-r--r--rakia/media-channel.c2096
1 files changed, 2096 insertions, 0 deletions
diff --git a/rakia/media-channel.c b/rakia/media-channel.c
new file mode 100644
index 0000000..089b2b9
--- /dev/null
+++ b/rakia/media-channel.c
@@ -0,0 +1,2096 @@
+/*
+ * sip-media-channel.c - Source for RakiaMediaChannel
+ * Copyright (C) 2005-2008 Collabora Ltd.
+ * Copyright (C) 2005-2010 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * Based on telepathy-gabble implementation (gabble-media-channel).
+ * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
+ *
+ * This work 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 work 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 work; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "rakia/media-channel.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/exportable-channel.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+
+#include <rakia/event-target.h>
+
+#define DEBUG_FLAG TPSIP_DEBUG_MEDIA
+#include "rakia/debug.h"
+
+#include <rakia/media-session.h>
+#include <rakia/base-connection.h>
+
+#define TPSIP_CHANNEL_CALL_STATE_PROCEEDING_MASK \
+ (TP_CHANNEL_CALL_STATE_RINGING | \
+ TP_CHANNEL_CALL_STATE_QUEUED | \
+ TP_CHANNEL_CALL_STATE_IN_PROGRESS)
+
+static void event_target_init (gpointer, gpointer);
+static void channel_iface_init (gpointer, gpointer);
+static void media_signalling_iface_init (gpointer, gpointer);
+static void streamed_media_iface_init (gpointer, gpointer);
+static void dtmf_iface_init (gpointer, gpointer);
+static void call_state_iface_init (gpointer, gpointer);
+static void hold_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (RakiaMediaChannel, rakia_media_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TPSIP_TYPE_EVENT_TARGET, event_target_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_PROPERTIES_INTERFACE,
+ tp_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MEDIA_SIGNALLING,
+ media_signalling_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_DTMF,
+ dtmf_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CALL_STATE,
+ call_state_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_HOLD,
+ hold_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAMED_MEDIA,
+ streamed_media_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL));
+
+static const gchar *rakia_media_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ TP_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING,
+ TP_IFACE_CHANNEL_INTERFACE_DTMF,
+ TP_IFACE_CHANNEL_INTERFACE_CALL_STATE,
+ TP_IFACE_CHANNEL_INTERFACE_HOLD,
+ TP_IFACE_PROPERTIES_INTERFACE,
+ NULL
+};
+
+/* properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_OBJECT_PATH,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_INITIATOR,
+ PROP_INITIATOR_ID,
+ PROP_REQUESTED,
+ PROP_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_INITIAL_AUDIO,
+ PROP_INITIAL_VIDEO,
+ PROP_IMMUTABLE_STREAMS,
+ /* Telepathy properties (see below too) */
+ PROP_NAT_TRAVERSAL,
+ PROP_STUN_SERVER,
+ PROP_STUN_PORT,
+ LAST_PROPERTY
+};
+
+/* TP channel properties */
+enum
+{
+ TP_PROP_NAT_TRAVERSAL = 0,
+ TP_PROP_STUN_SERVER,
+ TP_PROP_STUN_PORT,
+ NUM_TP_PROPS
+};
+
+static const TpPropertySignature media_channel_property_signatures[NUM_TP_PROPS] =
+{
+ { "nat-traversal", G_TYPE_STRING },
+ { "stun-server", G_TYPE_STRING },
+ { "stun-port", G_TYPE_UINT },
+};
+
+/* signals */
+enum
+{
+ SIG_INCOMING_CALL,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0 };
+
+
+/* private structure */
+typedef struct _RakiaMediaChannelPrivate RakiaMediaChannelPrivate;
+
+struct _RakiaMediaChannelPrivate
+{
+ RakiaBaseConnection *conn;
+ RakiaMediaSession *session;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandle initiator;
+ GHashTable *call_states;
+ gchar *stun_server;
+ guint stun_port;
+
+ gboolean initial_audio;
+ gboolean initial_video;
+ gboolean immutable_streams;
+ gboolean closed;
+ gboolean dispose_has_run;
+};
+
+#define TPSIP_MEDIA_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TPSIP_TYPE_MEDIA_CHANNEL, RakiaMediaChannelPrivate))
+
+/***********************************************************************
+ * Set: Gobject interface
+ ***********************************************************************/
+
+static void
+rakia_media_channel_init (RakiaMediaChannel *self)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ /* allocate any data required by the object here */
+ priv->call_states = g_hash_table_new (NULL, NULL);
+
+ /* initialise the properties mixin *before* GObject
+ * sets the construct-time properties */
+ tp_properties_mixin_init (G_OBJECT (self),
+ G_STRUCT_OFFSET (RakiaMediaChannel, properties));
+}
+
+static void
+rakia_media_channel_constructed (GObject *obj)
+{
+ RakiaMediaChannel *chan = TPSIP_MEDIA_CHANNEL (obj);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (chan);
+ TpBaseConnection *conn = (TpBaseConnection *)(priv->conn);
+ GObjectClass *parent_object_class =
+ G_OBJECT_CLASS (rakia_media_channel_parent_class);
+ TpDBusDaemon *bus;
+ TpHandleRepoIface *contact_repo;
+ TpIntSet *add;
+
+ if (parent_object_class->constructed != NULL)
+ parent_object_class->constructed (obj);
+
+ contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (priv->handle != 0)
+ tp_handle_ref (contact_repo, priv->handle);
+
+ /* register object on the bus */
+ bus = tp_base_connection_get_dbus_daemon (conn);
+
+ DEBUG("registering object to dbus path=%s", priv->object_path);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
+
+ /* initialize group mixin */
+ tp_group_mixin_init (obj,
+ G_STRUCT_OFFSET (RakiaMediaChannel, group),
+ contact_repo,
+ conn->self_handle);
+
+ /* automatically add initiator to channel, but also ref them again (because
+ * priv->initiator is the InitiatorHandle) */
+ g_assert (priv->initiator != 0);
+ tp_handle_ref (contact_repo, priv->initiator);
+
+ add = tp_intset_new_containing (priv->initiator);
+ tp_group_mixin_change_members (obj, "", add, NULL, NULL, NULL, 0, 0);
+ tp_intset_destroy (add);
+
+ /* We start off with lots of flags, and then delete them as we work out what
+ * kind of channel we are, rather than trying to track what we need to
+ * add/remove over time. We should always have the right flags before we are
+ * advertised on the bus. */
+ tp_group_mixin_change_flags (obj,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE |
+ TP_CHANNEL_GROUP_FLAG_CAN_RESCIND | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
+}
+
+static void rakia_media_channel_dispose (GObject *object);
+static void rakia_media_channel_finalize (GObject *object);
+static void rakia_media_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void rakia_media_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static void priv_create_session (RakiaMediaChannel *channel,
+ nua_handle_t *nh,
+ TpHandle peer);
+static void priv_destroy_session(RakiaMediaChannel *channel);
+
+static void priv_outbound_call (RakiaMediaChannel *channel,
+ TpHandle peer);
+
+static gboolean rakia_media_channel_remove_with_reason (
+ GObject *iface,
+ TpHandle handle,
+ const gchar *message,
+ guint reason,
+ GError **error);
+
+static void
+rakia_media_channel_class_init (RakiaMediaChannelClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "TargetID", "target-id", NULL },
+ { "InitiatorHandle", "initiator", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { "Requested", "requested", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinPropImpl streamed_media_props[] = {
+ { "InitialAudio", "initial-audio", NULL },
+ { "InitialVideo", "initial-video", NULL },
+ { "ImmutableStreams", "immutable-streams", NULL },
+ { NULL }
+ };
+
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ streamed_media_props,
+ },
+ { NULL }
+ };
+
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ DEBUG("enter");
+
+ g_type_class_add_private (klass, sizeof (RakiaMediaChannelPrivate));
+
+ object_class->constructed = rakia_media_channel_constructed;
+ object_class->dispose = rakia_media_channel_dispose;
+ object_class->finalize = rakia_media_channel_finalize;
+
+ object_class->get_property = rakia_media_channel_get_property;
+ object_class->set_property = rakia_media_channel_set_property;
+
+ 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_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+
+ 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", "RakiaConnection object",
+ "SIP connection object that owns this SIP media channel object.",
+ TPSIP_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_string ("nat-traversal", "NAT traversal mechanism",
+ "A string representing the type of NAT traversal that should be "
+ "performed for streams on this channel.",
+ "none",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NAT_TRAVERSAL, param_spec);
+
+ param_spec = g_param_spec_string ("stun-server", "STUN server",
+ "IP or address of STUN server.", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STUN_SERVER, param_spec);
+
+ param_spec = g_param_spec_uint ("stun-port", "STUN port",
+ "UDP port of STUN server.", 0, G_MAXUINT16, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STUN_PORT, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Addition 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", "Target SIP URI",
+ "Currently empty, because this channel always has handle 0.",
+ 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", "Channel initiator",
+ "The TpHandle representing the contact who created 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, param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Creator URI",
+ "The URI obtained by inspecting the initiator handle.",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID, param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_boolean ("initial-audio", "InitialAudio",
+ "Whether the channel initially contained an audio stream",
+ 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", "InitialVideo",
+ "Whether the channel initially contained a video stream",
+ 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_boolean ("immutable-streams", "ImmutableStreams",
+ "Whether the set of streams on this channel are fixed once requested",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_IMMUTABLE_STREAMS,
+ param_spec);
+
+ signals[SIG_INCOMING_CALL] =
+ g_signal_new ("incoming-call",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ tp_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (RakiaMediaChannelClass, properties_class),
+ media_channel_property_signatures, NUM_TP_PROPS, NULL);
+
+ klass->dbus_props_class.interfaces =
+ prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (RakiaMediaChannelClass, dbus_props_class));
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (RakiaMediaChannelClass, group_class),
+ _rakia_media_channel_add_member,
+ NULL);
+ tp_group_mixin_class_allow_self_removal (object_class);
+ tp_group_mixin_class_set_remove_with_reason_func(object_class,
+ rakia_media_channel_remove_with_reason);
+ tp_group_mixin_init_dbus_properties (object_class);
+
+}
+
+static void
+rakia_media_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaMediaChannel *chan = TPSIP_MEDIA_CHANNEL (object);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (chan);
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn);
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, priv->handle);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, priv->handle?
+ TP_HANDLE_TYPE_CONTACT : TP_HANDLE_TYPE_NONE);
+ break;
+ case PROP_TARGET_ID:
+ if (priv->handle != 0)
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value, tp_handle_inspect (repo, priv->handle));
+ }
+ else
+ g_value_set_static_string (value, "");
+ break;
+ case PROP_INITIATOR:
+ g_value_set_uint (value, priv->initiator);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value, tp_handle_inspect (repo, priv->initiator));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, (priv->initiator == base_conn->self_handle));
+ break;
+ case PROP_INTERFACES:
+ g_value_set_static_boxed (value, rakia_media_channel_interfaces);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, "InitialAudio",
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, "InitialVideo",
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, "ImmutableStreams",
+ NULL));
+ break;
+ case PROP_INITIAL_AUDIO:
+ g_value_set_boolean (value, priv->initial_audio);
+ break;
+ case PROP_INITIAL_VIDEO:
+ g_value_set_boolean (value, priv->initial_video);
+ break;
+ case PROP_IMMUTABLE_STREAMS:
+ g_value_set_boolean (value, priv->immutable_streams);
+ break;
+ case PROP_STUN_SERVER:
+ g_value_set_string (value, priv->stun_server);
+ break;
+ case PROP_STUN_PORT:
+ g_value_set_uint (value, priv->stun_port);
+ break;
+ default:
+ /* Some properties live in the mixin */
+ {
+ const gchar *param_name;
+ guint tp_property_id;
+ GValue *tp_property_value;
+
+ param_name = g_param_spec_get_name (pspec);
+ if (G_LIKELY (tp_properties_mixin_has_property (object, param_name,
+ &tp_property_id)))
+ {
+ tp_property_value =
+ chan->properties.properties[tp_property_id].value;
+
+ if (G_LIKELY (tp_property_value != NULL))
+ {
+ g_value_copy (tp_property_value, value);
+ return;
+ }
+ }
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+rakia_media_channel_set_tp_property (RakiaMediaChannel *chan,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GObject *obj = (GObject *) chan;
+ const gchar *param_name = g_param_spec_get_name (pspec);
+ guint tp_property_id;
+
+ if (G_LIKELY (tp_properties_mixin_has_property (obj, param_name,
+ &tp_property_id)))
+ {
+ tp_properties_mixin_change_value (obj, tp_property_id,
+ value, NULL);
+ tp_properties_mixin_change_flags (obj, tp_property_id,
+ TP_PROPERTY_FLAG_READ, 0, NULL);
+ return TRUE;
+ }
+ else
+ {
+ WARNING("Telepathy property '%s' is not defined for media channels",
+ param_name);
+ return FALSE;
+ }
+}
+
+static void
+rakia_media_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaMediaChannel *chan = TPSIP_MEDIA_CHANNEL (object);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (chan);
+
+ switch (property_id) {
+ case PROP_HANDLE_TYPE:
+ case PROP_CHANNEL_TYPE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+ case PROP_CONNECTION:
+ priv->conn = g_value_dup_object (value);
+ break;
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ /* XXX: this property is defined as writable,
+ * but don't set it after construction, mmkay? */
+ /* we don't ref it here because we don't necessarily have access to the
+ * contact repo yet - instead we ref it in constructed. */
+ priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_INITIATOR:
+ /* similarly we can't ref this yet */
+ priv->initiator = g_value_get_uint (value);
+ break;
+ case PROP_INITIAL_AUDIO:
+ priv->initial_audio = g_value_get_boolean (value);
+ break;
+ case PROP_INITIAL_VIDEO:
+ priv->initial_video = g_value_get_boolean (value);
+ break;
+ case PROP_IMMUTABLE_STREAMS:
+ priv->immutable_streams = g_value_get_boolean (value);
+ break;
+ case PROP_STUN_SERVER:
+ priv->stun_server = g_value_dup_string (value);
+ /* Also expose as a legacy Telepathy property */
+ rakia_media_channel_set_tp_property (chan, value, pspec);
+ break;
+ case PROP_STUN_PORT:
+ priv->stun_port = g_value_get_uint (value);
+ /* Also expose as a legacy Telepathy property */
+ rakia_media_channel_set_tp_property (chan, value, pspec);
+ break;
+ default:
+ /* some properties live in the mixin */
+ if (rakia_media_channel_set_tp_property (chan, value, pspec))
+ return;
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+rakia_media_channel_dispose (GObject *object)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (object);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_handles;
+
+ if (priv->dispose_has_run)
+ return;
+
+ DEBUG("enter");
+
+ priv->dispose_has_run = TRUE;
+
+ if (!priv->closed)
+ rakia_media_channel_close (self);
+
+ contact_handles = tp_base_connection_get_handles (
+ TP_BASE_CONNECTION (priv->conn), TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_unref (contact_handles, priv->initiator);
+ priv->initiator = 0;
+
+ g_object_unref (priv->conn);
+
+ if (G_OBJECT_CLASS (rakia_media_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (rakia_media_channel_parent_class)->dispose (object);
+
+ DEBUG("exit");
+}
+
+static void
+rakia_media_channel_finalize (GObject *object)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (object);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ g_hash_table_destroy (priv->call_states);
+
+ g_free (priv->stun_server);
+
+ g_free (priv->object_path);
+
+ tp_group_mixin_finalize (object);
+
+ tp_properties_mixin_finalize (object);
+
+ G_OBJECT_CLASS (rakia_media_channel_parent_class)->finalize (object);
+
+ DEBUG("exit");
+}
+
+/***********************************************************************
+ * Set: Channel interface implementation (same for 0.12/0.13)
+ ***********************************************************************/
+
+/**
+ * rakia_media_channel_close_async
+ *
+ * Implements DBus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+rakia_media_channel_dbus_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+
+ rakia_media_channel_close (self);
+ tp_svc_channel_return_from_close (context);
+}
+
+void
+rakia_media_channel_close (RakiaMediaChannel *obj)
+{
+ RakiaMediaChannelPrivate *priv;
+
+ DEBUG("enter");
+
+ g_assert (TPSIP_IS_MEDIA_CHANNEL (obj));
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (obj);
+
+ if (priv->closed)
+ return;
+
+ priv->closed = TRUE;
+
+ if (priv->session) {
+ rakia_media_session_terminate (priv->session);
+ g_assert (priv->session == NULL);
+ }
+
+ tp_svc_channel_emit_closed ((TpSvcChannel *)obj);
+
+ return;
+}
+
+/**
+ * rakia_media_channel_get_channel_type
+ *
+ * Implements DBus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+rakia_media_channel_get_channel_type (TpSvcChannel *obj,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+}
+
+
+/**
+ * rakia_media_channel_get_handle
+ *
+ * Implements DBus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+rakia_media_channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->handle != 0)
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+ priv->handle);
+ else
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_NONE, 0);
+}
+
+/**
+ * rakia_media_channel_get_interfaces
+ *
+ * Implements DBus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+rakia_media_channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ rakia_media_channel_interfaces);
+}
+
+/***********************************************************************
+ * Set: Channel.Interface.MediaSignalling Telepathy-0.13 interface
+ ***********************************************************************/
+
+/**
+ * rakia_media_channel_get_session_handlers
+ *
+ * Implements DBus method GetSessionHandlers
+ * on interface org.freedesktop.Telepathy.Channel.Interface.MediaSignalling
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occured, DBus will throw the error only if this
+ * function returns false.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+rakia_media_channel_get_session_handlers (TpSvcChannelInterfaceMediaSignalling *iface,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GPtrArray *ret;
+ GValue handler = { 0 };
+
+ DEBUG("enter");
+
+ g_assert (TPSIP_IS_MEDIA_CHANNEL (self));
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ ret = g_ptr_array_new ();
+
+ if (priv->session)
+ {
+ GType handler_type;
+ gchar *path;
+
+ g_object_get (priv->session,
+ "object-path", &path,
+ NULL);
+
+ handler_type = dbus_g_type_get_struct ("GValueArray",
+ DBUS_TYPE_G_OBJECT_PATH,
+ G_TYPE_STRING,
+ G_TYPE_INVALID);
+
+ g_value_init (&handler, handler_type);
+ g_value_take_boxed (&handler,
+ dbus_g_type_specialized_construct (handler_type));
+
+ dbus_g_type_struct_set (&handler,
+ 0, path,
+ 1, "rtp",
+ G_MAXUINT);
+
+ g_free (path);
+
+ g_ptr_array_add (ret, g_value_get_boxed (&handler));
+ }
+
+ tp_svc_channel_interface_media_signalling_return_from_get_session_handlers (
+ context, ret);
+
+ if (G_IS_VALUE(&handler))
+ g_value_unset (&handler);
+
+ g_ptr_array_free (ret, TRUE);
+}
+
+
+/***********************************************************************
+ * Set: Channel.Type.StreamedMedia Telepathy-0.13 interface
+ ***********************************************************************/
+
+/**
+ * rakia_media_channel_list_streams
+ *
+ * Implements D-Bus method ListStreams
+ * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
+ */
+static void
+rakia_media_channel_list_streams (TpSvcChannelTypeStreamedMedia *iface,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GPtrArray *ret = NULL;
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ ret = g_ptr_array_new ();
+
+ if (priv->session != NULL)
+ rakia_media_session_list_streams (priv->session, ret);
+
+ tp_svc_channel_type_streamed_media_return_from_list_streams (context, ret);
+
+ g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_INFO_LIST, ret);
+}
+
+/**
+ * rakia_media_channel_remove_streams
+ *
+ * Implements D-Bus method RemoveStreams
+ * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
+ */
+static void
+rakia_media_channel_remove_streams (TpSvcChannelTypeStreamedMedia *iface,
+ const GArray *streams,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GError *error = NULL;
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->immutable_streams)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Cannot remove streams from the existing channel");
+ }
+ else if (priv->session != NULL)
+ {
+ rakia_media_session_remove_streams(priv->session,
+ streams,
+ &error);
+ }
+ else
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "No session is available");
+ }
+
+ if (error != NULL)
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ tp_svc_channel_type_streamed_media_return_from_remove_streams (context);
+}
+
+/**
+ * rakia_media_channel_request_stream_direction
+ *
+ * Implements D-Bus method RequestStreamDirection
+ * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
+ */
+static void
+rakia_media_channel_request_stream_direction (TpSvcChannelTypeStreamedMedia *iface,
+ guint stream_id,
+ guint stream_direction,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GError *error = NULL;
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->immutable_streams)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Cannot change directions on an immutable channel" };
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ if (priv->session != NULL)
+ {
+ rakia_media_session_request_stream_direction (priv->session,
+ stream_id,
+ stream_direction,
+ &error);
+ }
+ else
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "The media session is not available");
+ }
+
+ if (error == NULL)
+ {
+ tp_svc_channel_type_streamed_media_return_from_request_stream_direction (context);
+ }
+ else
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+}
+
+
+/**
+ * rakia_media_channel_request_streams
+ *
+ * Implements D-Bus method RequestStreams
+ * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
+ */
+static void
+rakia_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface,
+ guint contact_handle,
+ const GArray *types,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ GError *error = NULL;
+ GPtrArray *ret = NULL;
+ RakiaMediaChannelPrivate *priv;
+ TpHandleRepoIface *contact_repo;
+
+ DEBUG("enter");
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->immutable_streams)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Cannot add streams to the immutable channel" };
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *)(priv->conn), TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handle_is_valid (contact_repo, contact_handle, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ priv_outbound_call (self, contact_handle);
+
+ ret = g_ptr_array_sized_new (types->len);
+
+ if (rakia_media_session_request_streams (priv->session, types, ret, &error))
+ {
+ g_assert (types->len == ret->len);
+ tp_svc_channel_type_streamed_media_return_from_request_streams (context,
+ ret);
+ }
+ else
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ }
+
+ g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_INFO_LIST, ret);
+
+ DEBUG ("exit");
+}
+
+/***********************************************************************
+ * Set: sip-media-channel API towards sip-connection
+ ***********************************************************************/
+
+void
+rakia_media_channel_create_initial_streams (RakiaMediaChannel *self)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ g_assert (priv->initiator != priv->handle);
+
+ /* RequestChannel(None, 0) => channel is anonymous:
+ * caller uses RequestStreams to set the peer and start the call. */
+ if (priv->handle == 0)
+ return;
+
+ priv_outbound_call (self, priv->handle);
+
+ g_assert (priv->session != NULL);
+
+ if (priv->initial_audio)
+ rakia_media_session_add_stream (priv->session,
+ TP_MEDIA_STREAM_TYPE_AUDIO,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
+ TRUE);
+
+ if (priv->initial_video)
+ rakia_media_session_add_stream (priv->session,
+ TP_MEDIA_STREAM_TYPE_VIDEO,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
+ TRUE);
+}
+
+/*
+ * Handles an incoming call, called shortly after the channel
+ * has been created with initiator handle of the sender, when remote SDP
+ * session data are reported by the NUA stack.
+ */
+static void
+rakia_media_channel_handle_incoming_call (RakiaMediaChannel *self,
+ nua_handle_t *nh,
+ const sdp_session_t *sdp)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *conn = TP_BASE_CONNECTION (priv->conn);
+
+ g_assert (priv->initiator != conn->self_handle);
+ g_assert (priv->session == NULL);
+
+ if (sdp != NULL)
+ {
+ /* Get the initial media properties from the session offer */
+ const sdp_media_t *media;
+
+ for (media = sdp->sdp_media; media != NULL; media = media->m_next)
+ {
+ if (media->m_rejected || media->m_port == 0)
+ continue;
+
+ switch (media->m_type)
+ {
+ case sdp_media_audio:
+ priv->initial_audio = TRUE;
+ DEBUG("has initial audio");
+ break;
+ case sdp_media_video:
+ priv->initial_video = TRUE;
+ DEBUG("has initial video");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* Tell the factory to emit NewChannel(s) */
+ g_signal_emit (self, signals[SIG_INCOMING_CALL], 0);
+
+ /* Offer the session handler to the client */
+ priv_create_session (self, nh, priv->initiator);
+
+ g_assert (priv->session != NULL);
+ rakia_media_session_receive_invite (priv->session);
+}
+
+static gboolean
+priv_nua_i_invite_cb (RakiaMediaChannel *self,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ gpointer foo)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ /* nua_i_invite delivered for a bound handle means a re-INVITE */
+
+ g_return_val_if_fail (priv->session != NULL, FALSE);
+
+ rakia_media_session_receive_reinvite (priv->session);
+
+ return TRUE;
+}
+
+static guint
+rakia_media_channel_get_call_state (RakiaMediaChannel *self,
+ TpHandle peer)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ return GPOINTER_TO_UINT (g_hash_table_lookup (priv->call_states,
+ GUINT_TO_POINTER (peer)));
+}
+
+static void
+rakia_media_channel_peer_error (RakiaMediaChannel *self,
+ TpHandle peer,
+ guint status,
+ const char* message)
+{
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
+ TpIntSet *remove;
+ guint reason = TP_CHANNEL_GROUP_CHANGE_REASON_ERROR;
+
+ switch (status)
+ {
+ case 410:
+ case 604:
+ reason = TP_CHANNEL_GROUP_CHANGE_REASON_INVALID_CONTACT;
+ break;
+ case 486:
+ case 600:
+ reason = TP_CHANNEL_GROUP_CHANGE_REASON_BUSY;
+ break;
+ case 408:
+ reason = TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER;
+ break;
+ case 404:
+ case 480:
+ reason = (rakia_media_channel_get_call_state (self, peer)
+ & TPSIP_CHANNEL_CALL_STATE_PROCEEDING_MASK)
+ ? TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER
+ : TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE;
+ break;
+ case 603:
+ /* No reason means roughly "rejected" */
+ reason = TP_CHANNEL_GROUP_CHANGE_REASON_NONE;
+ break;
+ case 403:
+ case 401:
+ case 407:
+ reason = TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED;
+ break;
+ }
+
+ if (message == NULL || !g_utf8_validate (message, -1, NULL))
+ message = "";
+
+ remove = tp_intset_new ();
+ tp_intset_add (remove, peer);
+ tp_intset_add (remove, mixin->self_handle);
+ tp_group_mixin_change_members ((GObject *)self, message,
+ NULL, remove, NULL, NULL, peer, reason);
+ tp_intset_destroy (remove);
+}
+
+guint
+rakia_media_channel_change_call_state (RakiaMediaChannel *self,
+ TpHandle peer,
+ guint flags_add,
+ guint flags_remove)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ gpointer key = GUINT_TO_POINTER (peer);
+ guint old_state;
+ guint new_state;
+
+ /* XXX: check if the peer is a member? */
+
+ old_state = GPOINTER_TO_UINT (g_hash_table_lookup (priv->call_states, key));
+ new_state = (old_state | flags_add) & ~flags_remove;
+
+ if (new_state != old_state)
+ {
+ DEBUG ("setting call state %u for peer %u", new_state, peer);
+ if (new_state == 0)
+ g_hash_table_remove (priv->call_states, key);
+ else
+ g_hash_table_replace (priv->call_states, key,
+ GUINT_TO_POINTER (new_state));
+
+ tp_svc_channel_interface_call_state_emit_call_state_changed (self,
+ peer,
+ new_state);
+ }
+
+ return new_state;
+}
+
+static gboolean
+priv_nua_i_bye_cb (RakiaMediaChannel *self,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ gpointer foo)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
+ TpIntSet *remove;
+ TpHandle peer;
+
+ g_return_val_if_fail (priv->session != NULL, FALSE);
+
+ peer = rakia_media_session_get_peer (priv->session);
+ remove = tp_intset_new ();
+ tp_intset_add (remove, peer);
+ tp_intset_add (remove, mixin->self_handle);
+
+ tp_group_mixin_change_members ((GObject *) self, "",
+ NULL, remove, NULL, NULL,
+ peer, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (remove);
+
+ return TRUE;
+}
+
+static gboolean
+priv_nua_i_cancel_cb (RakiaMediaChannel *self,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ gpointer foo)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
+ TpIntSet *remove;
+ TpHandle actor = 0;
+ TpHandle peer;
+ const sip_reason_t *reason;
+ guint cause = 0;
+ const gchar *message = NULL;
+
+ g_return_val_if_fail (priv->session != NULL, FALSE);
+
+ /* FIXME: implement cancellation of an incoming re-INVITE, if ever
+ * found in real usage and not caused by a request timeout */
+
+ if (ev->sip != NULL)
+ for (reason = ev->sip->sip_reason;
+ reason != NULL;
+ reason = reason->re_next)
+ {
+ const char *protocol = reason->re_protocol;
+ if (protocol == NULL || strcmp (protocol, "SIP") != 0)
+ continue;
+ if (reason->re_cause != NULL)
+ {
+ cause = (guint) g_ascii_strtoull (reason->re_cause, NULL, 10);
+ message = reason->re_text;
+ break;
+ }
+ }
+
+ peer = rakia_media_session_get_peer (priv->session);
+
+ switch (cause)
+ {
+ case 200:
+ case 603:
+ /* The user must have acted on another branch of the forked call */
+ actor = mixin->self_handle;
+ break;
+ default:
+ actor = peer;
+ }
+
+ if (message == NULL || !g_utf8_validate (message, -1, NULL))
+ message = "";
+
+ remove = tp_intset_new ();
+ tp_intset_add (remove, peer);
+ tp_intset_add (remove, mixin->self_handle);
+
+ tp_group_mixin_change_members ((GObject *) self, message,
+ NULL, remove, NULL, NULL,
+ actor, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (remove);
+
+ return TRUE;
+}
+
+static gboolean
+priv_nua_i_state_cb (RakiaMediaChannel *self,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ gpointer foo)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ const sdp_session_t *r_sdp = NULL;
+ int offer_recv = 0;
+ int answer_recv = 0;
+ int ss_state = nua_callstate_init;
+ gint status = ev->status;
+ TpHandle peer;
+
+ tl_gets(tags,
+ NUTAG_CALLSTATE_REF(ss_state),
+ NUTAG_OFFER_RECV_REF(offer_recv),
+ NUTAG_ANSWER_RECV_REF(answer_recv),
+ SOATAG_REMOTE_SDP_REF(r_sdp),
+ TAG_END());
+
+ DEBUG("call with handle %p is %s", ev->nua_handle, nua_callstate_name (ss_state));
+
+ if (ss_state == nua_callstate_received && priv->session == NULL)
+ {
+ /* We get the session data for initial media properties with this event;
+ * initialize the session before we can create any streams below.
+ */
+ rakia_media_channel_handle_incoming_call (self, ev->nua_handle, r_sdp);
+ }
+
+ g_return_val_if_fail (priv->session != NULL, FALSE);
+
+ if (r_sdp)
+ {
+ g_return_val_if_fail (answer_recv || offer_recv, FALSE);
+ if (!rakia_media_session_set_remote_media (priv->session, r_sdp))
+ {
+ rakia_media_channel_close (self);
+ return TRUE;
+ }
+ }
+
+ peer = rakia_media_session_get_peer (priv->session);
+
+ switch ((enum nua_callstate)ss_state)
+ {
+ case nua_callstate_proceeding:
+ switch (status)
+ {
+ case 180:
+ rakia_media_channel_change_call_state (self, peer,
+ TP_CHANNEL_CALL_STATE_RINGING, 0);
+ break;
+ case 182:
+ rakia_media_channel_change_call_state (self, peer,
+ TP_CHANNEL_CALL_STATE_QUEUED, 0);
+ break;
+ case 183:
+ rakia_media_channel_change_call_state (self, peer,
+ TP_CHANNEL_CALL_STATE_IN_PROGRESS, 0);
+ break;
+ }
+ break;
+
+ case nua_callstate_completing:
+ /* In auto-ack mode, we don't need to call nua_ack(), see NUTAG_AUTOACK() */
+ break;
+
+ case nua_callstate_ready:
+
+ /* Clear any pre-establishment call states */
+ rakia_media_channel_change_call_state (self, peer, 0,
+ TPSIP_CHANNEL_CALL_STATE_PROCEEDING_MASK);
+
+ if (status < 300)
+ {
+ TpIntSet *add = tp_intset_new_containing (peer);
+
+ tp_group_mixin_change_members ((GObject *) self,
+ "",
+ add, /* add */
+ NULL, /* remove */
+ NULL,
+ NULL,
+ peer,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (add);
+
+ rakia_media_session_accept (priv->session);
+ }
+ else if (status == 491)
+ rakia_media_session_resolve_glare (priv->session);
+ else
+ {
+ /* Was something wrong with our re-INVITE? We can't cope anyway. */
+ MESSAGE ("can't handle non-fatal response %d %s", status, ev->text);
+ rakia_media_session_terminate (priv->session);
+ }
+ break;
+
+ case nua_callstate_terminated:
+ /* In cases of self-inflicted termination,
+ * we should have already gone through the moves */
+ if (rakia_media_session_get_state (priv->session)
+ == TPSIP_MEDIA_SESSION_STATE_ENDED)
+ break;
+
+ if (status >= 300)
+ {
+ rakia_media_channel_peer_error (
+ self, peer, status, ev->text);
+ }
+
+ rakia_media_session_change_state (priv->session,
+ TPSIP_MEDIA_SESSION_STATE_ENDED);
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static void priv_session_state_changed_cb (RakiaMediaSession *session,
+ guint old_state,
+ guint state,
+ RakiaMediaChannel *channel)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (channel);
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (channel);
+ TpHandle self_handle;
+ TpHandle peer;
+ TpIntSet *set = NULL;
+
+ DEBUG("enter");
+
+ self_handle = mixin->self_handle;
+ peer = rakia_media_session_get_peer (session);
+
+ switch (state)
+ {
+ case TPSIP_MEDIA_SESSION_STATE_INVITE_SENT:
+ g_assert (priv->initiator == self_handle);
+
+ /* add the peer to remote pending */
+ set = tp_intset_new_containing (peer);
+ tp_group_mixin_change_members ((GObject *)channel,
+ "",
+ NULL, /* add */
+ NULL, /* remove */
+ NULL, /* local pending */
+ set, /* remote pending */
+ self_handle, /* actor */
+ TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
+
+ /* update flags: no more adding */
+ tp_group_mixin_change_flags ((GObject *)channel, 0,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD);
+
+ break;
+
+ case TPSIP_MEDIA_SESSION_STATE_INVITE_RECEIVED:
+ /* add ourself to local pending */
+ set = tp_intset_new_containing (self_handle);
+ tp_group_mixin_change_members ((GObject *) channel, "",
+ NULL, /* add */
+ NULL, /* remove */
+ set, /* local pending */
+ NULL, /* remote pending */
+ priv->initiator, /* actor */
+ TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
+
+ /* No adding more members to the incoming call. Therefore also not
+ * possible to add anyone to remote-pending, so rescinding would make
+ * utterly no sense. We also disallow removing the remote peer if
+ * we are not the initiator, so disallow that too.
+ * Removing yourself to end the call is not represented by group flags.
+ */
+ tp_group_mixin_change_flags ((GObject *) channel, 0,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE |
+ TP_CHANNEL_GROUP_FLAG_CAN_RESCIND);
+
+ break;
+
+ case TPSIP_MEDIA_SESSION_STATE_ACTIVE:
+ if (priv->initiator == self_handle)
+ {
+ if (!tp_handle_set_is_member (mixin->remote_pending, peer))
+ break; /* no-op */
+
+ /* the peer has promoted itself to members */
+ set = tp_intset_new_containing (peer);
+ tp_group_mixin_change_members ((GObject *)channel, "",
+ set, /* add */
+ NULL, /* remove */
+ NULL,
+ NULL,
+ peer, 0);
+ }
+ else
+ {
+ if (!tp_handle_set_is_member (mixin->local_pending, self_handle))
+ break; /* no-op */
+
+ /* promote ourselves to members */
+ set = tp_intset_new_containing (self_handle);
+ tp_group_mixin_change_members ((GObject *)channel, "",
+ set, /* add */
+ NULL, /* remove */
+ NULL,
+ NULL,
+ self_handle, 0);
+ }
+
+ /* update flags: deny adding and rescinding. Removing the remote peer is
+ * still allowed.
+ * Removing yourself to end the call is not represented by group flags.
+ */
+ tp_group_mixin_change_flags ((GObject *)channel, 0,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_RESCIND);
+
+ break;
+
+ case TPSIP_MEDIA_SESSION_STATE_ENDED:
+ set = tp_intset_new ();
+
+ /* remove us and the peer from the member list */
+ tp_intset_add (set, self_handle);
+ tp_intset_add (set, peer);
+ tp_group_mixin_change_members ((GObject *)channel, "",
+ NULL, /* add */
+ set, /* remove */
+ NULL,
+ NULL,
+ 0, 0);
+
+ /* Close the channel; destroy the session first to avoid
+ * the rakia_media_session_terminate() path in this case */
+ priv_destroy_session (channel);
+ rakia_media_channel_close (channel);
+ break;
+ }
+
+ if (set != NULL)
+ tp_intset_destroy (set);
+}
+
+void
+rakia_media_channel_attach_to_nua_handle (RakiaMediaChannel *self,
+ nua_handle_t *nh)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ rakia_event_target_attach (nh, (GObject *) self);
+
+ /* have the connection handle authentication, before all other
+ * response callbacks */
+ rakia_base_connection_add_auth_handler (priv->conn, TPSIP_EVENT_TARGET (self));
+
+ g_signal_connect (self,
+ "nua-event::nua_i_invite",
+ G_CALLBACK (priv_nua_i_invite_cb),
+ NULL);
+ g_signal_connect (self,
+ "nua-event::nua_i_bye",
+ G_CALLBACK (priv_nua_i_bye_cb),
+ NULL);
+ g_signal_connect (self,
+ "nua-event::nua_i_cancel",
+ G_CALLBACK (priv_nua_i_cancel_cb),
+ NULL);
+ g_signal_connect (self,
+ "nua-event::nua_i_state",
+ G_CALLBACK (priv_nua_i_state_cb),
+ NULL);
+
+}
+
+/**
+ * priv_create_session:
+ *
+ * Creates a RakiaMediaSession object for given peer.
+ **/
+static void
+priv_create_session (RakiaMediaChannel *channel,
+ nua_handle_t *nh,
+ TpHandle peer)
+{
+ RakiaMediaChannelPrivate *priv;
+ RakiaMediaSession *session;
+ TpBaseConnection *conn;
+ TpHandleRepoIface *contact_repo;
+ gchar *object_path;
+ gchar *local_ip_address = NULL;
+
+ DEBUG("enter");
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (channel);
+ conn = (TpBaseConnection *)(priv->conn);
+ contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ g_assert (priv->session == NULL);
+
+ object_path = g_strdup_printf ("%s/MediaSession%u", priv->object_path, peer);
+
+ DEBUG("allocating session, peer=%u", peer);
+
+ /* The channel manages references to the peer handle for the session */
+ tp_handle_ref (contact_repo, peer);
+
+ g_object_get (priv->conn,
+ "local-ip-address", &local_ip_address,
+ NULL);
+
+ session = g_object_new (TPSIP_TYPE_MEDIA_SESSION,
+ "dbus-daemon",
+ tp_base_connection_get_dbus_daemon (conn),
+ "media-channel", channel,
+ "object-path", object_path,
+ "nua-handle", nh,
+ "peer", peer,
+ "local-ip-address", local_ip_address,
+ NULL);
+
+ g_free (local_ip_address);
+
+ g_signal_connect_object (session,
+ "state-changed",
+ G_CALLBACK(priv_session_state_changed_cb),
+ channel,
+ 0);
+
+ priv->session = session;
+
+ tp_svc_channel_interface_media_signalling_emit_new_session_handler (
+ (TpSvcChannelInterfaceMediaSignalling *)channel, object_path, "rtp");
+
+ g_free (object_path);
+
+ DEBUG ("exit");
+}
+
+static void
+priv_destroy_session(RakiaMediaChannel *channel)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (channel);
+ RakiaMediaSession *session;
+ TpBaseConnection *conn;
+ TpHandleRepoIface *contact_repo;
+
+ session = priv->session;
+ if (session == NULL)
+ return;
+
+ DEBUG("enter");
+
+ /* Release the peer handle */
+ conn = (TpBaseConnection *)(priv->conn);
+ contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+ tp_handle_unref (contact_repo, rakia_media_session_get_peer (session));
+
+ priv->session = NULL;
+ g_object_unref (session);
+
+ DEBUG("exit");
+}
+
+/*
+ * Creates an outbound call session if a session does not exist
+ */
+static void
+priv_outbound_call (RakiaMediaChannel *channel,
+ TpHandle peer)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (channel);
+ nua_handle_t *nh;
+
+ if (priv->session == NULL)
+ {
+ DEBUG("making outbound call - setting peer handle to %u", peer);
+
+ nh = rakia_base_connection_create_handle (priv->conn, peer);
+ priv_create_session (channel, nh, peer);
+
+ /* Bind the channel object to the handle to handle NUA events */
+ rakia_media_channel_attach_to_nua_handle (channel, nh);
+
+ nua_handle_unref (nh);
+ }
+ else
+ DEBUG("session already exists");
+
+ g_assert (priv->session != NULL);
+}
+
+gboolean
+_rakia_media_channel_add_member (GObject *iface,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (iface);
+
+ DEBUG("mixin->self_handle=%d, handle=%d", mixin->self_handle, handle);
+
+ if (priv->initiator == mixin->self_handle)
+ {
+ TpIntSet *remote_pending;
+
+ /* case a: an old-school outbound call
+ * (we are the initiator, a new handle added with AddMembers) */
+
+ priv_outbound_call (self, handle);
+
+ /* Backwards compatible behavior:
+ * add the peer to remote pending without waiting for the actual request
+ * to be sent */
+ remote_pending = tp_intset_new_containing (handle);
+ tp_group_mixin_change_members (iface,
+ "",
+ NULL, /* add */
+ NULL, /* remove */
+ NULL, /* local pending */
+ remote_pending, /* remote pending */
+ mixin->self_handle, /* actor */
+ TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
+ tp_intset_destroy (remote_pending);
+
+ /* update flags: no more adding.
+ * Removal and rescinding are still allowed. */
+ tp_group_mixin_change_flags (iface, 0,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD);
+
+ return TRUE;
+ }
+ if (priv->session &&
+ handle == mixin->self_handle &&
+ tp_handle_set_is_member (mixin->local_pending, handle))
+ {
+ /* case b: an incoming invite */
+ DEBUG("accepting an incoming invite");
+ g_return_val_if_fail (priv->session != NULL, FALSE);
+
+ rakia_media_session_accept (priv->session);
+
+ return TRUE;
+ }
+
+ MESSAGE ("unsupported member change requested for a media channel");
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "handle %u cannot be added in the current state", handle);
+ return FALSE;
+}
+
+static gint
+rakia_status_from_tp_reason (TpChannelGroupChangeReason reason)
+{
+ switch (reason)
+ {
+ case TP_CHANNEL_GROUP_CHANGE_REASON_NONE:
+ return 603; /* Decline */
+ case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER:
+ case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE:
+ return 480; /* Temporarily Unavailable */
+ case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY:
+ return 486; /* Busy Here */
+ case TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED:
+ case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED:
+ return 403; /* Forbidden */
+ case TP_CHANNEL_GROUP_CHANGE_REASON_INVALID_CONTACT:
+ return 404; /* Not Found */
+ default:
+ return 500; /* Server Internal Error */
+ }
+}
+
+static gboolean
+rakia_media_channel_remove_with_reason (GObject *obj,
+ TpHandle handle,
+ const gchar *message,
+ guint reason,
+ GError **error)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (obj);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
+ TpIntSet *set = NULL;
+ TpHandle self_handle;
+ gboolean rejected;
+
+ self_handle = mixin->self_handle;
+
+ if (priv->initiator != self_handle && handle != self_handle)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_PERMISSION_DENIED,
+ "handle %u cannot be removed because you are not the initiator of the"
+ " channel", handle);
+
+ return FALSE;
+ }
+
+ if (priv->session == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "handle %u cannot be removed in the current state", handle);
+
+ return FALSE;
+ }
+
+ rejected = (handle == self_handle
+ && tp_handle_set_is_member (mixin->local_pending, handle));
+
+ /* We have excluded all the problem cases.
+ * Now we always want to remove both members on behalf of the local user */
+ set = tp_intset_new ();
+ tp_intset_add (set, self_handle);
+ tp_intset_add (set, rakia_media_session_get_peer (priv->session));
+ tp_group_mixin_change_members (obj, "",
+ NULL, /* add */
+ set, /* remove */
+ NULL,
+ NULL,
+ self_handle, 0);
+ tp_intset_destroy (set);
+
+ if (rejected)
+ {
+ /* The user has rejected the call */
+
+ gint status;
+
+ status = rakia_status_from_tp_reason (reason);
+
+ /* XXX: raise NotAvailable if it's the wrong state? */
+ rakia_media_session_respond (priv->session, status, message);
+
+ /* This session is effectively ended, prevent the nua_i_state handler
+ * from useless work */
+ rakia_media_session_change_state (priv->session,
+ TPSIP_MEDIA_SESSION_STATE_ENDED);
+ }
+ else
+ {
+ /* Want to terminate the call in whatever other situation;
+ * rescinding is handled by sending CANCEL */
+ rakia_media_session_terminate (priv->session);
+ }
+
+ return TRUE;
+}
+
+static void
+rakia_media_channel_get_call_states (TpSvcChannelInterfaceCallState *iface,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ tp_svc_channel_interface_call_state_return_from_get_call_states (
+ context,
+ priv->call_states);
+}
+
+static void
+rakia_media_channel_get_hold_state (TpSvcChannelInterfaceHold *iface,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ TpLocalHoldState hold_state = TP_LOCAL_HOLD_STATE_UNHELD;
+ TpLocalHoldStateReason hold_reason = TP_LOCAL_HOLD_STATE_REASON_NONE;
+
+ if (priv->session == NULL)
+ {
+ GError e = {TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "The media session is not available"};
+ dbus_g_method_return_error (context, &e);
+ }
+
+ g_object_get (priv->session,
+ "hold-state", &hold_state,
+ "hold-state-reason", &hold_reason,
+ NULL);
+
+ tp_svc_channel_interface_hold_return_from_get_hold_state (context,
+ hold_state,
+ hold_reason);
+}
+
+static void
+rakia_media_channel_request_hold (TpSvcChannelInterfaceHold *iface,
+ gboolean hold,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->immutable_streams)
+ {
+ GError e = {TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Session modification disabled"};
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+ else if (priv->session != NULL)
+ {
+ rakia_media_session_request_hold (priv->session, hold);
+ }
+ else
+ {
+ GError e = {TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "The media session is not available"};
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ tp_svc_channel_interface_hold_return_from_request_hold (context);
+}
+
+static void
+rakia_media_channel_start_tone (TpSvcChannelInterfaceDTMF *iface,
+ guint stream_id,
+ guchar event,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GError *error = NULL;
+
+ DEBUG("enter");
+
+ g_assert (TPSIP_IS_MEDIA_CHANNEL (self));
+
+ if (event >= NUM_TP_DTMF_EVENTS)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "event %u is not a known DTMF event", event);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ if (!rakia_media_session_start_telephony_event (priv->session,
+ stream_id,
+ event,
+ &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ tp_svc_channel_interface_dtmf_return_from_start_tone (context);
+}
+
+static void
+rakia_media_channel_stop_tone (TpSvcChannelInterfaceDTMF *iface,
+ guint stream_id,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GError *error = NULL;
+
+ DEBUG("enter");
+
+ g_assert (TPSIP_IS_MEDIA_CHANNEL (self));
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ if (!rakia_media_session_stop_telephony_event (priv->session,
+ stream_id,
+ &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ tp_svc_channel_interface_dtmf_return_from_stop_tone (context);
+}
+
+static void
+event_target_init(gpointer g_iface, gpointer iface_data)
+{
+}
+
+static void
+channel_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *)g_iface;
+
+ tp_svc_channel_implement_close (
+ klass, rakia_media_channel_dbus_close);
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (\
+ klass, rakia_media_channel_##x)
+ IMPLEMENT(get_channel_type);
+ IMPLEMENT(get_handle);
+ IMPLEMENT(get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+streamed_media_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelTypeStreamedMediaClass *klass = (TpSvcChannelTypeStreamedMediaClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_type_streamed_media_implement_##x (\
+ klass, rakia_media_channel_##x)
+ IMPLEMENT(list_streams);
+ IMPLEMENT(remove_streams);
+ IMPLEMENT(request_stream_direction);
+ IMPLEMENT(request_streams);
+#undef IMPLEMENT
+}
+
+static void
+media_signalling_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelInterfaceMediaSignallingClass *klass = (TpSvcChannelInterfaceMediaSignallingClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_interface_media_signalling_implement_##x (\
+ klass, rakia_media_channel_##x)
+ IMPLEMENT(get_session_handlers);
+#undef IMPLEMENT
+}
+
+static void
+dtmf_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelInterfaceDTMFClass *klass = (TpSvcChannelInterfaceDTMFClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_interface_dtmf_implement_##x (\
+ klass, rakia_media_channel_##x)
+ IMPLEMENT(start_tone);
+ IMPLEMENT(stop_tone);
+#undef IMPLEMENT
+}
+
+static void
+call_state_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelInterfaceCallStateClass *klass = g_iface;
+#define IMPLEMENT(x) tp_svc_channel_interface_call_state_implement_##x (\
+ klass, rakia_media_channel_##x)
+ IMPLEMENT (get_call_states);
+#undef IMPLEMENT
+}
+
+static void
+hold_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelInterfaceHoldClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_interface_hold_implement_##x (\
+ klass, rakia_media_channel_##x)
+ IMPLEMENT (get_hold_state);
+ IMPLEMENT (request_hold);
+#undef IMPLEMENT
+}