changeset 26167:d6741612b1d0

merge of '8abcc61528a93f4c18a6e1f200a7ba69f1694c28' and '96b230451345d5543554f1d018ab450c76820d01'
author Mike Ruprecht <maiku@soc.pidgin.im>
date Thu, 05 Feb 2009 00:15:33 +0000
parents 218f052b9bf5 (current diff) 0e8814c437b2 (diff)
children af42303654a5
files
diffstat 12 files changed, 540 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/mediamanager.c	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/mediamanager.c	Thu Feb 05 00:15:33 2009 +0000
@@ -39,6 +39,12 @@
 struct _PurpleMediaManagerPrivate
 {
 	GList *medias;
+	GList *elements;
+
+	PurpleMediaElementInfo *video_src;
+	PurpleMediaElementInfo *video_sink;
+	PurpleMediaElementInfo *audio_src;
+	PurpleMediaElementInfo *audio_sink;
 };
 
 #define PURPLE_MEDIA_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA_MANAGER, PurpleMediaManagerPrivate))
@@ -123,6 +129,8 @@
 			g_list_delete_link(priv->medias, priv->medias)) {
 		g_object_unref(priv->medias->data);
 	}
+	for (; priv->elements; priv->elements =
+			g_list_delete_link(priv->elements, priv->elements));
 	parent_class->finalize(media);
 }
 
@@ -205,19 +213,22 @@
 		PurpleMediaSessionType type)
 {
 	GstElement *ret = NULL;
-	GstElement *level = NULL;
 
 	/* TODO: If src, retrieve current src */
 	/* TODO: Send a signal here to allow for overriding the source/sink */
 
-	if (type & PURPLE_MEDIA_SEND_AUDIO)
-		purple_media_audio_init_src(&ret, &level);
-	else if (type & PURPLE_MEDIA_RECV_AUDIO)
-		purple_media_audio_init_recv(&ret, &level);
-	else if (type & PURPLE_MEDIA_SEND_VIDEO)
-		purple_media_video_init_src(&ret);
-	else if (type & PURPLE_MEDIA_RECV_VIDEO)
-		purple_media_video_init_recv(&ret);
+	if (type & PURPLE_MEDIA_SEND_AUDIO
+			&& manager->priv->audio_src != NULL)
+		ret = manager->priv->audio_src->create();
+	else if (type & PURPLE_MEDIA_RECV_AUDIO
+			&& manager->priv->audio_sink != NULL)
+		ret = manager->priv->audio_sink->create();
+	else if (type & PURPLE_MEDIA_SEND_VIDEO
+			&& manager->priv->video_src != NULL)
+		ret = manager->priv->video_src->create();
+	else if (type & PURPLE_MEDIA_RECV_VIDEO
+			&& manager->priv->video_sink != NULL)
+		ret = manager->priv->video_sink->create();
 
 	if (ret == NULL)
 		purple_debug_error("media", "Error creating source or sink\n");
@@ -225,4 +236,122 @@
 	return ret;
 }
 
+PurpleMediaElementInfo *
+purple_media_manager_get_element_info(PurpleMediaManager *manager,
+		const gchar *id)
+{
+	GList *iter;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
+
+	iter = manager->priv->elements;
+
+	for (; iter; iter = g_list_next(iter)) {
+		PurpleMediaElementInfo *info = iter->data;
+		if (!strcmp(info->id, id))
+			return info;
+	}
+
+	return NULL;
+}
+
+gboolean
+purple_media_manager_register_element(PurpleMediaManager *manager,
+		PurpleMediaElementInfo *info)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
+	g_return_val_if_fail(info != NULL, FALSE);
+
+	if (purple_media_manager_get_element_info(manager, info->id) != NULL)
+		return FALSE;
+
+	manager->priv->elements =
+			g_list_prepend(manager->priv->elements, info);
+	return TRUE;
+}
+
+gboolean
+purple_media_manager_unregister_element(PurpleMediaManager *manager,
+		const gchar *id)
+{
+	PurpleMediaElementInfo *info;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
+
+	info = purple_media_manager_get_element_info(manager, id);
+
+	if (info == NULL)
+		return FALSE;
+
+	if (manager->priv->audio_src == info)
+		manager->priv->audio_src = NULL;
+	if (manager->priv->audio_sink == info)
+		manager->priv->audio_sink = NULL;
+	if (manager->priv->video_src == info)
+		manager->priv->video_src = NULL;
+	if (manager->priv->video_sink == info)
+		manager->priv->video_sink = NULL;
+
+	manager->priv->elements = g_list_remove(
+			manager->priv->elements, info);
+	return TRUE;
+}
+
+gboolean
+purple_media_manager_set_active_element(PurpleMediaManager *manager,
+		PurpleMediaElementInfo *info)
+{
+	gboolean ret = FALSE;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), FALSE);
+	g_return_val_if_fail(info != NULL, FALSE);
+
+	if (purple_media_manager_get_element_info(manager, info->id) == NULL)
+		purple_media_manager_register_element(manager, info);
+
+	if (info->type & PURPLE_MEDIA_ELEMENT_SRC) {
+		if (info->type & PURPLE_MEDIA_ELEMENT_AUDIO) {
+			manager->priv->audio_src = info;
+			ret = TRUE;
+		}
+		if (info->type & PURPLE_MEDIA_ELEMENT_VIDEO) {
+			manager->priv->video_src = info;
+			ret = TRUE;
+		}
+	}
+	if (info->type & PURPLE_MEDIA_ELEMENT_SINK) {
+		if (info->type & PURPLE_MEDIA_ELEMENT_AUDIO) {
+			manager->priv->audio_sink = info;
+			ret = TRUE;
+		}
+		if (info->type & PURPLE_MEDIA_ELEMENT_VIDEO) {
+			manager->priv->video_sink = info;
+			ret = TRUE;
+		}
+	}
+
+	return ret;
+}
+
+PurpleMediaElementInfo *
+purple_media_manager_get_active_element(PurpleMediaManager *manager,
+		PurpleMediaElementType type)
+{
+	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
+
+	if (type & PURPLE_MEDIA_ELEMENT_SRC) {
+		if (type & PURPLE_MEDIA_ELEMENT_AUDIO)
+			return manager->priv->audio_src;
+		else if (type & PURPLE_MEDIA_ELEMENT_VIDEO)
+			return manager->priv->video_src;
+	} else if (type & PURPLE_MEDIA_ELEMENT_SINK) {
+		if (type & PURPLE_MEDIA_ELEMENT_AUDIO)
+			return manager->priv->audio_sink;
+		else if (type & PURPLE_MEDIA_ELEMENT_VIDEO)
+			return manager->priv->video_sink;
+	}
+
+	return NULL;
+}
+
 #endif  /* USE_VV */
--- a/libpurple/mediamanager.h	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/mediamanager.h	Thu Feb 05 00:15:33 2009 +0000
@@ -50,6 +50,8 @@
 typedef struct _PurpleMediaManagerClass PurpleMediaManagerClass;
 /** @copydoc _PurpleMediaManagerPrivate */
 typedef struct _PurpleMediaManagerPrivate PurpleMediaManagerPrivate;
+/** @copydoc _PurpleMediaElementInfo */
+typedef struct _PurpleMediaElementInfo PurpleMediaElementInfo;
 
 /** The media manager class. */
 struct _PurpleMediaManagerClass
@@ -64,6 +66,37 @@
 	PurpleMediaManagerPrivate *priv; /**< Private data for the manager. */
 };
 
+typedef enum {
+	PURPLE_MEDIA_ELEMENT_AUDIO = 1,			/** supports audio */
+	PURPLE_MEDIA_ELEMENT_VIDEO = 1 << 1,		/** supports video */
+	PURPLE_MEDIA_ELEMENT_AUDIO_VIDEO = PURPLE_MEDIA_ELEMENT_AUDIO
+			| PURPLE_MEDIA_ELEMENT_VIDEO, 	/** supports audio and video */
+
+	PURPLE_MEDIA_ELEMENT_NO_SRCS = 0,		/** has no src pads */
+	PURPLE_MEDIA_ELEMENT_ONE_SRC = 1 << 2,		/** has one src pad */
+	PURPLE_MEDIA_ELEMENT_MULTI_SRC = 1 << 3, 	/** has multiple src pads */
+	PURPLE_MEDIA_ELEMENT_REQUEST_SRC = 1 << 4, 	/** src pads must be requested */
+
+	PURPLE_MEDIA_ELEMENT_NO_SINKS = 0,		/** has no sink pads */
+	PURPLE_MEDIA_ELEMENT_ONE_SINK = 1 << 5, 	/** has one sink pad */
+	PURPLE_MEDIA_ELEMENT_MULTI_SINK = 1 << 6, 	/** has multiple sink pads */
+	PURPLE_MEDIA_ELEMENT_REQUEST_SINK = 1 << 7, 	/** sink pads must be requested */
+
+	PURPLE_MEDIA_ELEMENT_UNIQUE = 1 << 8,		/** This element is unique and
+							 only one instance of it should
+							 be created at a time */
+
+	PURPLE_MEDIA_ELEMENT_SRC = 1 << 9,		/** can be set as an active src */
+	PURPLE_MEDIA_ELEMENT_SINK = 1 << 10,		/** can be set as an active sink */
+} PurpleMediaElementType;
+
+struct _PurpleMediaElementInfo
+{
+	const gchar *id;
+	PurpleMediaElementType type;
+	GstElement *(*create)(void);
+};
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -131,6 +164,16 @@
 GstElement *purple_media_manager_get_element(PurpleMediaManager *manager,
 		PurpleMediaSessionType type);
 
+PurpleMediaElementInfo *purple_media_manager_get_element_info(
+		PurpleMediaManager *manager, const gchar *name);
+gboolean purple_media_manager_register_element(PurpleMediaManager *manager,
+		PurpleMediaElementInfo *info);
+gboolean purple_media_manager_unregister_element(PurpleMediaManager *manager,
+		const gchar *name);
+gboolean purple_media_manager_set_active_element(PurpleMediaManager *manager,
+		PurpleMediaElementInfo *info);
+PurpleMediaElementInfo *purple_media_manager_get_active_element(
+		PurpleMediaManager *manager, PurpleMediaElementType type);
 /*}@*/
 
 #ifdef __cplusplus
--- a/libpurple/protocols/jabber/disco.c	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Thu Feb 05 00:15:33 2009 +0000
@@ -452,7 +452,12 @@
 		if (!strcmp(name, "Google Talk")) {
 			purple_debug_info("jabber", "Google Talk!\n");
 			js->googletalk = TRUE;
-		}
+
+			/* autodiscover stun and relays */
+			jabber_google_send_jingle_info(js);
+		} else {
+			/* TODO: add external service discovery here... */
+		} 
 	}
 
 	for (child = xmlnode_get_child(query, "feature"); child;
--- a/libpurple/protocols/jabber/google.c	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Thu Feb 05 00:15:33 2009 +0000
@@ -23,6 +23,8 @@
 #include "mediamanager.h"
 #include "util.h"
 #include "privacy.h"
+#include "dnsquery.h"
+#include "network.h"
 
 #include "buddy.h"
 #include "google.h"
@@ -30,6 +32,8 @@
 #include "presence.h"
 #include "iq.h"
 
+#include "jingle/jingle.h"
+
 #ifdef USE_VV
 
 typedef struct {
@@ -124,7 +128,7 @@
 	sess = google_session_create_xmlnode(session, "candidates");
 	xmlnode_insert_child(iq->node, sess);
 	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
-	
+
 	for (;candidates;candidates = candidates->next) {
 		char port[8];
 		char pref[8];
@@ -132,7 +136,7 @@
 
 		if (!strcmp(transport->ip, "127.0.0.1"))
 			continue;
-	
+
 		candidate = xmlnode_new("candidate");
 
 		g_snprintf(port, sizeof(port), "%d", transport->port);
@@ -162,7 +166,6 @@
 		xmlnode_set_attrib(candidate, "generation", "0");
 		xmlnode_set_attrib(candidate, "network", "0");
 		xmlnode_insert_child(sess, candidate);
-		
 	}
 	jabber_iq_send(iq);
 }
@@ -246,6 +249,26 @@
 	}
 }
 
+static GParameter *
+jabber_google_session_get_params(JabberStream *js, guint *num)
+{
+	guint num_params;
+	GParameter *params = jingle_get_params(js, &num_params);
+	GParameter *new_params = g_new0(GParameter, num_params + 1);
+
+	memcpy(new_params, params, sizeof(GParameter) * num_params);
+
+	purple_debug_info("jabber", "setting Google jingle compatibility param\n");
+	new_params[num_params].name = "compatibility-mode";
+	g_value_init(&new_params[num_params].value, G_TYPE_UINT);
+	g_value_set_uint(&new_params[num_params].value, 1); /* NICE_COMPATIBILITY_GOOGLE */
+
+	g_free(params);
+	*num = num_params + 1;
+	return new_params;
+}
+
+
 PurpleMedia*
 jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type)
 {
@@ -253,7 +276,8 @@
 	JabberBuddy *jb;
 	JabberBuddyResource *jbr;
 	gchar *jid;
-	GParameter param;
+	GParameter *params;
+	guint num_params;
 
 	/* construct JID to send to */
 	jb = jabber_buddy_find(js, who, FALSE);
@@ -286,18 +310,15 @@
 			purple_media_manager_get(), js->gc,
 			"fsrtpconference", session->remote_jid, TRUE);
 
-	/* GTalk requires the NICE_COMPATIBILITY_GOOGLE param */
-	param.name = "compatibility-mode";
-	memset(&param.value, 0, sizeof(GValue));
-	g_value_init(&param.value, G_TYPE_UINT);
-	g_value_set_uint(&param.value, 1); /* NICE_COMPATIBILITY_GOOGLE */
+	params = jabber_google_session_get_params(js, &num_params);
 
 	if (purple_media_add_stream(session->media, "google-voice",
 				session->remote_jid, PURPLE_MEDIA_AUDIO,
-				"nice", 1, &param) == FALSE) {
+				"nice", num_params, params) == FALSE) {
 		purple_media_error(session->media, "Error adding stream.");
 		purple_media_hangup(session->media);
 		google_session_destroy(session);
+		g_free(params);
 		return NULL;
 	}
 
@@ -310,6 +331,7 @@
 		sessions = g_hash_table_new(google_session_id_hash,
 				google_session_id_equal);
 	g_hash_table_insert(sessions, &(session->id), session);
+	g_free(params);
 
 	return session->media;
 }
@@ -322,8 +344,9 @@
 	xmlnode *desc_element, *codec_element;
 	PurpleMediaCodec *codec;
 	const char *id, *encoding_name,  *clock_rate;
-	GParameter param;
-		
+	GParameter *params;
+	guint num_params;	
+
 	if (session->state != UNINIT) {
 		purple_debug_error("jabber", "Received initiate for active session.\n");
 		return;
@@ -332,22 +355,21 @@
 	session->media = purple_media_manager_create_media(purple_media_manager_get(), js->gc,
 							   "fsrtpconference", session->remote_jid, FALSE);
 
-	/* GTalk requires the NICE_COMPATIBILITY_GOOGLE param */
-	param.name = "compatibility-mode";
-	memset(&param.value, 0, sizeof(GValue));
-	g_value_init(&param.value, G_TYPE_UINT);
-	g_value_set_uint(&param.value, 1); /* NICE_COMPATIBILITY_GOOGLE */
+	params = jabber_google_session_get_params(js, &num_params);
 
 	if (purple_media_add_stream(session->media, "google-voice", session->remote_jid, 
-				PURPLE_MEDIA_AUDIO, "nice", 1, &param) == FALSE) {
+				PURPLE_MEDIA_AUDIO, "nice", num_params, params) == FALSE) {
 		purple_media_error(session->media, "Error adding stream.");
 		purple_media_hangup(session->media);
 		google_session_send_terminate(session);
+		g_free(params);
 		return;
 	}
 
+	g_free(params);
+
 	desc_element = xmlnode_get_child(sess, "description");
-	
+
 	for (codec_element = xmlnode_get_child(desc_element, "payload-type"); 
 	     codec_element; 
 	     codec_element = xmlnode_get_next_twin(codec_element)) {
@@ -368,7 +390,7 @@
 			G_CALLBACK(google_session_state_changed_cb), session);
 
 	purple_media_codec_list_free(codecs);
-	
+
 	result = jabber_iq_new(js, JABBER_IQ_RESULT);
 	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
 	xmlnode_set_attrib(result->node, "to", session->remote_jid);
@@ -1025,3 +1047,105 @@
 	const char *attr = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
 	return attr ? g_strdup_printf("♫ %s", attr) : g_strdup("");
 }
+
+static void
+jabber_google_stun_lookup_cb(GSList *hosts, gpointer data, 
+	const char *error_message)
+{
+	JabberStream *js = (JabberStream *) data;
+
+	if (error_message) {
+		purple_debug_error("jabber", "Google STUN lookup failed: %s\n",
+			error_message);
+		g_slist_free(hosts);
+		return;
+	}
+
+	if (hosts && g_slist_next(hosts)) {
+		struct sockaddr *addr = g_slist_next(hosts)->data; 
+		char dst[INET6_ADDRSTRLEN];
+		int port;
+
+		if (addr->sa_family == AF_INET6) {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, 
+				dst, sizeof(dst));
+			port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
+		} else {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, 
+				dst, sizeof(dst));
+			port = ntohs(((struct sockaddr_in *) addr)->sin_port);
+		}
+
+		if (js) {
+			if (js->stun_ip) {
+				g_free(js->stun_ip);
+			}
+			js->stun_ip = g_strdup(dst);
+			purple_debug_info("jabber", "set Google STUN IP address: %s\n", dst);
+			js->stun_port = port;
+			purple_debug_info("jabber", "set Google STUN port: %d\n", port);
+			purple_debug_info("jabber", "set Google STUN port: %d\n", port);
+			/* unmark ongoing query */
+			js->stun_query = NULL;
+		}
+	}
+
+	g_slist_free(hosts);
+}
+
+static void
+jabber_google_jingle_info_cb(JabberStream *js, xmlnode *result,
+	gpointer nullus)
+{	
+	if (result) {
+		const xmlnode *query = 
+			xmlnode_get_child_with_namespace(result, "query", 
+				GOOGLE_JINGLE_INFO_NAMESPACE);
+
+		if (query) {
+			const xmlnode *stun = xmlnode_get_child(query, "stun");
+
+			purple_debug_info("jabber", "got google:jingleinfo\n");
+
+			if (stun) {
+				xmlnode *server = xmlnode_get_child(stun, "server");
+
+				if (server) {
+					const gchar *host = xmlnode_get_attrib(server, "host");
+					const gchar *udp = xmlnode_get_attrib(server, "udp");
+
+					if (host && udp) {
+						int port = atoi(udp);
+						/* if there, would already be an ongoing query, 
+						 cancel it */
+						if (js->stun_query)
+							purple_dnsquery_destroy(js->stun_query);
+
+						js->stun_query = purple_dnsquery_a(host, port, 
+							jabber_google_stun_lookup_cb, js);
+					}
+				}
+			}
+			/* should perhaps handle relays later on, or maybe wait until
+			 Google supports a common standard... */
+		}
+	}
+}
+
+void
+jabber_google_handle_jingle_info(JabberStream *js, xmlnode *packet)
+{
+	jabber_google_jingle_info_cb(js, packet, NULL);
+}
+
+void
+jabber_google_send_jingle_info(JabberStream *js)
+{
+	JabberIq *jingle_info = 
+		jabber_iq_new_query(js, JABBER_IQ_GET, GOOGLE_JINGLE_INFO_NAMESPACE);
+
+	jabber_iq_set_callback(jingle_info, jabber_google_jingle_info_cb,
+		NULL);
+	purple_debug_info("jabber", "sending google:jingleinfo query\n");
+	jabber_iq_send(jingle_info);
+}
--- a/libpurple/protocols/jabber/google.h	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/protocols/jabber/google.h	Thu Feb 05 00:15:33 2009 +0000
@@ -27,6 +27,8 @@
 #include "jabber.h"
 #include "media.h"
 
+#define GOOGLE_JINGLE_INFO_NAMESPACE "google:jingleinfo"
+
 void jabber_gmail_init(JabberStream *js);
 void jabber_gmail_poke(JabberStream *js, xmlnode *node);
 
@@ -49,5 +51,7 @@
 PurpleMedia *jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type);
 void jabber_google_session_parse(JabberStream *js, xmlnode *node);
 
+void jabber_google_handle_jingle_info(JabberStream *js, xmlnode *packet);
+void jabber_google_send_jingle_info(JabberStream *js);
 
 #endif   /* _PURPLE_GOOGLE_H_ */
--- a/libpurple/protocols/jabber/iq.c	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Thu Feb 05 00:15:33 2009 +0000
@@ -448,6 +448,9 @@
 #ifdef USE_VV
 	jabber_iq_register_handler(JINGLE, jingle_parse);
 #endif
+	/* handle Google jingleinfo */
+	jabber_iq_register_handler(GOOGLE_JINGLE_INFO_NAMESPACE, 
+		jabber_google_handle_jingle_info);
 }
 
 void jabber_iq_uninit(void)
--- a/libpurple/protocols/jabber/jabber.c	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Thu Feb 05 00:15:33 2009 +0000
@@ -735,20 +735,24 @@
 	js->sessions = NULL;
 #endif
 
+	js->stun_ip = NULL;
+	js->stun_port = 0;
+	js->stun_query = NULL;
+
 	if(!js->user) {
 		purple_connection_error_reason (gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 			_("Invalid XMPP ID"));
 		return;
 	}
-	
+
 	if (!js->user->domain || *(js->user->domain) == '\0') {
 		purple_connection_error_reason (gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 			_("Invalid XMPP ID. Domain must be set."));
 		return;
 	}
-	
+
 	if((my_jb = jabber_buddy_find(js, purple_account_get_username(account), TRUE)))
 		my_jb->subscription |= JABBER_SUB_BOTH;
 
@@ -1222,6 +1226,10 @@
 	server = connect_server[0] ? connect_server : js->user->domain;
 	js->certificate_CN = g_strdup(server);
 
+	js->stun_ip = NULL;
+	js->stun_port = 0;
+	js->stun_query = NULL;
+
 	jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
 
 	if(purple_account_get_bool(account, "old_ssl", FALSE)) {
@@ -1425,6 +1433,15 @@
 	g_free(js->srv_rec);
 	js->srv_rec = NULL;
 
+	g_free(js->stun_ip);
+	js->stun_ip = NULL;
+
+	/* cancel DNS query for STUN, if one is ongoing */
+	if (js->stun_query) {
+		purple_dnsquery_destroy(js->stun_query);
+		js->stun_query = NULL;
+	}
+		
 	g_free(js);
 
 	gc->proto_data = NULL;
--- a/libpurple/protocols/jabber/jabber.h	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Thu Feb 05 00:15:33 2009 +0000
@@ -58,6 +58,7 @@
 #include "mediamanager.h"
 #include "roomlist.h"
 #include "sslconn.h"
+#include "dnsquery.h"
 
 #include "jutil.h"
 #include "xmlnode.h"
@@ -250,6 +251,12 @@
 #ifdef USE_VV
 	GHashTable *medias;
 #endif
+
+	/* maybe this should only be present when USE_VV? */
+	gchar *stun_ip;
+	int stun_port;
+	PurpleDnsQueryData *stun_query;
+	/* later add stuff to handle TURN relays... */
 };
 
 typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
--- a/libpurple/protocols/jabber/jingle/jingle.c	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Thu Feb 05 00:15:33 2009 +0000
@@ -20,6 +20,7 @@
  */
 
 #include "internal.h"
+#include "network.h"
 
 #include "content.h"
 #include "debug.h"
@@ -438,3 +439,32 @@
 				jingle_terminate_sessions_gh, NULL);
 }
 
+GParameter *
+jingle_get_params(JabberStream *js, guint *num)
+{
+	/* don't set a STUN server if one is set globally in prefs, in that case
+	 this will be handled in media.c */
+	gboolean has_account_stun = js->stun_ip && !purple_network_get_stun_ip();
+	guint num_params = has_account_stun ? 2 : 0;
+	GParameter *params = NULL;
+
+	if (num_params > 0) {
+		params = g_new0(GParameter, num_params);
+
+		purple_debug_info("jabber", 
+						  "setting param stun-ip for stream using Google auto-config: %s\n",
+						  js->stun_ip);
+		params[0].name = "stun-ip";
+		g_value_init(&params[0].value, G_TYPE_STRING);
+		g_value_set_string(&params[0].value, js->stun_ip);
+		purple_debug_info("jabber", 
+						  "setting param stun-port for stream using Google auto-config: %d\n",
+						  js->stun_port);
+		params[1].name = "stun-port";
+		g_value_init(&params[1].value, G_TYPE_UINT);
+		g_value_set_uint(&params[1].value, js->stun_port);
+	}
+
+	*num = num_params;
+	return params;
+}
--- a/libpurple/protocols/jabber/jingle/jingle.h	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.h	Thu Feb 05 00:15:33 2009 +0000
@@ -72,6 +72,10 @@
 
 void jingle_terminate_sessions(JabberStream *js);
 
+/* create a GParam array given autoconfigured STUN (and later perhaps TURN).
+	if google_talk is TRUE, set compatability mode to GOOGLE_TALK */
+GParameter *jingle_get_params(JabberStream *js, guint *num_params);
+
 #ifdef __cplusplus
 }
 #endif
--- a/libpurple/protocols/jabber/jingle/rtp.c	Thu Feb 05 00:15:13 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/rtp.c	Thu Feb 05 00:15:33 2009 +0000
@@ -402,6 +402,8 @@
 	gboolean is_audio;
 	PurpleMediaSessionType type;
 	JingleTransport *transport;
+	GParameter *params = NULL;
+	guint num_params;
 
 	/* maybe this create ought to just be in initiate and handle initiate */
 	if (media == NULL)
@@ -436,13 +438,16 @@
 		type = is_audio == TRUE ? PURPLE_MEDIA_RECV_AUDIO
 				: PURPLE_MEDIA_RECV_VIDEO;
 
+	params = 
+		jingle_get_params(jingle_session_get_js(session), &num_params);
 	purple_media_add_stream(media, name, remote_jid,
-			type, transmitter, 0, NULL);
+			type, transmitter, num_params, params);
 
 	g_free(name);
 	g_free(media_type);
 	g_free(remote_jid);
 	g_free(senders);
+	g_free(params);
 	g_object_unref(session);
 
 	return TRUE;
--- a/pidgin/gtkmedia.c	Thu Feb 05 00:15:13 2009 +0000
+++ b/pidgin/gtkmedia.c	Thu Feb 05 00:15:33 2009 +0000
@@ -57,6 +57,8 @@
 	GstElement *send_level;
 	GstElement *recv_level;
 
+	GtkWidget *statusbar;
+
 	GtkWidget *calling;
 	GtkWidget *accept;
 	GtkWidget *reject;
@@ -184,15 +186,58 @@
 	return FALSE;
 }
 
+static int
+pidgin_x_error_handler(Display *display, XErrorEvent *event)
+{
+	const gchar *error_type;
+	switch (event->error_code) {
+#define XERRORCASE(type) case type: error_type = #type; break
+		XERRORCASE(BadAccess);
+		XERRORCASE(BadAlloc);
+		XERRORCASE(BadAtom);
+		XERRORCASE(BadColor);
+		XERRORCASE(BadCursor);
+		XERRORCASE(BadDrawable);
+		XERRORCASE(BadFont);
+		XERRORCASE(BadGC);
+		XERRORCASE(BadIDChoice);
+		XERRORCASE(BadImplementation);
+		XERRORCASE(BadLength);
+		XERRORCASE(BadMatch);
+		XERRORCASE(BadName);
+		XERRORCASE(BadPixmap);
+		XERRORCASE(BadRequest);
+		XERRORCASE(BadValue);
+		XERRORCASE(BadWindow);
+#undef XERRORCASE
+		default:
+			error_type = "unknown";
+			break;
+	}
+	purple_debug_error("media", "A %s Xlib error has occurred. "
+			"The program would normally crash now.\n",
+			error_type);
+	return 0;
+}
+
 static void
 pidgin_media_init (PidginMedia *media)
 {
 	GtkWidget *vbox, *hbox;
 	media->priv = PIDGIN_MEDIA_GET_PRIVATE(media);
 
+	XSetErrorHandler(pidgin_x_error_handler);
+
 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	gtk_container_add(GTK_CONTAINER(media), vbox);
 
+	media->priv->statusbar = gtk_statusbar_new();
+	gtk_box_pack_end(GTK_BOX(vbox), media->priv->statusbar,
+			FALSE, FALSE, 0);
+	gtk_statusbar_push(GTK_STATUSBAR(media->priv->statusbar),
+			0, _("Connecting..."));
+	gtk_widget_show(media->priv->statusbar);
+
 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
 	gtk_widget_show(GTK_WIDGET(hbox));
@@ -215,7 +260,7 @@
 	gtk_widget_show_all(media->priv->accept);
 	gtk_widget_show_all(media->priv->reject);
 
-	media->priv->display = gtk_vbox_new(TRUE, PIDGIN_HIG_BOX_SPACE);
+	media->priv->display = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	gtk_box_pack_start(GTK_BOX(vbox), media->priv->display,
 			TRUE, TRUE, PIDGIN_HIG_BOX_SPACE);
 	gtk_widget_show(vbox);
@@ -366,6 +411,8 @@
 	if (conv != NULL)
 		purple_conversation_write(conv, NULL, error,
 				PURPLE_MESSAGE_ERROR, time(NULL));
+	gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar),
+			0, error);
 }
 
 static void
@@ -374,6 +421,8 @@
 {
 	pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_ACCEPTED);
 	pidgin_media_emit_message(gtkmedia, _("Call in progress."));
+	gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar),
+			0, _("Call in progress."));
 	gtk_widget_show(GTK_WIDGET(gtkmedia));
 }
 
@@ -430,6 +479,7 @@
 		GtkWidget *remote_video;
 		GtkWidget *plug;
 		GtkWidget *socket;
+		GdkColor color = {0, 0, 0, 0};
 
 		aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE);
 		gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN);
@@ -454,10 +504,11 @@
 		data->participant = g_strdup(gtkmedia->priv->screenname);
 
 		remote_video = gtk_drawing_area_new();
+		gtk_widget_modify_bg(remote_video, GTK_STATE_NORMAL, &color);
 		g_signal_connect(G_OBJECT(remote_video), "realize",
 				G_CALLBACK(realize_cb), data);
 		gtk_container_add(GTK_CONTAINER(plug), remote_video);
-		gtk_widget_set_size_request (GTK_WIDGET(remote_video), -1, 100);
+		gtk_widget_set_size_request (GTK_WIDGET(remote_video), 320, 240);
 		gtk_widget_show(remote_video);
 		gtk_widget_show(aspect);
 
@@ -469,6 +520,7 @@
 		GtkWidget *local_video;
 		GtkWidget *plug;
 		GtkWidget *socket;
+		GdkColor color = {0, 0, 0, 0};
 
 		aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE);
 		gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN);
@@ -493,10 +545,11 @@
 		data->participant = NULL;
 
 		local_video = gtk_drawing_area_new();
+		gtk_widget_modify_bg(local_video, GTK_STATE_NORMAL, &color);
 		g_signal_connect(G_OBJECT(local_video), "realize",
 				G_CALLBACK(realize_cb), data);
 		gtk_container_add(GTK_CONTAINER(plug), local_video);
-		gtk_widget_set_size_request (GTK_WIDGET(local_video), -1, 100);
+		gtk_widget_set_size_request (GTK_WIDGET(local_video), 160, 120);
 
 		gtk_widget_show(local_video);
 		gtk_widget_show(aspect);
@@ -551,6 +604,8 @@
 		pidgin_media_emit_message(gtkmedia, message);
 		g_free(message);
 	}
+
+	gtk_widget_show(gtkmedia->priv->display);
 }
 
 static void
@@ -573,15 +628,6 @@
 	} else if (type == PURPLE_MEDIA_STATE_CHANGED_NEW &&
 			sid != NULL && name != NULL) {
 		pidgin_media_ready_cb(media, gtkmedia, sid);
-	} else if (type == PURPLE_MEDIA_STATE_CHANGED_CONNECTED) {
-		GstElement *audiosendbin = NULL, *audiorecvbin = NULL;
-		GstElement *videosendbin = NULL, *videorecvbin = NULL;
-
-		purple_media_get_elements(media, &audiosendbin, &audiorecvbin,
-					  &videosendbin, &videorecvbin);
-
-		if (audiorecvbin || audiosendbin || videorecvbin || videosendbin)
-			gtk_widget_show(gtkmedia->priv->display);
 	}
 }
 
@@ -739,11 +785,88 @@
 	return TRUE;
 }
 
+static GstElement *
+create_default_video_src(void)
+{
+	GstElement *ret = NULL;
+	purple_media_video_init_src(&ret);
+	return ret;
+}
+
+static GstElement *
+create_default_video_sink(void)
+{
+	GstElement *ret = NULL;
+	purple_media_video_init_recv(&ret);
+	return ret;
+}
+
+static GstElement *
+create_default_audio_src(void)
+{
+	GstElement *ret = NULL, *level = NULL;
+	purple_media_audio_init_src(&ret, &level);
+	return ret;
+}
+
+static GstElement *
+create_default_audio_sink(void)
+{
+	GstElement *ret = NULL, *level = NULL;
+	purple_media_audio_init_recv(&ret, &level);
+	return ret;
+}
+
+static PurpleMediaElementInfo default_video_src =
+{
+	"pidgindefaultvideosrc",	/* id */
+	PURPLE_MEDIA_ELEMENT_VIDEO	/* type */
+			| PURPLE_MEDIA_ELEMENT_SRC
+			| PURPLE_MEDIA_ELEMENT_ONE_SRC
+			| PURPLE_MEDIA_ELEMENT_UNIQUE,
+	create_default_video_src,	/* create */
+};
+
+static PurpleMediaElementInfo default_video_sink =
+{
+	"pidgindefaultvideosink",	/* id */
+	PURPLE_MEDIA_ELEMENT_VIDEO	/* type */
+			| PURPLE_MEDIA_ELEMENT_SINK
+			| PURPLE_MEDIA_ELEMENT_ONE_SINK,
+	create_default_video_sink,	/* create */
+};
+
+static PurpleMediaElementInfo default_audio_src =
+{
+	"pidgindefaultaudiosrc",	/* id */
+	PURPLE_MEDIA_ELEMENT_AUDIO	/* type */
+			| PURPLE_MEDIA_ELEMENT_SRC
+			| PURPLE_MEDIA_ELEMENT_ONE_SRC
+			| PURPLE_MEDIA_ELEMENT_UNIQUE,
+	create_default_audio_src,	/* create */
+};
+
+static PurpleMediaElementInfo default_audio_sink =
+{
+	"pidgindefaultaudiosink",	/* id */
+	PURPLE_MEDIA_ELEMENT_AUDIO	/* type */
+			| PURPLE_MEDIA_ELEMENT_SINK
+			| PURPLE_MEDIA_ELEMENT_ONE_SINK,
+	create_default_audio_sink,	/* create */
+};
+
 void
 pidgin_medias_init(void)
 {
-	g_signal_connect(G_OBJECT(purple_media_manager_get()), "init-media",
+	PurpleMediaManager *manager = purple_media_manager_get();
+	g_signal_connect(G_OBJECT(manager), "init-media",
 			 G_CALLBACK(pidgin_media_new_cb), NULL);
+
+	purple_debug_info("gtkmedia", "Registering media element types\n");
+	purple_media_manager_set_active_element(manager, &default_video_src);
+	purple_media_manager_set_active_element(manager, &default_video_sink);
+	purple_media_manager_set_active_element(manager, &default_audio_src);
+	purple_media_manager_set_active_element(manager, &default_audio_sink);
 }
 
 #endif  /* USE_VV */