diff -r 000000000000 -r d0f3a028347a telepathygabble/src/gabble-media-stream.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/telepathygabble/src/gabble-media-stream.c Tue Feb 02 01:10:06 2010 +0200 @@ -0,0 +1,1836 @@ +/* + * gabble-media-stream.c - Source for GabbleMediaStream + * Copyright (C) 2006 Collabora Ltd. + * + * @author Ole Andre Vadla Ravnaas + * + * 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 +#include +#include +#include + + +#include "ansi.h" +#include "debug.h" +#include "handles.h" +#include "namespaces.h" + +#include "gabble-connection.h" +#include "gabble-media-channel.h" +#include "gabble-media-session.h" +#include "gabble-media-session-enumtypes.h" + +#include "telepathy-helpers.h" +#include "telepathy-constants.h" + +#include "gabble-media-stream.h" +#include "gabble-media-stream-signals-marshal.h" +#include "gabble-media-stream-glue.h" + +#include "gabble_enums.h" + +#ifndef EMULATOR +G_DEFINE_TYPE(GabbleMediaStream, gabble_media_stream, G_TYPE_OBJECT) +#endif + +#define DEBUG_FLAG GABBLE_DEBUG_MEDIA + +#ifdef DEBUG_FLAG +//#define DEBUG(format, ...) +#define DEBUGGING 0 +//#define NODE_DEBUG(n, s) ; +#endif /* DEBUG_FLAG */ + +/* signal enum */ +enum +{ + DESTROY, + + ADD_REMOTE_CANDIDATE, + CLOSE, + REMOVE_REMOTE_CANDIDATE, + SET_ACTIVE_CANDIDATE_PAIR, + SET_REMOTE_CANDIDATE_LIST, + SET_REMOTE_CODECS, + SET_STREAM_PLAYING, + SET_STREAM_SENDING, + + NEW_ACTIVE_CANDIDATE_PAIR, + NEW_NATIVE_CANDIDATE, + SUPPORTED_CODECS, + ERROR, + + LAST_SIGNAL +#ifdef EMULATOR + = LAST_SIGNAL_MED_STREAM +#endif + +}; + +#ifdef EMULATOR +#include "libgabble_wsd_solution.h" + + GET_STATIC_ARRAY_FROM_TLS(signals,gabble_med_stream,guint) + #define signals (GET_WSD_VAR_NAME(signals,gabble_med_stream, s)()) + + GET_STATIC_VAR_FROM_TLS(gabble_media_stream_parent_class,gabble_med_stream,gpointer) + #define gabble_media_stream_parent_class (*GET_WSD_VAR_NAME(gabble_media_stream_parent_class,gabble_med_stream,s)()) + + GET_STATIC_VAR_FROM_TLS(g_define_type_id,gabble_med_stream,GType) + #define g_define_type_id (*GET_WSD_VAR_NAME(g_define_type_id,gabble_med_stream,s)()) + + /*gchar** _s_gabble_med_stream_video_codec_params() { return (gchar**)((libgabble_ImpurePtr()->_s_gabble_med_stream_video_codec_params)); } + + #define video_codec_params (GET_WSD_VAR_NAME(video_codec_params,gabble_med_stream, s)())*/ + + +static void gabble_media_stream_init (GabbleMediaStream *self); +static void gabble_media_stream_class_init (GabbleMediaStreamClass *klass); +static void gabble_media_stream_class_intern_init (gpointer klass) +{ +gabble_media_stream_parent_class = g_type_class_peek_parent (klass); + gabble_media_stream_class_init ((GabbleMediaStreamClass*) klass); +} + EXPORT_C GType gabble_media_stream_get_type (void) + { + if ((g_define_type_id == 0)) + { + static const GTypeInfo g_define_type_info = { sizeof (GabbleMediaStreamClass), (GBaseInitFunc) ((void *)0), (GBaseFinalizeFunc) ((void *)0), (GClassInitFunc) gabble_media_stream_class_intern_init, (GClassFinalizeFunc) ((void *)0), ((void *)0), sizeof (GabbleMediaStream), 0, (GInstanceInitFunc) gabble_media_stream_init, ((void *)0) }; g_define_type_id = g_type_register_static ( ((GType) ((20) << (2))), g_intern_static_string ("GabbleMediaStream"), &g_define_type_info, (GTypeFlags) 0); { {} ; } } return g_define_type_id; + }; + + +#else + + static guint signals[LAST_SIGNAL] = {0}; + +#endif + + +/* properties */ +enum +{ + PROP_CONNECTION = 1, + PROP_MEDIA_SESSION, + PROP_OBJECT_PATH, + PROP_MODE, + PROP_NAME, + PROP_ID, + PROP_INITIATOR, + PROP_MEDIA_TYPE, + PROP_CONNECTION_STATE, + PROP_READY, + PROP_GOT_LOCAL_CODECS, + PROP_SIGNALLING_STATE, + PROP_PLAYING, + PROP_COMBINED_DIRECTION, + LAST_PROPERTY +}; + +/* private structure */ +typedef struct _GabbleMediaStreamPrivate GabbleMediaStreamPrivate; + +struct _GabbleMediaStreamPrivate +{ + GabbleConnection *conn; + GabbleMediaSession *session; + GabbleMediaSessionMode mode; + gchar *object_path; + guint id; + guint media_type; + + gboolean ready; + gboolean sending; + + GValue native_codecs; /* intersected codec list */ + GValue native_candidates; + + GValue remote_codecs; + GValue remote_candidates; + + guint remote_candidate_count; + + gboolean closed; + gboolean dispose_has_run; +}; + +#define GABBLE_MEDIA_STREAM_GET_PRIVATE(obj) \ + ((GabbleMediaStreamPrivate *)obj->priv) +//Vinod: add below definition +#define ENABLE_DEBUG + +#ifdef ENABLE_DEBUG +#if _GMS_DEBUG_LEVEL > 1 +static const char *tp_protocols[] = { + "TP_MEDIA_STREAM_PROTO_UDP (0)", + "TP_MEDIA_STREAM_PROTO_TCP (1)" +}; + +static const char *tp_transports[] = { + "TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL (0)", + "TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED (1)", + "TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY (2)" +}; +#endif +#endif + +static void push_native_candidates (GabbleMediaStream *stream); +static void push_remote_codecs (GabbleMediaStream *stream); +static void push_remote_candidates (GabbleMediaStream *stream); +static void push_playing (GabbleMediaStream *stream); +static void push_sending (GabbleMediaStream *stream); + +static void +gabble_media_stream_init (GabbleMediaStream *self) +{ + GabbleMediaStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GABBLE_TYPE_MEDIA_STREAM, GabbleMediaStreamPrivate); + + self->priv = priv; + + g_value_init (&priv->native_codecs, TP_TYPE_CODEC_LIST); + g_value_take_boxed (&priv->native_codecs, + dbus_g_type_specialized_construct (TP_TYPE_CODEC_LIST)); + + g_value_init (&priv->native_candidates, TP_TYPE_CANDIDATE_LIST); + g_value_take_boxed (&priv->native_candidates, + dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST)); + + g_value_init (&priv->remote_codecs, TP_TYPE_CODEC_LIST); + g_value_take_boxed (&priv->remote_codecs, + dbus_g_type_specialized_construct (TP_TYPE_CODEC_LIST)); + + g_value_init (&priv->remote_candidates, TP_TYPE_CANDIDATE_LIST); + g_value_take_boxed (&priv->remote_candidates, + dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST)); +} + +static GObject * +gabble_media_stream_constructor (GType type, guint n_props, + GObjectConstructParam *props) +{ + GObject *obj; + GabbleMediaStreamPrivate *priv; + DBusGConnection *bus; + + /* call base class constructor */ + obj = G_OBJECT_CLASS (gabble_media_stream_parent_class)-> + constructor (type, n_props, props); + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (GABBLE_MEDIA_STREAM (obj)); + + /* go for the bus */ + bus = tp_get_bus (); + dbus_g_connection_register_g_object (bus, priv->object_path, obj); + + return obj; +} + +static void +gabble_media_stream_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object); + GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + switch (property_id) { + case PROP_CONNECTION: + g_value_set_object (value, priv->conn); + break; + case PROP_MEDIA_SESSION: + g_value_set_object (value, priv->session); + break; + case PROP_OBJECT_PATH: + g_value_set_string (value, priv->object_path); + break; + case PROP_MODE: + g_value_set_enum (value, priv->mode); + break; + case PROP_NAME: + g_value_set_string (value, stream->name); + break; + case PROP_ID: + g_value_set_uint (value, priv->id); + break; + case PROP_INITIATOR: + g_value_set_uint (value, stream->initiator); + break; + case PROP_MEDIA_TYPE: + g_value_set_uint (value, priv->media_type); + break; + case PROP_CONNECTION_STATE: + g_value_set_uint (value, stream->connection_state); + break; + case PROP_READY: + g_value_set_boolean (value, priv->ready); + break; + case PROP_GOT_LOCAL_CODECS: + g_value_set_boolean (value, stream->got_local_codecs); + break; + case PROP_SIGNALLING_STATE: + g_value_set_uint (value, stream->signalling_state); + break; + case PROP_PLAYING: + g_value_set_boolean (value, stream->playing); + break; + case PROP_COMBINED_DIRECTION: + g_value_set_uint (value, stream->combined_direction); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gabble_media_stream_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object); + GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + switch (property_id) { + case PROP_CONNECTION: + priv->conn = g_value_get_object (value); + break; + case PROP_MEDIA_SESSION: + priv->session = g_value_get_object (value); + break; + case PROP_OBJECT_PATH: + g_free (priv->object_path); + priv->object_path = g_value_dup_string (value); + break; + case PROP_MODE: + priv->mode = g_value_get_enum (value); + break; + case PROP_NAME: + g_free (stream->name); + stream->name = g_value_dup_string (value); + break; + case PROP_ID: + priv->id = g_value_get_uint (value); + break; + case PROP_INITIATOR: + stream->initiator = g_value_get_uint (value); + break; + case PROP_MEDIA_TYPE: + priv->media_type = g_value_get_uint (value); + break; + case PROP_CONNECTION_STATE: + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "stream %s connection state %d", + stream->name, stream->connection_state); + stream->connection_state = g_value_get_uint (value); + break; + case PROP_READY: + priv->ready = g_value_get_boolean (value); + break; + case PROP_GOT_LOCAL_CODECS: + stream->got_local_codecs = g_value_get_boolean (value); + break; + case PROP_SIGNALLING_STATE: + { + StreamSignallingState old = stream->signalling_state; + stream->signalling_state = g_value_get_uint (value); + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "stream %s sig_state %d->%d", + stream->name, old, stream->signalling_state); + if (stream->signalling_state != old) + push_native_candidates (stream); + } + break; + case PROP_PLAYING: + { + gboolean old = stream->playing; + stream->playing = g_value_get_boolean (value); + if (stream->playing != old) + push_playing (stream); + } + break; + case PROP_COMBINED_DIRECTION: + stream->combined_direction = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void gabble_media_stream_dispose (GObject *object); +static void gabble_media_stream_finalize (GObject *object); + +static void +gabble_media_stream_class_init (GabbleMediaStreamClass *gabble_media_stream_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (gabble_media_stream_class); + GParamSpec *param_spec; + + g_type_class_add_private (gabble_media_stream_class, sizeof (GabbleMediaStreamPrivate)); + + object_class->constructor = gabble_media_stream_constructor; + + object_class->get_property = gabble_media_stream_get_property; + object_class->set_property = gabble_media_stream_set_property; + + object_class->dispose = gabble_media_stream_dispose; + object_class->finalize = gabble_media_stream_finalize; + + param_spec = g_param_spec_object ("connection", "GabbleConnection object", + "Gabble connection object that owns this " + "media stream's channel.", + GABBLE_TYPE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + param_spec = g_param_spec_object ("media-session", "GabbleMediaSession object", + "Gabble media session object that owns this " + "media stream object.", + GABBLE_TYPE_MEDIA_SESSION, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_MEDIA_SESSION, param_spec); + + 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_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec); + + param_spec = g_param_spec_enum ("mode", "Signalling mode", + "Which signalling mode used to control the " + "stream.", + gabble_media_session_mode_get_type(), + MODE_JINGLE, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_MODE, param_spec); + + param_spec = g_param_spec_string ("name", "Stream name", + "An opaque name for the stream used in the " + "signalling.", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_NAME, param_spec); + + param_spec = g_param_spec_uint ("id", "Stream ID", + "A stream number for the stream used in the " + "D-Bus API.", + 0, G_MAXUINT, 0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_ID, param_spec); + + param_spec = g_param_spec_uint ("initiator", "Stream initiator", + "An enum signifying which end initiated " + "the stream.", + INITIATOR_LOCAL, + INITIATOR_REMOTE, + INITIATOR_LOCAL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_INITIATOR, param_spec); + + param_spec = g_param_spec_uint ("media-type", "Stream media type", + "A constant indicating which media type the " + "stream carries.", + TP_MEDIA_STREAM_TYPE_AUDIO, + TP_MEDIA_STREAM_TYPE_VIDEO, + TP_MEDIA_STREAM_TYPE_AUDIO, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_MEDIA_TYPE, param_spec); + + param_spec = g_param_spec_uint ("connection-state", "Stream connection state", + "An integer indicating the state of the" + "stream's connection.", + TP_MEDIA_STREAM_STATE_DISCONNECTED, + TP_MEDIA_STREAM_STATE_CONNECTED, + TP_MEDIA_STREAM_STATE_DISCONNECTED, + G_PARAM_CONSTRUCT | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_CONNECTION_STATE, param_spec); + + param_spec = g_param_spec_boolean ("ready", "Ready?", + "A boolean signifying whether the user " + "is ready to handle signals from this " + "object.", + FALSE, + G_PARAM_CONSTRUCT | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_READY, param_spec); + + param_spec = g_param_spec_boolean ("got-local-codecs", "Got local codecs?", + "A boolean signifying whether we've got " + "the locally supported codecs from the user.", + FALSE, + G_PARAM_CONSTRUCT | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_GOT_LOCAL_CODECS, param_spec); + + param_spec = g_param_spec_uint ("signalling-state", "Signalling state", + "Whether the stream is newly created, " + "sent to the peer, or acknowledged.", + STREAM_SIG_STATE_NEW, + STREAM_SIG_STATE_REMOVING, + STREAM_SIG_STATE_NEW, + G_PARAM_CONSTRUCT | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_SIGNALLING_STATE, param_spec); + + param_spec = g_param_spec_boolean ("playing", "Set playing", + "A boolean signifying whether the stream " + "has been set playing yet.", + FALSE, + G_PARAM_CONSTRUCT | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_PLAYING, param_spec); + + param_spec = g_param_spec_uint ("combined-direction", + "Combined direction", + "An integer indicating the directions the stream currently sends in, " + "and the peers who have been asked to send.", + TP_MEDIA_STREAM_DIRECTION_NONE, + MAKE_COMBINED_DIRECTION (TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, + TP_MEDIA_STREAM_PENDING_LOCAL_SEND | + TP_MEDIA_STREAM_PENDING_REMOTE_SEND), + TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_COMBINED_DIRECTION, + param_spec); + + /* signals exported by D-Bus interface */ + signals[DESTROY] = + g_signal_new ("destroy", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[ADD_REMOTE_CANDIDATE] = + g_signal_new ("add-remote-candidate", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + gabble_media_stream_marshal_VOID__STRING_BOXED, + G_TYPE_NONE, 2, G_TYPE_STRING, TP_TYPE_TRANSPORT_LIST); + + signals[CLOSE] = + g_signal_new ("close", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[REMOVE_REMOTE_CANDIDATE] = + g_signal_new ("remove-remote-candidate", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + signals[SET_ACTIVE_CANDIDATE_PAIR] = + g_signal_new ("set-active-candidate-pair", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + gabble_media_stream_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + + signals[SET_REMOTE_CANDIDATE_LIST] = + g_signal_new ("set-remote-candidate-list", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, TP_TYPE_CANDIDATE_LIST); + + signals[SET_REMOTE_CODECS] = + g_signal_new ("set-remote-codecs", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, TP_TYPE_CODEC_LIST); + + /* signals not exported by D-Bus interface */ + signals[NEW_ACTIVE_CANDIDATE_PAIR] = + g_signal_new ("new-active-candidate-pair", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + gabble_media_stream_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + + signals[NEW_NATIVE_CANDIDATE] = + g_signal_new ("new-native-candidate", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + gabble_media_stream_marshal_VOID__STRING_BOXED, + G_TYPE_NONE, 2, G_TYPE_STRING, TP_TYPE_TRANSPORT_LIST); + + signals[SUPPORTED_CODECS] = + g_signal_new ("supported-codecs", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, TP_TYPE_CODEC_LIST); + + signals[SET_STREAM_PLAYING] = + g_signal_new ("set-stream-playing", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + signals[SET_STREAM_SENDING] = + g_signal_new ("set-stream-sending", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + signals[ERROR] = + g_signal_new ("error", + G_OBJECT_CLASS_TYPE (gabble_media_stream_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + gabble_media_stream_marshal_VOID__UINT_STRING, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (gabble_media_stream_class), &dbus_glib_gabble_media_stream_object_info); +} + +void +gabble_media_stream_dispose (GObject *object) +{ + GabbleMediaStream *self = GABBLE_MEDIA_STREAM (object); + GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self); + + if (priv->dispose_has_run) + return; + + _gabble_media_stream_close (self); + + g_signal_emit (self, signals[DESTROY], 0); + + priv->dispose_has_run = TRUE; + + if (G_OBJECT_CLASS (gabble_media_stream_parent_class)->dispose) + G_OBJECT_CLASS (gabble_media_stream_parent_class)->dispose (object); +} + +void +gabble_media_stream_finalize (GObject *object) +{ + GabbleMediaStream *self = GABBLE_MEDIA_STREAM (object); + GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self); + + g_free (priv->object_path); + + g_value_unset (&priv->native_codecs); + g_value_unset (&priv->native_candidates); + + g_value_unset (&priv->remote_codecs); + g_value_unset (&priv->remote_candidates); + + G_OBJECT_CLASS (gabble_media_stream_parent_class)->finalize (object); +} + +/** + * gabble_media_stream_codec_choice + * + * Implements D-Bus method CodecChoice + * on interface org.freedesktop.Telepathy.Media.StreamHandler + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_media_stream_codec_choice (GabbleMediaStream *self, + guint codec_id, + GError **error) +{ + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (self)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self); + + return TRUE; +} + + +/** + * gabble_media_stream_error + * + * Implements D-Bus method Error + * on interface org.freedesktop.Telepathy.Media.StreamHandler + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_media_stream_error (GabbleMediaStream *self, + guint errno, + const gchar *message, + GError **error) +{ + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (self)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self); + + _gabble_media_session_debug (priv->session, DEBUG_MSG_WARNING, "Media.StreamHandler::Error called, error %u (%s) -- emitting signal", errno, message); + + g_signal_emit (self, signals[ERROR], 0, errno, message); + + return TRUE; +} + + +/** + * gabble_media_stream_native_candidates_prepared + * + * Implements D-Bus method NativeCandidatesPrepared + * on interface org.freedesktop.Telepathy.Media.StreamHandler + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_media_stream_native_candidates_prepared (GabbleMediaStream *self, + GError **error) +{ + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (self)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self); + + return TRUE; +} + + +/** + * gabble_media_stream_new_active_candidate_pair + * + * Implements D-Bus method NewActiveCandidatePair + * on interface org.freedesktop.Telepathy.Media.StreamHandler + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_media_stream_new_active_candidate_pair (GabbleMediaStream *self, + const gchar *native_candidate_id, + const gchar *remote_candidate_id, + GError **error) +{ + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (self)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self); + + g_signal_emit (self, signals[NEW_ACTIVE_CANDIDATE_PAIR], 0, + native_candidate_id, remote_candidate_id); + + return TRUE; +} + + +/** + * gabble_media_stream_new_native_candidate + * + * Implements D-Bus method NewNativeCandidate + * on interface org.freedesktop.Telepathy.Media.StreamHandler + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_media_stream_new_native_candidate (GabbleMediaStream *self, + const gchar *candidate_id, + const GPtrArray *transports, + GError **error) +{ + GabbleMediaStreamPrivate *priv; + JingleSessionState state; + GPtrArray *candidates; + GValue candidate = { 0, }; + GValueArray *transport; + const gchar *addr; + + g_assert (GABBLE_IS_MEDIA_STREAM (self)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self); + + g_object_get (priv->session, "state", &state, NULL); + + /* FIXME: maybe this should be an assertion in case the channel + * isn't closed early enough right now? */ + if (state > JS_STATE_ACTIVE) + { + gabble_debug (DEBUG_FLAG, "state > JS_STATE_ACTIVE, doing nothing"); + return TRUE; + } + + candidates = g_value_get_boxed (&priv->native_candidates); + + g_value_init (&candidate, TP_TYPE_CANDIDATE_STRUCT); + g_value_take_boxed (&candidate, + dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_STRUCT)); + + dbus_g_type_struct_set (&candidate, + 0, candidate_id, + 1, transports, + G_MAXUINT); + + transport = g_ptr_array_index (transports, 0); + addr = g_value_get_string (g_value_array_get_nth (transport, 1)); + if (!strcmp (addr, "127.0.0.1")) + { + _gabble_media_session_debug (priv->session, DEBUG_MSG_WARNING, "%s: ignoring native localhost candidate", + G_STRFUNC); + return TRUE; + } + + g_ptr_array_add (candidates, g_value_get_boxed (&candidate)); + + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "put 1 native candidate from stream-engine into cache"); + + push_native_candidates (self); + + g_signal_emit (self, signals[NEW_NATIVE_CANDIDATE], 0, + candidate_id, transports); + + return TRUE; +} + + +/** + * gabble_media_stream_ready + * + * Implements D-Bus method Ready + * on interface org.freedesktop.Telepathy.Media.StreamHandler + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_media_stream_ready (GabbleMediaStream *self, + const GPtrArray *codecs, + GError **error) +{ + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (self)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self); + + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "ready called"); + + g_object_set (self, "ready", TRUE, NULL); + + push_remote_codecs (self); + push_remote_candidates (self); + push_playing (self); + push_sending (self); + + return gabble_media_stream_set_local_codecs (self, codecs, error); +} + + +/** + * gabble_media_stream_set_local_codecs + * + * Implements D-Bus method SetLocalCodecs + * on interface org.freedesktop.Telepathy.Media.StreamHandler + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_media_stream_set_local_codecs (GabbleMediaStream *self, + const GPtrArray *codecs, + GError **error) +{ + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (self)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self); + + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "putting list of all %d locally supported " + "codecs from stream-engine into cache", codecs->len); + + g_value_set_boxed (&priv->native_codecs, codecs); + + g_object_set (self, "got-local-codecs", TRUE, NULL); + + return TRUE; +} + + +/** + * gabble_media_stream_stream_state + * + * Implements D-Bus method StreamState + * on interface org.freedesktop.Telepathy.Media.StreamHandler + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_media_stream_stream_state (GabbleMediaStream *self, + guint connection_state, + GError **error) +{ + g_assert (GABBLE_IS_MEDIA_STREAM (self)); + + g_object_set (self, "connection-state", connection_state, NULL); + + return TRUE; +} + + +/** + * gabble_media_stream_supported_codecs + * + * Implements D-Bus method SupportedCodecs + * on interface org.freedesktop.Telepathy.Media.StreamHandler + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_media_stream_supported_codecs (GabbleMediaStream *self, + const GPtrArray *codecs, + GError **error) +{ + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (self)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self); + + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "got codec intersection containing %d " + "codecs from stream-engine", codecs->len); + + /* store the intersection for later on */ + g_value_set_boxed (&priv->native_codecs, codecs); + + g_signal_emit (self, signals[SUPPORTED_CODECS], 0, codecs); + + return TRUE; +} + +static LmHandlerResult +candidates_msg_reply_cb (GabbleConnection *conn, + LmMessage *sent_msg, + LmMessage *reply_msg, + GObject *object, + gpointer user_data) +{ + GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object); + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (priv->session, "candidates failed"); + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static void +_add_rtp_candidate_node (GabbleMediaSession *session, LmMessageNode *parent, + GValueArray *candidate) +{ + gchar *addr; + gchar *user; + gchar *pass; + gchar *port_str; + gchar *pref_str; + gchar *xml; + const gchar *type_str; + const gchar *candidate_id; + guint port; + gdouble pref; + TpMediaStreamProto proto; + TpMediaStreamTransportType type; + const GPtrArray *transports; + GValue transport = { 0, }; + LmMessageNode *cand_node; + + candidate_id = g_value_get_string (g_value_array_get_nth (candidate, 0)); + transports = g_value_get_boxed (g_value_array_get_nth (candidate, 1)); + + /* jingle audio only supports the concept of one transport per candidate */ + g_assert (transports->len == 1); + + g_value_init (&transport, TP_TYPE_TRANSPORT_STRUCT); + g_value_set_static_boxed (&transport, g_ptr_array_index (transports, 0)); + + dbus_g_type_struct_get (&transport, + 1, &addr, + 2, &port, + 3, &proto, + 6, &pref, + 7, &type, + 8, &user, + 9, &pass, + G_MAXUINT); + + port_str = g_strdup_printf ("%d", port); + pref_str = g_strdup_printf ("%f", pref); + + switch (type) { + case TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL: + type_str = "local"; + break; + case TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED: + type_str = "stun"; + break; + case TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY: + type_str = "relay"; + break; + default: + g_error ("%s: TpMediaStreamTransportType has an invalid value", + G_STRFUNC); + return; + } + + cand_node = lm_message_node_add_child (parent, "candidate", NULL); + lm_message_node_set_attributes (cand_node, + "name", "rtp", + "address", addr, + "port", port_str, + "username", user, + "password", pass, + "preference", pref_str, + "protocol", (proto == TP_MEDIA_STREAM_PROTO_UDP) ? "udp" : "tcp", + "type", type_str, + "network", "0", + "generation", "0", + NULL); + + xml = lm_message_node_to_string (cand_node); + _gabble_media_session_debug (session, DEBUG_MSG_DUMP, + " from Telepathy D-Bus struct: [%s\"%s\", %s[%s1, \"%s\", %d, %s, " + "\"%s\", \"%s\", %f, %s, \"%s\", \"%s\"%s]]", + ANSI_BOLD_OFF, candidate_id, ANSI_BOLD_ON, ANSI_BOLD_OFF, addr, port, + tp_protocols[proto], "RTP", "AVP", pref, tp_transports[type], user, pass, + ANSI_BOLD_ON); + _gabble_media_session_debug (session, DEBUG_MSG_DUMP, + " to Jingle XML: [%s%s%s]", ANSI_BOLD_OFF, xml, ANSI_BOLD_ON); + g_free (xml); + + g_free (addr); + g_free (user); + g_free (pass); + g_free (port_str); + g_free (pref_str); +} + +static LmMessage * +_gabble_media_stream_message_new (GabbleMediaStream *stream, + const gchar *action, + LmMessageNode **content_node) +{ + GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + LmMessage *msg; + LmMessageNode *session_node = NULL; + + /* construct a session message */ + msg = _gabble_media_session_message_new (priv->session, action, + &session_node); + + /* add our content node to it if necessary */ + *content_node = _gabble_media_stream_add_content_node (stream, session_node); + + return msg; +} + + +static void +push_candidate (GabbleMediaStream *stream, GValueArray *candidate) +{ + GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + LmMessage *msg; + LmMessageNode *content_node, *transport_node; + const gchar *action; + + if (priv->mode == MODE_GOOGLE) + action = "candidates"; + else + action = "transport-info"; + + /* construct a base message */ + msg = _gabble_media_stream_message_new (stream, action, &content_node); + + /* for jingle, add a transport */ + transport_node = _gabble_media_stream_content_node_add_transport (stream, + content_node); + + /* add transport info to it */ + _add_rtp_candidate_node (priv->session, transport_node, candidate); + + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "sending jingle session action \"%s\" to " + "peer", action); + + /* send it */ + _gabble_connection_send_with_reply (priv->conn, msg, candidates_msg_reply_cb, + G_OBJECT (stream), NULL, NULL); + + /* clean up */ + lm_message_unref (msg); +} + +static void +push_native_candidates (GabbleMediaStream *stream) +{ + GabbleMediaStreamPrivate *priv; + GPtrArray *candidates; + guint i; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + if (stream->signalling_state == STREAM_SIG_STATE_NEW || + stream->signalling_state == STREAM_SIG_STATE_REMOVING) + return; + + candidates = g_value_get_boxed (&priv->native_candidates); + + for (i = 0; i < candidates->len; i++) + push_candidate (stream, g_ptr_array_index (candidates, i)); + + g_value_take_boxed (&priv->native_candidates, + dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST)); +} + +void +_gabble_media_stream_close (GabbleMediaStream *stream) +{ + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + if (!priv->closed) + { + priv->closed = TRUE; + g_signal_emit (stream, signals[CLOSE], 0); + } +} + +gboolean +_gabble_media_stream_post_remote_codecs (GabbleMediaStream *stream, + LmMessage *message, + LmMessageNode *desc_node, + GError **error) +{ + GabbleMediaStreamPrivate *priv; + LmMessageNode *node; + GPtrArray *codecs; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + codecs = g_value_get_boxed (&priv->remote_codecs); + + g_assert (codecs->len == 0); + + for (node = desc_node->children; node; node = node->next) + { + guchar id; + const gchar *name, *str; + guint clockrate, channels; + GHashTable *params; + GValue codec = { 0, }; + + if (g_strdiff (node->name, "payload-type")) + continue; + + /* id of codec */ + str = lm_message_node_get_attribute (node, "id"); + if (str == NULL) + { + g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST, + "description has no ID"); + return FALSE; + } + + id = atoi(str); + + /* codec name */ + name = lm_message_node_get_attribute (node, "name"); + if (name == NULL) + { + name = ""; + } + + /* clock rate: jingle and newer GTalk */ + str = lm_message_node_get_attribute (node, "clockrate"); /* google */ + if (str == NULL) + str = lm_message_node_get_attribute (node, "rate"); /* jingle */ + + if (str != NULL) + { + clockrate = atoi (str); + } + else + { + clockrate = 0; + } + + /* number of channels: jingle only */ + str = lm_message_node_get_attribute (node, "channels"); + if (str != NULL) + { + channels = atoi (str); + } + else + { + channels = 1; + } + + params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); + + /* bitrate: newer GTalk only */ + str = lm_message_node_get_attribute (node, "bitrate"); + if (str != NULL) + { + g_hash_table_insert (params, "bitrate", g_strdup (str)); + } + + g_value_init (&codec, TP_TYPE_CODEC_STRUCT); + g_value_take_boxed (&codec, + dbus_g_type_specialized_construct (TP_TYPE_CODEC_STRUCT)); + + dbus_g_type_struct_set (&codec, + 0, id, + 1, name, + 2, TP_CODEC_MEDIA_TYPE_AUDIO, + 3, clockrate, + 4, channels, + 5, params, + G_MAXUINT); + + g_ptr_array_add (codecs, g_value_get_boxed (&codec)); + } + + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "put %d remote codecs from peer into cache", + codecs->len); + + push_remote_codecs (stream); + + return TRUE; +} + +static void +push_remote_codecs (GabbleMediaStream *stream) +{ + GabbleMediaStreamPrivate *priv; + GPtrArray *codecs; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + if (!priv->ready) + return; + + codecs = g_value_get_boxed (&priv->remote_codecs); + if (codecs->len == 0) + return; + + _gabble_media_session_debug (priv->session, DEBUG_MSG_EVENT, "passing %d remote codecs to stream-engine", + codecs->len); + + g_signal_emit (stream, signals[SET_REMOTE_CODECS], 0, codecs); + + g_value_take_boxed (&priv->remote_codecs, + dbus_g_type_specialized_construct (TP_TYPE_CODEC_LIST)); +} + +gboolean +_gabble_media_stream_post_remote_candidates (GabbleMediaStream *stream, + LmMessage *message, + LmMessageNode *transport_node, + GError **error) +{ + GabbleMediaStreamPrivate *priv; + LmMessageNode *node; + const gchar *str; + GPtrArray *candidates; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + candidates = g_value_get_boxed (&priv->remote_candidates); + + for (node = transport_node->children; node; node = node->next) + { + gchar *candidate_id; + const gchar *name, *addr; + guint16 port; + TpMediaStreamProto proto; + gdouble pref; + TpMediaStreamTransportType type; + const gchar *user, *pass; + guchar net, gen; + GValue candidate = { 0, }; + GPtrArray *transports; + GValue transport = { 0, }; + gchar *xml; + + if (g_strdiff (node->name, "candidate")) + continue; + + /* + * Candidate + */ + + /* stream name */ + name = lm_message_node_get_attribute (node, "name"); + if (name == NULL || strcmp (name, "rtp") != 0) + goto FAILURE; + + + /* + * Transport + */ + + /* ip address */ + addr = lm_message_node_get_attribute (node, "address"); + if (addr == NULL) + goto FAILURE; + + /* port */ + str = lm_message_node_get_attribute (node, "port"); + if (str == NULL) + goto FAILURE; + port = atoi (str); + + /* protocol */ + str = lm_message_node_get_attribute (node, "protocol"); + if (str == NULL) + goto FAILURE; + + if (strcmp (str, "udp") == 0) + { + proto = TP_MEDIA_STREAM_PROTO_UDP; + } + else if (strcmp (str, "tcp") == 0) + { + proto = TP_MEDIA_STREAM_PROTO_TCP; + } + else if (strcmp (str, "ssltcp") == 0) + { + _gabble_media_session_debug (priv->session, DEBUG_MSG_WARNING, "%s: ssltcp candidates " + "not yet supported", G_STRFUNC); + continue; + } + else + goto FAILURE; + + /* protocol profile: hardcoded to "AVP" for now */ + + /* preference */ + str = lm_message_node_get_attribute (node, "preference"); + if (str == NULL) + goto FAILURE; + pref = g_ascii_strtod (str, NULL); + + /* type */ + str = lm_message_node_get_attribute (node, "type"); + if (str == NULL) + goto FAILURE; + + if (strcmp (str, "local") == 0) + { + type = TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL; + } + else if (strcmp (str, "stun") == 0) + { + type = TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED; + } + else if (strcmp (str, "relay") == 0) + { + type = TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY; + } + else + goto FAILURE; + + /* username */ + user = lm_message_node_get_attribute (node, "username"); + if (user == NULL) + goto FAILURE; + + /* password */ + pass = lm_message_node_get_attribute (node, "password"); + if (pass == NULL) + goto FAILURE; + + /* unknown */ + str = lm_message_node_get_attribute (node, "network"); + if (str == NULL) + goto FAILURE; + net = atoi (str); + + /* unknown */ + str = lm_message_node_get_attribute (node, "generation"); + if (str == NULL) + goto FAILURE; + gen = atoi (str); + + + g_value_init (&transport, TP_TYPE_TRANSPORT_STRUCT); + g_value_take_boxed (&transport, + dbus_g_type_specialized_construct (TP_TYPE_TRANSPORT_STRUCT)); + + dbus_g_type_struct_set (&transport, + 0, 1, /* component number */ + 1, addr, + 2, port, + 3, proto, + 4, "RTP", + 5, "AVP", + 6, pref, + 7, type, + 8, user, + 9, pass, + G_MAXUINT); + + transports = g_ptr_array_sized_new (1); + g_ptr_array_add (transports, g_value_get_boxed (&transport)); + + + g_value_init (&candidate, TP_TYPE_CANDIDATE_STRUCT); + g_value_take_boxed (&candidate, + dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_STRUCT)); + + /* FIXME: is this naming scheme sensible? */ + candidate_id = g_strdup_printf ("R%d", ++priv->remote_candidate_count); + + dbus_g_type_struct_set (&candidate, + 0, candidate_id, + 1, transports, + G_MAXUINT); + + g_ptr_array_add (candidates, g_value_get_boxed (&candidate)); + + xml = lm_message_node_to_string (node); + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "put 1 remote candidate from peer into cache"); + _gabble_media_session_debug (priv->session, DEBUG_MSG_DUMP, " from Jingle XML: [%s%s%s]", + ANSI_BOLD_OFF, xml, ANSI_BOLD_ON); + _gabble_media_session_debug (priv->session, DEBUG_MSG_DUMP, " to Telepathy D-Bus struct: [%s\"%s\", %s[%s1, \"%s\", %d, %s, \"%s\", \"%s\", %f, %s, \"%s\", \"%s\"%s]]", + ANSI_BOLD_OFF, candidate_id, ANSI_BOLD_ON, + ANSI_BOLD_OFF, addr, port, tp_protocols[proto], "RTP", "AVP", pref, tp_transports[type], user, pass, ANSI_BOLD_ON); + g_free (xml); + + g_free (candidate_id); + } + +/*SUCCESS:*/ + push_remote_candidates (stream); + + return TRUE; + +FAILURE: + g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST, + "unable to parse candidate"); + + return FALSE; +} + +static void +push_remote_candidates (GabbleMediaStream *stream) +{ + GabbleMediaStreamPrivate *priv; + GPtrArray *candidates; + guint i; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + candidates = g_value_get_boxed (&priv->remote_candidates); + + if (candidates->len == 0) + return; + + if (!priv->ready) + return; + + for (i = 0; i < candidates->len; i++) + { + GValueArray *candidate = g_ptr_array_index (candidates, i); + const gchar *candidate_id; + const GPtrArray *transports; + + candidate_id = g_value_get_string (g_value_array_get_nth (candidate, 0)); + transports = g_value_get_boxed (g_value_array_get_nth (candidate, 1)); + + _gabble_media_session_debug (priv->session, DEBUG_MSG_EVENT, "passing 1 remote candidate " + "to stream-engine"); + + g_signal_emit (stream, signals[ADD_REMOTE_CANDIDATE], 0, + candidate_id, transports); + } + + g_value_take_boxed (&priv->remote_candidates, + dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST)); +} + +static void +push_playing (GabbleMediaStream *stream) +{ + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + if (!priv->ready) + return; + + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "stream %s emitting SetStreamPlaying(%s)", + stream->name, stream->playing ? "true" : "false"); + + g_signal_emit (stream, signals[SET_STREAM_PLAYING], 0, stream->playing); +} + +static void +push_sending (GabbleMediaStream *stream) +{ + GabbleMediaStreamPrivate *priv; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + if (!priv->ready) + return; + + _gabble_media_session_debug (priv->session, DEBUG_MSG_INFO, DEBUG_MSG_INFO, "stream %s emitting SetStreamSending(%s)", + stream->name, priv->sending ? "true" : "false"); + + g_signal_emit (stream, signals[SET_STREAM_SENDING], 0, priv->sending); +} + +/* + * oh sweet g_hash_table_foreach how beautiful thou be'st + * + * _\ / ^/ + * \/ \// 7_ __ + * ( 7 ) (__) (__) + * ^\\ |/__/___/ + * \\/_/ | <-- TP-cable kindly provided by Mika N. + * \ / O + * || /|\ + * || / \ + * || + * ____||_____________ + */ + +typedef struct { + GabbleMediaStreamPrivate *priv; + LmMessageNode *pt_node; +} CodecParamsFromTpContext; + +//#ifndef EMULATOR +static const gchar *video_codec_params[] = { + "x", "y", "width", "height", "layer", "transparent", +}; +//#endif + +static void +codec_params_from_tp_foreach (gpointer key, gpointer value, gpointer user_data) +{ + CodecParamsFromTpContext *ctx = user_data; + GabbleMediaStreamPrivate *priv = ctx->priv; + const gchar *pname = key, *pvalue = value; + + if (priv->media_type == TP_CODEC_MEDIA_TYPE_AUDIO) + { + if (priv->mode == MODE_GOOGLE && strcmp (pname, "bitrate") == 0) + { + lm_message_node_set_attribute (ctx->pt_node, pname, pvalue); + return; + } + } + else if (priv->mode == MODE_JINGLE) + { + gint i; + + for (i = 0; video_codec_params[i] != NULL; i++) + { + if (strcmp (pname, video_codec_params[i]) == 0) + { + lm_message_node_set_attribute (ctx->pt_node, pname, pvalue); + return; + } + } + } + + gabble_debug (DEBUG_FLAG, "ignoring %s=%s for %s %s stream", pname, pvalue, + (priv->mode == MODE_JINGLE) ? "jingle" : "google", + (priv->media_type == TP_CODEC_MEDIA_TYPE_AUDIO) ? "audio" : "video"); +} + +LmMessageNode * +_gabble_media_stream_add_content_node (GabbleMediaStream *stream, + LmMessageNode *session_node) +{ + GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + LmMessageNode *node = session_node; + + /* add our content node to it if in jingle mode */ + if (priv->mode == MODE_JINGLE) + { + node = lm_message_node_add_child (session_node, "content", NULL); + lm_message_node_set_attribute (node, "name", stream->name); + + if (priv->session->initiator == stream->initiator) + lm_message_node_set_attribute (node, "creator", "initiator"); + else + lm_message_node_set_attribute (node, "creator", "responder"); + } + + return node; +} + +void +_gabble_media_stream_content_node_add_description (GabbleMediaStream *stream, + LmMessageNode *content_node) +{ + GabbleMediaStreamPrivate *priv; + const GPtrArray *codecs; + LmMessageNode *desc_node; + guint i; + const gchar *xmlns; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + codecs = g_value_get_boxed (&priv->native_codecs); + + desc_node = lm_message_node_add_child (content_node, "description", NULL); + + if (priv->mode == MODE_GOOGLE) + xmlns = NS_GOOGLE_SESSION_PHONE; + else if (priv->media_type == TP_CODEC_MEDIA_TYPE_VIDEO) + xmlns = NS_JINGLE_DESCRIPTION_VIDEO; + else + xmlns = NS_JINGLE_DESCRIPTION_AUDIO; + + lm_message_node_set_attribute (desc_node, "xmlns", xmlns); + + for (i = 0; i < codecs->len; i++) + { + GValue codec = { 0, }; + guint id, clock_rate, channels; + gchar *name, buf[16]; + GHashTable *params; + LmMessageNode *pt_node; + CodecParamsFromTpContext ctx; + + g_value_init (&codec, TP_TYPE_CODEC_STRUCT); + g_value_set_static_boxed (&codec, g_ptr_array_index (codecs, i)); + + dbus_g_type_struct_get (&codec, + 0, &id, + 1, &name, + 3, &clock_rate, + 4, &channels, + 5, ¶ms, + G_MAXUINT); + + /* create a sub-node called "payload-type" and fill it */ + pt_node = lm_message_node_add_child (desc_node, "payload-type", NULL); + + /* id: required */ + sprintf (buf, "%u", id); + lm_message_node_set_attribute (pt_node, "id", buf); + + /* name: optional */ + if (*name != '\0') + { + lm_message_node_set_attribute (pt_node, "name", name); + } + + /* clock rate: optional */ + if (clock_rate != 0) + { + sprintf (buf, "%u", clock_rate); + lm_message_node_set_attribute (pt_node, + (priv->mode == MODE_GOOGLE) ? "clockrate" : "rate", buf); + } + + /* number of channels: optional, jingle only */ + /* FIXME: is it? */ + if (channels != 0 && priv->mode == MODE_JINGLE) + { + sprintf (buf, "%u", channels); + lm_message_node_set_attribute (pt_node, "channels", buf); + } + + /* parse the optional params */ + ctx.priv = priv; + ctx.pt_node = pt_node; + g_hash_table_foreach (params, codec_params_from_tp_foreach, &ctx); + + /* clean up */ + g_free (name); + g_hash_table_destroy (params); + } +} + +LmMessageNode * +_gabble_media_stream_content_node_add_transport (GabbleMediaStream *stream, + LmMessageNode *content_node) +{ + GabbleMediaStreamPrivate *priv; + LmMessageNode *node; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + if (priv->mode != MODE_JINGLE) + return content_node; + + node = lm_message_node_add_child (content_node, "transport", NULL); + + lm_message_node_set_attribute (node, "xmlns", NS_GOOGLE_TRANSPORT_P2P); + + return node; +} + +void +_gabble_media_stream_update_sending (GabbleMediaStream *stream, + gboolean start_sending) +{ + GabbleMediaStreamPrivate *priv; + gboolean new_sending; + + g_assert (GABBLE_IS_MEDIA_STREAM (stream)); + + priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream); + + new_sending = + ((stream->combined_direction & TP_MEDIA_STREAM_DIRECTION_SEND) != 0); + + if (priv->sending == new_sending) + return; + + if (new_sending && !start_sending) + return; + + priv->sending = new_sending; + push_sending (stream); +} +