[Telepathy-commits] [telepathy-gabble/master] bolted the new jingle engine onto gabble
Senko Rasic
senko at phyrexia.lan
Tue Dec 2 04:33:55 PST 2008
---
src/Makefile.am | 6 +-
src/jingle-content.c | 216 ++-
src/jingle-content.h | 16 +-
src/jingle-factory.c | 222 ++-
src/jingle-factory.h | 7 +-
src/jingle-media-rtp.c | 57 +-
src/jingle-media-rtp.h | 3 +
src/jingle-session.c | 476 ++++-
src/jingle-session.h | 22 +-
src/jingle-transport-google.c | 109 +-
src/jingle-transport-iface.c | 10 +
src/jingle-transport-iface.h | 2 +
src/media-channel.c | 942 ++++++----
src/media-channel.h | 1 -
src/media-factory.c | 24 +-
src/media-session.c | 2933 ----------------------------
src/media-session.h | 169 --
src/media-stream.c | 257 +++-
src/media-stream.h | 24 +-
src/namespaces.h | 3 +
src/presence-cache.c | 8 +
src/types.h | 6 +-
tests/twisted/jingle/jingletest.py | 3 +-
tests/twisted/jingle/test-incoming-call.py | 12 +-
24 files changed, 1809 insertions(+), 3719 deletions(-)
delete mode 100644 src/media-session.c
delete mode 100644 src/media-session.h
diff --git a/src/Makefile.am b/src/Makefile.am
index eca6b00..a1ec7fc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,9 +1,7 @@
BUILT_SOURCES = \
gabble-signals-marshal.h \
gabble-signals-marshal.c \
- gabble-signals-marshal.list \
- media-session-enumtypes.h \
- media-session-enumtypes.c
+ gabble-signals-marshal.list
CLEANFILES = $(BUILT_SOURCES)
@@ -65,8 +63,6 @@ libgabble_convenience_la_our_sources = \
im-factory.c \
media-channel.h \
media-channel.c \
- media-session.h \
- media-session.c \
media-stream.h \
media-stream.c \
media-factory.h \
diff --git a/src/jingle-content.c b/src/jingle-content.c
index 8d23d70..0129e4b 100644
--- a/src/jingle-content.c
+++ b/src/jingle-content.c
@@ -40,6 +40,7 @@
enum
{
READY,
+ NEW_CANDIDATES,
LAST_SIGNAL
};
@@ -55,6 +56,7 @@ enum
PROP_NAME,
PROP_SENDERS,
PROP_STATE,
+ PROP_READY,
LAST_PROPERTY
};
@@ -66,12 +68,12 @@ struct _GabbleJingleContentPrivate
gboolean created_by_initiator;
JingleContentState state;
JingleContentSenders senders;
+ gboolean ready;
gchar *content_ns;
gchar *transport_ns;
GabbleJingleTransportIface *transport;
- gboolean has_local_codecs;
gboolean dispose_has_run;
};
@@ -90,6 +92,9 @@ static const gchar *content_senders_table[] = {
G_DEFINE_TYPE(GabbleJingleContent, gabble_jingle_content, G_TYPE_OBJECT);
+static void new_transport_candidates_cb (GabbleJingleTransportIface *trans,
+ GList *candidates, GabbleJingleContent *content);
+
static void
gabble_jingle_content_init (GabbleJingleContent *obj)
{
@@ -159,6 +164,12 @@ gabble_jingle_content_get_property (GObject *object,
case PROP_STATE:
g_value_set_uint (value, priv->state);
break;
+ case PROP_READY:
+ g_value_set_boolean (value, priv->ready);
+ break;
+ case PROP_CONTENT_NS:
+ g_value_set_string (value, priv->content_ns);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -189,6 +200,30 @@ gabble_jingle_content_set_property (GObject *object,
case PROP_TRANSPORT_NS:
g_free (priv->transport_ns);
priv->transport_ns = g_value_dup_string (value);
+
+ /* We can't switch transports. */
+ g_assert (priv->transport == NULL);
+
+ if (priv->transport_ns != NULL) {
+ GType transport_type = GPOINTER_TO_INT (
+ g_hash_table_lookup (self->conn->jingle_factory->transports,
+ priv->transport_ns));
+
+ g_assert (transport_type != 0);
+
+ priv->transport = g_object_new (transport_type,
+ "content", self, "transport-ns", priv->transport_ns, NULL);
+
+ g_signal_connect (priv->transport, "new-candidates",
+ (GCallback) new_transport_candidates_cb, self);
+
+ }
+ break;
+ case PROP_NAME:
+ /* can't rename */
+ g_assert (priv->name == NULL);
+
+ priv->name = g_value_dup_string (value);
break;
case PROP_SENDERS:
priv->senders = g_value_get_uint (value);
@@ -196,6 +231,22 @@ gabble_jingle_content_set_property (GObject *object,
case PROP_STATE:
priv->state = g_value_get_uint (value);
break;
+ case PROP_READY:
+ DEBUG ("setting content ready from %u to %u",
+ priv->ready, g_value_get_boolean (value));
+
+ if (priv->ready == g_value_get_boolean (value))
+ {
+ DEBUG ("we're already ready, doing nothing");
+ return;
+ }
+
+ priv->ready = g_value_get_boolean (value);
+
+ /* if we have pending local candidates, now's the time
+ * to transmit them */
+ gabble_jingle_transport_iface_retransmit_candidates (priv->transport);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -237,7 +288,8 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
param_spec = g_param_spec_string ("name", "Content name",
"A unique content name in the session.",
NULL,
- G_PARAM_READABLE |
+ 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);
@@ -245,7 +297,6 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
param_spec = g_param_spec_string ("content-ns", "Content namespace",
"Namespace identifying the content type.",
NULL,
- G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB);
@@ -278,6 +329,15 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
G_PARAM_STATIC_BLURB);
g_object_class_install_property (object_class, PROP_STATE, param_spec);
+ param_spec = g_param_spec_boolean ("ready", "Ready?",
+ "A boolean signifying whether media for "
+ "this content is ready to be signalled.",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_READY, param_spec);
+
/* signal definitions */
signals[READY] =
@@ -288,6 +348,15 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
+
+ signals[NEW_CANDIDATES] = g_signal_new (
+ "new-candidates",
+ G_TYPE_FROM_CLASS (cls),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
+
}
#define SET_BAD_REQ(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST, txt)
@@ -295,6 +364,16 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
#define SET_CONFLICT(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_CONFLICT, txt)
static void
+new_transport_candidates_cb (GabbleJingleTransportIface *trans,
+ GList *candidates, GabbleJingleContent *content)
+{
+ DEBUG ("JingleContent %p: passing the signal on", content);
+
+ /* just pass the signal on */
+ g_signal_emit (content, signals[NEW_CANDIDATES], 0, candidates);
+}
+
+static void
parse_description (GabbleJingleContent *c, LmMessageNode *desc_node,
GError **error)
{
@@ -314,6 +393,9 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
LmMessageNode *trans_node, *desc_node;
GType transport_type = 0;
GabbleJingleTransportIface *trans = NULL;
+ JingleDialect dialect;
+
+ g_object_get (c->session, "dialect", &dialect, NULL);
desc_node = lm_message_node_get_child (content_node, "description");
trans_node = lm_message_node_get_child (content_node, "transport");
@@ -338,17 +420,28 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
if (trans_node == NULL)
{
/* gtalk lj0.3 assumes google-p2p transport */
+ DEBUG ("detecting GTalk3 dialect");
+
+ dialect = JINGLE_DIALECT_GTALK3;
g_object_set (c->session, "dialect", JINGLE_DIALECT_GTALK3, NULL);
transport_type = GPOINTER_TO_INT (
g_hash_table_lookup (c->conn->jingle_factory->transports, ""));
+ priv->transport_ns = g_strdup ("");
}
}
-
-
- if ((trans_node == NULL) || (creator == NULL) || (name == NULL))
+ else
{
- SET_BAD_REQ ("missing required content attributes or elements");
- return;
+ /* senders weren't mandatory back then */
+ if (dialect == JINGLE_DIALECT_V015) {
+ DEBUG ("old gabble detected, settings senders = both");
+ senders = "both";
+ }
+
+ if ((trans_node == NULL) || (creator == NULL) || (name == NULL) || (senders == NULL))
+ {
+ SET_BAD_REQ ("missing required content attributes or elements");
+ return;
+ }
}
/* if we didn't set it to google-p2p implicitly already, detect it */
@@ -370,7 +463,6 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
}
priv->created_by_initiator = (!tp_strdiff (creator, "initiator"));
- DEBUG ("senders == %s", senders);
priv->senders = _string_to_enum (content_senders_table, senders);
if (priv->senders == JINGLE_CONTENT_SENDERS_NONE)
{
@@ -389,6 +481,9 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
"transport-ns", priv->transport_ns,
NULL);
+ g_signal_connect (trans, "new-candidates",
+ (GCallback) new_transport_candidates_cb, c);
+
/* FIXME: I think candidates can't be specified in content addition/session
* init, so disabling this:
gabble_jingle_transport_iface_parse_candidates (trans, trans_node, error);
@@ -413,6 +508,53 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
}
void
+gabble_jingle_content_parse_accept (GabbleJingleContent *c,
+ LmMessageNode *content_node, gboolean google_mode, GError **error)
+{
+ GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (c);
+ const gchar *senders;
+ LmMessageNode *trans_node, *desc_node;
+
+ desc_node = lm_message_node_get_child (content_node, "description");
+ trans_node = lm_message_node_get_child (content_node, "transport");
+ senders = lm_message_node_get_attribute (content_node, "senders");
+
+ if (google_mode)
+ {
+ DEBUG ("parsing content-accept in google mode");
+
+ if (senders == NULL)
+ senders = "both";
+
+ if (trans_node == NULL)
+ {
+ DEBUG ("no transport node, assuming GTalk3 dialect");
+ /* gtalk lj0.3 assumes google-p2p transport */
+ g_object_set (c->session, "dialect", JINGLE_DIALECT_GTALK3, NULL);
+ }
+ }
+
+ DEBUG ("changing senders from %s to %s", _enum_to_string(content_senders_table, priv->senders), senders);
+ priv->senders = _string_to_enum (content_senders_table, senders);
+ if (priv->senders == JINGLE_CONTENT_SENDERS_NONE)
+ {
+ SET_BAD_REQ ("invalid content senders");
+ return;
+ }
+
+ parse_description (c, desc_node, error);
+ if (*error)
+ return;
+
+ // FIXME: this overlaps with _update_senders, maybe merge?
+ g_object_notify ((GObject *) c, "senders");
+
+ // If all went well, it means the content is finally ackd
+ priv->state = JINGLE_CONTENT_STATE_ACKNOWLEDGED;
+ g_object_notify ((GObject *) c, "state");
+}
+
+void
gabble_jingle_content_produce_node (GabbleJingleContent *c,
LmMessageNode *parent, gboolean full)
{
@@ -443,7 +585,7 @@ gabble_jingle_content_produce_node (GabbleJingleContent *c,
content_node = lm_message_node_add_child (parent, "content", NULL);
lm_message_node_set_attributes (content_node,
- "creator", priv->creator,
+ "creator", priv->created_by_initiator ? "initiator" : "responder",
"name", priv->name,
"senders", _enum_to_string (content_senders_table, priv->senders),
NULL);
@@ -481,6 +623,15 @@ gabble_jingle_content_update_senders (GabbleJingleContent *c,
}
void
+gabble_jingle_content_parse_transport_info (GabbleJingleContent *self,
+ LmMessageNode *trans_node, GError **error)
+{
+ GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
+
+ gabble_jingle_transport_iface_parse_candidates (priv->transport, trans_node, error);
+}
+
+void
gabble_jingle_content_add_candidates (GabbleJingleContent *self, GList *li)
{
GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
@@ -489,45 +640,29 @@ gabble_jingle_content_add_candidates (GabbleJingleContent *self, GList *li)
}
gboolean
-gabble_jingle_content_is_ready (GabbleJingleContent *self)
+gabble_jingle_content_is_ready (GabbleJingleContent *self, gboolean for_acceptance)
{
GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
JingleTransportState state;
- if (!priv->has_local_codecs)
+ /* Content is ready for initiatiation the moment the media using it is ready
+ * (ie. has local codecs). But in order to accept it, it must also be
+ * connected. */
+
+ /* if local codecs are not set, we're definitely not ready */
+ if (!priv->ready)
return FALSE;
+ /* we are okay for signalling content-add/session-initiate */
+ if (!for_acceptance)
+ return TRUE;
+
g_object_get (priv->transport, "state", &state, NULL);
+ /* we are even okay for accepting content/session, hooray */
return (state == JINGLE_TRANSPORT_STATE_CONNECTED);
}
-/* FIXME: if local codecs are set multiple times, this will be emitted
- * multiple times - is that scenario possible at all? */
-static void
-maybe_emit_ready (GabbleJingleContent *self)
-{
- if (!gabble_jingle_content_is_ready (self))
- return;
-
- g_signal_emit (self, signals[READY], 0);
-}
-
-void
-gabble_jingle_content_set_local_codecs (GabbleJingleContent *self)
-{
- GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
- priv->has_local_codecs = TRUE;
-
- /* FIXME: actually this function should call appropriate subclass interface
- * method to set/push local codecs */
-
- maybe_emit_ready (self);
-}
-
-/* FIXME: ok, situation sucks because Content sets the transport state,
- * but also catches the change notification. we ideally want user to
- * be able to access transprot directly, not through content? */
void
gabble_jingle_content_set_transport_state (GabbleJingleContent *self,
JingleTransportState state)
@@ -535,6 +670,9 @@ gabble_jingle_content_set_transport_state (GabbleJingleContent *self,
GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
g_object_set (priv->transport, "state", state, NULL);
- maybe_emit_ready (self);
+
+ /* FIXME: refactor this to use _is_ready, if neccessary
+ if ((state == JINGLE_TRANSPORT_STATE_CONNECTED) && (priv->ready))
+ g_object_notify (G_OBJECT (self), "ready"); */
}
diff --git a/src/jingle-content.h b/src/jingle-content.h
index 8ec0d37..d65bade 100644
--- a/src/jingle-content.h
+++ b/src/jingle-content.h
@@ -28,6 +28,12 @@
G_BEGIN_DECLS
typedef enum {
+ JINGLE_MEDIA_TYPE_NONE = 0,
+ JINGLE_MEDIA_TYPE_AUDIO,
+ JINGLE_MEDIA_TYPE_VIDEO
+} JingleMediaType;
+
+typedef enum {
JINGLE_CONTENT_STATE_EMPTY = 0,
JINGLE_CONTENT_STATE_NEW,
JINGLE_CONTENT_STATE_SENT,
@@ -41,7 +47,7 @@ struct _JingleCandidate {
int generation;
JingleTransportProtocol protocol;
- int preference;
+ gdouble preference;
JingleCandidateType type;
gchar *username;
gchar *password;
@@ -92,10 +98,14 @@ void gabble_jingle_content_update_senders (GabbleJingleContent *c,
LmMessageNode *content_node, GError **error);
void gabble_jingle_content_produce_node (GabbleJingleContent *c,
LmMessageNode *parent, gboolean full);
+void gabble_jingle_content_parse_accept (GabbleJingleContent *c,
+ LmMessageNode *content_node, gboolean google_mode, GError **error);
+void gabble_jingle_content_parse_transport_info (GabbleJingleContent *self,
+ LmMessageNode *trans_node, GError **error);
void gabble_jingle_content_add_candidates (GabbleJingleContent *self, GList *li);
-gboolean gabble_jingle_content_is_ready (GabbleJingleContent *self);
-void gabble_jingle_content_set_local_codecs (GabbleJingleContent *content);
+gboolean gabble_jingle_content_is_ready (GabbleJingleContent *self, gboolean for_acceptance);
+void gabble_jingle_content_set_local_codecs (GabbleJingleContent *content, GList *li);
void gabble_jingle_content_set_transport_state (GabbleJingleContent *content,
JingleTransportState state);
diff --git a/src/jingle-factory.c b/src/jingle-factory.c
index 68c19f8..7bd8fc9 100644
--- a/src/jingle-factory.c
+++ b/src/jingle-factory.c
@@ -63,6 +63,7 @@ struct _GabbleJingleFactoryPrivate
{
GabbleConnection *conn;
LmMessageHandler *jingle_cb;
+ LmMessageHandler *jingle_info_cb;
GHashTable *sessions;
gboolean dispose_has_run;
@@ -80,6 +81,9 @@ static GabbleJingleSession *create_session (GabbleJingleFactory *fac,
static void session_terminated_cb (GabbleJingleSession *sess,
gboolean local_terminator, GabbleJingleFactory *fac);
+static void connection_status_changed_cb (GabbleConnection *conn,
+ guint status, guint reason, GabbleJingleFactory *self);
+
static void
gabble_jingle_factory_init (GabbleJingleFactory *obj)
{
@@ -103,6 +107,144 @@ gabble_jingle_factory_init (GabbleJingleFactory *obj)
priv->dispose_has_run = FALSE;
}
+/*
+ * jingle_info_cb
+ *
+ * Called by loudmouth when we get an incoming <iq>. This handler
+ * is concerned only with Jingle info queries.
+ */
+static LmHandlerResult
+jingle_info_cb (LmMessageHandler *handler,
+ LmConnection *lmconn,
+ LmMessage *message,
+ gpointer user_data)
+{
+ GabbleJingleFactory *fac = GABBLE_JINGLE_FACTORY (user_data);
+ GabbleJingleFactoryPrivate *priv = GABBLE_JINGLE_FACTORY_GET_PRIVATE (fac);
+ LmMessageSubType sub_type;
+ LmMessageNode *query_node, *node;
+
+ query_node = lm_message_node_get_child_with_namespace (message->node,
+ "query", NS_GOOGLE_JINGLE_INFO);
+
+ if (query_node == NULL)
+ return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+
+ sub_type = lm_message_get_sub_type (message);
+
+ if (sub_type == LM_MESSAGE_SUB_TYPE_ERROR)
+ {
+ GabbleXmppError xmpp_error = XMPP_ERROR_UNDEFINED_CONDITION;
+
+ node = lm_message_node_get_child (message->node, "error");
+ if (node != NULL)
+ {
+ xmpp_error = gabble_xmpp_error_from_node (node);
+ }
+
+ DEBUG ("jingle info error: %s", gabble_xmpp_error_string (xmpp_error));
+
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ }
+
+ if (sub_type != LM_MESSAGE_SUB_TYPE_RESULT &&
+ sub_type != LM_MESSAGE_SUB_TYPE_SET)
+ {
+ DEBUG ("jingle info: unexpected IQ type, ignoring");
+
+ return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+ }
+
+ if (fac->get_stun_from_jingle)
+ node = lm_message_node_get_child (query_node, "stun");
+ else
+ node = NULL;
+
+ if (node != NULL)
+ {
+ node = lm_message_node_get_child (node, "server");
+
+ if (node != NULL)
+ {
+ const gchar *server;
+ const gchar *port;
+
+ server = lm_message_node_get_attribute (node, "host");
+ port = lm_message_node_get_attribute (node, "udp");
+
+ if (server != NULL)
+ {
+ DEBUG ("jingle info: got stun server %s", server);
+ g_free (fac->stun_server);
+ fac->stun_server = g_strdup (server);
+ }
+
+ if (port != NULL)
+ {
+ DEBUG ("jingle info: got stun port %s", port);
+ fac->stun_port = atoi (port);
+ }
+ }
+ }
+
+ node = lm_message_node_get_child (query_node, "relay");
+
+ if (node != NULL)
+ {
+ node = lm_message_node_get_child (node, "token");
+
+ if (node != NULL)
+ {
+ const gchar *token;
+
+ token = lm_message_node_get_value (node);
+
+ if (token != NULL)
+ {
+ DEBUG ("jingle info: got relay token %s", token);
+ g_free (fac->relay_token);
+ fac->relay_token = g_strdup (token);
+ }
+ }
+ }
+
+ if (sub_type == LM_MESSAGE_SUB_TYPE_SET)
+ {
+ _gabble_connection_acknowledge_set_iq (priv->conn, message);
+ }
+
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static void
+jingle_info_send_request (GabbleJingleFactory *fac)
+{
+ GabbleJingleFactoryPrivate *priv = GABBLE_JINGLE_FACTORY_GET_PRIVATE (fac);
+ TpBaseConnection *base = (TpBaseConnection *) priv->conn;
+ LmMessage *msg;
+ LmMessageNode *node;
+ const gchar *jid;
+ GError *error = NULL;
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ jid = tp_handle_inspect (contact_handles, base->self_handle);
+ msg = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ,
+ LM_MESSAGE_SUB_TYPE_GET);
+
+ node = lm_message_node_add_child (msg->node, "query", NULL);
+ lm_message_node_set_attribute (node, "xmlns", NS_GOOGLE_JINGLE_INFO);
+
+ if (!_gabble_connection_send (priv->conn, msg, &error))
+ {
+ DEBUG ("jingle info send failed: %s\n", error->message);
+ g_error_free (error);
+ }
+
+ lm_message_unref (msg);
+}
+
+
static void
gabble_jingle_factory_dispose (GObject *object)
{
@@ -126,6 +268,11 @@ gabble_jingle_factory_dispose (GObject *object)
lm_connection_unregister_message_handler (priv->conn->lmconn,
priv->jingle_cb, LM_MESSAGE_TYPE_IQ);
+ lm_connection_unregister_message_handler (priv->conn->lmconn,
+ priv->jingle_info_cb, LM_MESSAGE_TYPE_IQ);
+
+ g_free (fac->stun_server);
+ g_free (fac->relay_token);
if (G_OBJECT_CLASS (gabble_jingle_factory_parent_class)->dispose)
G_OBJECT_CLASS (gabble_jingle_factory_parent_class)->dispose (object);
@@ -184,11 +331,9 @@ gabble_jingle_factory_constructor (GType type,
self = GABBLE_JINGLE_FACTORY (obj);
priv = GABBLE_JINGLE_FACTORY_GET_PRIVATE (self);
- priv->jingle_cb = lm_message_handler_new (jingle_cb, self, NULL);
- lm_connection_register_message_handler (priv->conn->lmconn,
- priv->jingle_cb, LM_MESSAGE_TYPE_IQ, LM_HANDLER_PRIORITY_FIRST);
-
- // FIXME: better way to do it?
+ // FIXME: why was this in _constructed in media factory?
+ g_signal_connect (priv->conn,
+ "status-changed", (GCallback) connection_status_changed_cb, self);
jingle_media_rtp_register (self);
jingle_transport_google_register (self);
@@ -224,6 +369,70 @@ gabble_jingle_factory_class_init (GabbleJingleFactoryClass *cls)
G_TYPE_NONE, 1, G_TYPE_POINTER);
}
+static void
+connection_status_changed_cb (GabbleConnection *conn,
+ guint status,
+ guint reason,
+ GabbleJingleFactory *self)
+{
+ GabbleJingleFactoryPrivate *priv = GABBLE_JINGLE_FACTORY_GET_PRIVATE (self);
+
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_CONNECTING:
+ g_assert (priv->conn != NULL);
+ g_assert (priv->conn->lmconn != NULL);
+
+ DEBUG ("adding callbacks");
+ g_assert (priv->jingle_cb == NULL);
+ g_assert (priv->jingle_info_cb == NULL);
+
+ priv->jingle_cb = lm_message_handler_new (jingle_cb,
+ self, NULL);
+ lm_connection_register_message_handler (priv->conn->lmconn,
+ priv->jingle_cb, LM_MESSAGE_TYPE_IQ,
+ LM_HANDLER_PRIORITY_NORMAL);
+
+ priv->jingle_info_cb = lm_message_handler_new (
+ jingle_info_cb, self, NULL);
+ lm_connection_register_message_handler (priv->conn->lmconn,
+ priv->jingle_info_cb, LM_MESSAGE_TYPE_IQ,
+ LM_HANDLER_PRIORITY_NORMAL);
+
+ break;
+
+ case TP_CONNECTION_STATUS_CONNECTED:
+ {
+ gchar *stun_server = NULL;
+ guint stun_port = 0;
+
+ g_object_get (priv->conn,
+ "stun-server", &stun_server,
+ "stun-port", &stun_port,
+ NULL);
+
+ if (stun_server == NULL)
+ {
+ self->get_stun_from_jingle = TRUE;
+ }
+ else
+ {
+ g_free (self->stun_server);
+ self->stun_server = stun_server;
+ self->stun_port = stun_port;
+ }
+
+ if (priv->conn->features &
+ GABBLE_CONNECTION_FEATURES_GOOGLE_JINGLE_INFO)
+ {
+ jingle_info_send_request (self);
+ }
+ }
+ break;
+ }
+}
+
+
static gboolean
sid_in_use (GabbleJingleFactory *factory, const gchar *sid)
{
@@ -316,6 +525,9 @@ jingle_cb (LmMessageHandler *handler,
g_signal_emit (self, signals[NEW_SESSION], 0, sess);
}
+ /* all went well, we can acknowledge the IQ */
+ _gabble_connection_acknowledge_set_iq (priv->conn, msg);
+
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
diff --git a/src/jingle-factory.h b/src/jingle-factory.h
index 352bb08..f948016 100644
--- a/src/jingle-factory.h
+++ b/src/jingle-factory.h
@@ -62,6 +62,7 @@ typedef enum {
JINGLE_ACTION_SESSION_INITIATE,
JINGLE_ACTION_SESSION_TERMINATE,
JINGLE_ACTION_TRANSPORT_INFO,
+ JINGLE_ACTION_TRANSPORT_ACCEPT,
} JingleAction;
typedef enum {
@@ -85,7 +86,6 @@ typedef enum {
typedef enum {
JINGLE_CANDIDATE_TYPE_LOCAL,
- JINGLE_CANDIDATE_TYPE_DERIVED,
JINGLE_CANDIDATE_TYPE_STUN,
JINGLE_CANDIDATE_TYPE_RELAY
} JingleCandidateType;
@@ -124,6 +124,11 @@ struct _GabbleJingleFactory {
GHashTable *content_types;
GHashTable *transports;
+ gboolean get_stun_from_jingle;
+ gchar *stun_server;
+ guint16 stun_port;
+ gchar *relay_token;
+
gpointer priv;
};
diff --git a/src/jingle-media-rtp.c b/src/jingle-media-rtp.c
index 24c2e79..9ba8311 100644
--- a/src/jingle-media-rtp.c
+++ b/src/jingle-media-rtp.c
@@ -56,17 +56,11 @@ static guint signals[LAST_SIGNAL] = {0};
/* properties */
enum
{
- PROP_MEDIA_TYPE,
+ PROP_MEDIA_TYPE = 1,
LAST_PROPERTY
};
typedef enum {
- JINGLE_MEDIA_TYPE_NONE = -1,
- JINGLE_MEDIA_TYPE_AUDIO = 0,
- JINGLE_MEDIA_TYPE_VIDEO
-} JingleMediaType;
-
-typedef enum {
JINGLE_MEDIA_PROFILE_RTP_AVP,
} JingleMediaProfile;
@@ -81,7 +75,7 @@ typedef struct _GabbleJingleMediaRtpPrivate GabbleJingleMediaRtpPrivate;
struct _GabbleJingleMediaRtpPrivate
{
GList *local_codecs;
- // GList *remote_codecs;
+ GList *remote_codecs;
JingleMediaType media_type;
gboolean dispose_has_run;
};
@@ -125,8 +119,8 @@ gabble_jingle_media_rtp_dispose (GObject *object)
DEBUG ("dispose called");
priv->dispose_has_run = TRUE;
- // _free_codecs (priv->remote_codecs);
- // priv->remote_codecs = NULL;
+ _free_codecs (priv->remote_codecs);
+ priv->remote_codecs = NULL;
_free_codecs (priv->local_codecs);
priv->local_codecs = NULL;
@@ -185,7 +179,7 @@ gabble_jingle_media_rtp_class_init (GabbleJingleMediaRtpClass *cls)
{
GObjectClass *object_class = G_OBJECT_CLASS (cls);
GabbleJingleContentClass *content_class = GABBLE_JINGLE_CONTENT_CLASS (cls);
- // GParamSpec *param_spec;
+ GParamSpec *param_spec;
g_type_class_add_private (cls, sizeof (GabbleJingleMediaRtpPrivate));
@@ -196,6 +190,13 @@ gabble_jingle_media_rtp_class_init (GabbleJingleMediaRtpClass *cls)
content_class->parse_description = parse_description;
content_class->produce_description = produce_description;
+ param_spec = g_param_spec_uint ("media-type", "RTP media type",
+ "Media type.",
+ JINGLE_MEDIA_TYPE_NONE, G_MAXUINT32, JINGLE_MEDIA_TYPE_NONE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_MEDIA_TYPE, param_spec);
+
/* signal definitions */
signals[REMOTE_CODECS] = g_signal_new ("remote-codecs",
@@ -307,7 +308,7 @@ parse_description (GabbleJingleContent *content,
p = g_new0 (JingleCodec, 1);
p->id = id;
- p->name = (gchar *) name;
+ p->name = g_strdup (name);
p->clockrate = clockrate;
p->channels = channels;
@@ -334,10 +335,11 @@ parse_description (GabbleJingleContent *content,
priv->media_type = mtype;
+ DEBUG ("emitting remote-codecs signal");
g_signal_emit (self, signals[REMOTE_CODECS], 0, codecs);
/* append them to the known remote codecs */
- // priv->remote_codecs = g_list_concat (priv->remote_codecs, codecs);
+ priv->remote_codecs = g_list_concat (priv->remote_codecs, codecs);
}
static void
@@ -427,16 +429,18 @@ produce_description (GabbleJingleContent *obj, LmMessageNode *content_node)
}
}
-/*
-static void
-content_iface_init (gpointer g_iface, gpointer iface_data)
+void
+jingle_media_rtp_set_local_codecs (GabbleJingleMediaRtp *self, GList *codecs)
{
- GabbleJingleContentClass *klass = (GabbleJingleContentClass *) g_iface;
+ GabbleJingleMediaRtpPrivate *priv =
+ GABBLE_JINGLE_MEDIA_RTP_GET_PRIVATE (self);
- klass->parse_description = parse_description;
- klass->produce_description = produce_node;
+ DEBUG ("adding new local codecs, yippie");
+
+ priv->local_codecs = g_list_concat (priv->local_codecs, codecs);
+
+ g_object_set (self, "ready", TRUE, NULL);
}
-*/
void
jingle_media_rtp_register (GabbleJingleFactory *factory)
@@ -460,3 +464,16 @@ jingle_media_rtp_register (GabbleJingleFactory *factory)
GABBLE_TYPE_JINGLE_MEDIA_RTP);
}
+/* We can't get remote codecs when they're signalled, because
+ * the signal is emitted immediately upon JingleContent creation,
+ * and parsing, which is before a corresponding MediaStream is
+ * created. */
+GList *
+gabble_jingle_media_rtp_get_remote_codecs (GabbleJingleMediaRtp *self)
+{
+ GabbleJingleMediaRtpPrivate *priv =
+ GABBLE_JINGLE_MEDIA_RTP_GET_PRIVATE (self);
+
+ return priv->remote_codecs;
+}
+
diff --git a/src/jingle-media-rtp.h b/src/jingle-media-rtp.h
index ec8d107..1b7b2d5 100644
--- a/src/jingle-media-rtp.h
+++ b/src/jingle-media-rtp.h
@@ -61,6 +61,9 @@ struct _GabbleJingleMediaRtp {
const gchar *gabble_jingle_media_rtp_parse (GabbleJingleMediaRtp *sess,
LmMessage *message, GError **error);
void jingle_media_rtp_register (GabbleJingleFactory *factory);
+void jingle_media_rtp_set_local_codecs (GabbleJingleMediaRtp *self,
+ GList *codecs);
+GList *gabble_jingle_media_rtp_get_remote_codecs (GabbleJingleMediaRtp *self);
#endif /* __JINGLE_MEDIA_RTP_H__ */
diff --git a/src/jingle-session.c b/src/jingle-session.c
index 92d8abe..606f376 100644
--- a/src/jingle-session.c
+++ b/src/jingle-session.c
@@ -1,21 +1,21 @@
- /*
- * gabble-jingle-session.c - Source for GabbleJingleSession
- * Copyright (C) 2008 Collabora Ltd.
- *
- * 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
- */
+/*
+ * gabble-jingle-session.c - Source for GabbleJingleSession
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * 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 "jingle-session.h"
@@ -35,8 +35,6 @@
#include "jingle-factory.h"
#include "jingle-content.h"
-#include "media-session.h"
-
G_DEFINE_TYPE(GabbleJingleSession, gabble_jingle_session, G_TYPE_OBJECT);
/* signal enum */
@@ -72,6 +70,7 @@ struct _GabbleJingleSessionPrivate
gchar *initiator;
gboolean local_initiator;
GHashTable *contents;
+ GHashTable *initial_contents;
JingleDialect dialect;
JingleState state;
gchar *sid;
@@ -97,6 +96,7 @@ static const gchar *action_table[] = {
"session-initiate",
"session-terminate",
"transport-info",
+ "transport-accept",
NULL
};
@@ -109,7 +109,8 @@ static JingleAction allowed_actions[6][8] = {
/* JS_STATE_PENDING_CREATED */
{ JINGLE_ACTION_SESSION_INITIATE, JINGLE_ACTION_UNKNOWN },
/* JS_STATE_PENDING_INITIATE_SENT */
- { JINGLE_ACTION_SESSION_TERMINATE, JINGLE_ACTION_UNKNOWN },
+ { JINGLE_ACTION_SESSION_TERMINATE, JINGLE_ACTION_SESSION_ACCEPT,
+ JINGLE_ACTION_TRANSPORT_INFO, JINGLE_ACTION_UNKNOWN },
/* JS_STATE_PENDING_INITIATED */
{ JINGLE_ACTION_SESSION_ACCEPT, JINGLE_ACTION_SESSION_TERMINATE,
JINGLE_ACTION_TRANSPORT_INFO,
@@ -136,7 +137,10 @@ gabble_jingle_session_init (GabbleJingleSession *obj)
priv->contents = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_object_unref);
+ priv->initial_contents = g_hash_table_new_full (g_str_hash,
+ g_str_equal, g_free, NULL);
+ priv->state = JS_STATE_PENDING_CREATED;
priv->locally_accepted = FALSE;
priv->dispose_has_run = FALSE;
}
@@ -155,13 +159,12 @@ gabble_jingle_session_dispose (GObject *object)
DEBUG ("dispose called");
priv->dispose_has_run = TRUE;
- // Note: dispose is called FROM unregister_session (because refcount
- // decreases when hash table releses the session object)
- // _jingle_factory_unregister_session (priv->conn->jingle_factory, priv->sid);
-
g_hash_table_destroy (priv->contents);
priv->contents = NULL;
+ g_hash_table_destroy (priv->initial_contents);
+ priv->initial_contents = NULL;
+
if (sess->peer)
{
tp_handle_unref (contact_repo, sess->peer);
@@ -234,7 +237,6 @@ gabble_jingle_session_set_property (GObject *object,
switch (property_id) {
case PROP_CONNECTION:
priv->conn = g_value_get_object (value);
- g_debug ("i am %p, priv->conn == %p", sess, priv->conn);
g_assert (priv->conn != NULL);
break;
case PROP_SESSION_ID:
@@ -253,6 +255,7 @@ gabble_jingle_session_set_property (GObject *object,
case PROP_PEER_RESOURCE:
g_free (priv->peer_resource);
priv->peer_resource = g_value_dup_string (value);
+ DEBUG ("setting peer resource to %s", priv->peer_resource);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -319,8 +322,7 @@ gabble_jingle_session_class_init (GabbleJingleSessionClass *cls)
"with whom this session communicates, "
"if applicable",
NULL,
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_WRITABLE |
+ G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB);
g_object_class_install_property (object_class, PROP_PEER_RESOURCE,
@@ -345,6 +347,11 @@ gabble_jingle_session_class_init (GabbleJingleSessionClass *cls)
/* signal definitions */
+ signals[NEW_CONTENT] = g_signal_new ("new-content",
+ G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
signals[TERMINATED] = g_signal_new ("terminated",
G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
0, NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN,
@@ -412,7 +419,10 @@ produce_action (JingleAction act, JingleDialect dialect)
case JINGLE_ACTION_SESSION_ACCEPT:
return "accept";
case JINGLE_ACTION_TRANSPORT_INFO:
- return "candidates";
+ if (dialect == JINGLE_DIALECT_GTALK3)
+ return "candidates";
+ else
+ break;
default:
return _enum_to_string (action_table, act);
}
@@ -436,6 +446,7 @@ action_is_allowed (JingleAction action, JingleState state)
}
static void set_state (GabbleJingleSession *sess, JingleState state);
+static GabbleJingleContent *_get_any_content (GabbleJingleSession *session);
#define SET_BAD_REQ(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST, txt)
#define SET_OUT_ORDER(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_JINGLE_OUT_OF_ORDER, txt)
@@ -466,7 +477,8 @@ _foreach_content (GabbleJingleSession *sess, LmMessageNode *node,
}
}
-static void content_ready_cb (GabbleJingleContent *c, GabbleJingleSession *sess);
+static void content_ready_cb (GabbleJingleContent *c,
+ GParamSpec *arg, GabbleJingleSession *sess);
static void
_each_content_add (GabbleJingleSession *sess, GabbleJingleContent *c,
@@ -526,18 +538,29 @@ _each_content_add (GabbleJingleSession *sess, GabbleJingleContent *c,
"content-ns", content_ns,
NULL);
- g_signal_connect (c, "ready",
+ g_signal_connect (c, "notify::ready",
(GCallback) content_ready_cb, sess);
- gabble_jingle_content_parse_add (c, content_node, FALSE, error);
+ gabble_jingle_content_parse_add (c, content_node,
+ ((priv->dialect == JINGLE_DIALECT_GTALK3) ||
+ (priv->dialect == JINGLE_DIALECT_GTALK4)), error);
+
if (*error)
{
g_object_unref (c);
return;
}
+ /* gtalk streams don't have name, so use whatever Content came up with */
+ if (name == NULL)
+ {
+ g_object_get (c, "name", &name, NULL);
+ }
+
/* This will override existing content if it exists. */
g_hash_table_replace (priv->contents, g_strdup (name), c);
+
+ g_signal_emit (sess, signals[NEW_CONTENT], 0, c);
}
static void
@@ -568,10 +591,9 @@ static void
_each_content_modify (GabbleJingleSession *sess, GabbleJingleContent *c,
LmMessageNode *content_node, GError **error)
{
- const gchar *name = lm_message_node_get_attribute (content_node, "name");
-
if (c == NULL)
{
+ const gchar *name = lm_message_node_get_attribute (content_node, "name");
SET_BAD_REQ ("content called \"%s\" doesn't exist", name);
return;
}
@@ -595,6 +617,32 @@ _each_content_replace (GabbleJingleSession *sess, GabbleJingleContent *c,
}
static void
+_each_content_accept (GabbleJingleSession *sess, GabbleJingleContent *c,
+ LmMessageNode *content_node ,GError **error)
+{
+ GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+
+ if (c == NULL)
+ {
+ const gchar *name = lm_message_node_get_attribute (content_node, "name");
+ SET_BAD_REQ ("content called \"%s\" doesn't exist", name);
+ return;
+ }
+
+ gabble_jingle_content_parse_accept (c, content_node,
+ (priv->dialect <= JINGLE_DIALECT_GTALK4), error);
+}
+
+static void
+_mark_each_initial_content (gpointer key, gpointer data, gpointer user_data)
+{
+ GabbleJingleSession *sess = GABBLE_JINGLE_SESSION (user_data);
+ GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+
+ g_hash_table_insert (priv->initial_contents, key, GUINT_TO_POINTER (TRUE));
+}
+
+static void
on_session_initiate (GabbleJingleSession *sess, LmMessageNode *node,
GError **error)
{
@@ -619,7 +667,14 @@ on_session_initiate (GabbleJingleSession *sess, LmMessageNode *node,
_foreach_content (sess, node, _each_content_add, error);
}
- set_state (sess, JS_STATE_PENDING_INITIATED);
+ if (*error == NULL)
+ {
+ /* mark all newly defined contents as initial contents */
+ g_hash_table_foreach (priv->initial_contents,
+ _mark_each_initial_content, sess);
+
+ set_state (sess, JS_STATE_PENDING_INITIATED);
+ }
}
static void
@@ -659,6 +714,32 @@ on_content_accept (GabbleJingleSession *sess, LmMessageNode *node,
}
static void
+on_session_accept (GabbleJingleSession *sess, LmMessageNode *node,
+ GError **error)
+{
+ GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+
+ DEBUG ("called");
+
+ /* handle single-content special case */
+ if ((priv->dialect == JINGLE_DIALECT_GTALK3) ||
+ (priv->dialect == JINGLE_DIALECT_GTALK4))
+ {
+ GabbleJingleContent *c = _get_any_content (sess);
+ _each_content_accept (sess, c, node, error);
+ }
+ else
+ {
+ _foreach_content (sess, node, _each_content_accept, error);
+ }
+
+ if (*error)
+ return;
+
+ set_state (sess, JS_STATE_ACTIVE);
+}
+
+static void
on_session_terminate (GabbleJingleSession *sess, LmMessageNode *node,
GError **error)
{
@@ -667,34 +748,85 @@ on_session_terminate (GabbleJingleSession *sess, LmMessageNode *node,
g_signal_emit (sess, signals[TERMINATED], 0, FALSE);
}
+static void
+on_transport_info (GabbleJingleSession *sess, LmMessageNode *node,
+ GError **error)
+{
+ GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+ GabbleJingleContent *c = NULL;
+
+ /* FIXME: we need to do dialect detection here!!! */
+
+ if (priv->dialect <= JINGLE_DIALECT_GTALK4)
+ {
+ /* GTalk has only one content anyways */
+ GList *cs = g_hash_table_get_values (priv->contents);
+
+ /* Check that's the case */
+ /* FIXME: report error to peer instead of assert */
+ g_assert (g_list_length (cs) == 1);
+
+ c = cs->data;
+
+ g_list_free (cs);
+
+ /* FIXME: here we need to take care if we receive <candidates>
+ * and we're in gtalk4 mode, to switch to gtalk3? */
+ /* GTalk4 includes "transport" subnode */
+ if (priv->dialect == JINGLE_DIALECT_GTALK4)
+ node = lm_message_node_get_child (node, "transport");
+ }
+ else
+ {
+ const gchar *name;
+
+ node = lm_message_node_get_child (node, "content");
+
+ name = lm_message_node_get_attribute (node, "name");
+
+ c = g_hash_table_lookup (priv->contents, name);
+
+ /* FIXME: report error to peer instead of assert */
+ g_assert (c != NULL);
+
+ /* we need transport child of content node */
+ node = lm_message_node_get_child (node, "transport");
+ }
+
+ gabble_jingle_content_parse_transport_info (c, node, error);
+}
+
+static void
+on_transport_accept (GabbleJingleSession *sess, LmMessageNode *node,
+ GError **error)
+{
+ DEBUG ("Ignoring 'transport-accept' action from peer");
+}
+
+
static HandlerFunc handlers[] = {
- on_content_accept,
- on_content_add,
- on_content_modify,
- on_content_remove,
- on_content_replace,
- NULL, /* jingle_on_session_accept */
+ on_content_accept,
+ on_content_add,
+ on_content_modify,
+ on_content_remove,
+ on_content_replace,
+ on_session_accept, /* jingle_on_session_accept */
NULL, /* jingle_on_session_info */
on_session_initiate,
on_session_terminate, /* jingle_on_session_terminate */
- NULL /* jingle_on_transport_info */
+ on_transport_info, /* jingle_on_transport_info */
+ on_transport_accept
};
static void
jingle_state_machine_dance (GabbleJingleSession *sess, JingleAction action,
LmMessageNode *node, GError **error)
-{
- GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
-
- if (!action_is_allowed (action, priv->state))
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_JINGLE_OUT_OF_ORDER,
- "action \"%s\" not allowed in current state",
- _enum_to_string (action_table, action));
- return;
- }
+ {
+ GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
- g_assert (handlers[action] != NULL);
+ /* parser should've checked this already */
+ g_assert (action_is_allowed (action, priv->state));
+ g_assert (handlers[action] != NULL);
handlers[action] (sess, node, error);
}
@@ -816,9 +948,11 @@ gabble_jingle_session_parse (GabbleJingleSession *sess, LmMessage *message, GErr
if (sess == NULL)
return sid;
- DEBUG("jingle action '%s' from '%s' in session '%s'", actxt, from, sid);
-
priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+
+ DEBUG("jingle action '%s' from '%s' in session '%s' dialect %u", actxt, from,
+ sid, dialect);
+
contact_repo = tp_base_connection_get_handles (
(TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
@@ -853,6 +987,43 @@ gabble_jingle_session_parse (GabbleJingleSession *sess, LmMessage *message, GErr
return sid;
}
+static gchar *
+get_jid_for_contact (GabbleJingleSession *session,
+ TpHandle handle)
+{
+ GabbleJingleSessionPrivate *priv;
+ TpBaseConnection *conn;
+ const gchar *base_jid;
+ TpHandle self;
+ TpHandleRepoIface *contact_handles;
+
+ g_assert (GABBLE_IS_JINGLE_SESSION (session));
+
+ priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (session);
+ conn = (TpBaseConnection *)priv->conn;
+ contact_handles = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+ self = conn->self_handle;
+
+ base_jid = tp_handle_inspect (contact_handles, handle);
+ g_assert (base_jid != NULL);
+
+ if (handle == self)
+ {
+ gchar *resource, *ret;
+ g_object_get (priv->conn, "resource", &resource, NULL);
+ g_assert (resource != NULL);
+ ret = g_strdup_printf ("%s/%s", base_jid, resource);
+ g_free (resource);
+ return ret;
+ }
+ else
+ {
+ g_assert (priv->peer_resource != NULL);
+ return g_strdup_printf ("%s/%s", base_jid, priv->peer_resource);
+ }
+}
+
LmMessage *
gabble_jingle_session_new_message (GabbleJingleSession *sess,
JingleAction action, LmMessageNode **sess_node)
@@ -863,6 +1034,19 @@ gabble_jingle_session_new_message (GabbleJingleSession *sess,
gchar *el = NULL, *ns = NULL;
gboolean gtalk_mode = FALSE;
+ DEBUG ("creating new message to peer: %s", priv->peer_jid);
+
+ /* possibly this is the first message in an outgoing session,
+ * meaning that we have to set up initiator */
+ if (priv->initiator == NULL) {
+ TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
+ priv->initiator = get_jid_for_contact (sess, conn->self_handle);
+ }
+ /* likewise ^^ */
+ if (priv->peer_jid == NULL) {
+ priv->peer_jid = get_jid_for_contact (sess, sess->peer);
+ }
+
msg = lm_message_new_with_sub_type (
priv->peer_jid,
LM_MESSAGE_TYPE_IQ,
@@ -905,12 +1089,21 @@ gabble_jingle_session_new_message (GabbleJingleSession *sess,
}
static void
-_try_session_accept_check (gpointer key, gpointer data, gpointer user_data)
+_check_content_for_acceptance (gpointer key, gpointer data, gpointer user_data)
{
GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (data);
gboolean *is_ready = (gboolean *) user_data;
- *is_ready = gabble_jingle_content_is_ready (c);
+ *is_ready = gabble_jingle_content_is_ready (c, TRUE);
+}
+
+static void
+_check_content_for_initiation (gpointer key, gpointer data, gpointer user_data)
+{
+ GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (data);
+ gboolean *is_ready = (gboolean *) user_data;
+
+ *is_ready = gabble_jingle_content_is_ready (c, FALSE);
}
static void
@@ -918,8 +1111,13 @@ _try_session_accept_fill (gpointer key, gpointer data, gpointer user_data)
{
GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (data);
LmMessageNode *sess_node = user_data;
+ JingleContentState state;
+
+ g_object_get (c, "state", &state, NULL);
- gabble_jingle_content_produce_node (c, sess_node, TRUE);
+ /* we only want to acknowledge newly added contents */
+ if (state == JINGLE_CONTENT_STATE_NEW)
+ gabble_jingle_content_produce_node (c, sess_node, TRUE);
}
static void
@@ -928,17 +1126,22 @@ try_session_accept (GabbleJingleSession *sess)
GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
LmMessage *msg;
LmMessageNode *sess_node;
+ gboolean content_ready = TRUE;
- /* FIXME: should check if we're in the state where accepting is possible */
+ g_assert (g_hash_table_size (priv->contents) > 0);
- gboolean content_ready = TRUE;
+ if (priv->state != JS_STATE_PENDING_INITIATED)
+ {
+ DEBUG ("session is in state %u, won't try to accept", priv->state);
+ return;
+ }
if (!priv->locally_accepted)
return;
- g_hash_table_foreach (priv->contents, _try_session_accept_check, &content_ready);
+ g_hash_table_foreach (priv->contents, _check_content_for_acceptance, &content_ready);
- if ((g_hash_table_size (priv->contents) == 0) || (content_ready == FALSE))
+ if (!content_ready)
return;
msg = gabble_jingle_session_new_message (sess, JINGLE_ACTION_SESSION_ACCEPT,
@@ -946,14 +1149,44 @@ try_session_accept (GabbleJingleSession *sess)
g_hash_table_foreach (priv->contents, _try_session_accept_fill, sess_node);
- DEBUG ("i'm really trying to accept this session!");
-
_gabble_connection_send (priv->conn, msg, NULL);
lm_message_unref (msg);
set_state (sess, JS_STATE_ACTIVE);
- /* FIXME: i'm certain i forgot something */
+}
+
+static void
+try_session_initiate (GabbleJingleSession *sess)
+{
+ GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+ LmMessage *msg;
+ LmMessageNode *sess_node;
+ gboolean content_ready = TRUE;
+
+ g_assert (g_hash_table_size (priv->contents) > 0);
+
+ if (priv->state != JS_STATE_PENDING_CREATED)
+ {
+ DEBUG ("session is in state %u, won't try to initiate", priv->state);
+ return;
+ }
+
+ g_hash_table_foreach (priv->contents, _check_content_for_initiation, &content_ready);
+
+ if (!content_ready)
+ return;
+
+ msg = gabble_jingle_session_new_message (sess, JINGLE_ACTION_SESSION_INITIATE,
+ &sess_node);
+
+ g_hash_table_foreach (priv->contents, _try_session_accept_fill, sess_node);
+
+ _gabble_connection_send (priv->conn, msg, NULL);
+
+ lm_message_unref (msg);
+
+ set_state (sess, JS_STATE_PENDING_INITIATE_SENT);
}
static void
@@ -962,6 +1195,7 @@ set_state (GabbleJingleSession *sess, JingleState state)
GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
priv->state = state;
+ DEBUG ("Setting state of JingleSession: %p (priv = %p), state = %u", sess, priv, priv->state);
g_object_notify (G_OBJECT (sess), "state");
}
@@ -978,14 +1212,24 @@ gabble_jingle_session_accept (GabbleJingleSession *sess)
void
gabble_jingle_session_terminate (GabbleJingleSession *sess)
{
- // GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+ GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
LmMessage *msg;
LmMessageNode *sess_node;
msg = gabble_jingle_session_new_message (sess, JINGLE_ACTION_SESSION_TERMINATE,
&sess_node);
- /* FIXME - unref and cleanup everything, change session state, etc */
+ _gabble_connection_send (priv->conn, msg, NULL);
+
+ lm_message_unref (msg);
+
+ /* NOTE: on "terminated", jingle factory and media channel will unref
+ * it, bringing refcount to 0, so dispose will be called, and it
+ * takes care of cleanup */
+
+ DEBUG ("we are terminating this session");
+ set_state (sess, JS_STATE_ENDED);
+ g_signal_emit (sess, signals[TERMINATED], 0, FALSE);
}
void
@@ -1012,18 +1256,22 @@ gabble_jingle_session_remove_content (GabbleJingleSession *sess,
g_hash_table_remove (priv->contents, content_name);
}
-gboolean
-gabble_jingle_session_add_content (GabbleJingleSession *sess, const gchar *name,
+GabbleJingleContent *
+gabble_jingle_session_add_content (GabbleJingleSession *sess, JingleMediaType mtype,
const gchar *content_ns, const gchar *transport_ns)
{
GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
GabbleJingleContent *c;
GType content_type;
+ gchar *name = NULL;
+ gint id = g_hash_table_size (priv->contents) + 1;
- if (g_hash_table_lookup (priv->contents, name) != NULL)
+ do
{
- return FALSE;
+ g_free (name);
+ name = g_strdup_printf ("stream%d", id++);
}
+ while (g_hash_table_lookup (priv->contents, name) != NULL);
content_type =
GPOINTER_TO_INT (g_hash_table_lookup (priv->conn->jingle_factory->content_types,
@@ -1034,21 +1282,40 @@ gabble_jingle_session_add_content (GabbleJingleSession *sess, const gchar *name,
c = g_object_new (content_type,
"connection", priv->conn,
"session", sess,
+ "media-type", mtype,
"content-ns", content_ns,
"transport-ns", transport_ns,
+ "name", name,
+ "senders", JINGLE_CONTENT_SENDERS_BOTH,
NULL);
+ g_signal_connect (c, "notify::ready",
+ (GCallback) content_ready_cb, sess);
+
g_hash_table_insert (priv->contents, g_strdup (name), c);
g_signal_emit (sess, signals[NEW_CONTENT], 0, c);
- if (!priv->local_initiator || (priv->state > JS_STATE_PENDING_CREATED))
+ /* FIXME BUG: for session acceptance, we should only consider
+ * contents with disposition != "session". we'll burn that bridge
+ * when we come to it. */
+
+ if (priv->dialect >= JINGLE_DIALECT_V026)
{
- /* FIXME: we should transmit "content-add" action to the peer. or
- * when it's ready? */
+ /* In 0.30 onwards, content-add is ok in PENDING state, so */
+ LmMessage *msg;
+ LmMessageNode *sess_node;
+
+ msg = gabble_jingle_session_new_message (sess, JINGLE_ACTION_SESSION_ACCEPT,
+ &sess_node);
+ gabble_jingle_content_produce_node (c, sess_node, TRUE);
+ _gabble_connection_send (priv->conn, msg, NULL);
+ lm_message_unref (msg);
+
+ g_object_set (c, "state", JINGLE_CONTENT_STATE_SENT, NULL);
}
- return TRUE;
+ return c;
}
void
@@ -1074,6 +1341,25 @@ gabble_jingle_session_change_direction (GabbleJingleSession *sess,
/* FIXME: send the message, mark the nodes as pending change, etc */
}
+/* Get any content. Either we're in google mode (so we only have one content
+ * anyways), or we just need any content type to figure out what use case
+ * we're in (media, ft, etc). */
+static GabbleJingleContent *
+_get_any_content (GabbleJingleSession *session)
+{
+ GabbleJingleContent *c;
+
+ GList *li = gabble_jingle_session_get_contents (session);
+
+ if (li == NULL)
+ return NULL;
+
+ c = li->data;
+ g_list_free (li);
+
+ return c;
+}
+
/* Note: if there are multiple content types, not guaranteed which one will
* be returned. Typically, the same GType will know how to handle related
* contents found in a session (e.g. media-rtp for audio/video), so that
@@ -1081,17 +1367,11 @@ gabble_jingle_session_change_direction (GabbleJingleSession *sess,
GType
gabble_jingle_session_get_content_type (GabbleJingleSession *sess)
{
- GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
- GabbleJingleContent *c;
-
- GList *li = g_hash_table_get_values (priv->contents);
+ GabbleJingleContent *c = _get_any_content (sess);
- if (li == NULL)
+ if (c == NULL)
return 0;
- c = li->data;
- g_list_free (li);
-
return G_OBJECT_TYPE (c);
}
@@ -1104,9 +1384,35 @@ gabble_jingle_session_get_contents (GabbleJingleSession *sess)
}
static void
-content_ready_cb (GabbleJingleContent *c, GabbleJingleSession *sess)
+content_ready_cb (GabbleJingleContent *c,
+ GParamSpec *arg, GabbleJingleSession *sess)
{
- try_session_accept (sess);
+ GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+ const gchar *name;
+
+ DEBUG ("called");
+
+ g_object_get (c, "name", &name, NULL);
+
+ if (g_hash_table_lookup (priv->initial_contents, name))
+ {
+ /* it's one of the contents from session-initiate */
+ if (priv->local_initiator)
+ {
+ try_session_initiate (sess);
+ }
+ else
+ {
+ try_session_accept (sess);
+ }
+ }
+ else
+ {
+ /* FIXME: should do analogous thing; if we are the content
+ * initiator (hm, maybe we should really remember that),
+ * and content is ready for acceptance, then accept it
+ * ... */
+ }
}
diff --git a/src/jingle-session.h b/src/jingle-session.h
index 6fcad38..a54cd1e 100644
--- a/src/jingle-session.h
+++ b/src/jingle-session.h
@@ -25,8 +25,26 @@
#include "types.h"
#include "jingle-factory.h"
+#include "jingle-content.h"
+
G_BEGIN_DECLS
+typedef enum
+{
+ MODE_GOOGLE,
+ MODE_JINGLE
+} GabbleMediaSessionMode;
+
+typedef enum {
+ JS_STATE_INVALID = -1,
+ JS_STATE_PENDING_CREATED = 0,
+ JS_STATE_PENDING_INITIATE_SENT,
+ JS_STATE_PENDING_INITIATED,
+ JS_STATE_PENDING_ACCEPT_SENT,
+ JS_STATE_ACTIVE,
+ JS_STATE_ENDED
+} JingleSessionState;
+
typedef struct _GabbleJingleSessionClass GabbleJingleSessionClass;
GType gabble_jingle_session_get_type (void);
@@ -74,8 +92,8 @@ void gabble_jingle_session_remove_content (GabbleJingleSession *sess,
void gabble_jingle_session_change_direction (GabbleJingleSession *sess,
const gchar *content_name, JingleContentSenders senders);
-gboolean
-gabble_jingle_session_add_content (GabbleJingleSession *sess, const gchar *name,
+GabbleJingleContent *
+gabble_jingle_session_add_content (GabbleJingleSession *sess, JingleMediaType mtype,
const gchar *content_ns, const gchar *transport_ns);
GType gabble_jingle_session_get_content_type (GabbleJingleSession *);
diff --git a/src/jingle-transport-google.c b/src/jingle-transport-google.c
index 202fb57..5d21a45 100644
--- a/src/jingle-transport-google.c
+++ b/src/jingle-transport-google.c
@@ -71,6 +71,12 @@ struct _GabbleJingleTransportGooglePrivate
gchar *transport_ns;
GList *local_candidates;
+
+ /* A pointer into "local_candidates" list to mark the
+ * candidates that are still not transmitted, or NULL
+ * if all of them are transmitted. */
+
+ GList *pending_candidates;
// GList *remote_candidates;
gboolean dispose_has_run;
};
@@ -236,7 +242,7 @@ gabble_jingle_transport_google_class_init (GabbleJingleTransportGoogleClass *cls
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
- g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_POINTER);
+ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
}
@@ -248,22 +254,19 @@ static void
parse_candidates (GabbleJingleTransportIface *obj,
LmMessageNode *transport_node, GError **error)
{
- GabbleJingleTransportGoogle *t = GABBLE_JINGLE_TRANSPORT_GOOGLE (obj);
- GabbleJingleTransportGooglePrivate *priv =
- GABBLE_JINGLE_TRANSPORT_GOOGLE_GET_PRIVATE (t);
+// GabbleJingleTransportGoogle *t = GABBLE_JINGLE_TRANSPORT_GOOGLE (obj);
+// GabbleJingleTransportGooglePrivate *priv =
+// GABBLE_JINGLE_TRANSPORT_GOOGLE_GET_PRIVATE (t);
GList *candidates = NULL;
+ LmMessageNode *node;
- LmMessageNode *cnode, *node;
-
- /* special-case for GTalk libjingle0.3 */
- cnode = lm_message_node_get_child (transport_node, "candidate");
+ DEBUG ("called");
- if (cnode == NULL)
+#if 0
+ if (!tp_strdiff (transport_node->name, "candidate"))
{
JingleDialect dialect;
- cnode = transport_node;
-
g_object_get (priv->content->session, "dialect", &dialect, NULL);
if (dialect == JINGLE_DIALECT_GTALK4)
@@ -276,8 +279,9 @@ parse_candidates (GabbleJingleTransportIface *obj,
transmit_candidates (t, priv->local_candidates);
}
}
+#endif
- for (node = cnode->children; node; node = node->next)
+ for (node = transport_node->children; node; node = node->next)
{
const gchar *name, *address, *user, *pass, *str;
guint port, net, gen;
@@ -286,6 +290,8 @@ parse_candidates (GabbleJingleTransportIface *obj,
JingleCandidateType ctype;
JingleCandidate *c;
+ DEBUG ("Parsing node %s", node->name);
+
if (tp_strdiff (node->name, "candidate"))
continue;
@@ -296,6 +302,7 @@ parse_candidates (GabbleJingleTransportIface *obj,
address = lm_message_node_get_attribute (node, "address");
if (address == NULL)
break;
+ DEBUG ("AAA");
str = lm_message_node_get_attribute (node, "port");
if (str == NULL)
@@ -330,6 +337,7 @@ parse_candidates (GabbleJingleTransportIface *obj,
else
{
/* unknown protocol */
+ DEBUG ("unknown protocol: %s", str);
break;
}
@@ -349,7 +357,7 @@ parse_candidates (GabbleJingleTransportIface *obj,
}
else if (!tp_strdiff (str, "stun"))
{
- ctype = JINGLE_CANDIDATE_TYPE_DERIVED;
+ ctype = JINGLE_CANDIDATE_TYPE_STUN;
}
else if (!tp_strdiff (str, "relay"))
{
@@ -358,9 +366,11 @@ parse_candidates (GabbleJingleTransportIface *obj,
else
{
/* unknown candidate type */
+ DEBUG ("unknown candidate type: %s", str);
break;
}
+ DEBUG ("XXX");
user = lm_message_node_get_attribute (node, "username");
if (user == NULL)
break;
@@ -373,6 +383,7 @@ parse_candidates (GabbleJingleTransportIface *obj,
if (str == NULL)
break;
net = atoi (str);
+ DEBUG ("YYY");
str = lm_message_node_get_attribute (node, "generation");
if (str == NULL)
@@ -391,11 +402,13 @@ parse_candidates (GabbleJingleTransportIface *obj,
c->network = net;
c->generation = gen;
+ DEBUG ("all well, adding candidate %s:%d!", c->address, c->port);
candidates = g_list_append (candidates, c);
}
if (node != NULL)
{
+ DEBUG ("not all nodes were processed, reporting error");
/* rollback these */
while (candidates != NULL)
{
@@ -409,7 +422,9 @@ parse_candidates (GabbleJingleTransportIface *obj,
return;
}
- g_signal_emit_by_name (priv->content, "remote-candidates", candidates);
+ DEBUG ("emitting %d new remote candidates", g_list_length (candidates));
+
+ g_signal_emit (obj, signals[NEW_CANDIDATES], 0, candidates);
/* append them to the known remote candidates */
@@ -437,11 +452,27 @@ transmit_candidates (GabbleJingleTransportGoogle *transport, GList *candidates)
{
trans_node = sess_node;
}
- else
+ else if (dialect == JINGLE_DIALECT_GTALK4)
{
trans_node = lm_message_node_add_child (sess_node, "transport", NULL);
lm_message_node_set_attribute (trans_node, "xmlns", NS_GOOGLE_TRANSPORT_P2P);
}
+ else
+ {
+ const gchar *cname, *cns;
+
+ g_object_get (GABBLE_JINGLE_CONTENT (priv->content),
+ "name", &cname, "content-ns", &cns, NULL);
+
+ /* we need the <content> ... */
+ trans_node = lm_message_node_add_child (sess_node, "content", NULL);
+ lm_message_node_set_attribute (trans_node, "xmlns", cns);
+ lm_message_node_set_attribute (trans_node, "name", cname);
+
+ /* .. and the <transport> node */
+ trans_node = lm_message_node_add_child (trans_node, "transport", NULL);
+ lm_message_node_set_attribute (trans_node, "xmlns", priv->transport_ns);
+ }
for (li = candidates; li; li = li->next)
{
@@ -450,14 +481,14 @@ transmit_candidates (GabbleJingleTransportGoogle *transport, GList *candidates)
LmMessageNode *cnode;
sprintf (port_str, "%d", c->port);
- sprintf (pref_str, "%d", c->preference);
+ sprintf (pref_str, "%lf", c->preference);
switch (c->type) {
case JINGLE_CANDIDATE_TYPE_LOCAL:
type_str = "local";
break;
- case JINGLE_CANDIDATE_TYPE_DERIVED:
- type_str = "derived";
+ case JINGLE_CANDIDATE_TYPE_STUN:
+ type_str = "stun";
break;
case JINGLE_CANDIDATE_TYPE_RELAY:
type_str = "relay";
@@ -488,6 +519,7 @@ transmit_candidates (GabbleJingleTransportGoogle *transport, GList *candidates)
"password", c->password,
"preference", pref_str,
"protocol", proto_str,
+ "type", type_str,
"name", "rtp",
"network", "0",
@@ -505,14 +537,50 @@ add_candidates (GabbleJingleTransportIface *obj, GList *new_candidates)
GABBLE_JINGLE_TRANSPORT_GOOGLE (obj);
GabbleJingleTransportGooglePrivate *priv =
GABBLE_JINGLE_TRANSPORT_GOOGLE_GET_PRIVATE (transport);
-
- transmit_candidates (transport, new_candidates);
+ gboolean ready;
+
+ g_object_get (priv->content, "ready", &ready, NULL);
+
+ if (ready) {
+ DEBUG ("content ready, transmitting new candidates");
+ transmit_candidates (transport, new_candidates);
+ priv->pending_candidates = NULL;
+ } else {
+ DEBUG ("content not ready, not transmitting candidates");
+
+ /* if we already have pending candidates, the new ones will
+ * be in the local_candidates list after them. but these
+ * are the first pending ones, we must mark them. */
+ if (priv->pending_candidates == NULL)
+ priv->pending_candidates = new_candidates;
+ }
priv->local_candidates = g_list_concat (priv->local_candidates,
new_candidates);
}
static void
+retransmit_candidates (GabbleJingleTransportIface *obj)
+{
+ GabbleJingleTransportGoogle *transport =
+ GABBLE_JINGLE_TRANSPORT_GOOGLE (obj);
+ GabbleJingleTransportGooglePrivate *priv =
+ GABBLE_JINGLE_TRANSPORT_GOOGLE_GET_PRIVATE (transport);
+ gboolean ready;
+
+ g_object_get (priv->content, "ready", &ready, NULL);
+
+ g_assert (ready);
+
+ /* now transmit all pending candidates */
+ if (priv->pending_candidates != NULL) {
+ transmit_candidates (transport, priv->pending_candidates);
+ priv->pending_candidates = NULL;
+ }
+}
+
+
+static void
transport_iface_init (gpointer g_iface, gpointer iface_data)
{
GabbleJingleTransportIfaceClass *klass = (GabbleJingleTransportIfaceClass *) g_iface;
@@ -520,6 +588,7 @@ transport_iface_init (gpointer g_iface, gpointer iface_data)
klass->parse_candidates = parse_candidates;
// FIXME: klass->produce = produce_candidates;
klass->add_candidates = add_candidates;
+ klass->retransmit_candidates = retransmit_candidates;
}
void
diff --git a/src/jingle-transport-iface.c b/src/jingle-transport-iface.c
index 1388f6f..852c530 100644
--- a/src/jingle-transport-iface.c
+++ b/src/jingle-transport-iface.c
@@ -60,6 +60,16 @@ gabble_jingle_transport_iface_add_candidates (GabbleJingleTransportIface *self,
virtual_method (self, candidates);
}
+void
+gabble_jingle_transport_iface_retransmit_candidates (GabbleJingleTransportIface *self)
+{
+ void (*virtual_method)(GabbleJingleTransportIface *) =
+ GABBLE_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->retransmit_candidates;
+
+ g_assert (virtual_method != NULL);
+ virtual_method (self);
+}
+
static void
gabble_jingle_transport_iface_base_init (gpointer klass)
diff --git a/src/jingle-transport-iface.h b/src/jingle-transport-iface.h
index 440a54f..e2675d0 100644
--- a/src/jingle-transport-iface.h
+++ b/src/jingle-transport-iface.h
@@ -47,6 +47,7 @@ struct _GabbleJingleTransportIfaceClass {
void (*add_candidates) (GabbleJingleTransportIface *,
GList *);
+ void (*retransmit_candidates) (GabbleJingleTransportIface *);
};
GType gabble_jingle_transport_iface_get_type (void);
@@ -66,6 +67,7 @@ void gabble_jingle_transport_iface_parse_candidates (GabbleJingleTransportIface
LmMessageNode *, GError **);
void gabble_jingle_transport_iface_produce (GabbleJingleTransportIface *, LmMessageNode *);
void gabble_jingle_transport_iface_add_candidates (GabbleJingleTransportIface *, GList *);
+void gabble_jingle_transport_iface_retransmit_candidates (GabbleJingleTransportIface *);
G_END_DECLS
diff --git a/src/media-channel.c b/src/media-channel.c
index c9d96e3..432252c 100644
--- a/src/media-channel.c
+++ b/src/media-channel.c
@@ -40,8 +40,6 @@
#include "debug.h"
#include "exportable-channel.h"
#include "media-factory.h"
-#include "media-session.h"
-#include "media-session.h"
#include "media-stream.h"
#include "presence-cache.h"
#include "presence.h"
@@ -50,6 +48,9 @@
#include "jingle-session.h"
#include "jingle-content.h"
#include "jingle-media-rtp.h"
+#include "namespaces.h"
+
+#define MAX_STREAMS 99
static void call_state_iface_init (gpointer, gpointer);
static void channel_iface_init (gpointer, gpointer);
@@ -177,144 +178,60 @@ gabble_media_channel_init (GabbleMediaChannel *self)
static void session_state_changed_cb (GabbleJingleSession *session,
GParamSpec *arg1, GabbleMediaChannel *channel);
-static void session_stream_added_cb (GabbleJingleSession *session,
- GabbleMediaStream *stream, GabbleMediaChannel *chan);
static void session_terminated_cb (GabbleJingleSession *session,
gboolean local_terminator, gpointer user_data);
+static void session_new_content_cb (GabbleJingleSession *session,
+ GabbleJingleContent *c, gpointer user_data);
+static GabbleMediaStream *
+create_stream_from_content (GabbleMediaChannel *chan, GabbleJingleContent *c);
+static gboolean contact_is_media_capable (GabbleMediaChannel *chan, TpHandle peer);
-
-static void
-_ensure_session (GabbleMediaChannel *chan,
- TpHandle peer, const gchar *peer_resource, GError **error)
+static gboolean
+_create_streams (GabbleMediaChannel *chan)
{
GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+ GList *contents, *li;
- if (priv->session != NULL)
+ contents = gabble_jingle_session_get_contents (priv->session);
+ for (li = contents; li; li = li->next)
{
- TpIntSet *set;
- GList *contents, *li;
-
- DEBUG ("%p: Latching onto incoming session %p", chan, priv->session);
-
- /* make us local pending */
- set = tp_intset_new ();
- tp_intset_add (set, ((TpBaseConnection *) priv->conn)->self_handle);
-
- tp_group_mixin_change_members (G_OBJECT (chan),
- "", NULL, NULL, set, NULL, priv->session->peer, 0);
-
- tp_intset_destroy (set);
+ create_stream_from_content (chan, GABBLE_JINGLE_CONTENT (li->data));
+ }
- /* and update flags accordingly */
- tp_group_mixin_change_flags (G_OBJECT (chan),
- TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE,
- 0);
+ g_list_free (contents);
- priv->streams = g_ptr_array_sized_new (1);
+ return FALSE;
+}
- /* FIXME: put these in separate function and execute afterwards? */
- /* For each supported content in the session, create accompanying MediaStream. */
- contents = gabble_jingle_session_get_contents (priv->session);
- for (li = contents; li; li = li->next)
- {
- GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (li->data);
- if (G_OBJECT_TYPE (c) == GABBLE_TYPE_JINGLE_MEDIA_RTP)
- {
- GabbleMediaStream *stream;
- gchar *name;
- guint id = _gabble_media_channel_get_stream_id (chan);
- gchar *object_path = g_strdup_printf ("%s/MediaStream%u",
- priv->object_path, id);
-
- g_object_get (c, "name", &name, NULL);
-
- stream = g_object_new (GABBLE_TYPE_MEDIA_STREAM,
- "object-path", object_path,
- "content", c,
- "name", name,
- NULL);
-
- /* FIXME port functions to media chan from media session
- g_signal_connect (stream, "notify::connection-state",
- (GCallback) stream_connection_state_changed_cb,
- session);
- g_signal_connect (stream, "notify::got-local-codecs",
- (GCallback) stream_got_local_codecs_changed_cb,
- session);
- */
- g_ptr_array_add (priv->streams, stream);
-
- /* fugly hack, clean it up! */
- session_stream_added_cb (priv->session, stream, chan);
-
- /* all of the streams are bidirectional from farsight's point of view, it's
- * just in the signalling they change */
- tp_svc_media_session_handler_emit_new_stream_handler (chan,
- object_path, id, TP_MEDIA_STREAM_TYPE_AUDIO, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
-
- g_free (object_path);
-
- /* FIXME copyypasted from media session
- if (priv->ready) {
- // _emit_new_stream (session, stream);
- }A
- */
- }
- }
+static void
+create_session (GabbleMediaChannel *chan, TpHandle peer)
+{
+ GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
- g_list_free (contents);
- }
- else
- {
- GabblePresence *presence;
-#ifdef ENABLE_DEBUG
- TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
- TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (
- conn, TP_HANDLE_TYPE_CONTACT);
-#endif
+ g_assert (priv->session == NULL);
- DEBUG ("%p: Creating new outgoing session", chan);
+ DEBUG ("%p: Creating new outgoing session", chan);
- presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
+ priv->session = gabble_jingle_factory_create_session (
+ priv->conn->jingle_factory, peer, NULL);
- if (presence == NULL)
- {
- DEBUG ("failed to add contact %d (%s) to media channel: "
- "no presence available", peer,
- tp_handle_inspect (contact_handles, peer));
- goto NO_CAPS;
- }
+ g_signal_connect (priv->session, "notify::state",
+ (GCallback) session_state_changed_cb, chan);
- if ((_gabble_media_channel_caps_to_typeflags (presence->caps) &
- (TP_CHANNEL_MEDIA_CAPABILITY_AUDIO |
- TP_CHANNEL_MEDIA_CAPABILITY_VIDEO)) == 0)
- {
- DEBUG ("failed to add contact %d (%s) to media channel: "
- "caps %x aren't sufficient", peer,
- tp_handle_inspect (contact_handles, peer),
- presence->caps);
- goto NO_CAPS;
- }
+ g_signal_connect (priv->session, "new-content",
+ (GCallback) session_new_content_cb, chan);
- priv->session = gabble_jingle_factory_create_session (
- priv->conn->jingle_factory, peer, peer_resource);
+ g_signal_connect (priv->session, "terminated",
+ (GCallback) session_terminated_cb, chan);
- priv->streams = g_ptr_array_sized_new (1);
- }
+ g_assert (priv->streams == NULL);
- g_signal_connect (priv->session, "terminated",
- (GCallback) session_terminated_cb, chan);
+ priv->streams = g_ptr_array_sized_new (1);
- /* FIXME is it complete? */
tp_svc_channel_interface_media_signalling_emit_new_session_handler (
G_OBJECT (chan), priv->object_path, "rtp");
return;
-
-NO_CAPS:
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "handle %u has no media capabilities", peer);
- return;
}
static GObject *
@@ -327,6 +244,7 @@ gabble_media_channel_constructor (GType type, guint n_props,
DBusGConnection *bus;
TpIntSet *set;
TpHandleRepoIface *contact_handles;
+ GabbleJingleFactory *jf;
obj = G_OBJECT_CLASS (gabble_media_channel_parent_class)->
constructor (type, n_props, props);
@@ -364,185 +282,43 @@ gabble_media_channel_constructor (GType type, guint n_props,
tp_group_mixin_change_flags (obj,
TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
+ /* Set up Google relay related properties */
+ jf = priv->conn->jingle_factory;
+ if (jf->stun_server != NULL)
+ g_object_set (obj, "stun-server", jf->stun_server, NULL);
+ if (jf->stun_port != 0)
+ g_object_set (obj, "stun-port", jf->stun_port, NULL);
+ if (jf->relay_token != NULL)
+ g_object_set (obj, "gtalk-p2p-relay-token", jf->relay_token, NULL);
+
/* act on incoming session */
if (priv->session != NULL)
{
- _ensure_session (GABBLE_MEDIA_CHANNEL (obj), 0, NULL, NULL);
- }
-
- return obj;
-}
-
-
-/**
- * create_session
- *
- * Creates a GabbleJingleSession object for given peer.
- *
- * If sid is set to NULL a unique sid is generated and
- * the "initiator" property of the newly created
- * GabbleJingleSession is set to our own handle.
- */
-static GabbleJingleSession *
-create_session (GabbleMediaChannel *channel,
- TpHandle peer,
- const gchar *peer_resource,
- const gchar *sid,
- GError **error)
-{
- GabbleMediaChannelPrivate *priv;
- GabbleJingleSession *session;
- gchar *object_path;
- JingleInitiator initiator;
-
- g_assert (GABBLE_IS_MEDIA_CHANNEL (channel));
-
- priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (channel);
-
- g_assert (priv->session == NULL);
-
- object_path = g_strdup_printf ("%s/MediaSession%u", priv->object_path, peer);
-
- if (sid == NULL)
- {
- /* We are the initiator */
- GabblePresence *presence;
-#ifdef ENABLE_DEBUG
- TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
- TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (
- conn, TP_HANDLE_TYPE_CONTACT);
-#endif
-
- initiator = INITIATOR_LOCAL;
-
- presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
-
- if (presence == NULL)
- {
- DEBUG ("failed to add contact %d (%s) to media channel: "
- "no presence available", peer,
- tp_handle_inspect (contact_handles, peer));
- goto NO_CAPS;
- }
-
- if ((_gabble_media_channel_caps_to_typeflags (presence->caps) &
- (TP_CHANNEL_MEDIA_CAPABILITY_AUDIO |
- TP_CHANNEL_MEDIA_CAPABILITY_VIDEO)) == 0)
- {
- DEBUG ("failed to add contact %d (%s) to media channel: "
- "caps %x aren't sufficient", peer,
- tp_handle_inspect (contact_handles, peer),
- presence->caps);
- goto NO_CAPS;
- }
-
- // FIXME sid = _gabble_media_factory_allocate_sid (priv->factory, channel);
- sid = NULL;
- g_assert_not_reached ();
- }
- else
- {
- initiator = INITIATOR_REMOTE;
- g_assert_not_reached ();
- // FIXME _gabble_media_factory_register_sid (priv->factory, sid, channel);
- }
-
- session = g_object_new (GABBLE_TYPE_JINGLE_SESSION,
- "connection", priv->conn,
- "media-channel", channel,
- "object-path", object_path,
- "session-id", sid,
- "initiator", initiator,
- "peer", peer,
- "peer-resource", peer_resource,
- NULL);
-
- g_signal_connect (session, "notify::state",
- (GCallback) session_state_changed_cb, channel);
- g_signal_connect (session, "stream-added",
- (GCallback) session_stream_added_cb, channel);
- g_signal_connect (session, "terminated",
- (GCallback) session_terminated_cb, channel);
-
- priv->session = session;
-
- priv->streams = g_ptr_array_sized_new (1);
-
- tp_svc_channel_interface_media_signalling_emit_new_session_handler (
- channel, object_path, "rtp");
-
- g_free (object_path);
-
- return session;
-
-NO_CAPS:
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "handle %u has no media capabilities", peer);
- return NULL;
-}
-
-gboolean
-_gabble_media_channel_dispatch_session_action (GabbleMediaChannel *chan,
- TpHandle peer,
- const gchar *peer_resource,
- const gchar *sid,
- LmMessage *message,
- LmMessageNode *session_node,
- const gchar *action,
- GError **error)
-{
- GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
- GabbleJingleSession *session = priv->session;
- gboolean session_is_new = FALSE;
-
- /* If this assertion fails, create_session() would think we're the
- * initiator. However, GabbleMediaFactory checks this, so it can't fail */
- g_return_val_if_fail (sid != NULL, FALSE);
-
- if (session == NULL)
- {
- TpGroupMixin *mixin = TP_GROUP_MIXIN (chan);
- TpIntSet *set;
-
- session = create_session (chan, peer, peer_resource, sid, NULL);
- g_assert (session != NULL);
- session_is_new = TRUE;
+ DEBUG ("%p: Latching onto incoming session %p", obj, priv->session);
/* make us local pending */
set = tp_intset_new ();
- tp_intset_add (set, mixin->self_handle);
+ tp_intset_add (set, ((TpBaseConnection *) priv->conn)->self_handle);
- tp_group_mixin_change_members ((GObject *) chan,
- "", NULL, NULL, set, NULL, peer, 0);
+ tp_group_mixin_change_members (obj, "", NULL, NULL, set, NULL,
+ priv->session->peer, 0);
tp_intset_destroy (set);
/* and update flags accordingly */
- tp_group_mixin_change_flags ((GObject *) chan,
- TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE,
- 0);
- }
+ tp_group_mixin_change_flags (obj,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0);
- g_object_ref (session);
+ priv->streams = g_ptr_array_sized_new (1);
- /* TODO
- if (_gabble_media_session_handle_action (session, message, session_node,
- action, error))
- {
- g_object_unref (session);
- return TRUE;
- }
- else
- {
- if (session_is_new)
- _gabble_media_session_terminate (session, INITIATOR_LOCAL,
- TP_CHANNEL_GROUP_CHANGE_REASON_ERROR);
+ /* We want streams to appear on DBus after the channel is signalled */
+ g_idle_add ((GSourceFunc) _create_streams, GABBLE_MEDIA_CHANNEL (obj));
- g_object_unref (session);
- return FALSE;
- } */
+ tp_svc_channel_interface_media_signalling_emit_new_session_handler (
+ obj, priv->object_path, "rtp");
+ }
- return FALSE;
+ return obj;
}
static void
@@ -667,7 +443,21 @@ gabble_media_channel_set_property (GObject *object,
priv->factory = g_value_get_object (value);
break;
case PROP_SESSION:
+ g_assert (priv->session == NULL);
+
priv->session = g_value_dup_object (value);
+
+ if (priv->session)
+ {
+ g_signal_connect (priv->session, "notify::state",
+ (GCallback) session_state_changed_cb, chan);
+
+ g_signal_connect (priv->session, "new-content",
+ (GCallback) session_new_content_cb, chan);
+
+ g_signal_connect (priv->session, "terminated",
+ (GCallback) session_terminated_cb, chan);
+ }
break;
default:
param_name = g_param_spec_get_name (pspec);
@@ -936,9 +726,7 @@ gabble_media_channel_close (GabbleMediaChannel *self)
if (priv->session)
{
- /* TODO
- _gabble_media_session_terminate (priv->session, INITIATOR_LOCAL,
- TP_CHANNEL_GROUP_CHANGE_REASON_NONE); */
+ gabble_jingle_session_terminate (priv->session);
}
tp_svc_channel_emit_closed (self);
@@ -1012,7 +800,6 @@ gabble_media_channel_get_session_handlers (TpSvcChannelInterfaceMediaSignalling
{
GValue handler = { 0, };
TpHandle member;
- gchar *path;
g_value_init (&handler, info_type);
g_value_take_boxed (&handler,
@@ -1020,16 +807,13 @@ gabble_media_channel_get_session_handlers (TpSvcChannelInterfaceMediaSignalling
g_object_get (priv->session,
"peer", &member,
- "object-path", &path,
NULL);
dbus_g_type_struct_set (&handler,
- 0, path,
+ 0, priv->object_path,
1, "rtp",
G_MAXUINT);
- g_free (path);
-
ret = g_ptr_array_sized_new (1);
g_ptr_array_add (ret, g_value_get_boxed (&handler));
}
@@ -1210,7 +994,14 @@ gabble_media_channel_remove_streams (TpSvcChannelTypeStreamedMedia *iface,
/* TODO
if (stream_objs->len > 0)
_gabble_media_session_remove_streams (priv->session, (GabbleMediaStream **)
- stream_objs->pdata, stream_objs->len); */
+ stream_objs->pdata, stream_objs->len);
+
+ For each stream, call gabble_media_session_remove_content (stream->priv->content),
+ unref the stream, and remove it from the list. When removing the last content,
+ this should force jinglesession to terminate
+
+
+ */
OUT:
g_ptr_array_free (stream_objs, TRUE);
@@ -1284,6 +1075,312 @@ gabble_media_channel_request_stream_direction (TpSvcChannelTypeStreamedMedia *if
} */
}
+static const gchar *
+_pick_best_content_type (GabbleMediaChannel *chan, TpHandle peer,
+ const gchar *resource, JingleMediaType type)
+{
+ GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+ GabblePresence *presence;
+
+ presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
+
+ if (gabble_presence_resource_has_caps (presence, resource,
+ PRESENCE_CAP_JINGLE_RTP_TMP))
+ {
+ return NS_JINGLE_RTP_TMP;
+ }
+
+ if ((type == JINGLE_MEDIA_TYPE_VIDEO) &&
+ gabble_presence_resource_has_caps (presence, resource,
+ PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO))
+ {
+ return NS_JINGLE_DESCRIPTION_VIDEO;
+ }
+
+ if ((type == JINGLE_MEDIA_TYPE_AUDIO) &&
+ gabble_presence_resource_has_caps (presence, resource,
+ PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO))
+ {
+ return NS_JINGLE_DESCRIPTION_VIDEO;
+ }
+ if ((type == JINGLE_MEDIA_TYPE_AUDIO) &&
+ gabble_presence_resource_has_caps (presence, resource,
+ PRESENCE_CAP_GOOGLE_VOICE))
+ {
+ return NS_GOOGLE_SESSION_PHONE;
+ }
+
+ return NULL;
+}
+
+
+static const gchar *
+_pick_best_resource (GabbleMediaChannel *chan,
+ TpHandle peer, gboolean want_audio, gboolean want_video,
+ const char **transport_ns, JingleDialect *dialect)
+{
+ GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+ GabblePresence *presence;
+ GabblePresenceCapabilities caps;
+ const gchar *resource = NULL;
+
+ presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
+
+#if 0
+ /* FORCE GTALK!!3 FIXME */
+ caps = PRESENCE_CAP_GOOGLE_VOICE;
+ resource = gabble_presence_pick_resource_by_caps (presence, caps);
+ g_assert (resource != NULL);
+ *content_ns = NS_GOOGLE_SESSION_PHONE;
+ *transport_ns = "";
+ *dialect = JINGLE_DIALECT_GTALK3;
+ return resource;
+#endif
+
+ *dialect = JINGLE_DIALECT_ERROR;
+ *transport_ns = NULL;
+
+ g_return_val_if_fail (want_audio || want_video, NULL);
+
+ /* Try newest Jingle standard */
+ caps = PRESENCE_CAP_JINGLE_RTP_TMP;
+ resource = gabble_presence_pick_resource_by_caps (presence, caps);
+
+ if (resource != NULL)
+ {
+ *dialect = JINGLE_DIALECT_V026;
+ goto CHOOSE_TRANSPORT;
+ }
+
+ /* Else try older Jingle draft, audio + video */
+ caps = PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO |
+ PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO;
+ resource = gabble_presence_pick_resource_by_caps (presence, caps);
+
+ if (resource != NULL)
+ {
+ *dialect = JINGLE_DIALECT_V015;
+ goto CHOOSE_TRANSPORT;
+ }
+
+ /* In this unlikely case, we can get by with just video */
+ if (!want_audio)
+ {
+ caps = PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO;
+ resource = gabble_presence_pick_resource_by_caps (presence, caps);
+
+ if (resource != NULL)
+ {
+ *dialect = JINGLE_DIALECT_V015;
+ goto CHOOSE_TRANSPORT;
+ }
+ }
+
+ /* Uh, huh, we can't provide what's requested. */
+ if (want_video)
+ return NULL;
+
+ /* Ok, try just older Jingle draft, audio */
+ caps = PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO;
+ resource = gabble_presence_pick_resource_by_caps (presence, caps);
+
+ if (resource != NULL)
+ {
+ *dialect = JINGLE_DIALECT_V015;
+ goto CHOOSE_TRANSPORT;
+ }
+
+ /* There is still hope, try GTalk */
+ caps = PRESENCE_CAP_GOOGLE_VOICE;
+ resource = gabble_presence_pick_resource_by_caps (presence, caps);
+
+ if (resource != NULL)
+ {
+ *dialect = JINGLE_DIALECT_GTALK4;
+ goto CHOOSE_TRANSPORT;
+ }
+
+ /* Nope, nothing we can do. */
+ return NULL;
+
+CHOOSE_TRANSPORT:
+ /* We prefer ICE, Google-P2P, then raw UDP */
+
+ if (gabble_presence_resource_has_caps (presence, resource,
+ PRESENCE_CAP_JINGLE_TRANSPORT_ICE))
+ {
+ *transport_ns = NS_JINGLE_TRANSPORT_ICE;
+ }
+ else if (gabble_presence_resource_has_caps (presence, resource,
+ PRESENCE_CAP_GOOGLE_TRANSPORT_P2P))
+ {
+ *transport_ns = NS_GOOGLE_TRANSPORT_P2P;
+ }
+ else if (gabble_presence_resource_has_caps (presence, resource,
+ PRESENCE_CAP_JINGLE_TRANSPORT_RAWUDP))
+ {
+ *transport_ns = NS_JINGLE_TRANSPORT_RAWUDP;
+ }
+ else if (*dialect == JINGLE_DIALECT_GTALK4)
+ {
+ /* (Some) GTalk clients don't advertise gtalk-p2p, though
+ * they support it. If we know it's GTalk and there's no
+ * transport, we can assume it also. */
+ *transport_ns = NS_GOOGLE_TRANSPORT_P2P;
+ }
+
+ if (*transport_ns == NULL)
+ return NULL;
+
+ return resource;
+}
+
+static gboolean
+_gabble_media_channel_request_streams (GabbleMediaChannel *chan,
+ const GArray *media_types,
+ GPtrArray **ret,
+ GError **error)
+{
+ GabbleMediaChannelPrivate *priv;
+ gboolean want_audio, want_video;
+ JingleDialect dialect;
+ guint idx;
+ TpHandle peer;
+ const gchar *peer_resource;
+ const gchar *transport_ns = NULL;
+
+ priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+
+ DEBUG ("called");
+
+ g_object_get (priv->session, "peer", &peer,
+ "peer-resource", &peer_resource, NULL);
+
+ if (!contact_is_media_capable (chan, peer))
+ {
+ DEBUG ("peer has no a/v capabilities");
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "member has no audio/video capabilities");
+
+ return FALSE;
+ }
+
+ want_audio = want_video = FALSE;
+
+ for (idx = 0; idx < media_types->len; idx++)
+ {
+ guint media_type = g_array_index (media_types, guint, idx);
+
+ if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
+ {
+ want_audio = TRUE;
+ }
+ else if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
+ {
+ want_video = TRUE;
+ }
+ else
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "given media type %u is invalid", media_type);
+ return FALSE;
+ }
+ }
+
+ g_object_get(priv->session, "dialect", &dialect, NULL);
+
+ /* existing call; the recipient and the mode has already been decided */
+ if (dialect != JINGLE_DIALECT_ERROR)
+ {
+ g_assert_not_reached ();
+
+ /* is a google call... we have no other option */
+ if (dialect <= JINGLE_DIALECT_GTALK4)
+ {
+ DEBUG ("already in Google mode; can't add new stream");
+
+ g_assert (priv->streams->len == 1);
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Google Talk calls may only contain one stream");
+
+ return FALSE;
+ }
+
+ DEBUG ("in Jingle mode, and have necessary caps");
+ }
+ /* no existing call; we should choose a recipient and a mode */
+ else
+ {
+ DEBUG ("picking the best resource (want audio: %u, want video: %u",
+ want_audio, want_video);
+
+ g_assert (priv->streams->len == 0);
+
+ peer_resource = _pick_best_resource (chan, peer, want_audio, want_video,
+ &transport_ns, &dialect);
+
+ if (peer_resource == NULL)
+ {
+ DEBUG ("contact doesn't have a resource with suitable capabilities");
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "member does not have the desired audio/video capabilities");
+
+ return FALSE;
+ }
+
+ DEBUG ("Picking resource '%s' (transport: %s, dialect: %u)",
+ peer_resource, transport_ns, dialect);
+
+ g_object_set (priv->session, "dialect", dialect,
+ "peer-resource", peer_resource, NULL);
+ }
+
+ /* check it's not a ridiculous number of streams */
+ if ((priv->streams->len + media_types->len) > MAX_STREAMS)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "I think that's quite enough streams already");
+ return FALSE;
+ }
+
+ /* if we've got here, we're good to make the streams */
+
+ *ret = g_ptr_array_sized_new (media_types->len);
+
+ for (idx = 0; idx < media_types->len; idx++)
+ {
+ guint media_type = g_array_index (media_types, guint, idx);
+ GabbleJingleContent *c;
+ GabbleMediaStream *stream;
+ const gchar *content_ns;
+
+ content_ns = _pick_best_content_type (chan, peer, peer_resource,
+ media_type == TP_MEDIA_STREAM_TYPE_AUDIO ?
+ JINGLE_MEDIA_TYPE_AUDIO : JINGLE_MEDIA_TYPE_VIDEO);
+
+ /* if we got this far, resource should be capable enough, so we
+ * should not fail in choosing ns */
+ g_assert (content_ns != NULL);
+
+ DEBUG ("Creating new jingle content with namespace %s", content_ns);
+
+ c = gabble_jingle_session_add_content (priv->session,
+ media_type == TP_MEDIA_STREAM_TYPE_AUDIO ?
+ JINGLE_MEDIA_TYPE_AUDIO : JINGLE_MEDIA_TYPE_VIDEO,
+ content_ns, transport_ns);
+
+ g_assert (c != NULL);
+
+ /* stream is created in "new-content" callback, and appended to streams */
+ stream = g_ptr_array_index (priv->streams, priv->streams->len - 1);
+ g_ptr_array_add (*ret, stream);
+ }
+
+ return TRUE;
+}
+
/**
* gabble_media_channel_request_streams
@@ -1319,13 +1416,7 @@ gabble_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface,
if (priv->session == NULL)
{
- _ensure_session (self, contact_handle, NULL, &error);
- if (error != NULL)
- {
- dbus_g_method_return_error (context, error);
- g_error_free (error);
- return;
- }
+ create_session (self, contact_handle);
}
else
{
@@ -1346,11 +1437,9 @@ gabble_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface,
g_assert (priv->session != NULL);
- /* TODO
- if (!_gabble_media_session_request_streams (priv->session, types, &streams,
+ if (!_gabble_media_channel_request_streams (self, types, &streams,
&error))
- goto error; */
- streams = NULL;
+ goto error;
ret = make_stream_list (self, streams);
@@ -1366,6 +1455,38 @@ error:
}
+static gboolean
+contact_is_media_capable (GabbleMediaChannel *chan, TpHandle peer)
+{
+ GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+ GabblePresence *presence;
+ TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (
+ conn, TP_HANDLE_TYPE_CONTACT);
+ GabblePresenceCapabilities caps;
+
+ caps = PRESENCE_CAP_GOOGLE_VOICE | PRESENCE_CAP_JINGLE_RTP_TMP |
+ PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO | PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO;
+
+ presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
+
+ if (presence == NULL)
+ {
+ DEBUG ("contact %d (%s) has no presence available", peer,
+ tp_handle_inspect (contact_handles, peer));
+ return FALSE;
+ }
+
+ if ((presence->caps & caps) == 0)
+ {
+ DEBUG ("contact %d (%s) doesn't have sufficient media caps", peer,
+ tp_handle_inspect (contact_handles, peer));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
gboolean
_gabble_media_channel_add_member (GObject *obj,
TpHandle handle,
@@ -1381,21 +1502,13 @@ _gabble_media_channel_add_member (GObject *obj,
{
TpIntSet *set;
- /* yes: invite the peer */
+ /* yes: check we don't have a peer already, invite this onis one */
- if (priv->session == NULL)
- {
- /* create a new session */
- if (create_session (chan, handle, NULL, NULL, error) == NULL)
- return FALSE;
- }
- else
+ if (priv->session != NULL)
{
TpHandle peer;
- g_object_get (priv->session,
- "peer", &peer,
- NULL);
+ g_object_get (priv->session, "peer", &peer, NULL);
if (peer != handle)
{
@@ -1406,6 +1519,14 @@ _gabble_media_channel_add_member (GObject *obj,
}
}
+ if (!contact_is_media_capable (chan, handle))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "handle %u cannot be added: has no media capabilities",
+ handle);
+ return FALSE;
+ }
+
/* make the peer remote pending */
set = tp_intset_new ();
tp_intset_add (set, handle);
@@ -1424,7 +1545,7 @@ _gabble_media_channel_add_member (GObject *obj,
else
{
/* no: has a session been created, is the handle being added ours,
- * and are we in local pending? */
+ * and are we in local pending? (call answer) */
if (priv->session &&
handle == mixin->self_handle &&
@@ -1488,9 +1609,8 @@ gabble_media_channel_remove_member (GObject *obj,
return FALSE;
}
- /* TODO
- _gabble_media_session_terminate (priv->session, INITIATOR_LOCAL,
- TP_CHANNEL_GROUP_CHANGE_REASON_NONE); */
+ // FIXME: initiator = LOCAL, change reason = NONE
+ gabble_jingle_session_terminate (priv->session);
/* remove the member */
set = tp_intset_new ();
@@ -1517,7 +1637,6 @@ session_terminated_cb (GabbleJingleSession *session,
TpGroupMixin *mixin = TP_GROUP_MIXIN (channel);
guint reason = TP_CHANNEL_GROUP_CHANGE_REASON_NONE;
guint terminator;
- gchar *sid;
JingleSessionState state;
TpHandle peer;
TpIntSet *set;
@@ -1546,11 +1665,6 @@ session_terminated_cb (GabbleJingleSession *session,
TP_CHANNEL_GROUP_FLAG_CAN_ADD,
TP_CHANNEL_GROUP_FLAG_CAN_REMOVE);
- /* free the session ID */
- g_object_get (priv->session, "session-id", &sid, NULL);
- // FIXME _gabble_media_factory_free_sid (priv->factory, sid);
- g_free (sid);
-
/* unref streams */
if (priv->streams != NULL)
{
@@ -1583,6 +1697,8 @@ session_state_changed_cb (GabbleJingleSession *session,
TpHandle peer;
TpIntSet *set;
+ DEBUG ("called");
+
g_object_get (session,
"state", &state,
"peer", &peer,
@@ -1610,6 +1726,9 @@ session_state_changed_cb (GabbleJingleSession *session,
if (state == JS_STATE_ACTIVE &&
priv->creator == mixin->self_handle)
{
+
+ DEBUG ("adding peer to the member list and updating flags");
+
/* add the peer to the member list */
tp_group_mixin_change_members ((GObject *) channel,
"", set, NULL, NULL, NULL, 0, 0);
@@ -1619,6 +1738,23 @@ session_state_changed_cb (GabbleJingleSession *session,
tp_group_mixin_change_flags ((GObject *) channel,
TP_CHANNEL_GROUP_FLAG_CAN_REMOVE,
TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_RESCIND);
+
+ }
+
+ if (state == JS_STATE_ACTIVE)
+ {
+ guint i;
+
+ /* set all streams as playing */
+ /* FIXME: all? */
+ for (i = 0; i < priv->streams->len; i++)
+ {
+ GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
+
+ g_object_set (stream, "playing", TRUE, NULL);
+ _gabble_media_stream_update_sending (stream, TRUE);
+
+ }
}
tp_intset_destroy (set);
@@ -1887,14 +2023,80 @@ stream_direction_changed_cb (GabbleMediaStream *stream,
chan, id, direction, pending_send);
}
-static void
-session_stream_added_cb (GabbleJingleSession *session,
- GabbleMediaStream *stream,
- GabbleMediaChannel *chan)
+#define GTALK_CAPS \
+ ( PRESENCE_CAP_GOOGLE_VOICE )
+
+#define JINGLE_CAPS \
+ ( PRESENCE_CAP_JINGLE \
+ | PRESENCE_CAP_GOOGLE_TRANSPORT_P2P )
+
+#define JINGLE_AUDIO_CAPS \
+ ( PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO )
+
+#define JINGLE_VIDEO_CAPS \
+ ( PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO )
+
+GabblePresenceCapabilities
+_gabble_media_channel_typeflags_to_caps (TpChannelMediaCapabilities flags)
+{
+ GabblePresenceCapabilities caps = 0;
+
+ /* currently we can only signal any (GTalk or Jingle calls) using
+ * the GTalk-P2P transport */
+ if (flags & TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P)
+ {
+ caps |= JINGLE_CAPS;
+
+ if (flags & TP_CHANNEL_MEDIA_CAPABILITY_AUDIO)
+ caps |= GTALK_CAPS | JINGLE_AUDIO_CAPS;
+
+ if (flags & TP_CHANNEL_MEDIA_CAPABILITY_VIDEO)
+ caps |= JINGLE_VIDEO_CAPS;
+ }
+
+ return caps;
+}
+
+static GabbleMediaStream *
+create_stream_from_content (GabbleMediaChannel *chan, GabbleJingleContent *c)
{
GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+ GabbleMediaStream *stream;
+ JingleMediaType type;
+ gchar *name;
+ guint id;
+ gchar *object_path;
+
+ g_object_get (c, "name", &name, NULL);
+
+ if (G_OBJECT_TYPE (c) != GABBLE_TYPE_JINGLE_MEDIA_RTP)
+ {
+ DEBUG ("ignoring non MediaRtp content '%s'", name);
+ return NULL;
+ }
+
+ /* This onelier replaces "get_channel_stream_id()" function */
+ id = priv->next_stream_id++;
+
+ object_path = g_strdup_printf ("%s/MediaStream%u",
+ priv->object_path, id);
+
+ stream = g_object_new (GABBLE_TYPE_MEDIA_STREAM,
+ "object-path", object_path,
+ "content", c,
+ "name", name,
+ NULL);
+
+ DEBUG ("created new MediaStream %p for content '%s'", stream, name);
- guint id, handle, type;
+ /* FIXME port functions to media chan from media session
+ g_signal_connect (stream, "notify::connection-state",
+ (GCallback) stream_connection_state_changed_cb,
+ session);
+ g_signal_connect (stream, "notify::got-local-codecs",
+ (GCallback) stream_got_local_codecs_changed_cb,
+ session);
+ */
/* keep track of the stream */
g_object_ref (stream);
@@ -1914,57 +2116,38 @@ session_stream_added_cb (GabbleJingleSession *session,
(GCallback) stream_hold_state_changed, chan);
/* emit StreamAdded */
- g_object_get (session, "peer", &handle, NULL);
- g_object_get (stream, "id", &id, "media-type", &type, NULL);
+ g_object_get (GABBLE_JINGLE_MEDIA_RTP (c), "media-type", &type, NULL);
- DEBUG ("emitting stream added");
tp_svc_channel_type_streamed_media_emit_stream_added (
- chan, id, handle, type);
+ chan, id, priv->session->peer,
+ type == JINGLE_MEDIA_TYPE_AUDIO ?
+ TP_MEDIA_STREAM_TYPE_AUDIO : TP_MEDIA_STREAM_TYPE_VIDEO);
/* A stream being added might cause the "total" hold state to change */
stream_hold_state_changed (stream, NULL, chan);
-}
-
-guint
-_gabble_media_channel_get_stream_id (GabbleMediaChannel *chan)
-{
- GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
-
- return priv->next_stream_id++;
-}
-#define GTALK_CAPS \
- ( PRESENCE_CAP_GOOGLE_VOICE )
-
-#define JINGLE_CAPS \
- ( PRESENCE_CAP_JINGLE \
- | PRESENCE_CAP_GOOGLE_TRANSPORT_P2P )
+ if (priv->ready)
+ {
+ /* all of the streams are bidirectional from farsight's point of view, it's
+ * just in the signalling they change */
+ tp_svc_media_session_handler_emit_new_stream_handler (chan,
+ object_path, id, TP_MEDIA_STREAM_TYPE_AUDIO, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
+ }
-#define JINGLE_AUDIO_CAPS \
- ( PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO )
+ g_free (object_path);
-#define JINGLE_VIDEO_CAPS \
- ( PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO )
+ return stream;
+}
-GabblePresenceCapabilities
-_gabble_media_channel_typeflags_to_caps (TpChannelMediaCapabilities flags)
+static void
+session_new_content_cb (GabbleJingleSession *session,
+ GabbleJingleContent *c, gpointer user_data)
{
- GabblePresenceCapabilities caps = 0;
+ GabbleMediaChannel *chan = GABBLE_MEDIA_CHANNEL (user_data);
- /* currently we can only signal any (GTalk or Jingle calls) using
- * the GTalk-P2P transport */
- if (flags & TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P)
- {
- caps |= JINGLE_CAPS;
-
- if (flags & TP_CHANNEL_MEDIA_CAPABILITY_AUDIO)
- caps |= GTALK_CAPS | JINGLE_AUDIO_CAPS;
+ DEBUG ("called");
- if (flags & TP_CHANNEL_MEDIA_CAPABILITY_VIDEO)
- caps |= JINGLE_VIDEO_CAPS;
- }
-
- return caps;
+ create_stream_from_content (chan, c);
}
TpChannelMediaCapabilities
@@ -2072,6 +2255,29 @@ gabble_media_channel_request_hold (TpSvcChannelInterfaceHold *iface,
}
static void
+_emit_new_stream (GabbleMediaChannel *chan,
+ GabbleMediaStream *stream)
+{
+ gchar *object_path;
+ guint id, media_type;
+
+ g_object_get (stream,
+ "object-path", &object_path,
+ "id", &id,
+ "media-type", &media_type,
+ NULL);
+
+ /* all of the streams are bidirectional from farsight's point of view, it's
+ * just in the signalling they change */
+ DEBUG ("emitting MediaSessionHandler:NewStreamHandler signal");
+ tp_svc_media_session_handler_emit_new_stream_handler (chan,
+ object_path, id, media_type, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
+
+ g_free (object_path);
+}
+
+
+static void
gabble_media_channel_ready (TpSvcMediaSessionHandler *iface,
DBusGMethodInvocation *context)
{
@@ -2081,14 +2287,12 @@ gabble_media_channel_ready (TpSvcMediaSessionHandler *iface,
if (!priv->ready)
{
- // guint i;
+ guint i;
priv->ready = TRUE;
- /* FIXME
for (i = 0; i < priv->streams->len; i++)
_emit_new_stream (self, g_ptr_array_index (priv->streams, i));
- */
}
tp_svc_media_session_handler_return_from_ready (context);
diff --git a/src/media-channel.h b/src/media-channel.h
index a0dc459..ec7bedc 100644
--- a/src/media-channel.h
+++ b/src/media-channel.h
@@ -27,7 +27,6 @@
#include <telepathy-glib/group-mixin.h>
#include <telepathy-glib/properties-mixin.h>
-#include "media-session.h"
#include "presence.h"
G_BEGIN_DECLS
diff --git a/src/media-factory.c b/src/media-factory.c
index 9e355f0..56602c6 100644
--- a/src/media-factory.c
+++ b/src/media-factory.c
@@ -70,11 +70,6 @@ struct _GabbleMediaFactoryPrivate
GHashTable *session_chans;
- gboolean get_stun_from_jingle;
- gchar *stun_server;
- guint16 stun_port;
- gchar *relay_token;
-
gboolean dispose_has_run;
};
@@ -123,9 +118,6 @@ gabble_media_factory_dispose (GObject *object)
priv->session_chans = NULL;
}
- g_free (priv->stun_server);
- g_free (priv->relay_token);
-
if (G_OBJECT_CLASS (gabble_media_factory_parent_class)->dispose)
G_OBJECT_CLASS (gabble_media_factory_parent_class)->dispose (object);
}
@@ -276,20 +268,6 @@ new_media_channel (GabbleMediaFactory *fac,
"session", sess,
NULL);
- if (priv->stun_server != NULL)
- {
- g_object_set ((GObject *) chan, "stun-server", priv->stun_server, NULL);
-
- if (priv->stun_port != 0)
- g_object_set ((GObject *) chan, "stun-port", priv->stun_port, NULL);
- }
-
- if (priv->relay_token != NULL)
- {
- g_object_set ((GObject *) chan, "gtalk-p2p-relay-token",
- priv->relay_token, NULL);
- }
-
DEBUG ("object path %s", object_path);
g_signal_connect (chan, "closed", (GCallback) media_channel_closed_cb, fac);
@@ -371,6 +349,7 @@ connection_status_changed_cb (GabbleConnection *conn,
G_CALLBACK (new_jingle_session_cb), self);
break;
+#if 0
case TP_CONNECTION_STATUS_CONNECTED:
{
gchar *stun_server = NULL;
@@ -399,6 +378,7 @@ connection_status_changed_cb (GabbleConnection *conn,
}
}
break;
+#endif
case TP_CONNECTION_STATUS_DISCONNECTED:
gabble_media_factory_close_all (self);
diff --git a/src/media-session.c b/src/media-session.c
deleted file mode 100644
index 7ec010b..0000000
--- a/src/media-session.c
+++ /dev/null
@@ -1,2933 +0,0 @@
-/*
- * gabble-media-session.c - Source for GabbleMediaSession
- * Copyright (C) 2006 Collabora Ltd.
- * Copyright (C) 2006 Nokia Corporation
- * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas at collabora.co.uk>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include "config.h"
-#include "media-session.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#include <dbus/dbus-glib.h>
-#include <telepathy-glib/debug-ansi.h>
-#include <telepathy-glib/dbus.h>
-#include <telepathy-glib/errors.h>
-#include <telepathy-glib/svc-media-interfaces.h>
-
-#define DEBUG_FLAG GABBLE_DEBUG_MEDIA
-
-#include "connection.h"
-#include "debug.h"
-#include "gabble-signals-marshal.h"
-#include "media-channel.h"
-#include "media-stream.h"
-#include "namespaces.h"
-#include "presence-cache.h"
-#include "presence.h"
-#include "util.h"
-
-static void session_handler_iface_init (gpointer, gpointer);
-
-G_DEFINE_TYPE_WITH_CODE(GabbleMediaSession,
- gabble_media_session,
- G_TYPE_OBJECT,
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_MEDIA_SESSION_HANDLER,
- session_handler_iface_init)
- )
-
-#define DEFAULT_SESSION_TIMEOUT 60000
-
-#define GTALK_STREAM_NAME "gtalk"
-
-/* 99 streams gives us a max name length of 8 (videoXX\0 or audioXX\0) */
-#define MAX_STREAMS 99
-#define MAX_STREAM_NAME_LEN 8
-
-/* signal enum */
-enum
-{
- STREAM_ADDED,
- TERMINATED,
- LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = {0};
-
-/* properties */
-enum
-{
- PROP_CONNECTION = 1,
- PROP_MEDIA_CHANNEL,
- PROP_OBJECT_PATH,
- PROP_SESSION_ID,
- PROP_INITIATOR,
- PROP_PEER,
- PROP_PEER_RESOURCE,
- PROP_STATE,
- LAST_PROPERTY
-};
-
-/* private structure */
-
-struct _GabbleMediaSessionPrivate
-{
- GabbleConnection *conn;
- GabbleMediaChannel *channel;
- GabbleMediaSessionMode mode;
- gchar *object_path;
-
- GPtrArray *streams;
- GPtrArray *remove_requests;
-
- gchar *id;
- TpHandle peer;
- gchar *peer_resource;
-
- JingleSessionState state;
- gboolean ready;
- gboolean locally_accepted;
- gboolean terminated;
-
- guint timer_id;
-
- gboolean dispose_has_run;
-};
-
-#define GABBLE_MEDIA_SESSION_GET_PRIVATE(obj) ((obj)->priv)
-
-typedef struct {
- gchar *name;
- gchar *attributes;
-} SessionStateDescription;
-
-static const SessionStateDescription session_states[] =
-{
- { "JS_STATE_PENDING_CREATED",
- TP_ANSI_BOLD_ON TP_ANSI_FG_BLACK TP_ANSI_BG_WHITE },
- { "JS_STATE_PENDING_INITIATE_SENT",
- TP_ANSI_BOLD_ON TP_ANSI_BG_CYAN },
- { "JS_STATE_PENDING_INITIATED",
- TP_ANSI_BOLD_ON TP_ANSI_BG_MAGENTA },
- { "JS_STATE_PENDING_ACCEPT_SENT",
- TP_ANSI_BOLD_ON TP_ANSI_BG_CYAN },
- { "JS_STATE_ACTIVE",
- TP_ANSI_BOLD_ON TP_ANSI_BG_BLUE },
- { "JS_STATE_ENDED",
- TP_ANSI_BG_RED }
-};
-
-static void
-gabble_media_session_init (GabbleMediaSession *self)
-{
- GabbleMediaSessionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
- GABBLE_TYPE_MEDIA_SESSION, GabbleMediaSessionPrivate);
-
- self->priv = priv;
-
- priv->mode = MODE_JINGLE;
- priv->state = JS_STATE_PENDING_CREATED;
- priv->streams = g_ptr_array_new ();
- priv->remove_requests = g_ptr_array_new ();
-}
-
-static void stream_connection_state_changed_cb (GabbleMediaStream *stream,
- GParamSpec *param,
- GabbleMediaSession *session);
-static void stream_got_local_codecs_changed_cb (GabbleMediaStream *stream,
- GParamSpec *param,
- GabbleMediaSession *session);
-
-static void
-_emit_new_stream (GabbleMediaSession *session,
- GabbleMediaStream *stream)
-{
- gchar *object_path;
- guint id, media_type;
-
- g_object_get (stream,
- "object-path", &object_path,
- "id", &id,
- "media-type", &media_type,
- NULL);
-
- /* all of the streams are bidirectional from farsight's point of view, it's
- * just in the signalling they change */
- tp_svc_media_session_handler_emit_new_stream_handler (session,
- object_path, id, media_type, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
-
- g_free (object_path);
-}
-
-
-static GabbleMediaStream *
-_lookup_stream_by_name_and_initiator (GabbleMediaSession *session,
- const gchar *stream_name,
- JingleInitiator stream_initiator)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
- if (tp_strdiff (stream->name, stream_name))
- continue;
-
- if (stream_initiator != INITIATOR_INVALID &&
- stream_initiator != stream->initiator)
- continue;
-
- return stream;
- }
-
- return NULL;
-}
-
-
-static GabbleMediaStream *
-create_media_stream (GabbleMediaSession *session,
- const gchar *name,
- JingleInitiator initiator,
- guint media_type)
-{
- GabbleMediaSessionPrivate *priv;
- gchar *object_path;
- GabbleMediaStream *stream;
- guint id;
-
- g_assert (GABBLE_IS_MEDIA_SESSION (session));
- g_assert (name != NULL);
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- /* assert that if we're in google mode:
- * - we only try to make one stream
- * - it's an audio stream
- * - it's called GTALK_STREAM_NAME */
- if (priv->mode == MODE_GOOGLE)
- {
- g_assert (priv->streams->len == 0);
- g_assert (media_type == TP_MEDIA_STREAM_TYPE_AUDIO);
- g_assert (!tp_strdiff (name, GTALK_STREAM_NAME));
- }
-
- g_assert (priv->streams->len < MAX_STREAMS);
- g_assert (_lookup_stream_by_name_and_initiator (session, name, initiator) ==
- NULL);
-
- id = _gabble_media_channel_get_stream_id (priv->channel);
-
- GMS_DEBUG_INFO (session,
- "creating new %s %s stream called \"%s\" with id %u",
- priv->mode == MODE_GOOGLE ? "google" : "jingle",
- media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video",
- name, id);
-
- object_path = g_strdup_printf ("%s/MediaStream%u", priv->object_path, id);
-
- stream = g_object_new (GABBLE_TYPE_MEDIA_STREAM,
- "connection", priv->conn,
- "media-session", session,
- "object-path", object_path,
- "mode", priv->mode,
- "name", name,
- "id", id,
- "initiator", initiator,
- "media-type", media_type,
- NULL);
-
- g_signal_connect (stream, "notify::connection-state",
- (GCallback) stream_connection_state_changed_cb,
- session);
- g_signal_connect (stream, "notify::got-local-codecs",
- (GCallback) stream_got_local_codecs_changed_cb,
- session);
-
- g_ptr_array_add (priv->streams, stream);
-
- g_free (object_path);
-
- if (priv->ready)
- _emit_new_stream (session, stream);
-
- g_signal_emit (session, signals[STREAM_ADDED], 0, stream);
-
- return stream;
-}
-
-static void
-destroy_media_stream (GabbleMediaSession *session,
- GabbleMediaStream *stream)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- _gabble_media_stream_close (stream);
- g_ptr_array_remove_fast (priv->streams, stream);
- g_object_unref (stream);
-}
-
-static GObject *
-gabble_media_session_constructor (GType type, guint n_props,
- GObjectConstructParam *props)
-{
- GObject *obj;
- GabbleMediaSessionPrivate *priv;
- DBusGConnection *bus;
- TpHandleRepoIface *contact_handles;
-
- obj = G_OBJECT_CLASS (gabble_media_session_parent_class)->
- constructor (type, n_props, props);
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (GABBLE_MEDIA_SESSION (obj));
-
- bus = tp_get_bus ();
- dbus_g_connection_register_g_object (bus, priv->object_path, obj);
-
- contact_handles = tp_base_connection_get_handles (
- (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
-
- tp_handle_ref (contact_handles, priv->peer);
-
- return obj;
-}
-
-static void
-gabble_media_session_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec)
-{
- GabbleMediaSession *session = GABBLE_MEDIA_SESSION (object);
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- switch (property_id) {
- case PROP_CONNECTION:
- g_value_set_object (value, priv->conn);
- break;
- case PROP_MEDIA_CHANNEL:
- g_value_set_object (value, priv->channel);
- break;
- case PROP_OBJECT_PATH:
- g_value_set_string (value, priv->object_path);
- break;
- case PROP_SESSION_ID:
- g_value_set_string (value, priv->id);
- break;
- case PROP_INITIATOR:
- g_value_set_uint (value, session->initiator);
- break;
- case PROP_PEER:
- g_value_set_uint (value, priv->peer);
- break;
- case PROP_PEER_RESOURCE:
- g_value_set_string (value, priv->peer_resource);
- break;
- case PROP_STATE:
- g_value_set_uint (value, priv->state);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
-
-static void session_state_changed (GabbleMediaSession *session,
- JingleSessionState prev_state,
- JingleSessionState new_state);
-
-static void
-gabble_media_session_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- GabbleMediaSession *session = GABBLE_MEDIA_SESSION (object);
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- JingleSessionState prev_state;
-
- switch (property_id) {
- case PROP_CONNECTION:
- priv->conn = g_value_get_object (value);
- break;
- case PROP_MEDIA_CHANNEL:
- priv->channel = 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_SESSION_ID:
- g_free (priv->id);
- priv->id = g_value_dup_string (value);
- break;
- case PROP_INITIATOR:
- session->initiator = g_value_get_uint (value);
- break;
- case PROP_PEER:
- priv->peer = g_value_get_uint (value);
- break;
- case PROP_PEER_RESOURCE:
- g_free (priv->peer_resource);
- priv->peer_resource = g_value_dup_string (value);
- break;
- case PROP_STATE:
- prev_state = priv->state;
- priv->state = g_value_get_uint (value);
-
- if (priv->state == JS_STATE_ENDED)
- g_assert (priv->terminated);
-
- if (priv->state != prev_state)
- session_state_changed (session, prev_state, priv->state);
-
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
-
-static void gabble_media_session_dispose (GObject *object);
-static void gabble_media_session_finalize (GObject *object);
-
-static void
-gabble_media_session_class_init (GabbleMediaSessionClass *gabble_media_session_class)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (gabble_media_session_class);
- GParamSpec *param_spec;
-
- g_type_class_add_private (gabble_media_session_class,
- sizeof (GabbleMediaSessionPrivate));
-
- object_class->constructor = gabble_media_session_constructor;
-
- object_class->get_property = gabble_media_session_get_property;
- object_class->set_property = gabble_media_session_set_property;
-
- object_class->dispose = gabble_media_session_dispose;
- object_class->finalize = gabble_media_session_finalize;
-
- param_spec = g_param_spec_object ("connection", "GabbleConnection object",
- "Gabble connection object that owns this "
- "media session'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-channel",
- "GabbleMediaChannel object",
- "Gabble media channel object that owns this media session object.",
- GABBLE_TYPE_MEDIA_CHANNEL,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NICK |
- G_PARAM_STATIC_BLURB);
- g_object_class_install_property (object_class, PROP_MEDIA_CHANNEL,
- 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_string ("session-id", "Session ID",
- "A unique session identifier used "
- "throughout all communication.",
- NULL,
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_READWRITE |
- G_PARAM_STATIC_NAME |
- G_PARAM_STATIC_BLURB);
- g_object_class_install_property (object_class, PROP_SESSION_ID, param_spec);
-
- param_spec = g_param_spec_uint ("initiator", "Session initiator",
- "An enum signifying which end initiated "
- "the session.",
- 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 ("peer", "Session peer",
- "The TpHandle representing the contact "
- "with whom this session communicates.",
- 0, G_MAXUINT32, 0,
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_READWRITE |
- G_PARAM_STATIC_NAME |
- G_PARAM_STATIC_BLURB);
- g_object_class_install_property (object_class, PROP_PEER, param_spec);
-
- param_spec = g_param_spec_string ("peer-resource",
- "Session peer's resource",
- "The resource of the contact "
- "with whom this session communicates, "
- "if applicable",
- NULL,
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_WRITABLE |
- G_PARAM_STATIC_NAME |
- G_PARAM_STATIC_BLURB);
- g_object_class_install_property (object_class, PROP_PEER_RESOURCE,
- param_spec);
-
- param_spec = g_param_spec_uint ("state", "Session state",
- "The current state that the session is in.",
- 0, G_MAXUINT32, 0,
- G_PARAM_READWRITE |
- G_PARAM_STATIC_NAME |
- G_PARAM_STATIC_BLURB);
- g_object_class_install_property (object_class, PROP_STATE, param_spec);
-
- signals[STREAM_ADDED] =
- g_signal_new ("stream-added",
- G_OBJECT_CLASS_TYPE (gabble_media_session_class),
- G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
- 0,
- NULL, NULL,
- g_cclosure_marshal_VOID__OBJECT,
- G_TYPE_NONE, 1, G_TYPE_OBJECT);
-
- signals[TERMINATED] =
- g_signal_new ("terminated",
- G_OBJECT_CLASS_TYPE (gabble_media_session_class),
- G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
- 0,
- NULL, NULL,
- gabble_marshal_VOID__UINT_UINT,
- G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
-}
-
-static void
-gabble_media_session_dispose (GObject *object)
-{
- GabbleMediaSession *self = GABBLE_MEDIA_SESSION (object);
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (self);
- guint i;
- TpHandleRepoIface *contact_handles;
-
- DEBUG ("called");
-
- if (priv->dispose_has_run)
- return;
-
- priv->dispose_has_run = TRUE;
-
- _gabble_media_session_terminate (self, INITIATOR_LOCAL,
- TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-
- if (priv->timer_id != 0)
- g_source_remove (priv->timer_id);
-
- if (priv->streams != NULL)
- {
- for (i = 0; i < priv->streams->len; i++)
- g_object_unref (g_ptr_array_index (priv->streams, i));
- g_ptr_array_free (priv->streams, TRUE);
- priv->streams = NULL;
- }
-
- for (i = 0; i < priv->remove_requests->len; i++)
- g_ptr_array_free (g_ptr_array_index (priv->remove_requests, i), TRUE);
- g_ptr_array_free (priv->remove_requests, TRUE);
- priv->remove_requests = NULL;
-
- contact_handles = tp_base_connection_get_handles (
- (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
-
- tp_handle_unref (contact_handles, priv->peer);
-
- if (G_OBJECT_CLASS (gabble_media_session_parent_class)->dispose)
- G_OBJECT_CLASS (gabble_media_session_parent_class)->dispose (object);
-}
-
-static void
-gabble_media_session_finalize (GObject *object)
-{
- GabbleMediaSession *self = GABBLE_MEDIA_SESSION (object);
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (self);
-
- g_free (priv->id);
- g_free (priv->object_path);
- g_free (priv->peer_resource);
- G_OBJECT_CLASS (gabble_media_session_parent_class)->finalize (object);
-}
-
-
-/**
- * gabble_media_session_error
- *
- * Implements D-Bus method Error
- * on interface org.freedesktop.Telepathy.Media.SessionHandler
- */
-static void
-gabble_media_session_error (TpSvcMediaSessionHandler *iface,
- guint errno,
- const gchar *message,
- DBusGMethodInvocation *context)
-{
- GabbleMediaSession *self = GABBLE_MEDIA_SESSION (iface);
- GabbleMediaSessionPrivate *priv;
- GPtrArray *tmp;
- guint i;
-
- g_assert (GABBLE_IS_MEDIA_SESSION (self));
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (self);
-
- GMS_DEBUG_INFO (self, "Media.SessionHandler::Error called, error %u (%s) -- "
- "emitting error on each stream", errno, message);
-
- if (priv->state == JS_STATE_ENDED)
- {
- tp_svc_media_session_handler_return_from_error (context);
- return;
- }
- else if (priv->state == JS_STATE_PENDING_CREATED)
- {
- /* shortcut to prevent sending remove actions if we haven't sent an
- * initiate yet */
- g_object_set (self, "state", JS_STATE_ENDED, NULL);
- tp_svc_media_session_handler_return_from_error (context);
- return;
- }
-
- g_assert (priv->streams != NULL);
-
- tmp = priv->streams;
- priv->streams = NULL;
-
- for (i = 0; i < tmp->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (tmp, i);
-
- gabble_media_stream_error (stream, errno, message, NULL);
- }
-
- g_ptr_array_free (tmp, TRUE);
-
- tp_svc_media_session_handler_return_from_error (context);
-}
-
-
-/**
- * gabble_media_session_ready
- *
- * Implements D-Bus method Ready
- * on interface org.freedesktop.Telepathy.Media.SessionHandler
- */
-static void
-gabble_media_session_ready (TpSvcMediaSessionHandler *iface,
- DBusGMethodInvocation *context)
-{
- GabbleMediaSession *self = GABBLE_MEDIA_SESSION (iface);
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (self);
-
- if (!priv->ready)
- {
- guint i;
-
- priv->ready = TRUE;
-
- for (i = 0; i < priv->streams->len; i++)
- _emit_new_stream (self, g_ptr_array_index (priv->streams, i));
- }
-
- tp_svc_media_session_handler_return_from_ready (context);
-}
-
-
-static gboolean
-_handle_create (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *content_node,
- const gchar *stream_name,
- GabbleMediaStream *stream,
- LmMessageNode *desc_node,
- LmMessageNode *trans_node,
- GError **error)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- GabbleMediaSessionMode session_mode;
- TpMediaStreamType stream_type;
- gboolean override_existing = FALSE;
-
- if ((priv->state == JS_STATE_PENDING_CREATED) &&
- (session->initiator == INITIATOR_LOCAL))
- {
- DEBUG ("we're trying to call ourselves, rejecting with busy");
- _gabble_media_session_terminate (session, INITIATOR_REMOTE,
- TP_CHANNEL_GROUP_CHANGE_REASON_BUSY);
- return FALSE;
- }
-
-
- if (stream != NULL)
- {
- /* streams added by the session initiator may replace similarly-named
- * streams which we are trying to add (but havn't had acknowledged) */
- if (stream->signalling_state < STREAM_SIG_STATE_ACKNOWLEDGED)
- {
- if (session->initiator == INITIATOR_REMOTE)
- {
- override_existing = TRUE;
- }
- else
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_CONFLICT,
- "session initiator is creating a stream named \"%s\" "
- "already", stream_name);
- return FALSE;
- }
- }
- else
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_CONFLICT,
- "can't create new stream called \"%s\", it already exists, "
- "rejecting", stream_name);
- return FALSE;
- }
- }
-
- if (desc_node == NULL)
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
- "unable to create stream without a content description");
- return FALSE;
- }
-
- if (lm_message_node_has_namespace (desc_node,
- NS_GOOGLE_SESSION_PHONE, NULL))
- {
- session_mode = MODE_GOOGLE;
- stream_type = TP_MEDIA_STREAM_TYPE_AUDIO;
- }
- else if (lm_message_node_has_namespace (desc_node,
- NS_JINGLE_DESCRIPTION_AUDIO, NULL))
- {
- session_mode = MODE_JINGLE;
- stream_type = TP_MEDIA_STREAM_TYPE_AUDIO;
- }
- else if (lm_message_node_has_namespace (desc_node,
- NS_JINGLE_DESCRIPTION_VIDEO, NULL))
- {
- session_mode = MODE_JINGLE;
- stream_type = TP_MEDIA_STREAM_TYPE_VIDEO;
- }
- else
- {
- g_set_error (error, GABBLE_XMPP_ERROR,
- XMPP_ERROR_JINGLE_UNSUPPORTED_CONTENT,
- "refusing to create stream for unsupported content description");
- return FALSE;
- }
-
- /* MODE_GOOGLE is allowed to have a null transport node */
- if (session_mode == MODE_JINGLE && trans_node == NULL)
- {
- g_set_error (error, GABBLE_XMPP_ERROR,
- XMPP_ERROR_JINGLE_UNSUPPORTED_TRANSPORT,
- "refusing to create stream for unsupported transport");
- return FALSE;
- }
-
- if (session_mode != priv->mode)
- {
- if (priv->streams->len > 0)
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_UNEXPECTED_REQUEST,
- "refusing to change mode because streams already exist");
- return FALSE;
- }
- else
- {
- GMS_DEBUG_INFO (session, "setting session mode to %s",
- session_mode == MODE_GOOGLE ? "google" : "jingle");
- priv->mode = session_mode;
- }
- }
-
- if (override_existing)
- {
- GMS_DEBUG_INFO (session, "removing our unacknowledged stream \"%s\" "
- "in favour of the session initiator's", stream_name);
-
- /* disappear this stream */
- destroy_media_stream (session, stream);
-
- stream = NULL;
- }
-
- if (priv->streams->len == MAX_STREAMS)
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_RESOURCE_CONSTRAINT,
- "refusing to create more than " G_STRINGIFY (MAX_STREAMS)
- " streams");
- return FALSE;
- }
-
- stream = create_media_stream (session, stream_name, INITIATOR_REMOTE,
- stream_type);
-
- /* set the signalling state to ACKNOWLEDGED */
- g_object_set (stream,
- "signalling-state", STREAM_SIG_STATE_ACKNOWLEDGED,
- NULL);
-
- /* for jingle streams, set the direction to none, so that the
- * direction handler adds the right flags */
- if (priv->mode == MODE_JINGLE)
- g_object_set (stream,
- "combined-direction", TP_MEDIA_STREAM_DIRECTION_NONE,
- NULL);
-
- return TRUE;
-}
-
-
-static TpMediaStreamDirection
-_senders_to_direction (GabbleMediaSession *session,
- const gchar *senders)
-{
- TpMediaStreamDirection ret = TP_MEDIA_STREAM_DIRECTION_NONE;
-
- if (!tp_strdiff (senders, "initiator"))
- {
- if (session->initiator == INITIATOR_LOCAL)
- ret = TP_MEDIA_STREAM_DIRECTION_SEND;
- else
- ret = TP_MEDIA_STREAM_DIRECTION_RECEIVE;
- }
- else if (!tp_strdiff (senders, "responder"))
- {
- if (session->initiator == INITIATOR_REMOTE)
- ret = TP_MEDIA_STREAM_DIRECTION_SEND;
- else
- ret = TP_MEDIA_STREAM_DIRECTION_RECEIVE;
- }
- else if (!tp_strdiff (senders, "both"))
- {
- ret = TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL;
- }
-
- return ret;
-}
-
-static gboolean
-_handle_direction (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *content_node,
- const gchar *stream_name,
- GabbleMediaStream *stream,
- LmMessageNode *desc_node,
- LmMessageNode *trans_node,
- GError **error)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- const gchar *senders;
- CombinedStreamDirection new_combined_dir;
- TpMediaStreamDirection requested_dir, current_dir;
- TpMediaStreamPendingSend pending_send;
-
- if (priv->mode == MODE_GOOGLE)
- return TRUE;
-
- requested_dir = TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL;
-
- senders = lm_message_node_get_attribute (content_node, "senders");
- if (senders != NULL)
- requested_dir = _senders_to_direction (session, senders);
-
- if (requested_dir == TP_MEDIA_STREAM_DIRECTION_NONE)
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
- "received invalid content senders value \"%s\" on stream \"%s\"; "
- "rejecting", senders, stream_name);
- return FALSE;
- }
-
- current_dir = COMBINED_DIRECTION_GET_DIRECTION (stream->combined_direction);
- pending_send = COMBINED_DIRECTION_GET_PENDING_SEND
- (stream->combined_direction);
-
- GMS_DEBUG_INFO (session, "received request for senders \"%s\" on stream "
- "\"%s\"", senders, stream_name);
-
- /* if local sending has been added, remove it,
- * and set the pending local send flag */
- if (((current_dir & TP_MEDIA_STREAM_DIRECTION_SEND) == 0) &&
- ((requested_dir & TP_MEDIA_STREAM_DIRECTION_SEND) != 0))
- {
- GMS_DEBUG_INFO (session, "setting pending local send flag");
- requested_dir &= ~TP_MEDIA_STREAM_DIRECTION_SEND;
- pending_send |= TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
- }
-
-#if 0
- /* clear any pending remote send */
- if ((pending_send & TP_MEDIA_STREAM_PENDING_REMOTE_SEND) != 0)
- {
- GMS_DEBUG_INFO (session, "setting pending local send flag");
- pending_send &= ~TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
- }
-#endif
-
- /* make any necessary changes */
- new_combined_dir = MAKE_COMBINED_DIRECTION (requested_dir, pending_send);
- if (new_combined_dir != stream->combined_direction)
- {
- g_object_set (stream, "combined-direction", new_combined_dir, NULL);
- _gabble_media_stream_update_sending (stream, FALSE);
- }
-
- return TRUE;
-}
-
-
-static gboolean
-_handle_accept (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *content_node,
- const gchar *stream_name,
- GabbleMediaStream *stream,
- LmMessageNode *desc_node,
- LmMessageNode *trans_node,
- GError **error)
-{
- g_object_set (stream, "playing", TRUE, NULL);
-
- _gabble_media_stream_update_sending (stream, TRUE);
-
- return TRUE;
-}
-
-
-static gboolean
-_handle_codecs (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *content_node,
- const gchar *stream_name,
- GabbleMediaStream *stream,
- LmMessageNode *desc_node,
- LmMessageNode *trans_node,
- GError **error)
-{
- if (desc_node == NULL)
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
- "unable to handle codecs without a content description node");
- return FALSE;
- }
-
- if (!_gabble_media_stream_post_remote_codecs (stream, message, desc_node,
- error))
- return FALSE;
-
- return TRUE;
-}
-
-
-static gboolean
-_handle_candidates (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *content_node,
- const gchar *stream_name,
- GabbleMediaStream *stream,
- LmMessageNode *desc_node,
- LmMessageNode *trans_node,
- GError **error)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- if (trans_node == NULL)
- {
- if (priv->mode == MODE_GOOGLE)
- {
- trans_node = content_node;
- }
- else
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
- "unable to handle candidates without a transport node");
- return FALSE;
- }
- }
-
- if (!_gabble_media_stream_post_remote_candidates (stream, message,
- trans_node, error))
- return FALSE;
-
- return TRUE;
-}
-
-static guint
-_count_non_removing_streams (GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- guint i, ret = 0;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
- if (stream->signalling_state < STREAM_SIG_STATE_REMOVING)
- ret++;
- }
-
- return ret;
-}
-
-static gboolean
-_handle_remove (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *content_node,
- const gchar *stream_name,
- GabbleMediaStream *stream,
- LmMessageNode *desc_node,
- LmMessageNode *trans_node,
- GError **error)
-{
- /* reducing a session to contain 0 streams is invalid; instead the peer
- * should terminate the session. I guess we'll do it for them... */
- if (_count_non_removing_streams (session) == 1)
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
- "unable to remove the last stream in a Jingle call");
- return FALSE;
- }
-
- /* close the stream */
- destroy_media_stream (session, stream);
-
- return TRUE;
-}
-
-
-static gboolean
-_handle_terminate (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *content_node,
- const gchar *stream_name,
- GabbleMediaStream *stream,
- LmMessageNode *desc_node,
- LmMessageNode *trans_node,
- GError **error)
-{
- DEBUG ("called for %s", stream_name);
-
- _gabble_media_session_terminate (session, INITIATOR_REMOTE,
- TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-
- return TRUE;
-}
-
-
-typedef gboolean (*StreamHandlerFunc)(GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *content_node,
- const gchar *stream_name,
- GabbleMediaStream *stream,
- LmMessageNode *desc_node,
- LmMessageNode *trans_node,
- GError **error);
-
-typedef struct _Handler Handler;
-
-struct _Handler {
- const gchar *actions[3];
- JingleSessionState min_allowed_state;
- JingleSessionState max_allowed_state;
- StreamHandlerFunc stream_handlers[4];
- JingleSessionState new_state;
-};
-
-static Handler handlers[] = {
- {
- { "initiate", "session-initiate", NULL },
- JS_STATE_PENDING_CREATED,
- JS_STATE_PENDING_CREATED,
- { _handle_create, _handle_direction, _handle_codecs, NULL },
- JS_STATE_PENDING_INITIATED
- },
- {
- { "accept", "session-accept", NULL },
- JS_STATE_PENDING_INITIATED,
- JS_STATE_PENDING_INITIATED,
- { _handle_direction, _handle_codecs, _handle_accept, NULL },
- JS_STATE_ACTIVE
- },
- {
- { "reject", NULL },
- JS_STATE_PENDING_INITIATE_SENT,
- JS_STATE_PENDING_INITIATED,
- { _handle_terminate, NULL },
- JS_STATE_INVALID
- },
- {
- { "terminate", "session-terminate", NULL },
- JS_STATE_PENDING_INITIATED,
- JS_STATE_ENDED,
- { _handle_terminate, NULL },
- JS_STATE_INVALID
- },
- {
- { "candidates", "transport-info", NULL },
- JS_STATE_PENDING_INITIATED,
- JS_STATE_ACTIVE,
- { _handle_candidates, NULL },
- JS_STATE_INVALID
- },
- {
- { "content-add", NULL },
- JS_STATE_ACTIVE,
- JS_STATE_ACTIVE,
- { _handle_create, _handle_direction, _handle_codecs, NULL },
- JS_STATE_INVALID,
- },
- {
- { "content-modify", NULL },
- JS_STATE_PENDING_INITIATED,
- JS_STATE_ACTIVE,
- { _handle_direction, NULL },
- JS_STATE_INVALID
- },
- {
- { "content-accept", NULL },
- JS_STATE_PENDING_INITIATED,
- JS_STATE_ACTIVE,
- { _handle_direction, _handle_codecs, _handle_accept, NULL },
- JS_STATE_INVALID
- },
- {
- { "content-remove", "content-decline", NULL },
- JS_STATE_PENDING_INITIATED,
- JS_STATE_ACTIVE,
- { _handle_remove, NULL },
- JS_STATE_INVALID
- },
- {
- { NULL },
- JS_STATE_INVALID,
- JS_STATE_INVALID,
- { NULL },
- JS_STATE_INVALID
- }
-};
-
-
-static gboolean
-_call_handlers_on_stream (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *content_node,
- const gchar *stream_name,
- JingleInitiator stream_creator,
- StreamHandlerFunc *func,
- GError **error)
-{
- GabbleMediaStream *stream = NULL;
- LmMessageNode *desc_node = NULL, *trans_node = NULL;
- StreamHandlerFunc *tmp;
- gboolean stream_created = FALSE;
-
- if (content_node != NULL)
- {
- desc_node = lm_message_node_get_child (content_node, "description");
-
- trans_node = lm_message_node_get_child_with_namespace (content_node,
- "transport", NS_GOOGLE_TRANSPORT_P2P);
- }
-
- for (tmp = func; *tmp != NULL; tmp++)
- {
- /* handlers may create the stream */
- if (stream == NULL && stream_name != NULL)
- stream = _lookup_stream_by_name_and_initiator (session, stream_name,
- stream_creator);
-
- /* the create handler is able to check whether or not the stream
- * exists, and act accordingly (sometimes it will replace an existing
- * stream, sometimes it will reject). the termination handler
- * also requires no stream to do it's job. */
- if (*tmp != _handle_create && *tmp != _handle_terminate)
- {
- /* all other handlers require the stream to exist */
- if (stream == NULL)
- {
- const gchar *created = "";
-
- if (stream_creator == INITIATOR_LOCAL)
- created = "locally-created ";
- else if (stream_creator == INITIATOR_REMOTE)
- created = "remotely-created ";
-
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_ITEM_NOT_FOUND,
- "unable to handle action for unknown %sstream \"%s\" ",
- created, stream_name);
-
- return FALSE;
- }
- else
- {
- /* don't do anything with actions on streams which have not been
- * acknowledged, or that we're trying to remove, to deal with
- * adding/removing race conditions (actions sent by the other end
- * before they're aware that we've added or removed a stream) */
- if (stream->signalling_state != STREAM_SIG_STATE_ACKNOWLEDGED)
- {
- GMS_DEBUG_WARNING (session, "ignoring action because stream "
- "%s is in state %d, not ACKNOWLEDGED", stream->name,
- stream->signalling_state);
- return TRUE;
- }
- }
- }
-
- if (!(*tmp) (session, message, content_node, stream_name, stream,
- desc_node, trans_node, error))
- {
- /* if we successfully created the stream but failed to do something
- * with it later, remove it */
- if (stream_created)
- destroy_media_stream (session, stream);
-
- return FALSE;
- }
-
- if (*tmp == _handle_create)
- {
- stream_created = TRUE;
- /* force a stream lookup after the create handler, even if we
- * already had one (it has replacement semantics in certain
- * situations) */
- stream = NULL;
- }
- }
-
- return TRUE;
-}
-
-
-static JingleInitiator
-_creator_to_initiator (GabbleMediaSession *session, const gchar *creator)
-{
- if (!tp_strdiff (creator, "initiator"))
- {
- if (session->initiator == INITIATOR_LOCAL)
- return INITIATOR_LOCAL;
- else
- return INITIATOR_REMOTE;
- }
- else if (!tp_strdiff (creator, "responder"))
- {
- if (session->initiator == INITIATOR_LOCAL)
- return INITIATOR_REMOTE;
- else
- return INITIATOR_LOCAL;
- }
- else
- return INITIATOR_INVALID;
-}
-
-
-static gboolean
-_call_handlers_on_streams (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *session_node,
- StreamHandlerFunc *func,
- GError **error)
-{
- LmMessageNode *content_node;
-
- if (lm_message_node_has_namespace (session_node, NS_GOOGLE_SESSION, NULL))
- return _call_handlers_on_stream (session, message, session_node,
- GTALK_STREAM_NAME, INITIATOR_INVALID, func, error);
-
- if (session_node->children == NULL)
- return _call_handlers_on_stream (session, message, NULL, NULL,
- INITIATOR_INVALID, func, error);
-
- for (content_node = session_node->children;
- NULL != content_node;
- content_node = content_node->next)
- {
- const gchar *stream_name, *stream_creator;
- JingleInitiator stream_initiator;
-
- if (tp_strdiff (content_node->name, "content"))
- continue;
-
- stream_name = lm_message_node_get_attribute (content_node, "name");
-
- if (stream_name == NULL)
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
- "rejecting content node with no name");
- return FALSE;
- }
-
- stream_creator = lm_message_node_get_attribute (content_node, "creator");
- stream_initiator = _creator_to_initiator (session, stream_creator);
-
- /* we allow NULL creator to mean INITIATOR_INVALID for backwards
- * compatibility with clients that don't put a creator attribute in */
- if (stream_creator != NULL && stream_initiator == INITIATOR_INVALID)
- {
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
- "rejecting content node with invalid creators value");
- return FALSE;
- }
-
- if (!_call_handlers_on_stream (session, message, content_node,
- stream_name, stream_initiator, func, error))
- return FALSE;
- }
-
- return TRUE;
-}
-
-
-gboolean
-_gabble_media_session_handle_action (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *session_node,
- const gchar *action,
- GError **error)
-{
- GabbleMediaSessionPrivate *priv;
- StreamHandlerFunc *funcs = NULL;
- JingleSessionState new_state = JS_STATE_INVALID;
- Handler *i;
- const gchar **tmp;
-
- g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- GMS_DEBUG_INFO (session, "got jingle session action \"%s\" from peer",
- action);
-
- /* do the state machine dance */
-
- /* search the table of handlers for the action */
- for (i = handlers; NULL != i->actions[0]; i++)
- {
- for (tmp = i->actions; NULL != *tmp; tmp++)
- if (0 == strcmp (*tmp, action))
- break;
-
- if (NULL == *tmp)
- continue;
-
- /* if we're outside the allowable states for this action, return an error
- * immediately */
- if (priv->state < i->min_allowed_state ||
- priv->state > i->max_allowed_state)
- {
- g_set_error (error, GABBLE_XMPP_ERROR,
- XMPP_ERROR_JINGLE_OUT_OF_ORDER,
- "action \"%s\" not allowed in current state", action);
- goto ERROR;
- }
-
- funcs = i->stream_handlers;
- new_state = i->new_state;
-
- break;
- }
-
- /* pointer is not NULL if we found a matching action */
- if (NULL == funcs)
- {
- g_set_error (error, GABBLE_XMPP_ERROR,
- XMPP_ERROR_FEATURE_NOT_IMPLEMENTED, "action \"%s\" not implemented",
- action);
- goto ERROR;
- }
-
- /* call handlers if there are any (NULL-terminated array) */
- if (NULL != *funcs)
- {
- if (!_call_handlers_on_streams (session, message, session_node, funcs,
- error))
- {
- if (*error == NULL)
- g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST,
- "unknown error encountered with action \"%s\"",
- action);
-
- goto ERROR;
- }
- }
-
- /* acknowledge the IQ before changing the state because the new state
- * could perform some actions which the other end will only accept
- * if this action has been acknowledged */
- _gabble_connection_acknowledge_set_iq (priv->conn, message);
-
- /* if the action specified a new state to go to, set it */
- if (JS_STATE_INVALID != new_state)
- g_object_set (session, "state", new_state, NULL);
-
- return TRUE;
-
-ERROR:
- g_assert (error != NULL);
- GMS_DEBUG_ERROR (session, (*error)->message);
- return FALSE;
-}
-
-static gboolean
-timeout_session (gpointer data)
-{
- GabbleMediaSession *session = data;
-
- DEBUG ("session timed out");
-
- if (session->initiator == INITIATOR_LOCAL)
- _gabble_media_session_terminate (session, INITIATOR_LOCAL,
- TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER);
- else
- _gabble_media_session_terminate (session, INITIATOR_LOCAL,
- TP_CHANNEL_GROUP_CHANGE_REASON_ERROR);
-
- return FALSE;
-}
-
-static void do_content_add (GabbleMediaSession *, GabbleMediaStream *);
-
-static void
-_add_ready_new_streams (GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
- GMS_DEBUG_DUMP (session, "pondering accept-time add for stream: %s, got "
- "local codecs: %s, initiator: %s, signalling state: %d",
- stream->name, stream->got_local_codecs ? "true" : "false",
- stream->initiator == INITIATOR_LOCAL ? "local" : "remote",
- stream->signalling_state);
-
- if (stream->got_local_codecs == FALSE)
- continue;
-
- if (stream->initiator == INITIATOR_REMOTE)
- continue;
-
- if (stream->signalling_state > STREAM_SIG_STATE_NEW)
- continue;
-
- do_content_add (session, stream);
- }
-}
-
-static void
-session_state_changed (GabbleMediaSession *session,
- JingleSessionState prev_state,
- JingleSessionState new_state)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- GMS_DEBUG_EVENT (session, "state changed from %s to %s",
- session_states[prev_state].name,
- session_states[new_state].name);
-
- /*
- * If the state goes from CREATED to INITIATED (which means the remote
- * end initiated), set the timer. If, OTOH, we're the end which just sent an
- * initiate, set the timer.
- */
- if ((prev_state == JS_STATE_PENDING_CREATED &&
- new_state == JS_STATE_PENDING_INITIATED) ||
- (new_state == JS_STATE_PENDING_INITIATE_SENT))
- {
- priv->timer_id =
- g_timeout_add (DEFAULT_SESSION_TIMEOUT, timeout_session, session);
- }
- else if (new_state == JS_STATE_ACTIVE)
- {
- g_source_remove (priv->timer_id);
- priv->timer_id = 0;
-
- /* signal any streams to the remote end which were added locally &
- * became ready before the session was accepted, so haven't been
- * mentioned yet */
- _add_ready_new_streams (session);
- }
-}
-
-static void
-_mark_local_streams_sent (GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
- if (stream->initiator == INITIATOR_REMOTE)
- continue;
-
- GMS_DEBUG_INFO (session, "marking local stream %s as signalled",
- stream->name);
-
- g_object_set (stream, "signalling-state", STREAM_SIG_STATE_SENT, NULL);
- }
-}
-
-static void
-_mark_local_streams_acked (GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
- if (stream->initiator == INITIATOR_REMOTE)
- continue;
-
- if (stream->signalling_state != STREAM_SIG_STATE_SENT)
- continue;
-
- GMS_DEBUG_INFO (session, "marking local stream %s as acknowledged",
- stream->name);
-
- g_object_set (stream,
- "signalling-state", STREAM_SIG_STATE_ACKNOWLEDGED,
- NULL);
- }
-}
-
-static void
-_set_remote_streams_playing (GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
- if (stream->initiator == INITIATOR_LOCAL)
- continue;
-
- GMS_DEBUG_INFO (session, "setting remote stream %s as playing",
- stream->name);
-
- g_object_set (stream, "playing", TRUE, NULL);
- }
-}
-
-static const gchar *_direction_to_senders (GabbleMediaSession *,
- TpMediaStreamDirection);
-
-static void
-_add_content_descriptions_one (GabbleMediaSession *session,
- GabbleMediaStream *stream,
- LmMessageNode *session_node)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- LmMessageNode *content_node;
-
- if (priv->mode == MODE_GOOGLE)
- {
- content_node = session_node;
- }
- else
- {
- TpMediaStreamDirection direction;
- TpMediaStreamPendingSend pending_send;
-
- content_node = _gabble_media_stream_add_content_node (stream,
- session_node);
-
- direction = COMBINED_DIRECTION_GET_DIRECTION (stream->combined_direction);
- pending_send = COMBINED_DIRECTION_GET_PENDING_SEND
- (stream->combined_direction);
-
- /* if we have a pending local send flag set, the signalled (ie understood
- * by both ends) direction of the stream is assuming that we are actually
- * sending, so we should OR that into the direction before deciding what
- * to signal the stream with. we don't need to consider pending remote
- * send because it doesn't happen in Jingle */
-
- if ((pending_send & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0)
- direction |= TP_MEDIA_STREAM_DIRECTION_SEND;
-
- if (direction != TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL)
- {
- const gchar *senders;
- senders = _direction_to_senders (session, direction);
- lm_message_node_set_attribute (content_node, "senders", senders);
- }
- }
-
- _gabble_media_stream_content_node_add_description (stream, content_node);
-
- _gabble_media_stream_content_node_add_transport (stream, content_node);
-}
-
-static void
-_add_content_descriptions (GabbleMediaSession *session,
- LmMessageNode *session_node,
- JingleInitiator stream_initiator)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
- if (stream->initiator != stream_initiator)
- {
- GMS_DEBUG_INFO (session,
- "not adding content description for %s stream %s",
- stream->initiator == INITIATOR_LOCAL ? "local" : "remote",
- stream->name);
- continue;
- }
-
- _add_content_descriptions_one (session, stream, session_node);
- }
-}
-
-static LmHandlerResult
-accept_msg_reply_cb (GabbleConnection *conn,
- LmMessage *sent_msg,
- LmMessage *reply_msg,
- GObject *object,
- gpointer user_data)
-{
- GabbleMediaSession *session = GABBLE_MEDIA_SESSION (object);
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- guint i;
-
- MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (session, "accept failed");
-
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
- if (stream->initiator == INITIATOR_LOCAL)
- continue;
-
- _gabble_media_stream_update_sending (stream, TRUE);
- }
-
- g_object_set (session, "state", JS_STATE_ACTIVE, NULL);
-
- return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static gboolean
-_stream_not_ready_for_accept (GabbleMediaSession *session,
- GabbleMediaStream *stream)
-{
- /* locally initiated streams shouldn't delay acceptance */
- if (stream->initiator == INITIATOR_LOCAL)
- return FALSE;
-
- if (!stream->got_local_codecs)
- {
- GMS_DEBUG_INFO (session, "stream %s does not yet have local codecs",
- stream->name);
-
- return TRUE;
- }
-
- if (stream->connection_state != TP_MEDIA_STREAM_STATE_CONNECTED)
- {
- GMS_DEBUG_INFO (session, "stream %s is not yet connected", stream->name);
-
- return TRUE;
- }
-
- return FALSE;
-}
-
-static void
-try_session_accept (GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- LmMessage *msg;
- LmMessageNode *session_node;
- const gchar *action;
- guint i;
-
- if (priv->state < JS_STATE_ACTIVE && !priv->locally_accepted)
- {
- GMS_DEBUG_INFO (session, "not sending accept yet, waiting for local "
- "user to accept call");
- return;
- }
-
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
- if (_stream_not_ready_for_accept (session, stream))
- {
- GMS_DEBUG_INFO (session, "not sending accept yet, found a stream "
- "which was not yet connected or was missing local codecs");
- return;
- }
- }
-
- if (priv->mode == MODE_GOOGLE)
- action = "accept";
- else
- action = "session-accept";
-
- /* construct a session acceptance message */
- msg = _gabble_media_session_message_new (session, action, &session_node);
-
- /* only accept REMOTE streams; any LOCAL streams were added by the local
- * user before accepting and should be signalled after the accept */
- _add_content_descriptions (session, session_node, INITIATOR_REMOTE);
-
- GMS_DEBUG_INFO (session, "sending jingle session action \"%s\" to peer",
- action);
-
- /* send the final acceptance message */
- _gabble_connection_send_with_reply (priv->conn, msg, accept_msg_reply_cb,
- G_OBJECT (session), NULL, NULL);
-
- lm_message_unref (msg);
-
- /* set remote streams playing */
- _set_remote_streams_playing (session);
-
- g_object_set (session, "state", JS_STATE_PENDING_ACCEPT_SENT, NULL);
-}
-
-static LmHandlerResult
-content_accept_msg_reply_cb (GabbleConnection *conn,
- LmMessage *sent_msg,
- LmMessage *reply_msg,
- GObject *object,
- gpointer user_data)
-{
- GabbleMediaSession *session = GABBLE_MEDIA_SESSION (user_data);
- GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object);
-
- if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT)
- {
- GMS_DEBUG_ERROR (session, "content-accept failed; removing stream");
- NODE_DEBUG (sent_msg->node, "message sent");
- NODE_DEBUG (reply_msg->node, "message reply");
-
- _gabble_media_session_remove_streams (session, &stream, 1);
-
- return LM_HANDLER_RESULT_REMOVE_MESSAGE;
- }
-
- _gabble_media_stream_update_sending (stream, TRUE);
-
- return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static void
-try_content_accept (GabbleMediaSession *session,
- GabbleMediaStream *stream)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- LmMessage *msg;
- LmMessageNode *session_node;
-
- g_assert (priv->state == JS_STATE_ACTIVE);
- g_assert (priv->mode == MODE_JINGLE);
-
- if (_stream_not_ready_for_accept (session, stream))
- {
- GMS_DEBUG_INFO (session, "not sending content-accept yet, stream %s "
- "is disconnected or missing local codecs", stream->name);
- return;
- }
-
- /* send a content acceptance message */
- msg = _gabble_media_session_message_new (session, "content-accept",
- &session_node);
-
- _add_content_descriptions_one (session, stream, session_node);
-
- GMS_DEBUG_INFO (session, "sending jingle session action \"content-accept\" "
- "to peer for stream %s", stream->name);
-
- _gabble_connection_send_with_reply (priv->conn, msg,
- content_accept_msg_reply_cb, G_OBJECT (stream), session, NULL);
-
- lm_message_unref (msg);
-
- /* set stream playing */
- g_object_set (stream, "playing", TRUE, NULL);
-}
-
-static LmHandlerResult
-initiate_msg_reply_cb (GabbleConnection *conn,
- LmMessage *sent_msg,
- LmMessage *reply_msg,
- GObject *object,
- gpointer user_data)
-{
- GabbleMediaSession *session = GABBLE_MEDIA_SESSION (object);
-
- MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (session, "initiate failed");
-
- g_object_set (session, "state", JS_STATE_PENDING_INITIATED, NULL);
-
- /* mark all of the streams that we sent in the initiate as acknowledged */
- _mark_local_streams_acked (session);
-
- return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static gboolean
-_stream_not_ready_for_initiate (GabbleMediaSession *session,
- GabbleMediaStream *stream)
-{
- if (!stream->got_local_codecs)
- {
- GMS_DEBUG_INFO (session, "stream %s does not yet have local codecs",
- stream->name);
-
- return TRUE;
- }
-
- return FALSE;
-}
-
-static void
-try_session_initiate (GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- LmMessage *msg;
- LmMessageNode *session_node;
- const gchar *action;
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
-
- if (_stream_not_ready_for_initiate (session, stream))
- {
- GMS_DEBUG_INFO (session, "not sending initiate yet, found a stream "
- "which was missing local codecs");
- return;
- }
- }
-
- if (priv->mode == MODE_GOOGLE)
- action = "initiate";
- else
- action = "session-initiate";
-
- msg = _gabble_media_session_message_new (session, action, &session_node);
-
- _add_content_descriptions (session, session_node, INITIATOR_LOCAL);
-
- GMS_DEBUG_INFO (session, "sending jingle action \"%s\" to peer", action);
-
- _gabble_connection_send_with_reply (priv->conn, msg, initiate_msg_reply_cb,
- G_OBJECT (session), NULL, NULL);
-
- lm_message_unref (msg);
-
- /* mark local streams as sent (so that eg candidates will be sent) */
- _mark_local_streams_sent (session);
-
- g_object_set (session, "state", JS_STATE_PENDING_INITIATE_SENT, NULL);
-}
-
-static LmHandlerResult
-content_add_msg_reply_cb (GabbleConnection *conn,
- LmMessage *sent_msg,
- LmMessage *reply_msg,
- GObject *object,
- gpointer user_data)
-{
- GabbleMediaSession *session = GABBLE_MEDIA_SESSION (user_data);
- GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object);
-
- if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT)
- {
- if (session->initiator == INITIATOR_REMOTE &&
- stream->signalling_state == STREAM_SIG_STATE_ACKNOWLEDGED)
- {
- GMS_DEBUG_INFO (session, "ignoring content-add failure, stream has "
- "been successfully created by the session initiator");
- }
- else
- {
- GMS_DEBUG_ERROR (session, "content-add failed; removing stream");
- NODE_DEBUG (sent_msg->node, "message sent");
- NODE_DEBUG (reply_msg->node, "message reply");
-
- _gabble_media_stream_close (stream);
- }
- }
- else
- {
- if (stream->signalling_state == STREAM_SIG_STATE_SENT)
- {
- GMS_DEBUG_INFO (session, "content-add succeeded, marking stream as "
- "ACKNOWLEDGED");
-
- g_object_set (stream,
- "signalling-state", STREAM_SIG_STATE_ACKNOWLEDGED,
- NULL);
- }
- else
- {
- GMS_DEBUG_INFO (session, "content-add succeeded, but not marking"
- "stream as ACKNOWLEDGED, it's in state %d",
- stream->signalling_state);
- }
- }
-
- return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static void
-do_content_add (GabbleMediaSession *session,
- GabbleMediaStream *stream)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- LmMessage *msg;
- LmMessageNode *session_node;
-
- g_assert (priv->state == JS_STATE_ACTIVE);
- g_assert (priv->mode == MODE_JINGLE);
-
- if (_stream_not_ready_for_initiate (session, stream))
- {
- GMS_DEBUG_ERROR (session, "trying to send content-add for stream %s "
- "but we have no local codecs. what?!", stream->name);
- g_assert_not_reached ();
- return;
- }
-
- msg = _gabble_media_session_message_new (session, "content-add",
- &session_node);
-
- _add_content_descriptions_one (session, stream, session_node);
-
- GMS_DEBUG_INFO (session, "sending jingle action \"content-add\" to peer for "
- "stream %s", stream->name);
-
- _gabble_connection_send_with_reply (priv->conn, msg,
- content_add_msg_reply_cb, G_OBJECT (stream), session, NULL);
-
- lm_message_unref (msg);
-
- /* mark stream as sent */
- g_object_set (stream, "signalling-state", STREAM_SIG_STATE_SENT, NULL);
-}
-
-static void
-stream_connection_state_changed_cb (GabbleMediaStream *stream,
- GParamSpec *param,
- GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv;
-
- g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- if (stream->connection_state != TP_MEDIA_STREAM_STATE_CONNECTED)
- return;
-
- GMS_DEBUG_INFO (session, "stream %s has gone connected", stream->name);
-
- if (stream->playing)
- {
- GMS_DEBUG_INFO (session, "doing nothing, stream is already playing");
- return;
- }
-
- /* after session is active, we do things per-stream with content-* actions */
- if (priv->state < JS_STATE_ACTIVE)
- {
- /* send a session accept if the session was initiated by the peer */
- if (session->initiator == INITIATOR_REMOTE)
- {
- try_session_accept (session);
- }
- else
- {
- GMS_DEBUG_INFO (session, "session initiated by us, so we're not "
- "going to consider sending an accept");
- }
- }
- else
- {
- /* send a content accept if the stream was added by the peer */
- if (stream->initiator == INITIATOR_REMOTE)
- {
- try_content_accept (session, stream);
- }
- else
- {
- GMS_DEBUG_INFO (session, "stream added by us, so we're not going "
- "to send an accept");
- }
- }
-}
-
-static void
-stream_got_local_codecs_changed_cb (GabbleMediaStream *stream,
- GParamSpec *param,
- GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv;
-
- g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- if (!stream->got_local_codecs)
- return;
-
- GMS_DEBUG_INFO (session, "stream %s has got local codecs", stream->name);
-
- if (stream->playing)
- {
- GMS_DEBUG_ERROR (session, "stream was already playing and we got local "
- "codecs. what?!");
- g_assert_not_reached ();
- return;
- }
-
- /* after session is active, we do things per-stream with content-* actions */
- if (priv->state < JS_STATE_ACTIVE)
- {
- if (session->initiator == INITIATOR_REMOTE)
- {
- if (priv->state < JS_STATE_PENDING_ACCEPT_SENT)
- {
- try_session_accept (session);
- }
- else
- {
- GMS_DEBUG_INFO (session, "stream added after sending accept; "
- "not doing content-add until remote end acknowledges");
- }
- }
- else
- {
- if (priv->state < JS_STATE_PENDING_INITIATE_SENT)
- {
- try_session_initiate (session);
- }
- else
- {
- GMS_DEBUG_INFO (session, "stream added after sending initiate; "
- "not doing content-add until remote end accepts");
- }
- }
- }
- else
- {
- if (stream->initiator == INITIATOR_REMOTE)
- {
- try_content_accept (session, stream);
- }
- else
- {
- do_content_add (session, stream);
- }
- }
-}
-
-static gchar *
-get_jid_for_contact (GabbleMediaSession *session,
- TpHandle handle)
-{
- GabbleMediaSessionPrivate *priv;
- TpBaseConnection *conn;
- const gchar *base_jid;
- TpHandle self;
- TpHandleRepoIface *contact_handles;
-
- g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- conn = (TpBaseConnection *) priv->conn;
- contact_handles = tp_base_connection_get_handles (conn,
- TP_HANDLE_TYPE_CONTACT);
- self = conn->self_handle;
-
- base_jid = tp_handle_inspect (contact_handles, handle);
- g_assert (base_jid != NULL);
-
- if (handle == self)
- {
- gchar *resource, *ret;
- g_object_get (priv->conn, "resource", &resource, NULL);
- g_assert (resource != NULL);
- ret = g_strdup_printf ("%s/%s", base_jid, resource);
- g_free (resource);
- return ret;
- }
- else
- {
- g_assert (priv->peer_resource != NULL);
- return g_strdup_printf ("%s/%s", base_jid, priv->peer_resource);
- }
-}
-
-LmMessage *
-_gabble_media_session_message_new (GabbleMediaSession *session,
- const gchar *action,
- LmMessageNode **session_node)
-{
- GabbleMediaSessionPrivate *priv;
- TpBaseConnection *conn;
- LmMessage *msg;
- LmMessageNode *iq_node, *node;
- gchar *peer_jid, *initiator_jid;
- TpHandle initiator_handle;
- const gchar *element, *xmlns;
-
- g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- conn = (TpBaseConnection *) priv->conn;
-
- peer_jid = get_jid_for_contact (session, priv->peer);
-
- msg = lm_message_new_with_sub_type (
- peer_jid,
- LM_MESSAGE_TYPE_IQ,
- LM_MESSAGE_SUB_TYPE_SET);
-
- g_free (peer_jid);
-
- iq_node = lm_message_get_node (msg);
-
- if (priv->mode == MODE_GOOGLE)
- element = "session";
- else
- element = "jingle";
-
- if (session->initiator == INITIATOR_LOCAL)
- initiator_handle = conn->self_handle;
- else
- initiator_handle = priv->peer;
-
- node = lm_message_node_add_child (iq_node, element, NULL);
- initiator_jid = get_jid_for_contact (session, initiator_handle);
-
- lm_message_node_set_attributes (node,
- (priv->mode == MODE_GOOGLE) ? "id" : "sid", priv->id,
- (priv->mode == MODE_GOOGLE) ? "type" : "action", action,
- "initiator", initiator_jid,
- NULL);
-
- if (priv->mode == MODE_GOOGLE)
- xmlns = NS_GOOGLE_SESSION;
- else
- xmlns = NS_JINGLE;
-
- lm_message_node_set_attribute (node, "xmlns", xmlns);
- g_free (initiator_jid);
-
- if (session_node)
- *session_node = node;
-
- return msg;
-}
-
-void
-_gabble_media_session_accept (GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- guint i;
-
- priv->locally_accepted = TRUE;
-
- /* accept any local pending sends */
- for (i = 0; i < priv->streams->len; i++)
- {
- GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i);
- CombinedStreamDirection combined_dir = stream->combined_direction;
- TpMediaStreamDirection current_dir;
- TpMediaStreamPendingSend pending_send;
-
- current_dir = COMBINED_DIRECTION_GET_DIRECTION (combined_dir);
- pending_send = COMBINED_DIRECTION_GET_PENDING_SEND (combined_dir);
-
- if ((pending_send & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0)
- {
- GMS_DEBUG_INFO (session, "accepting pending local send on stream %s",
- stream->name);
-
- current_dir |= TP_MEDIA_STREAM_DIRECTION_SEND;
- pending_send &= ~TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
- combined_dir = MAKE_COMBINED_DIRECTION (current_dir, pending_send);
- g_object_set (stream, "combined-direction", combined_dir, NULL);
- _gabble_media_stream_update_sending (stream, FALSE);
- }
- }
-
- try_session_accept (session);
-}
-
-static LmHandlerResult
-content_remove_msg_reply_cb (GabbleConnection *conn,
- LmMessage *sent_msg,
- LmMessage *reply_msg,
- GObject *object,
- gpointer user_data)
-{
- GabbleMediaSession *session = GABBLE_MEDIA_SESSION (object);
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- GPtrArray *removing = (GPtrArray *) user_data;
- guint i;
-
- MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (session,
- "stream removal failed");
-
- for (i = 0; i < removing->len; i++)
- destroy_media_stream (session,
- GABBLE_MEDIA_STREAM (g_ptr_array_index (removing, i)));
-
- g_ptr_array_remove_fast (priv->remove_requests, removing);
- g_ptr_array_free (removing, TRUE);
-
- return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-void
-_gabble_media_session_remove_streams (GabbleMediaSession *session,
- GabbleMediaStream **streams,
- guint len)
-{
- GabbleMediaSessionPrivate *priv;
- LmMessage *msg = NULL;
- LmMessageNode *session_node;
- GPtrArray *removing = NULL;
- guint i;
-
- g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- /* end the session if there'd be no streams left after reducing it */
- if (_count_non_removing_streams (session) == len)
- {
- _gabble_media_session_terminate (session, INITIATOR_LOCAL,
- TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
- return;
- }
-
- /* construct a remove message if we're in a state greater than CREATED (ie
- * something has been sent/received about this session) */
- if (priv->state > JS_STATE_PENDING_CREATED)
- {
- msg = _gabble_media_session_message_new (session, "content-remove",
- &session_node);
- removing = g_ptr_array_sized_new (len);
- }
-
- /* right, remove them */
- for (i = 0; i < len; i++)
- {
- GabbleMediaStream *stream = streams[i];
-
- switch (stream->signalling_state)
- {
- case STREAM_SIG_STATE_NEW:
- destroy_media_stream (session, stream);
- break;
- case STREAM_SIG_STATE_SENT:
- case STREAM_SIG_STATE_ACKNOWLEDGED:
- {
- g_assert (priv->state > JS_STATE_PENDING_CREATED);
- g_assert (msg != NULL);
- g_assert (removing != NULL);
- g_assert (session_node != NULL);
-
- _gabble_media_stream_add_content_node (stream, session_node);
-
- g_object_set (stream,
- "playing", FALSE,
- "signalling-state", STREAM_SIG_STATE_REMOVING,
- NULL);
-
- /* close the stream now, but don't forget about it until the
- * removal message is acknowledged, since we need to be able to
- * detect content-remove cross-talk */
- _gabble_media_stream_close (stream);
- g_ptr_array_add (removing, stream);
- }
- break;
- case STREAM_SIG_STATE_REMOVING:
- break;
- }
- }
-
- /* send the remove message if necessary */
- if (msg != NULL)
- {
- if (removing->len > 0)
- {
- GMS_DEBUG_INFO (session, "sending jingle session action "
- "\"content-remove\" to peer");
-
- _gabble_connection_send_with_reply (priv->conn, msg,
- content_remove_msg_reply_cb, G_OBJECT (session), removing, NULL);
-
- g_ptr_array_add (priv->remove_requests, removing);
- }
- else
- {
- g_ptr_array_free (removing, TRUE);
- }
-
- lm_message_unref (msg);
- }
- else
- {
- GMS_DEBUG_INFO (session, "not sending jingle session action "
- "\"content-remove\" to peer, no initiates or adds sent for "
- "these streams");
- if (priv->state < JS_STATE_PENDING_INITIATE_SENT)
- {
- try_session_initiate (session);
- }
- }
-}
-
-static void
-send_reject_message (GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- LmMessage *msg;
- LmMessageNode *session_node;
-
- /* this should only happen in google mode, and we should only arrive in that
- * mode when we've ended up talking to a resource that doesn't support
- * jingle */
- g_assert (priv->mode == MODE_GOOGLE);
- g_assert (priv->peer_resource != NULL);
-
- /* construct a session terminate message */
- msg = _gabble_media_session_message_new (session, "reject", &session_node);
-
- GMS_DEBUG_INFO (session, "sending jingle session action \"reject\" to peer");
-
- /* send it */
- _gabble_connection_send_with_reply (priv->conn, msg, NULL,
- G_OBJECT (session), NULL, NULL);
-
- lm_message_unref (msg);
-}
-
-static void
-send_terminate_message (GabbleMediaSession *session)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- const gchar *action;
- LmMessage *msg;
- LmMessageNode *session_node;
-
- /* construct a session terminate message */
- if (priv->mode == MODE_GOOGLE)
- action = "terminate";
- else
- action = "session-terminate";
-
- msg = _gabble_media_session_message_new (session, action, &session_node);
-
- GMS_DEBUG_INFO (session, "sending jingle session action \"%s\" to peer",
- action);
-
- /* send it */
- _gabble_connection_send_with_reply (priv->conn, msg, NULL,
- G_OBJECT (session), NULL, NULL);
-
- lm_message_unref (msg);
-}
-
-void
-_gabble_media_session_terminate (GabbleMediaSession *session,
- JingleInitiator who,
- TpChannelGroupChangeReason why)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
- TpHandle actor;
-
- if (priv->state == JS_STATE_ENDED)
- return;
-
- if (who == INITIATOR_REMOTE)
- {
- actor = priv->peer;
- }
- else
- {
- actor = conn->self_handle;
-
- /* Need to tell them that it's all over. */
-
- /* Jingle doesn't have a "reject" action; a termination before an
- * acceptance indicates that the call has been declined */
-
- if (session->initiator == INITIATOR_REMOTE &&
- priv->state == JS_STATE_PENDING_INITIATED &&
- priv->mode == MODE_GOOGLE)
- {
- send_reject_message (session);
- }
-
- /* if we're still in CREATED, then we've not sent or received any
- * messages about this session yet, so no terminate is necessary */
- else if (priv->state > JS_STATE_PENDING_CREATED)
- {
- send_terminate_message (session);
- }
-
- while (priv->streams->len > 0)
- destroy_media_stream (session, g_ptr_array_index (priv->streams, 0));
- }
-
- priv->terminated = TRUE;
- g_object_set (session, "state", JS_STATE_ENDED, NULL);
- g_signal_emit (session, signals[TERMINATED], 0, actor, why);
-}
-
-#if defined (ENABLE_DEBUG) && _GMS_DEBUG_LEVEL
-void
-_gabble_media_session_debug (GabbleMediaSession *session,
- DebugMessageType type,
- const gchar *format, ...)
-{
- if (DEBUGGING)
- {
- va_list list;
- gchar buf[512];
- GabbleMediaSessionPrivate *priv;
- time_t curtime;
- struct tm *loctime;
- gchar stamp[10];
- const gchar *type_str;
-
- g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- curtime = time (NULL);
- loctime = localtime (&curtime);
-
- strftime (stamp, sizeof (stamp), "%T", loctime);
-
- va_start (list, format);
-
- vsnprintf (buf, sizeof (buf), format, list);
-
- va_end (list);
-
- switch (type) {
- case DEBUG_MSG_INFO:
- type_str = TP_ANSI_BOLD_ON TP_ANSI_FG_WHITE;
- break;
- case DEBUG_MSG_DUMP:
- type_str = TP_ANSI_BOLD_ON TP_ANSI_FG_GREEN;
- break;
- case DEBUG_MSG_WARNING:
- type_str = TP_ANSI_BOLD_ON TP_ANSI_FG_YELLOW;
- break;
- case DEBUG_MSG_ERROR:
- type_str = TP_ANSI_BOLD_ON TP_ANSI_FG_WHITE TP_ANSI_BG_RED;
- break;
- case DEBUG_MSG_EVENT:
- type_str = TP_ANSI_BOLD_ON TP_ANSI_FG_CYAN;
- break;
- default:
- g_assert_not_reached ();
- return;
- }
-
- printf ("[%s%s%s] %s%-26s%s %s%s%s\n",
- TP_ANSI_BOLD_ON TP_ANSI_FG_WHITE,
- stamp,
- TP_ANSI_RESET,
- session_states[priv->state].attributes,
- session_states[priv->state].name,
- TP_ANSI_RESET,
- type_str,
- buf,
- TP_ANSI_RESET);
-
- fflush (stdout);
- }
-}
-
-#endif /* _GMS_DEBUG_LEVEL */
-
-static const gchar *
-_name_stream (GabbleMediaSession *session,
- TpMediaStreamType media_type)
-{
- GabbleMediaSessionPrivate *priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- static gchar ret[MAX_STREAM_NAME_LEN] = GTALK_STREAM_NAME;
-
- if (priv->mode != MODE_GOOGLE)
- {
- guint i = 1;
-
- do {
- g_snprintf (ret, MAX_STREAM_NAME_LEN, "%s%u",
- media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video",
- i++);
-
- /* even though we now have seperate namespaces for local and remote,
- * actually check in both so that we can still support clients which
- * have 1 namespace (such as our older selves :D) */
- if (_lookup_stream_by_name_and_initiator (session, ret,
- INITIATOR_INVALID) != NULL)
- {
- ret[0] = '\0';
- }
- } while (ret[0] == '\0');
- }
-
- return ret;
-}
-
-
-gboolean
-_gabble_media_session_request_streams (GabbleMediaSession *session,
- const GArray *media_types,
- GPtrArray **ret,
- GError **error)
-{
- /* if you change the caps in this function, you almost certainly also need
- * to change _gabble_media_channel_typeflags_to_caps and
- * _gabble_media_channel_caps_to_typeflags in media-channel.c */
- static GabblePresenceCapabilities google_audio_caps =
- PRESENCE_CAP_GOOGLE_VOICE;
- static GabblePresenceCapabilities jingle_audio_caps =
- PRESENCE_CAP_JINGLE | PRESENCE_CAP_JINGLE_DESCRIPTION_AUDIO |
- PRESENCE_CAP_GOOGLE_TRANSPORT_P2P;
- static GabblePresenceCapabilities jingle_video_caps =
- PRESENCE_CAP_JINGLE | PRESENCE_CAP_JINGLE_DESCRIPTION_VIDEO |
- PRESENCE_CAP_GOOGLE_TRANSPORT_P2P;
-
- GabbleMediaSessionPrivate *priv;
- GabblePresence *presence;
- gboolean want_audio, want_video;
- GabblePresenceCapabilities jingle_desired_caps;
- guint idx;
- gchar *dump;
-
- g_assert (GABBLE_IS_MEDIA_SESSION (session));
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- presence = gabble_presence_cache_get (priv->conn->presence_cache,
- priv->peer);
-
- if (presence == NULL)
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "member has no audio/video capabilities");
-
- return FALSE;
- }
-
- dump = gabble_presence_dump (presence);
- GMS_DEBUG_DUMP (session, "presence for peer %d:\n%s", priv->peer, dump);
- g_free (dump);
-
- want_audio = want_video = FALSE;
-
- for (idx = 0; idx < media_types->len; idx++)
- {
- guint media_type = g_array_index (media_types, guint, idx);
-
- if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO)
- {
- want_audio = TRUE;
- }
- else if (media_type == TP_MEDIA_STREAM_TYPE_VIDEO)
- {
- want_video = TRUE;
- }
- else
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
- "given media type %u is invalid", media_type);
- return FALSE;
- }
- }
-
- /* work out what we'd need to do these streams with jingle */
- jingle_desired_caps = 0;
-
- if (want_audio)
- jingle_desired_caps |= jingle_audio_caps;
-
- if (want_video)
- jingle_desired_caps |= jingle_video_caps;
-
- GMS_DEBUG_INFO (session, "want audio: %s; want video: %s",
- want_audio ? "yes" : "no", want_video ? "yes" : "no");
-
- /* existing call; the recipient and the mode has already been decided */
- if (priv->peer_resource)
- {
- /* is a google call... we have no other option */
- if (priv->mode == MODE_GOOGLE)
- {
- GMS_DEBUG_INFO (session, "already in Google mode; can't add new "
- "stream");
-
- g_assert (priv->streams->len == 1);
-
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "Google Talk calls may only contain one stream");
-
- return FALSE;
- }
-
- if (!gabble_presence_resource_has_caps (presence, priv->peer_resource,
- jingle_desired_caps))
- {
- GMS_DEBUG_INFO (session,
- "in Jingle mode but have insufficient caps for requested streams");
-
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "existing call member doesn't support all requested media"
- " types");
-
- return FALSE;
- }
-
- GMS_DEBUG_INFO (session,
- "in Jingle mode, and have necessary caps");
- }
-
- /* no existing call; we should choose a recipient and a mode */
- else
- {
- const gchar *resource;
-
- g_assert (priv->streams->len == 0);
-
- /* see if we have a fully-capable jingle resource; regardless of the
- * desired media type it's best if we can add/remove the others later */
- resource = gabble_presence_pick_resource_by_caps (presence,
- jingle_audio_caps | jingle_video_caps);
-
- if (resource == NULL)
- {
- GMS_DEBUG_INFO (session, "contact is not fully jingle-capable");
-
- /* ok, no problem. see if we can do just what's wanted with jingle */
- resource = gabble_presence_pick_resource_by_caps (presence,
- jingle_desired_caps);
-
- if (resource == NULL && want_audio && !want_video)
- {
- GMS_DEBUG_INFO (session,
- "contact doesn't have desired Jingle capabilities");
-
- /* last ditch... if we want only audio and not video, we can make
- * do with google talk */
- resource = gabble_presence_pick_resource_by_caps (presence,
- google_audio_caps);
-
- if (resource != NULL)
- {
- /* only one stream possible with google */
- if (media_types->len == 1)
- {
- GMS_DEBUG_INFO (session,
- "contact has no Jingle capabilities; "
- "falling back to Google audio call");
- priv->mode = MODE_GOOGLE;
- }
- else
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "Google Talk calls may only contain one stream");
-
- return FALSE;
- }
- }
- else
- {
- GMS_DEBUG_INFO (session,
- "contact doesn't have desired Google capabilities");
- }
- }
- }
-
- if (resource == NULL)
- {
- GMS_DEBUG_INFO (session,
- "contact doesn't have a resource with suitable capabilities");
-
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "member does not have the desired audio/video capabilities");
-
- return FALSE;
- }
-
- priv->peer_resource = g_strdup (resource);
- }
-
- /* check it's not a ridiculous number of streams */
- if ((priv->streams->len + media_types->len) > MAX_STREAMS)
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "I think that's quite enough streams already");
- return FALSE;
- }
-
- /* if we've got here, we're good to make the streams */
-
- *ret = g_ptr_array_sized_new (media_types->len);
-
- for (idx = 0; idx < media_types->len; idx++)
- {
- guint media_type = g_array_index (media_types, guint, idx);
- GabbleMediaStream *stream;
- const gchar *stream_name;
-
- if (priv->mode == MODE_GOOGLE)
- stream_name = GTALK_STREAM_NAME;
- else
- stream_name = _name_stream (session, media_type);
-
- stream = create_media_stream (session, stream_name, INITIATOR_LOCAL,
- media_type);
-
- g_ptr_array_add (*ret, stream);
- }
-
- return TRUE;
-}
-
-static const gchar *
-_direction_to_senders (GabbleMediaSession *session,
- TpMediaStreamDirection dir)
-{
- const gchar *ret = NULL;
-
- switch (dir)
- {
- case TP_MEDIA_STREAM_DIRECTION_NONE:
- g_assert_not_reached ();
- break;
- case TP_MEDIA_STREAM_DIRECTION_SEND:
- if (session->initiator == INITIATOR_LOCAL)
- ret = "initiator";
- else
- ret = "responder";
- break;
- case TP_MEDIA_STREAM_DIRECTION_RECEIVE:
- if (session->initiator == INITIATOR_REMOTE)
- ret = "initiator";
- else
- ret = "responder";
- break;
- case TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL:
- ret = "both";
- break;
- }
-
- g_assert (ret != NULL);
-
- return ret;
-}
-
-static LmHandlerResult
-direction_msg_reply_cb (GabbleConnection *conn,
- LmMessage *sent_msg,
- LmMessage *reply_msg,
- GObject *object,
- gpointer user_data)
-{
- GabbleMediaSession *session = GABBLE_MEDIA_SESSION (user_data);
- GabbleMediaStream *stream = GABBLE_MEDIA_STREAM (object);
-
- MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (session,
- "direction change failed");
-
- if (stream->playing)
- {
- _gabble_media_stream_update_sending (stream, TRUE);
- }
-
- return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static gboolean
-send_direction_change (GabbleMediaSession *session,
- GabbleMediaStream *stream,
- TpMediaStreamDirection dir,
- GError **error)
-{
- GabbleMediaSessionPrivate *priv;
- const gchar *senders;
- LmMessage *msg;
- LmMessageNode *session_node, *content_node;
- gboolean ret;
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
- senders = _direction_to_senders (session, dir);
-
- if (stream->signalling_state == STREAM_SIG_STATE_NEW ||
- stream->signalling_state == STREAM_SIG_STATE_REMOVING)
- {
- GMS_DEBUG_INFO (session, "not sending content-modify for %s stream %s",
- stream->signalling_state == STREAM_SIG_STATE_NEW ? "new" : "removing",
- stream->name);
- return TRUE;
- }
-
- GMS_DEBUG_INFO (session, "sending jingle session action \"content-modify\" "
- "to peer for stream %s (senders=%s)", stream->name, senders);
-
- msg = _gabble_media_session_message_new (session, "content-modify",
- &session_node);
- content_node = _gabble_media_stream_add_content_node (stream, session_node);
-
- lm_message_node_set_attribute (content_node, "senders", senders);
-
- ret = _gabble_connection_send_with_reply (priv->conn, msg,
- direction_msg_reply_cb, G_OBJECT (stream), session, error);
-
- lm_message_unref (msg);
-
- return ret;
-}
-
-gboolean
-_gabble_media_session_request_stream_direction (GabbleMediaSession *session,
- GabbleMediaStream *stream,
- TpMediaStreamDirection requested_dir,
- GError **error)
-{
- GabbleMediaSessionPrivate *priv;
- CombinedStreamDirection new_combined_dir;
- TpMediaStreamDirection current_dir; //, new_dir;
- TpMediaStreamPendingSend pending_send;
-
- priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (session);
-
- current_dir = COMBINED_DIRECTION_GET_DIRECTION (stream->combined_direction);
- pending_send = COMBINED_DIRECTION_GET_PENDING_SEND
- (stream->combined_direction);
-
- if (priv->mode == MODE_GOOGLE)
- {
- g_assert (current_dir == TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
-
- if (requested_dir == TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL)
- return TRUE;
-
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "Google Talk calls can only be bi-directional");
- return FALSE;
- }
-
- if (requested_dir == TP_MEDIA_STREAM_DIRECTION_NONE)
- {
- GMS_DEBUG_INFO (session, "request for NONE direction; removing stream");
-
- _gabble_media_session_remove_streams (session, &stream, 1);
-
- return TRUE;
- }
-
- /* if we're awaiting a local decision on sending... */
- if ((pending_send & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0)
- {
- /* clear the flag */
- pending_send &= ~TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
-
- /* make our current_dir match what other end thinks (he thinks we're
- * bidirectional) so that we send the correct transitions */
- current_dir ^= TP_MEDIA_STREAM_DIRECTION_SEND;
- }
-
-#if 0
- /* if we're asking the remote end to start sending, set the pending flag and
- * don't change our directionality just yet */
- new_dir = requested_dir;
- if (((current_dir & TP_MEDIA_STREAM_DIRECTION_RECEIVE) == 0) &&
- ((new_dir & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 0))
- {
- pending_send ^= TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
- new_dir &= ~TP_MEDIA_STREAM_DIRECTION_RECEIVE;
- }
-#endif
-
- /* make any necessary changes */
- new_combined_dir = MAKE_COMBINED_DIRECTION (requested_dir, pending_send);
- if (new_combined_dir != stream->combined_direction)
- {
- g_object_set (stream, "combined-direction", new_combined_dir, NULL);
- _gabble_media_stream_update_sending (stream, FALSE);
- }
-
- /* short-circuit sending a request if we're not asking for anything new */
- if (current_dir == requested_dir)
- return TRUE;
-
- /* send request */
- return send_direction_change (session, stream, requested_dir, error);
-}
-
-static void
-session_handler_iface_init (gpointer g_iface, gpointer iface_data)
-{
- TpSvcMediaSessionHandlerClass *klass =
- (TpSvcMediaSessionHandlerClass *) g_iface;
-
-#define IMPLEMENT(x) tp_svc_media_session_handler_implement_##x (\
- klass, gabble_media_session_##x)
- IMPLEMENT(error);
- IMPLEMENT(ready);
-#undef IMPLEMENT
-}
diff --git a/src/media-session.h b/src/media-session.h
deleted file mode 100644
index a7765f0..0000000
--- a/src/media-session.h
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * gabble-media-session.h - Header for GabbleMediaSession
- * Copyright (C) 2006 Collabora Ltd.
- * Copyright (C) 2006 Nokia Corporation
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#ifndef __GABBLE_MEDIA_SESSION_H__
-#define __GABBLE_MEDIA_SESSION_H__
-
-#include <glib-object.h>
-#include <loudmouth/loudmouth.h>
-
-#include "types.h"
-#include "media-stream.h"
-#include <telepathy-glib/enums.h>
-
-G_BEGIN_DECLS
-
-typedef enum
-{
- MODE_GOOGLE,
- MODE_JINGLE
-} GabbleMediaSessionMode;
-
-typedef enum {
- JS_STATE_INVALID = -1,
- JS_STATE_PENDING_CREATED = 0,
- JS_STATE_PENDING_INITIATE_SENT,
- JS_STATE_PENDING_INITIATED,
- JS_STATE_PENDING_ACCEPT_SENT,
- JS_STATE_ACTIVE,
- JS_STATE_ENDED
-} JingleSessionState;
-
-typedef enum {
- DEBUG_MSG_INFO = 0,
- DEBUG_MSG_DUMP,
- DEBUG_MSG_WARNING,
- DEBUG_MSG_ERROR,
- DEBUG_MSG_EVENT
-} DebugMessageType;
-
-typedef struct _GabbleMediaSession GabbleMediaSession;
-typedef struct _GabbleMediaSessionClass GabbleMediaSessionClass;
-typedef struct _GabbleMediaSessionPrivate GabbleMediaSessionPrivate;
-
-struct _GabbleMediaSessionClass {
- GObjectClass parent_class;
-};
-
-struct _GabbleMediaSession {
- GObject parent;
-
- JingleInitiator initiator;
-
- GabbleMediaSessionPrivate *priv;
-};
-
-GType gabble_media_session_get_type (void);
-
-/* TYPE MACROS */
-#define GABBLE_TYPE_MEDIA_SESSION \
- (gabble_media_session_get_type ())
-#define GABBLE_MEDIA_SESSION(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST((obj), GABBLE_TYPE_MEDIA_SESSION, \
- GabbleMediaSession))
-#define GABBLE_MEDIA_SESSION_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), GABBLE_TYPE_MEDIA_SESSION, \
- GabbleMediaSessionClass))
-#define GABBLE_IS_MEDIA_SESSION(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE((obj), GABBLE_TYPE_MEDIA_SESSION))
-#define GABBLE_IS_MEDIA_SESSION_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), GABBLE_TYPE_MEDIA_SESSION))
-#define GABBLE_MEDIA_SESSION_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), GABBLE_TYPE_MEDIA_SESSION, \
- GabbleMediaSessionClass))
-
-/* CONVENIENCE MACROS */
-#define MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL(s, m) \
- G_STMT_START { \
- if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT) \
- { \
- GMS_DEBUG_ERROR (s, m); \
- NODE_DEBUG (sent_msg->node, "message sent"); \
- NODE_DEBUG (reply_msg->node, "message reply"); \
- _gabble_media_session_terminate (s, INITIATOR_LOCAL, \
- TP_CHANNEL_GROUP_CHANGE_REASON_ERROR); \
- return LM_HANDLER_RESULT_REMOVE_MESSAGE; \
- } \
- } G_STMT_END
-
-gboolean
-_gabble_media_session_handle_action (GabbleMediaSession *session,
- LmMessage *message,
- LmMessageNode *session_node,
- const gchar *action,
- GError **error);
-
-LmMessage *_gabble_media_session_message_new (GabbleMediaSession *session,
- const gchar *action,
- LmMessageNode **session_node);
-
-void _gabble_media_session_accept (GabbleMediaSession *session);
-void _gabble_media_session_remove_streams (GabbleMediaSession *session,
- GabbleMediaStream **streams, guint len);
-void _gabble_media_session_terminate (GabbleMediaSession *session,
- JingleInitiator who, TpChannelGroupChangeReason why);
-
-gboolean _gabble_media_session_request_streams (GabbleMediaSession *session,
- const GArray *types,
- GPtrArray **ret,
- GError **error);
-
-gboolean _gabble_media_session_request_stream_direction (GabbleMediaSession *,
- GabbleMediaStream *, TpMediaStreamDirection, GError **);
-
-#ifndef _GMS_DEBUG_LEVEL
-#define _GMS_DEBUG_LEVEL 2
-#endif
-
-#if defined (ENABLE_DEBUG) && _GMS_DEBUG_LEVEL
-
-#define GMS_DEBUG_INFO(s, ...) \
- _gabble_media_session_debug (s, DEBUG_MSG_INFO, __VA_ARGS__)
-#if _GMS_DEBUG_LEVEL > 1
-#define GMS_DEBUG_DUMP(s, ...) \
- _gabble_media_session_debug (s, DEBUG_MSG_DUMP, __VA_ARGS__)
-#else
-#define GMS_DEBUG_DUMP(s, ...)
-#endif
-#define GMS_DEBUG_WARNING(s, ...) \
- _gabble_media_session_debug (s, DEBUG_MSG_WARNING, __VA_ARGS__)
-#define GMS_DEBUG_ERROR(s, ...) \
- _gabble_media_session_debug (s, DEBUG_MSG_ERROR, __VA_ARGS__)
-#define GMS_DEBUG_EVENT(s, ...) \
- _gabble_media_session_debug (s, DEBUG_MSG_EVENT, __VA_ARGS__)
-
-void _gabble_media_session_debug (GabbleMediaSession *session,
- DebugMessageType type,
- const gchar *format, ...)
- G_GNUC_PRINTF (3, 4);
-
-#else
-
-#define GMS_DEBUG_INFO(s, ...)
-#define GMS_DEBUG_DUMP(s, ...)
-#define GMS_DEBUG_WARNING(s, ...)
-#define GMS_DEBUG_ERROR(s, ...)
-#define GMS_DEBUG_EVENT(s, ...)
-
-#endif /* defined (ENABLE_DEBUG) && _GMS_DEBUG_LEVEL */
-
-G_END_DECLS
-
-#endif /* #ifndef __GABBLE_MEDIA_SESSION_H__*/
diff --git a/src/media-stream.c b/src/media-stream.c
index 2a2690b..b506602 100644
--- a/src/media-stream.c
+++ b/src/media-stream.c
@@ -42,10 +42,11 @@
#include "gabble-signals-marshal.h"
#include "media-channel.h"
#include "media-session-enumtypes.h"
-#include "media-session.h"
#include "namespaces.h"
#include "jingle-content.h"
+#include "jingle-session.h"
+#include "jingle-media-rtp.h"
static void stream_handler_iface_init (gpointer, gpointer);
@@ -101,7 +102,7 @@ struct _GabbleMediaStreamPrivate
GabbleJingleContent *content;
GabbleConnection *conn;
- GabbleMediaSession *session;
+ gpointer session;
GabbleMediaSessionMode mode;
gchar *object_path;
guint id;
@@ -140,12 +141,16 @@ static const char *tp_transports[] = {
#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 new_remote_candidates_cb (GabbleJingleContent *content,
+ GList *clist, GabbleMediaStream *stream);
+static void new_remote_codecs_cb (GabbleJingleContent *content,
+ GList *clist, GabbleMediaStream *stream);
+
static void
gabble_media_stream_init (GabbleMediaStream *self)
{
@@ -215,7 +220,7 @@ gabble_media_stream_get_property (GObject *object,
g_value_set_string (value, priv->object_path);
break;
case PROP_MODE:
- g_value_set_enum (value, priv->mode);
+ g_value_set_uint (value, priv->mode);
break;
case PROP_NAME:
g_value_set_string (value, stream->name);
@@ -280,7 +285,7 @@ gabble_media_stream_set_property (GObject *object,
priv->object_path = g_value_dup_string (value);
break;
case PROP_MODE:
- priv->mode = g_value_get_enum (value);
+ priv->mode = g_value_get_uint (value);
break;
case PROP_NAME:
g_free (stream->name);
@@ -312,8 +317,10 @@ gabble_media_stream_set_property (GObject *object,
stream->signalling_state = g_value_get_uint (value);
DEBUG ("stream %s sig_state %d->%d",
stream->name, old, stream->signalling_state);
+ /* FIXME
if (stream->signalling_state != old)
push_native_candidates (stream);
+ */
}
break;
case PROP_PLAYING:
@@ -328,7 +335,23 @@ gabble_media_stream_set_property (GObject *object,
stream->combined_direction = g_value_get_uint (value);
break;
case PROP_CONTENT:
+ g_assert (priv->content == NULL);
+
priv->content = g_value_get_object (value);
+
+ DEBUG ("connecting to content %p signals", priv->content);
+ g_signal_connect (priv->content, "new-candidates",
+ (GCallback) new_remote_candidates_cb, stream);
+
+ /* we need this also, if we're the initiator of the stream
+ * (so remote codecs arrive later) */
+ g_signal_connect (priv->content, "remote-codecs",
+ (GCallback) new_remote_codecs_cb, stream);
+
+ /* we can immediately get the codecs if we're responder */
+ new_remote_codecs_cb (priv->content,
+ gabble_jingle_media_rtp_get_remote_codecs (GABBLE_JINGLE_MEDIA_RTP (priv->content)),
+ stream);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -373,7 +396,7 @@ gabble_media_stream_class_init (GabbleMediaStreamClass *gabble_media_stream_clas
param_spec = g_param_spec_object ("media-session",
"GabbleMediaSession object",
"Gabble media session object that owns this media stream object.",
- GABBLE_TYPE_MEDIA_SESSION,
+ GABBLE_TYPE_JINGLE_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,
@@ -389,11 +412,10 @@ gabble_media_stream_class_init (GabbleMediaStreamClass *gabble_media_stream_clas
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",
+ param_spec = g_param_spec_uint ("mode", "Signalling mode",
"Which signalling mode used to control the "
"stream.",
- gabble_media_session_mode_get_type (),
- MODE_JINGLE,
+ 0, G_MAXUINT, MODE_JINGLE, // FIXME
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
@@ -612,6 +634,13 @@ gabble_media_stream_finalize (GObject *object)
G_OBJECT_CLASS (gabble_media_stream_parent_class)->finalize (object);
}
+typedef struct {
+ guchar id;
+ gchar *name;
+ guint clockrate;
+ guint channels;
+} JingleCodec;
+
/**
* gabble_media_stream_codec_choice
*
@@ -646,8 +675,7 @@ gabble_media_stream_error (GabbleMediaStream *self,
priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);
- GMS_DEBUG_WARNING (priv->session,
- "Media.StreamHandler::Error called, error %u (%s) -- emitting signal",
+ DEBUG ( "Media.StreamHandler::Error called, error %u (%s) -- emitting signal",
errno, message);
g_signal_emit (self, signals[ERROR], 0, errno, message);
@@ -849,7 +877,7 @@ gabble_media_stream_new_native_candidate (TpSvcMediaStreamHandler *iface,
GError only_one = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "google p2p "
"connections only support the concept of one transport per "
"candidate" };
- GMS_DEBUG_WARNING (priv->session, "%s: number of transports was not 1; "
+ DEBUG ("%s: number of transports was not 1; "
"rejecting", G_STRFUNC);
dbus_g_method_return_error (context, &only_one);
return;
@@ -859,8 +887,11 @@ gabble_media_stream_new_native_candidate (TpSvcMediaStreamHandler *iface,
addr = g_value_get_string (g_value_array_get_nth (transport, 1));
if (!strcmp (addr, "127.0.0.1"))
{
+ DEBUG ("ignoring native localhost candidate");
+ /*
GMS_DEBUG_WARNING (priv->session,
- "%s: ignoring native localhost candidate", G_STRFUNC);
+ "%s: ignoring native localhost candidate", G_STRFUNC); */
+
tp_svc_media_stream_handler_return_from_new_native_candidate (context);
return;
}
@@ -920,18 +951,24 @@ gabble_media_stream_ready (TpSvcMediaStreamHandler *iface,
DEBUG ("ready called");
- g_object_set (self, "ready", TRUE, NULL);
+ if (priv->ready == FALSE)
+ {
+ g_object_set (self, "ready", TRUE, NULL);
- push_remote_codecs (self);
- push_remote_candidates (self);
- push_playing (self);
- push_sending (self);
+ push_remote_codecs (self);
+ push_remote_candidates (self);
+ push_playing (self);
+ push_sending (self);
+ }
+ else
+ {
+ DEBUG ("Ready called twice, running plain SetLocalCodecs instead");
+ }
/* set_local_codecs and ready return the same thing, so we can do... */
gabble_media_stream_set_local_codecs (iface, codecs, context);
}
-
/**
* gabble_media_stream_set_local_codecs
*
@@ -945,6 +982,9 @@ gabble_media_stream_set_local_codecs (TpSvcMediaStreamHandler *iface,
{
GabbleMediaStream *self = GABBLE_MEDIA_STREAM (iface);
GabbleMediaStreamPrivate *priv;
+ GList *li = NULL;
+ JingleCodec *c;
+ guint i;
g_assert (GABBLE_IS_MEDIA_STREAM (self));
@@ -957,8 +997,38 @@ gabble_media_stream_set_local_codecs (TpSvcMediaStreamHandler *iface,
g_object_set (self, "got-local-codecs", TRUE, NULL);
- /* FIXME: actually set them :-) */
- gabble_jingle_content_set_local_codecs (priv->content);
+ for (i = 0; i < codecs->len; i++)
+ {
+ GType codec_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC;
+
+ GValue codec = { 0, };
+ guint id, clock_rate, channels;
+ gchar *name;
+ GHashTable *params;
+
+ g_value_init (&codec, codec_struct_type);
+ 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);
+
+ c = g_new0 (JingleCodec, 1);
+
+ c->id = id;
+ c->name = g_strdup (name);
+ c->clockrate = clock_rate;
+ c->channels = channels;
+
+ DEBUG ("adding codec %s (%u %u %u)", c->name, c->id, c->clockrate, c->channels);
+ li = g_list_append (li, c);
+ }
+
+ jingle_media_rtp_set_local_codecs (GABBLE_JINGLE_MEDIA_RTP (priv->content), li);
tp_svc_media_stream_handler_return_from_set_local_codecs (context);
}
@@ -1005,7 +1075,7 @@ gabble_media_stream_supported_codecs (TpSvcMediaStreamHandler *iface,
priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);
- GMS_DEBUG_INFO (priv->session, "got codec intersection containing %d "
+ DEBUG ("got codec intersection containing %d "
"codecs from stream-engine", codecs->len);
/* store the intersection for later on */
@@ -1016,6 +1086,7 @@ gabble_media_stream_supported_codecs (TpSvcMediaStreamHandler *iface,
tp_svc_media_stream_handler_return_from_supported_codecs (context);
}
+#if 0
static LmHandlerResult
candidates_msg_reply_cb (GabbleConnection *conn,
LmMessage *sent_msg,
@@ -1030,12 +1101,15 @@ candidates_msg_reply_cb (GabbleConnection *conn,
priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);
+ /* FIXME
MSG_REPLY_CB_END_SESSION_IF_NOT_SUCCESSFUL (priv->session,
- "candidates failed");
+ "candidates failed"); */
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
+#endif
+#if 0
static void
_add_rtp_candidate_node (GabbleMediaSession *session, LmMessageNode *parent,
GValueArray *candidate)
@@ -1142,7 +1216,9 @@ out:
g_free (port_str);
g_free (pref_str);
}
+#endif
+#if 0
static LmMessage *
_gabble_media_stream_message_new (GabbleMediaStream *stream,
const gchar *action,
@@ -1161,8 +1237,9 @@ _gabble_media_stream_message_new (GabbleMediaStream *stream,
return msg;
}
+#endif
-
+#if 0
static void
push_candidate (GabbleMediaStream *stream, GValueArray *candidate)
{
@@ -1196,7 +1273,9 @@ push_candidate (GabbleMediaStream *stream, GValueArray *candidate)
/* clean up */
lm_message_unref (msg);
}
+#endif
+#if 0
static void
push_native_candidates (GabbleMediaStream *stream)
{
@@ -1222,6 +1301,7 @@ push_native_candidates (GabbleMediaStream *stream)
g_value_take_boxed (&priv->native_candidates,
dbus_g_type_specialized_construct (candidate_list_type));
}
+#endif
void
_gabble_media_stream_close (GabbleMediaStream *stream)
@@ -1239,6 +1319,7 @@ _gabble_media_stream_close (GabbleMediaStream *stream)
}
}
+#if 0
gboolean
_gabble_media_stream_post_remote_codecs (GabbleMediaStream *stream,
LmMessage *message,
@@ -1344,6 +1425,56 @@ _gabble_media_stream_post_remote_codecs (GabbleMediaStream *stream,
return TRUE;
}
+#endif
+
+static void
+new_remote_codecs_cb (GabbleJingleContent *content,
+ GList *clist, GabbleMediaStream *stream)
+{
+ GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);
+ GList *li;
+ GPtrArray *codecs;
+ GType codec_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC;
+
+ DEBUG ("called");
+
+ 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 (li = clist; li; li = li->next)
+ {
+ GValue codec = { 0, };
+ JingleCodec *c = li->data;
+
+ g_value_init (&codec, codec_struct_type);
+ g_value_take_boxed (&codec,
+ dbus_g_type_specialized_construct (codec_struct_type));
+
+ DEBUG ("new remote codec: %u '%s' %u %u %u",
+ c->id, c->name, priv->media_type, c->clockrate, c->channels);
+
+ dbus_g_type_struct_set (&codec,
+ 0, c->id,
+ 1, c->name,
+ 2, priv->media_type,
+ 3, c->clockrate,
+ 4, c->channels,
+ 5, g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free),
+ G_MAXUINT);
+
+ g_ptr_array_add (codecs, g_value_get_boxed (&codec));
+ }
+
+ DEBUG ("pushing remote codecs");
+
+ push_remote_codecs (stream);
+}
+
static void
push_remote_codecs (GabbleMediaStream *stream)
@@ -1364,7 +1495,7 @@ push_remote_codecs (GabbleMediaStream *stream)
if (codecs->len == 0)
return;
- GMS_DEBUG_EVENT (priv->session, "passing %d remote codecs to stream-engine",
+ DEBUG ("passing %d remote codecs to stream-engine",
codecs->len);
tp_svc_media_stream_handler_emit_set_remote_codecs (stream, codecs);
@@ -1373,6 +1504,73 @@ push_remote_codecs (GabbleMediaStream *stream)
dbus_g_type_specialized_construct (codec_list_type));
}
+static void
+new_remote_candidates_cb (GabbleJingleContent *content,
+ GList *clist, GabbleMediaStream *stream)
+{
+ GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (stream);
+ GPtrArray *candidates;
+ GList *li;
+
+ candidates = g_value_get_boxed (&priv->remote_candidates);
+
+ DEBUG ("got new remote candidates");
+
+ for (li = clist; li; li = li->next)
+ {
+ gchar *candidate_id;
+ GValue candidate = { 0, };
+ GPtrArray *transports;
+ GValue transport = { 0, };
+ JingleCandidate *c = li->data;
+ GType transport_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT;
+ GType candidate_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE;
+
+ g_value_init (&transport, transport_struct_type);
+ g_value_take_boxed (&transport,
+ dbus_g_type_specialized_construct (transport_struct_type));
+
+ dbus_g_type_struct_set (&transport,
+ 0, 1, /* component number */
+ 1, c->address,
+ 2, c->port,
+ 3, c->protocol == JINGLE_TRANSPORT_PROTOCOL_UDP ? 0 : 1,
+ 4, "RTP",
+ 5, "AVP",
+ 6, c->preference,
+ 7, c->type, /* FIXME: we're relying on 1:1 tp/jingle candidate type enums */
+ 8, c->username,
+ 9, c->password,
+ G_MAXUINT);
+
+ transports = g_ptr_array_sized_new (1);
+ g_ptr_array_add (transports, g_value_get_boxed (&transport));
+
+ g_value_init (&candidate, candidate_struct_type);
+ g_value_take_boxed (&candidate,
+ dbus_g_type_specialized_construct (candidate_struct_type));
+
+ /* 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));
+ }
+
+ push_remote_candidates (stream);
+
+ while (clist != NULL)
+ {
+ g_free (clist->data);
+ clist = clist->next;
+ }
+}
+
+#if 0
gboolean
_gabble_media_stream_post_remote_candidates (GabbleMediaStream *stream,
LmMessage *message,
@@ -1583,6 +1781,7 @@ FAILURE:
return FALSE;
}
+#endif
static void
push_remote_candidates (GabbleMediaStream *stream)
@@ -1614,8 +1813,11 @@ push_remote_candidates (GabbleMediaStream *stream)
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));
+ DEBUG ("passing 1 remote candidate to stream engine: %s", candidate_id);
+ /*
GMS_DEBUG_EVENT (priv->session, "passing 1 remote candidate "
"to stream-engine");
+ */
tp_svc_media_stream_handler_emit_add_remote_candidate (
stream, candidate_id, transports);
@@ -1683,10 +1885,12 @@ typedef struct {
LmMessageNode *pt_node;
} CodecParamsFromTpContext;
+#if 0
static const gchar *video_codec_params[] = {
"x", "y", "width", "height", "layer", "transparent",
};
+// FIXME - we need to have extended params for video stream
static void
codec_params_from_tp_foreach (gpointer key, gpointer value, gpointer user_data)
{
@@ -1720,7 +1924,9 @@ codec_params_from_tp_foreach (gpointer key, gpointer value, gpointer user_data)
(priv->mode == MODE_JINGLE) ? "jingle" : "google",
(priv->media_type == TP_MEDIA_STREAM_TYPE_AUDIO) ? "audio" : "video");
}
+#endif
+#if 0
LmMessageNode *
_gabble_media_stream_add_content_node (GabbleMediaStream *stream,
LmMessageNode *session_node)
@@ -1851,6 +2057,7 @@ _gabble_media_stream_content_node_add_transport (GabbleMediaStream *stream,
return node;
}
+#endif
void
_gabble_media_stream_update_sending (GabbleMediaStream *stream,
diff --git a/src/media-stream.h b/src/media-stream.h
index 719a5b6..c378d90 100644
--- a/src/media-stream.h
+++ b/src/media-stream.h
@@ -22,7 +22,6 @@
#define __GABBLE_MEDIA_STREAM_H__
#include <glib-object.h>
-#include <loudmouth/loudmouth.h>
#include "types.h"
#include <telepathy-glib/enums.h>
@@ -93,17 +92,18 @@ gboolean gabble_media_stream_error (GabbleMediaStream *self, guint errno,
const gchar *message, GError **error);
void _gabble_media_stream_close (GabbleMediaStream *close);
-gboolean _gabble_media_stream_post_remote_codecs (GabbleMediaStream *stream,
- LmMessage *message, LmMessageNode *desc_node, GError **error);
-gboolean _gabble_media_stream_post_remote_candidates (
- GabbleMediaStream *stream, LmMessage *message,
- LmMessageNode *transport_node, GError **error);
-LmMessageNode *_gabble_media_stream_add_content_node (
- GabbleMediaStream *stream, LmMessageNode *session_node);
-void _gabble_media_stream_content_node_add_description (
- GabbleMediaStream *stream, LmMessageNode *content_node);
-LmMessageNode *_gabble_media_stream_content_node_add_transport (
- GabbleMediaStream *stream, LmMessageNode *content_node);
+// gboolean _gabble_media_stream_post_remote_codecs (GabbleMediaStream *stream,
+// LmMessage *message, LmMessageNode *desc_node, GError **error);
+// gboolean _gabble_media_stream_post_remote_candidates (
+// GabbleMediaStream *stream, LmMessage *message,
+// LmMessageNode *transport_node, GError **error);
+//LmMessageNode *_gabble_media_stream_add_content_node (
+// GabbleMediaStream *stream, LmMessageNode *session_node);
+//void _gabble_media_stream_content_node_add_description (
+// GabbleMediaStream *stream, LmMessageNode *content_node);
+//LmMessageNode *_gabble_media_stream_content_node_add_transport (
+// GabbleMediaStream *stream, LmMessageNode *content_node);
+
void _gabble_media_stream_update_sending (GabbleMediaStream *stream,
gboolean start_sending);
void gabble_media_stream_hold (GabbleMediaStream *stream, gboolean hold);
diff --git a/src/namespaces.h b/src/namespaces.h
index d46db58..59140a0 100644
--- a/src/namespaces.h
+++ b/src/namespaces.h
@@ -77,6 +77,9 @@
#define NS_GOOGLE_SESSION "http://www.google.com/session"
#define NS_GOOGLE_SESSION_PHONE "http://www.google.com/session/phone"
#define NS_GOOGLE_TRANSPORT_P2P "http://www.google.com/transport/p2p"
+#define NS_JINGLE_TMP "urn:xmpp:tmp:jingle"
#define NS_JINGLE_RTP_TMP "urn:xmpp:tmp:jingle:apps:rtp"
+#define NS_JINGLE_TRANSPORT_RAWUDP \
+ "http://jabber.org/protocol/jingle/transport/rawudp"
#endif /* __GABBLE_NAMESPACES__H__ */
diff --git a/src/presence-cache.c b/src/presence-cache.c
index b21f94e..cc4c9f2 100644
--- a/src/presence-cache.c
+++ b/src/presence-cache.c
@@ -805,6 +805,14 @@ _caps_disco_cb (GabbleDisco *disco,
!tp_strdiff (var, NS_OLPC_CURRENT_ACTIVITY "+notify") ||
!tp_strdiff (var, NS_OLPC_ACTIVITY_PROPS "+notify"))
caps |= PRESENCE_CAP_OLPC_1;
+ else if (!tp_strdiff (var, NS_JINGLE_RTP_TMP))
+ caps |= PRESENCE_CAP_JINGLE_RTP_TMP;
+ else if (!tp_strdiff (var, NS_JINGLE_TMP))
+ caps |= PRESENCE_CAP_JINGLE_TMP;
+ else if (!tp_strdiff (var, NS_JINGLE_TRANSPORT_ICE))
+ caps |= PRESENCE_CAP_JINGLE_TRANSPORT_ICE;
+ else if (!tp_strdiff (var, NS_JINGLE_TRANSPORT_RAWUDP))
+ caps |= PRESENCE_CAP_JINGLE_TRANSPORT_RAWUDP;
}
handle = tp_handle_ensure (contact_repo, jid, NULL, NULL);
diff --git a/src/types.h b/src/types.h
index 557b25b..539e560 100644
--- a/src/types.h
+++ b/src/types.h
@@ -67,7 +67,11 @@ typedef enum {
PRESENCE_CAP_IBB = 1 << 8,
PRESENCE_CAP_SI_TUBES = 1 << 9,
PRESENCE_CAP_FILE_TRANSFER = 1 << 10,
- PRESENCE_CAP_OLPC_1 = 1 << 11
+ PRESENCE_CAP_OLPC_1 = 1 << 11,
+ PRESENCE_CAP_JINGLE_RTP_TMP = 1 << 12,
+ PRESENCE_CAP_JINGLE_TMP = 1 << 13,
+ PRESENCE_CAP_JINGLE_TRANSPORT_ICE = 1 << 14,
+ PRESENCE_CAP_JINGLE_TRANSPORT_RAWUDP = 1 << 15,
} GabblePresenceCapabilities;
G_END_DECLS
diff --git a/tests/twisted/jingle/jingletest.py b/tests/twisted/jingle/jingletest.py
index 6704dfd..1102451 100644
--- a/tests/twisted/jingle/jingletest.py
+++ b/tests/twisted/jingle/jingletest.py
@@ -190,7 +190,8 @@ class JingleTest:
content = domish.Element((None, 'content'))
content['creator'] = 'initiator'
- content['name'] = 'audio1'
+ content['name'] = 'stream1'
+ content['senders'] = 'both'
jingle.addChild(content)
desc = domish.Element(("http://jabber.org/protocol/jingle/description/audio", 'description'))
diff --git a/tests/twisted/jingle/test-incoming-call.py b/tests/twisted/jingle/test-incoming-call.py
index e40717e..393f58f 100644
--- a/tests/twisted/jingle/test-incoming-call.py
+++ b/tests/twisted/jingle/test-incoming-call.py
@@ -59,12 +59,6 @@ def test(q, bus, conn, stream):
media_chan = make_channel_proxy(conn, tp_path_prefix + e.path, 'Channel.Interface.Group')
- # S-E gets notified about a newly-created stream
- e = q.expect('dbus-signal', signal='NewStreamHandler')
-
- stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler')
-
-
# S-E gets notified about new session handler, and calls Ready on it
e = q.expect('dbus-signal', signal='NewSessionHandler')
assert e.args[1] == 'rtp'
@@ -72,6 +66,12 @@ def test(q, bus, conn, stream):
session_handler = make_channel_proxy(conn, e.args[0], 'Media.SessionHandler')
session_handler.Ready()
+
+ # S-E gets notified about a newly-created stream
+ e = q.expect('dbus-signal', signal='NewStreamHandler')
+
+ stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler')
+
"""
# Exercise channel properties
future_props = media_chan.GetAll(
--
1.5.6.5
More information about the Telepathy-commits
mailing list