changeset 26101:f06eb6e7d907

merge of '1cd039d2f04c877820b4097e462f26831c2d828e' and 'fd3a51d3135cc4ef80591280934a987837bd3a2e'
author Mike Ruprecht <maiku@soc.pidgin.im>
date Thu, 05 Feb 2009 11:47:40 +0000
parents dcff28a0415c (diff) 23038e1a1754 (current diff)
children 8f1ce165de35 f44b5f61d4b7
files libpurple/media.c
diffstat 42 files changed, 1354 insertions(+), 605 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Thu Feb 05 11:47:36 2009 +0000
+++ b/COPYRIGHT	Thu Feb 05 11:47:40 2009 +0000
@@ -314,6 +314,7 @@
 Ted Percival
 Eduardo Pérez
 Matt Perry
+Luke Petre
 Diego Petten
 Nathan Peterson
 Sebastián E. Peyrott
--- a/ChangeLog	Thu Feb 05 11:47:36 2009 +0000
+++ b/ChangeLog	Thu Feb 05 11:47:40 2009 +0000
@@ -2,17 +2,29 @@
 
 version 2.5.5 (??/??/????):
 	libpurple:
+	* Fix a crash when removing an account with an unknown protocol id.
+	* Beta support for SSL connections for AIM and ICQ accounts.  To
+	  enable, check the "Use SSL" option from the Advanced tab when
+	  editing your AIM or ICQ account. (Paul Aurich)
+	* Fix a memory leak in SILC. (Luke Petre)
+
+	ICQ:
+	* Fix retrieval of status messages from users of ICQ 6.x, Miranda, and
+	  other libpurple clients. (Daniel Ljungborg)
+	* Change client ID to match ICQ Basic 14.34.3096.  This fixes publishing
+	  of buddy icons and available messages.
+	* Properly publish status messages for statuses other than Available.
+	  ICQ 6.x users can now see these status messages. (Daniel Ljungborg)
+
+	MSN:
 	* Fix transfer of buddy icons, custom smileys, and files from the
 	  latest Windows Live Messenger 9 official client. (Thomas
 	  Gibson-Robinson)
-	* Fix a crash when removing an account with an unknown protocol id.
-	* Large (multi-part) messages on MSN are now correctly re-combined.
-	* Beta support for SSL connections for AIM and ICQ accounts.  To
-	  enable, check the "Use SSL" option from the Advanced tab when
-	  editing your AIM or ICQ account. (Paul Aurich)
-	* Fix retrieval of ICQ status messages from users of ICQ 6.x, Miranda,
-	  and other libpurple clients (fixes with libpurple users only on
-	  statuses other than Available). (Daniel Ljungborg)
+	* Large (multi-part) messages are now correctly re-combined.
+	* Federated/Yahoo! buddies should now stop creating sync issues at
+	  every signin.  You may need to remove duplicates in the Address
+	  Book.  See the FAQ for more information.
+	* Messages from Yahoo! buddies are no longer silently dropped.
 
 	Finch:
 	* Allow rebinding keys to change the focused widget (details in the
--- a/ChangeLog.API	Thu Feb 05 11:47:36 2009 +0000
+++ b/ChangeLog.API	Thu Feb 05 11:47:40 2009 +0000
@@ -12,6 +12,8 @@
 		* purple_status_type_new now defaults "saveable" to TRUE.
 		  This was necessary in order to maintain the current behavior
 		  while fixing non-saveable statuses not to be saved.
+		* xmlnode_get_prefix, xmlnode_to_str and xmlnode_to_formatted_str
+		  now all take a const xmlnode* instead of an xmlnode*
 
 version 2.5.4 (01/12/2009):
 	perl:
--- a/ChangeLog.win32	Thu Feb 05 11:47:36 2009 +0000
+++ b/ChangeLog.win32	Thu Feb 05 11:47:40 2009 +0000
@@ -1,3 +1,8 @@
+version 2.5.5 (??/??/2009):
+	* Remove the "Flash window when chat messages are received" pref from
+	  the Windows Pidgin Options plugin - the Message Notification plugin
+	  does this (and much more).
+
 version 2.5.4 (01/12/2009):
 	* Fix the "Hang on Exit" issue that a number of users encountered.
 	* Updated GTK+ to 2.14.6
--- a/libpurple/dbus-analyze-functions.py	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/dbus-analyze-functions.py	Thu Feb 05 11:47:40 2009 +0000
@@ -497,7 +497,7 @@
         # This is a total hack, but self.call is set up before the parameters
         #  are processed, so we can't tell it to pass a parameter by reference.
         self.call = "%s(%s)" % (self.function.name,
-                                ", ".join(param.name if param.name != "len" else "&len" for param in self.params))
+                                ", ".join([(param.name, "&len")[param.name == "len"] for param in self.params]))
 
         self.cdecls.append("\tgconstpointer %s;" % name)
         self.ccode.append("\t%s = %s;" % (name, self.call))
--- a/libpurple/media.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/media.c	Thu Feb 05 11:47:40 2009 +0000
@@ -75,8 +75,8 @@
 
 	gboolean candidates_prepared;
 
-	FsCandidate *local_candidate;
-	FsCandidate *remote_candidate;
+	GList *active_local_candidates;
+	GList *active_remote_candidates;
 
 	gulong window_id;
 };
@@ -250,10 +250,10 @@
 	if (stream->remote_candidates)
 		fs_candidate_list_destroy(stream->remote_candidates);
 
-	if (stream->local_candidate)
-		fs_candidate_destroy(stream->local_candidate);
-	if (stream->remote_candidate)
-		fs_candidate_destroy(stream->remote_candidate);
+	if (stream->active_local_candidates)
+		fs_candidate_list_destroy(stream->active_local_candidates);
+	if (stream->active_remote_candidates)
+		fs_candidate_list_destroy(stream->active_remote_candidates);
 
 	g_free(stream);
 }
@@ -1253,10 +1253,39 @@
 				
 			} else if (gst_structure_has_name(msg->structure,
 					"farsight-component-state-changed")) {
-				
+				FsStreamState fsstate = g_value_get_enum(gst_structure_get_value(msg->structure, "state"));
+				guint component = g_value_get_uint(gst_structure_get_value(msg->structure, "component"));
+				const gchar *state;
+				switch (fsstate) {
+					case FS_STREAM_STATE_FAILED:
+						state = "FAILED";
+						break;
+					case FS_STREAM_STATE_DISCONNECTED:
+						state = "DISCONNECTED";
+						break;
+					case FS_STREAM_STATE_GATHERING:
+						state = "GATHERING";
+						break;
+					case FS_STREAM_STATE_CONNECTING:
+						state = "CONNECTING";
+						break;
+					case FS_STREAM_STATE_CONNECTED:
+						state = "CONNECTED";
+						break;
+					case FS_STREAM_STATE_READY:
+						state = "READY";
+						break;
+					default:
+						state = "UNKNOWN";
+						break;
+				}
+				purple_debug_info("media", "farsight-component-state-changed: component: %u state: %s\n", component, state);
 			} else if (gst_structure_has_name(msg->structure,
 					"farsight-send-codec-changed")) {
-				
+				FsCodec *codec = g_value_get_boxed(gst_structure_get_value(msg->structure, "codec"));
+				gchar *codec_str = fs_codec_to_string(codec);
+				purple_debug_info("media", "farsight-send-codec-changed: codec: %s\n", codec_str);
+				g_free(codec_str);
 			} else if (gst_structure_has_name(msg->structure,
 					"farsight-codecs-changed")) {
 				GList *sessions = g_hash_table_get_values(PURPLE_MEDIA(media)->priv->sessions);
@@ -1641,6 +1670,7 @@
 	gchar *name;
 	FsParticipant *participant;
 	PurpleMediaStream *stream;
+	GList *iter;
 
 	g_return_if_fail(FS_IS_STREAM(fsstream));
 	g_return_if_fail(session != NULL);
@@ -1651,8 +1681,41 @@
 
 	stream = purple_media_get_stream(session->media, session->id, name);
 
-	stream->local_candidate = fs_candidate_copy(native_candidate);
-	stream->remote_candidate = fs_candidate_copy(remote_candidate);
+	iter = stream->active_local_candidates;
+	for(; iter; iter = g_list_next(iter)) {
+		FsCandidate *c = iter->data;
+		if (native_candidate->component_id == c->component_id) {
+			fs_candidate_destroy(c);
+			stream->active_local_candidates =
+					g_list_delete_link(iter, iter);
+			stream->active_local_candidates = g_list_prepend(
+					stream->active_local_candidates,
+					fs_candidate_copy(native_candidate));
+			break;
+		}
+	}
+	if (iter == NULL)
+		stream->active_local_candidates = g_list_prepend(
+				stream->active_local_candidates,
+				fs_candidate_copy(native_candidate));
+
+	iter = stream->active_remote_candidates;
+	for(; iter; iter = g_list_next(iter)) {
+		FsCandidate *c = iter->data;
+		if (native_candidate->component_id == c->component_id) {
+			fs_candidate_destroy(c);
+			stream->active_remote_candidates =
+					g_list_delete_link(iter, iter);
+			stream->active_remote_candidates = g_list_prepend(
+					stream->active_remote_candidates,
+					fs_candidate_copy(remote_candidate));
+			break;
+		}
+	}
+	if (iter == NULL)
+		stream->active_remote_candidates = g_list_prepend(
+				stream->active_remote_candidates,
+				fs_candidate_copy(remote_candidate));
 
 	purple_debug_info("media", "candidate pair established\n");
 }
@@ -1739,7 +1802,14 @@
 		g_free(filename);
 
 		if (err != NULL) {
-			purple_debug_error("media", "Error reading codec configuration file: %s\n", err->message);
+			if (err->code == 4)
+				purple_debug_info("media", "Couldn't read "
+						"fs-codec.conf: %s\n",
+						err->message);
+			else
+				purple_debug_error("media", "Error reading "
+						"fs-codec.conf: %s\n",
+						err->message);
 			g_error_free(err);
 		}
 
@@ -1987,22 +2057,26 @@
 	}
 }
 
-PurpleMediaCandidate *
-purple_media_get_local_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+GList *
+purple_media_get_active_local_candidates(PurpleMedia *media,
+		const gchar *sess_id, const gchar *name)
 {
 	PurpleMediaStream *stream;
 	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
 	stream = purple_media_get_stream(media, sess_id, name);
-	return purple_media_candidate_from_fs(stream->local_candidate);
+	return purple_media_candidate_list_from_fs(
+			stream->active_local_candidates);
 }
 
-PurpleMediaCandidate *
-purple_media_get_remote_candidate(PurpleMedia *media, const gchar *sess_id, const gchar *name)
+GList *
+purple_media_get_active_remote_candidates(PurpleMedia *media,
+		const gchar *sess_id, const gchar *name)
 {
 	PurpleMediaStream *stream;
 	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
 	stream = purple_media_get_stream(media, sess_id, name);
-	return purple_media_candidate_from_fs(stream->remote_candidate);
+	return purple_media_candidate_list_from_fs(
+			stream->active_remote_candidates);
 }
 
 gboolean
@@ -2044,8 +2118,14 @@
 
 	for (; sessions; sessions = sessions->next) {
 		const gchar *session = sessions->data;
-		if (!purple_media_get_local_candidate(media, session, name) ||
-				!purple_media_get_remote_candidate(media, session, name))
+		GList *local = purple_media_get_active_local_candidates(
+				media, session, name);
+		GList *remote = purple_media_get_active_remote_candidates(
+				media, session, name);
+		gboolean result = (local == NULL || remote == NULL);
+		purple_media_candidate_list_free(local);
+		purple_media_candidate_list_free(remote);
+		if (!result)
 			return FALSE;
 	}
 
--- a/libpurple/media.h	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/media.h	Thu Feb 05 11:47:40 2009 +0000
@@ -533,27 +533,27 @@
 					 const gchar *name);
 
 /**
- * Gets the active local candidate for the stream.
+ * Gets the active local candidates for the stream.
  *
  * @param media The media object to find the session in.
  * @param sess_id The session id of the session to find the stream in.
  * @param name The name of the remote user to get the active candidate from.
  *
- * @return The active candidate retrieved.
+ * @return The active candidates retrieved.
  */
-PurpleMediaCandidate *purple_media_get_local_candidate(PurpleMedia *media,
+GList *purple_media_get_active_local_candidates(PurpleMedia *media,
 		const gchar *sess_id, const gchar *name);
 
 /**
- * Gets the active remote candidate for the stream.
+ * Gets the active remote candidates for the stream.
  *
  * @param media The media object to find the session in.
  * @param sess_id The session id of the session to find the stream in.
  * @param name The name of the remote user to get the remote candidate from.
  *
- * @return The remote candidate retrieved.
+ * @return The remote candidates retrieved.
  */
-PurpleMediaCandidate *purple_media_get_remote_candidate(PurpleMedia *media,
+GList *purple_media_get_active_remote_candidates(PurpleMedia *media,
 		const gchar *sess_id, const gchar *name);
 
 /**
--- a/libpurple/mediamanager.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/mediamanager.c	Thu Feb 05 11:47:40 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 11:47:36 2009 +0000
+++ b/libpurple/mediamanager.h	Thu Feb 05 11:47:40 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/network.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/network.c	Thu Feb 05 11:47:40 2009 +0000
@@ -75,8 +75,12 @@
 
 /* Mutex for the other global vars */
 static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
-static gboolean network_initialized;
-static HANDLE network_change_handle;
+static gboolean network_initialized = FALSE;
+static HANDLE network_change_handle = NULL;
+static int (WSAAPI *MyWSANSPIoctl) (
+		HANDLE hLookup, DWORD dwControlCode, LPVOID lpvInBuffer,
+		DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer,
+		LPDWORD lpcbBytesReturned, LPWSACOMPLETION lpCompletion) = NULL;
 #endif
 
 struct _PurpleNetworkListenData {
@@ -543,27 +547,28 @@
 	return FALSE;
 }
 
+static gboolean _print_debug_msg(gpointer data) {
+	gchar *msg = data;
+	purple_debug_warning("network", msg);
+	g_free(msg);
+	return FALSE;
+}
+
 static gpointer wpurple_network_change_thread(gpointer data)
 {
 	WSAQUERYSET qs;
 	WSAEVENT *nla_event;
-	time_t last_trigger = time(NULL);
-
-	int (WSAAPI *MyWSANSPIoctl) (
-		HANDLE hLookup, DWORD dwControlCode, LPVOID lpvInBuffer,
-		DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer,
-		LPDWORD lpcbBytesReturned, LPWSACOMPLETION lpCompletion) = NULL;
-
-	if (!(MyWSANSPIoctl = (void*) wpurple_find_and_loadproc("ws2_32.dll", "WSANSPIoctl"))) {
-		g_thread_exit(NULL);
-		return NULL;
-	}
+	time_t last_trigger = time(NULL) - 31;
+	char buf[4096];
+	WSAQUERYSET *res = (LPWSAQUERYSET) buf;
+	DWORD size;
 
 	if ((nla_event = WSACreateEvent()) == WSA_INVALID_EVENT) {
 		int errorid = WSAGetLastError();
 		gchar *msg = g_win32_error_message(errorid);
-		purple_debug_warning("network", "Couldn't create WSA event. "
-			"Message: %s (%d).\n", msg, errorid);
+		purple_timeout_add(0, _print_debug_msg,
+						   g_strdup_printf("Couldn't create WSA event. "
+										   "Message: %s (%d).\n", msg, errorid));
 		g_free(msg);
 		g_thread_exit(NULL);
 		return NULL;
@@ -584,30 +589,26 @@
 			return NULL;
 		}
 
-		memset(&qs, 0, sizeof(WSAQUERYSET));
-		qs.dwSize = sizeof(WSAQUERYSET);
-		qs.dwNameSpace = NS_NLA;
-		if (WSALookupServiceBegin(&qs, 0, &network_change_handle) == SOCKET_ERROR) {
-			int errorid = WSAGetLastError();
-			gchar *msg = g_win32_error_message(errorid);
-			purple_debug_warning("network", "Couldn't retrieve NLA SP lookup handle. "
-				"NLA service is probably not running. Message: %s (%d).\n",
-				msg, errorid);
-			g_free(msg);
-			WSACloseEvent(nla_event);
-			g_static_mutex_unlock(&mutex);
-			g_thread_exit(NULL);
-			return NULL;
+		if (network_change_handle == NULL) {
+			memset(&qs, 0, sizeof(WSAQUERYSET));
+			qs.dwSize = sizeof(WSAQUERYSET);
+			qs.dwNameSpace = NS_NLA;
+			if (WSALookupServiceBegin(&qs, 0, &network_change_handle) == SOCKET_ERROR) {
+				int errorid = WSAGetLastError();
+				gchar *msg = g_win32_error_message(errorid);
+				purple_timeout_add(0, _print_debug_msg,
+								   g_strdup_printf("Couldn't retrieve NLA SP lookup handle. "
+												   "NLA service is probably not running. Message: %s (%d).\n",
+													msg, errorid));
+				g_free(msg);
+				WSACloseEvent(nla_event);
+				g_static_mutex_unlock(&mutex);
+				g_thread_exit(NULL);
+				return NULL;
+			}
 		}
 		g_static_mutex_unlock(&mutex);
 
-		/* Make sure at least 30 seconds have elapsed since the last
-		 * notification so we don't peg the cpu if this keeps changing. */
-		if ((time(NULL) - last_trigger) < 30)
-			Sleep(30000);
-
-		last_trigger = time(NULL);
-
 		memset(&completion, 0, sizeof(WSACOMPLETION));
 		completion.Type = NSP_NOTIFY_EVENT;
 		overlapped.hEvent = nla_event;
@@ -615,18 +616,34 @@
 
 		if (MyWSANSPIoctl(network_change_handle, SIO_NSP_NOTIFY_CHANGE, NULL, 0, NULL, 0, &retLen, &completion) == SOCKET_ERROR) {
 			int errorid = WSAGetLastError();
+			if (errorid == WSA_INVALID_HANDLE) {
+				purple_timeout_add(0, _print_debug_msg,
+								   g_strdup("Invalid NLA handle; resetting.\n"));
+				g_static_mutex_lock(&mutex);
+				retval = WSALookupServiceEnd(network_change_handle);
+				network_change_handle = NULL;
+				g_static_mutex_unlock(&mutex);
+				continue;
 			/* WSA_IO_PENDING indicates successful async notification will happen */
-			if (errorid != WSA_IO_PENDING) {
+			} else if (errorid != WSA_IO_PENDING) {
 				gchar *msg = g_win32_error_message(errorid);
-				purple_debug_warning("network", "Unable to wait for changes. Message: %s (%d).\n",
-					msg, errorid);
+				purple_timeout_add(0, _print_debug_msg,
+								   g_strdup_printf("Unable to wait for changes. Message: %s (%d).\n",
+												   msg, errorid));
 				g_free(msg);
 			}
 		}
 
+		/* Make sure at least 30 seconds have elapsed since the last
+		 * notification so we don't peg the cpu if this keeps changing. */
+		if ((time(NULL) - last_trigger) < 30)
+			Sleep(30000);
+
 		/* This will block until NLA notifies us */
 		retval = WaitForSingleObjectEx(nla_event, WSA_INFINITE, TRUE);
 
+		last_trigger = time(NULL);
+
 		g_static_mutex_lock(&mutex);
 		if (network_initialized == FALSE) {
 			/* Time to die */
@@ -636,8 +653,14 @@
 			return NULL;
 		}
 
-		retval = WSALookupServiceEnd(network_change_handle);
-		network_change_handle = NULL;
+		size = sizeof(buf);
+		while ((retval = WSALookupServiceNext(network_change_handle, 0, &size, res)) == ERROR_SUCCESS) {
+			/*purple_timeout_add(0, _print_debug_msg,
+							   g_strdup_printf("thread found network '%s'\n",
+											   res->lpszServiceInstanceName ? res->lpszServiceInstanceName : "(NULL)"));*/
+			size = sizeof(buf);
+		}
+
 		WSAResetEvent(nla_event);
 		g_static_mutex_unlock(&mutex);
 
@@ -863,11 +886,12 @@
 	if (cnt < 0) /* Assume there is a network */
 		current_network_count = 1;
 	/* Don't listen for network changes if we can't tell anyway */
-	else
-	{
+	else {
 		current_network_count = cnt;
-		if (!g_thread_create(wpurple_network_change_thread, NULL, FALSE, &err))
-			purple_debug_error("network", "Couldn't create Network Monitor thread: %s\n", err ? err->message : "");
+		if ((MyWSANSPIoctl = (void*) wpurple_find_and_loadproc("ws2_32.dll", "WSANSPIoctl"))) {
+			if (!g_thread_create(wpurple_network_change_thread, NULL, FALSE, &err))
+				purple_debug_error("network", "Couldn't create Network Monitor thread: %s\n", err ? err->message : "");
+		}
 	}
 #endif
 
@@ -953,12 +977,14 @@
 				msg, errorid);
 			g_free(msg);
 		}
+		network_change_handle = NULL;
+
 	}
 	g_static_mutex_unlock(&mutex);
 
 #endif
 	purple_signal_unregister(purple_network_get_handle(),
-	                         "network-configuration-changed");
+							 "network-configuration-changed");
 	
 	if (stun_ip)
 		g_free(stun_ip);
--- a/libpurple/protocols/jabber/disco.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Thu Feb 05 11:47:40 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 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Thu Feb 05 11:47:40 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 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/google.h	Thu Feb 05 11:47:40 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 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Thu Feb 05 11:47:40 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 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Thu Feb 05 11:47:40 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;
@@ -1881,7 +1898,7 @@
 
 	type = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE,
 			jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE),
-			NULL, FALSE, TRUE, FALSE,
+			NULL, TRUE, TRUE, FALSE,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
 			NULL);
 	types = g_list_append(types, type);
--- a/libpurple/protocols/jabber/jabber.h	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Thu Feb 05 11:47:40 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/iceudp.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/iceudp.c	Thu Feb 05 11:47:40 2009 +0000
@@ -57,6 +57,7 @@
 	new_candidate->component = candidate->component;
 	new_candidate->foundation = g_strdup(candidate->foundation);
 	new_candidate->generation = candidate->generation;
+	new_candidate->id = g_strdup(candidate->id);
 	new_candidate->ip = g_strdup(candidate->ip);
 	new_candidate->network = candidate->network;
 	new_candidate->port = candidate->port;
@@ -74,6 +75,7 @@
 jingle_iceudp_candidate_free(JingleIceUdpCandidate *candidate)
 {
 	g_free(candidate->foundation);
+	g_free(candidate->id);
 	g_free(candidate->ip);
 	g_free(candidate->protocol);
 	g_free(candidate->type);
@@ -97,14 +99,16 @@
 
 JingleIceUdpCandidate *
 jingle_iceudp_candidate_new(guint component, const gchar *foundation,
-		guint generation, const gchar *ip, guint network,
-		guint port, guint priority, const gchar *protocol,
-		const gchar *type, const gchar *username, const gchar *password)
+		guint generation, const gchar *id, const gchar *ip,
+		guint network, guint port, guint priority,
+		const gchar *protocol, const gchar *type,
+		const gchar *username, const gchar *password)
 {
 	JingleIceUdpCandidate *candidate = g_new0(JingleIceUdpCandidate, 1);
 	candidate->component = component;
 	candidate->foundation = g_strdup(foundation);
 	candidate->generation = generation;
+	candidate->id = g_strdup(id);
 	candidate->ip = g_strdup(ip);
 	candidate->network = network;
 	candidate->port = port;
@@ -233,8 +237,7 @@
 
 	for (; iter; iter = g_list_next(iter)) {
 		JingleIceUdpCandidate *c = iter->data;
-		if ((c->component == candidate->component) &&
-				!strcmp(c->foundation, candidate->foundation)) {
+		if (!strcmp(c->id, candidate->id)) {
 			guint generation = c->generation + 1;
 
 			g_boxed_free(JINGLE_TYPE_ICEUDP_CANDIDATE, c);
@@ -261,13 +264,12 @@
 
 static JingleIceUdpCandidate *
 jingle_iceudp_get_remote_candidate_by_id(JingleIceUdp *iceudp,
-		guint component, const gchar *foundation)
+		const gchar *id)
 {
 	GList *iter = iceudp->priv->remote_candidates;
 	for (; iter; iter = g_list_next(iter)) {
 		JingleIceUdpCandidate *candidate = iter->data;
-		if ((candidate->component == component) &&
-				!strcmp(candidate->foundation, foundation)) {
+		if (!strcmp(candidate->id, id)) {
 			return candidate;
 		}
 	}
@@ -280,7 +282,7 @@
 	JingleIceUdpPrivate *priv = JINGLE_ICEUDP_GET_PRIVATE(iceudp);
 	JingleIceUdpCandidate *iceudp_candidate =
 			jingle_iceudp_get_remote_candidate_by_id(iceudp,
-					candidate->component, candidate->foundation);
+					candidate->id);
 	if (iceudp_candidate != NULL) {
 		priv->remote_candidates = g_list_remove(
 				priv->remote_candidates, iceudp_candidate);
@@ -304,6 +306,7 @@
 				atoi(xmlnode_get_attrib(candidate, "component")),
 				xmlnode_get_attrib(candidate, "foundation"),
 				atoi(xmlnode_get_attrib(candidate, "generation")),
+				xmlnode_get_attrib(candidate, "id"),
 				xmlnode_get_attrib(candidate, "ip"),
 				atoi(xmlnode_get_attrib(candidate, "network")),
 				atoi(xmlnode_get_attrib(candidate, "port")),
@@ -347,6 +350,7 @@
 			xmlnode_set_attrib(xmltransport, "component", component);
 			xmlnode_set_attrib(xmltransport, "foundation", candidate->foundation);
 			xmlnode_set_attrib(xmltransport, "generation", generation);
+			xmlnode_set_attrib(xmltransport, "id", candidate->id);
 			xmlnode_set_attrib(xmltransport, "ip", candidate->ip);
 			xmlnode_set_attrib(xmltransport, "network", network);
 			xmlnode_set_attrib(xmltransport, "port", port);
--- a/libpurple/protocols/jabber/jingle/iceudp.h	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/iceudp.h	Thu Feb 05 11:47:40 2009 +0000
@@ -66,6 +66,7 @@
 	guint component;
 	gchar *foundation;
 	guint generation;
+	gchar *id;
 	gchar *ip;
 	guint network;
 	guint port;
@@ -91,9 +92,10 @@
 GType jingle_iceudp_get_type(void);
 
 JingleIceUdpCandidate *jingle_iceudp_candidate_new(guint component,
-		const gchar *foundation, guint generation, const gchar *ip,
-		guint network, guint port, guint priority, const gchar *protocol,
-		const gchar *type, const gchar *username, const gchar *password);
+		const gchar *foundation, guint generation, const gchar *id,
+		const gchar *ip, guint network, guint port, guint priority,
+		const gchar *protocol, const gchar *type,
+		const gchar *username, const gchar *password);
 void jingle_iceudp_add_local_candidate(JingleIceUdp *iceudp, JingleIceUdpCandidate *candidate);
 GList *jingle_iceudp_get_remote_candidates(JingleIceUdp *iceudp);
 
--- a/libpurple/protocols/jabber/jingle/jingle.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Thu Feb 05 11:47:40 2009 +0000
@@ -20,6 +20,7 @@
  */
 
 #include "internal.h"
+#include "network.h"
 
 #include "content.h"
 #include "debug.h"
@@ -40,8 +41,12 @@
 			return "content-add";
 		case JINGLE_CONTENT_MODIFY:
 			return "content-modify";
+		case JINGLE_CONTENT_REJECT:
+			return "content-reject";
 		case JINGLE_CONTENT_REMOVE:
 			return "content-remove";
+		case JINGLE_DESCRIPTION_INFO:
+			return "description-info";
 		case JINGLE_SESSION_ACCEPT:
 			return "session-accept";
 		case JINGLE_SESSION_INFO:
@@ -54,6 +59,8 @@
 			return "transport-accept";
 		case JINGLE_TRANSPORT_INFO:
 			return "transport-info";
+		case JINGLE_TRANSPORT_REJECT:
+			return "transport-reject";
 		case JINGLE_TRANSPORT_REPLACE:
 			return "transport-replace";
 		default:
@@ -70,8 +77,12 @@
 		return JINGLE_CONTENT_ADD;
 	else if (!strcmp(action, "content-modify"))
 		return JINGLE_CONTENT_MODIFY;
+	else if (!strcmp(action, "content-reject"))
+		return JINGLE_CONTENT_REJECT;
 	else if (!strcmp(action, "content-remove"))
 		return JINGLE_CONTENT_REMOVE;
+	else if (!strcmp(action, "description-info"))
+		return JINGLE_DESCRIPTION_INFO;
 	else if (!strcmp(action, "session-accept"))
 		return JINGLE_SESSION_ACCEPT;
 	else if (!strcmp(action, "session-info"))
@@ -84,6 +95,8 @@
 		return JINGLE_TRANSPORT_ACCEPT;
 	else if (!strcmp(action, "transport-info"))
 		return JINGLE_TRANSPORT_INFO;
+	else if (!strcmp(action, "transport-reject"))
+		return JINGLE_TRANSPORT_REJECT;
 	else if (!strcmp(action, "transport-replace"))
 		return JINGLE_TRANSPORT_REPLACE;
 	else
@@ -206,6 +219,30 @@
 }
 
 static void
+jingle_handle_description_info(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	jingle_session_accept_session(session);
+	
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *parsed_content =
+				jingle_session_find_content(session, name, creator);
+		if (parsed_content == NULL) {
+			purple_debug_error("jingle", "Error parsing content\n");
+			/* XXX: send error */
+		} else {
+			jingle_content_handle_action(parsed_content, content,
+					JINGLE_DESCRIPTION_INFO);
+		}
+	}
+}
+
+static void
 jingle_handle_session_accept(JingleSession *session, xmlnode *jingle)
 {
 	xmlnode *content = xmlnode_get_child(jingle, "content");
@@ -407,6 +444,8 @@
 		jingle_handle_content_reject(session, jingle);
 	} else if (!strcmp(action, "content-remove")) {
 		jingle_handle_content_remove(session, jingle);
+	} else if (!strcmp(action, "description-info")) {
+		jingle_handle_description_info(session, jingle);
 	} else if (!strcmp(action, "session-accept")) {
 		jingle_handle_session_accept(session, jingle);
 	} else if (!strcmp(action, "session-info")) {
@@ -438,3 +477,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 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.h	Thu Feb 05 11:47:40 2009 +0000
@@ -33,9 +33,9 @@
 #define JINGLE "urn:xmpp:jingle:0"
 #define JINGLE_ERROR "urn:xmpp:jingle:errors:0"
 #define JINGLE_APP_FT "urn:xmpp:jingle:apps:file-transfer:0"
-#define JINGLE_APP_RTP "urn:xmpp:jingle:apps:rtp:0"
-#define JINGLE_APP_RTP_ERROR "urn:xmpp:jingle:apps:rtp:errors:0"
-#define JINGLE_APP_RTP_INFO "urn:xmpp:jingle:apps:rtp:info:0"
+#define JINGLE_APP_RTP "urn:xmpp:jingle:apps:rtp:1"
+#define JINGLE_APP_RTP_ERROR "urn:xmpp:jingle:apps:rtp:errors:1"
+#define JINGLE_APP_RTP_INFO "urn:xmpp:jingle:apps:rtp:info:1"
 #define JINGLE_APP_RTP_SUPPORT_AUDIO "urn:xmpp:jingle:apps:rtp:audio"
 #define JINGLE_APP_RTP_SUPPORT_VIDEO "urn:xmpp:jingle:apps:rtp:video"
 #define JINGLE_APP_XML "urn:xmpp:tmp:jingle:apps:xmlstream"
@@ -43,8 +43,7 @@
 #define JINGLE_TRANSPORT_SOCKS "urn:xmpp:jingle:transports:bytestreams:0"
 #define JINGLE_TRANSPORT_IBB "urn:xmpp:jingle:transports:ibb:0"
 #define JINGLE_TRANSPORT_ICEUDP "urn:xmpp:jingle:transports:ice-udp:0"
-#define JINGLE_TRANSPORT_RAWUDP "urn:xmpp:jingle:transports:raw-udp:0"
-#define JINGLE_TRANSPORT_RAWUDP_INFO "urn:xmpp:jingle:transports:raw-udp:info:0"
+#define JINGLE_TRANSPORT_RAWUDP "urn:xmpp:jingle:transports:raw-udp:1"
 
 typedef enum {
 	JINGLE_UNKNOWN_TYPE,
@@ -53,6 +52,7 @@
 	JINGLE_CONTENT_MODIFY,
 	JINGLE_CONTENT_REJECT,
 	JINGLE_CONTENT_REMOVE,
+	JINGLE_DESCRIPTION_INFO,
 	JINGLE_SESSION_ACCEPT,
 	JINGLE_SESSION_INFO,
 	JINGLE_SESSION_INITIATE,
@@ -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 11:47:36 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/rtp.c	Thu Feb 05 11:47:40 2009 +0000
@@ -189,26 +189,28 @@
 jingle_rtp_candidates_to_transport(JingleSession *session, GType type, guint generation, GList *candidates)
 {
 	if (type == JINGLE_TYPE_RAWUDP) {
-		gchar *id = jabber_get_next_id(jingle_session_get_js(session));
 		JingleTransport *transport = jingle_transport_create(JINGLE_TRANSPORT_RAWUDP);
 		JingleRawUdpCandidate *rawudp_candidate;
 		for (; candidates; candidates = g_list_next(candidates)) {
 			PurpleMediaCandidate *candidate = candidates->data;
-			id = jabber_get_next_id(jingle_session_get_js(session));
+			gchar *id = jabber_get_next_id(
+					jingle_session_get_js(session));
 			rawudp_candidate = jingle_rawudp_candidate_new(id,
 					generation, candidate->component_id,
 					candidate->ip, candidate->port);
 			jingle_rawudp_add_local_candidate(JINGLE_RAWUDP(transport), rawudp_candidate);
+			g_free(id);
 		}
-		g_free(id);
 		return transport;
 	} else if (type == JINGLE_TYPE_ICEUDP) {
 		JingleTransport *transport = jingle_transport_create(JINGLE_TRANSPORT_ICEUDP);
 		JingleIceUdpCandidate *iceudp_candidate;
 		for (; candidates; candidates = g_list_next(candidates)) {
 			PurpleMediaCandidate *candidate = candidates->data;
+			gchar *id = jabber_get_next_id(
+					jingle_session_get_js(session));
 			iceudp_candidate = jingle_iceudp_candidate_new(candidate->component_id,
-					candidate->foundation, generation, candidate->ip,
+					candidate->foundation, generation, id, candidate->ip,
 					0, candidate->port, candidate->priority, "udp",
 					candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_HOST ? "host" :
 					candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX ? "srflx" :
@@ -216,6 +218,7 @@
 					candidate->type == PURPLE_MEDIA_CANDIDATE_TYPE_RELAY ? "relay" : "",
 					candidate->username, candidate->password);
 			jingle_iceudp_add_local_candidate(JINGLE_ICEUDP(transport), iceudp_candidate);
+			g_free(id);
 		}
 		return transport;
 	} else {
@@ -402,6 +405,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 +441,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/libpurple/protocols/msn/contact.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/msn/contact.c	Thu Feb 05 11:47:40 2009 +0000
@@ -158,6 +158,113 @@
 	state->action |= action;
 }
 
+/***************************************************************
+ * General SOAP handling
+ ***************************************************************/
+
+static const char *
+msn_contact_operation_str(MsnCallbackAction action)
+{
+	/* Make sure this is large enough when adding more */
+	static char buf[BUF_LEN];
+	buf[0] = '\0';
+
+	if (action & MSN_ADD_BUDDY)
+		strcat(buf, "Adding Buddy,");
+	if (action & MSN_MOVE_BUDDY)
+		strcat(buf, "Moving Buddy,");
+	if (action & MSN_ACCEPTED_BUDDY)
+		strcat(buf, "Accepted Buddy,");
+	if (action & MSN_DENIED_BUDDY)
+		strcat(buf, "Denied Buddy,");
+	if (action & MSN_ADD_GROUP)
+		strcat(buf, "Adding Group,");
+	if (action & MSN_DEL_GROUP)
+		strcat(buf, "Deleting Group,");
+	if (action & MSN_RENAME_GROUP)
+		strcat(buf, "Renaming Group,");
+	if (action & MSN_UPDATE_INFO)
+		strcat(buf, "Updating Contact Info,");
+
+	return buf;
+}
+
+static gboolean msn_contact_request(MsnCallbackState *state);
+
+static void
+msn_contact_request_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
+	gpointer data)
+{
+	MsnCallbackState *state = data;
+	xmlnode *fault;
+	char *faultcode_str;
+
+	if (resp == NULL) {
+		purple_debug_error("msn",
+		                   "Operation {%s} failed. No response received from server.\n",
+		                   msn_contact_operation_str(state->action));
+		return;
+	}
+
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
+
+	if (fault == NULL) {
+		/* No errors */
+		if (state->cb)
+			((MsnSoapCallback)state->cb)(req, resp, data);
+		msn_callback_state_free(state);
+		return;
+	}
+
+	faultcode_str = xmlnode_get_data(xmlnode_get_child(fault, "faultcode"));
+
+	if (faultcode_str && g_str_equal(faultcode_str, "q0:BadContextToken")) {
+		purple_debug_info("msn",
+		                  "Contact Operation {%s} failed because of bad token."
+		                  " Updating token now and retrying operation.\n",
+		                  msn_contact_operation_str(state->action));
+		/* Token has expired, so renew it, and try again later */
+		msn_nexus_update_token(state->session->nexus, MSN_AUTH_CONTACTS,
+		                       (GSourceFunc)msn_contact_request, data);
+	}
+	else
+	{
+		if (state->cb) {
+			((MsnSoapCallback)state->cb)(req, resp, data);
+		} else {
+			/* We don't know how to respond to this faultcode, so log it */
+			char *str = xmlnode_to_str(fault, NULL);
+			purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+			                   msn_contact_operation_str(state->action), str);
+			g_free(str);
+		}
+		msn_callback_state_free(state);
+	}
+
+	g_free(faultcode_str);
+}
+
+static gboolean
+msn_contact_request(MsnCallbackState *state)
+{
+	if (state->token == NULL)
+		state->token = xmlnode_get_child(state->body,
+			"Header/ABAuthHeader/TicketToken");
+	/* delete old & replace with new token */
+	xmlnode_free(state->token->child);
+	xmlnode_insert_data(state->token,
+		msn_nexus_get_token_str(state->session->nexus, MSN_AUTH_CONTACTS), -1);
+	msn_soap_message_send(state->session,
+		msn_soap_message_new(state->post_action, xmlnode_copy(state->body)),
+		MSN_CONTACT_SERVER, state->post_url, FALSE,
+		msn_contact_request_cb, state);
+	return FALSE;
+}
+
+/***************************************************************
+ * Address Book and Membership List Operations
+ ***************************************************************/
+
 /*get MSN member role utility*/
 static MsnListId
 msn_get_memberrole(const char *role)
@@ -180,9 +287,10 @@
 static void
 msn_create_address_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
 {
+	MsnCallbackState *state = data;
 	if (resp && xmlnode_get_child(resp->xml, "Body/Fault") == NULL) {
 		purple_debug_info("msn", "Address Book successfully created!\n");
-		msn_get_address_book((MsnSession *)data, MSN_PS_INITIAL, NULL, NULL);
+		msn_get_address_book(state->session, MSN_PS_INITIAL, NULL, NULL);
 	} else {
 		purple_debug_info("msn", "Address Book creation failed!\n");
 	}
@@ -192,7 +300,7 @@
 msn_create_address_book(MsnSession *session)
 {
 	gchar *body;
-	gchar *token_str;
+	MsnCallbackState *state;
 
 	g_return_if_fail(session != NULL);
 	g_return_if_fail(session->user != NULL);
@@ -200,17 +308,15 @@
 	
 	purple_debug_info("msn", "Creating an Address Book.\n");
 
-	token_str = g_markup_escape_text(
-		msn_nexus_get_token_str(session->nexus, MSN_AUTH_CONTACTS), -1);
 	body = g_strdup_printf(MSN_ADD_ADDRESSBOOK_TEMPLATE,
-		token_str, session->user->passport);
-	g_free(token_str);
+	                       session->user->passport);
 
-	msn_soap_message_send(session,
-		msn_soap_message_new(MSN_ADD_ADDRESSBOOK_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, FALSE,
-		msn_create_address_cb, session);
+	state = msn_callback_state_new(session);
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_ADD_ADDRESSBOOK_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_create_address_cb;
+	msn_contact_request(state);
 
 	g_free(body);
 }
@@ -243,7 +349,7 @@
 			g_free(name);
 		}
 	}
- 
+
 	purple_debug_info("msn", "CL: %s name: %s, Type: %s, MembershipID: %s, NetworkID: %u\n",
 		node, passport, type, member_id == NULL ? "(null)" : member_id, nid);
 
@@ -362,8 +468,8 @@
 msn_get_contact_list_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
 	gpointer data)
 {
-	GetContactListCbData *cb_data = data;
-	MsnSession *session = cb_data->session;
+	MsnCallbackState *state = data;
+	MsnSession *session = state->session;
 
 	g_return_if_fail(session != NULL);
 
@@ -379,7 +485,7 @@
 		dynamicItemLastChange = purple_account_get_string(session->account,
 			"dynamicItemLastChange", NULL);
 
-		if (cb_data->which == MSN_PS_INITIAL) {
+		if (state->partner_scenario == MSN_PS_INITIAL) {
 #ifdef MSN_PARTIAL_LISTS
 			/* XXX: this should be enabled when we can correctly do partial
 			   syncs with the server. Currently we need to retrieve the whole
@@ -390,8 +496,6 @@
 #endif
 		}
 	}
-
-	g_free(cb_data);
 }
 
 /*SOAP  get contact list*/
@@ -401,8 +505,7 @@
 {
 	gchar *body = NULL;
 	gchar *update_str = NULL;
-	gchar *token_str;
-	GetContactListCbData cb_data = { session, partner_scenario };
+	MsnCallbackState *state;
 	const gchar *partner_scenario_str = MsnSoapPartnerScenarioText[partner_scenario];
 
 	purple_debug_misc("msn", "Getting Contact List.\n");
@@ -412,17 +515,16 @@
 		update_str = g_strdup_printf(MSN_GET_CONTACT_UPDATE_XML, update_time);
 	}
 
-	token_str = g_markup_escape_text(
-		msn_nexus_get_token_str(session->nexus, MSN_AUTH_CONTACTS), -1);
 	body = g_strdup_printf(MSN_GET_CONTACT_TEMPLATE, partner_scenario_str,
-		token_str, update_str ? update_str : "");
-	g_free(token_str);
+	                       update_str ? update_str : "");
 
-	msn_soap_message_send(session,
-		msn_soap_message_new(MSN_GET_CONTACT_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_GET_CONTACT_POST_URL, FALSE,
-		msn_get_contact_list_cb, g_memdup(&cb_data, sizeof(cb_data)));
+	state = msn_callback_state_new(session);
+	state->partner_scenario = partner_scenario;
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_GET_CONTACT_SOAP_ACTION;
+	state->post_url = MSN_GET_CONTACT_POST_URL;
+	state->cb = msn_get_contact_list_cb;
+	msn_contact_request(state);
 
 	g_free(update_str);
 	g_free(body);
@@ -525,7 +627,7 @@
 
 	for(contactNode = xmlnode_get_child(node, "Contact"); contactNode;
 				contactNode = xmlnode_get_next_twin(contactNode)) {
-		xmlnode *contactId, *contactInfo, *contactType, *passportName, *displayName, *guid, *groupIds, *messenger_user;
+		xmlnode *contactId, *contactInfo, *contactType, *passportName, *displayName, *guid, *groupIds;
 		xmlnode *annotation;
 		MsnUser *user;
 
@@ -556,58 +658,52 @@
 			continue; /* Not adding own account as buddy to buddylist */
 		}
 
-		/* ignore non-messenger contacts */
-		if ((messenger_user = xmlnode_get_child(contactInfo, "isMessengerUser"))) {
-			char *is_messenger_user = xmlnode_get_data(messenger_user);
-
-			if(is_messenger_user && !strcmp(is_messenger_user, "false")) {
-				g_free(is_messenger_user);
-				continue;
-			}
-
-			g_free(is_messenger_user);
-		}
-
 		passportName = xmlnode_get_child(contactInfo, "passportName");
 		if (passportName == NULL) {
 			xmlnode *emailsNode, *contactEmailNode, *emailNode;
 			xmlnode *messengerEnabledNode;
 			char *msnEnabled;
 
-			/*TODO: add it to the none-instant Messenger group and recognize as email Membership*/
-			/*Yahoo User?*/
+			/*TODO: add it to the non-instant Messenger group and recognize as email Membership*/
+			/* Yahoo/Federated User? */
 			emailsNode = xmlnode_get_child(contactInfo, "emails");
 			if (emailsNode == NULL) {
 				/*TODO:  need to support the Mobile type*/
 				continue;
 			}
 			for (contactEmailNode = xmlnode_get_child(emailsNode, "ContactEmail"); contactEmailNode;
-					contactEmailNode = xmlnode_get_next_twin(contactEmailNode) ){
-				if (!(messengerEnabledNode = xmlnode_get_child(contactEmailNode, "isMessengerEnabled"))) {
-					/* XXX: Should this be a continue instead of a break? It seems like it'd cause unpredictable results otherwise. */
-					break;
-				}
+					contactEmailNode = xmlnode_get_next_twin(contactEmailNode)) {
+				if (!(messengerEnabledNode = xmlnode_get_child(contactEmailNode, "isMessengerEnabled")))
+					continue;
 
 				msnEnabled = xmlnode_get_data(messengerEnabledNode);
 
-				if ((emailNode = xmlnode_get_child(contactEmailNode, "email"))) {
-					g_free(passport);
-					passport = xmlnode_get_data(emailNode);
-				}
+				if (msnEnabled && !strcmp(msnEnabled, "true")) {
+					if ((emailNode = xmlnode_get_child(contactEmailNode, "email")))
+						passport = xmlnode_get_data(emailNode);
 
-				if (msnEnabled && !strcmp(msnEnabled, "true")) {
 					/*Messenger enabled, Get the Passport*/
-					purple_debug_info("msn", "AB Yahoo User %s\n", passport ? passport : "(null)");
+					purple_debug_info("msn", "AB Yahoo/Federated User %s\n", passport ? passport : "(null)");
 					g_free(msnEnabled);
 					break;
-				} else {
-					/*TODO maybe we can just ignore it in Purple?*/
-					purple_debug_info("msn", "AB Other type user\n");
 				}
 
 				g_free(msnEnabled);
 			}
 		} else {
+			xmlnode *messenger_user;
+			/* ignore non-messenger contacts */
+			if ((messenger_user = xmlnode_get_child(contactInfo, "isMessengerUser"))) {
+				char *is_messenger_user = xmlnode_get_data(messenger_user);
+
+				if (is_messenger_user && !strcmp(is_messenger_user, "false")) {
+					g_free(is_messenger_user);
+					continue;
+				}
+
+				g_free(is_messenger_user);
+			}
+
 			passport = xmlnode_get_data(passportName);
 		}
 
@@ -777,10 +873,8 @@
 static void
 msn_get_address_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
 {
-	MsnSession *session = data;
-
-	if (resp == NULL)
-		return;
+	MsnCallbackState *state = data;
+	MsnSession *session = state->session;
 
 	g_return_if_fail(session != NULL);
 
@@ -809,7 +903,7 @@
 	const char *dynamicItemLastChange)
 {
 	char *body, *update_str = NULL;
-	gchar *token_str;
+	MsnCallbackState *state;
 
 	purple_debug_misc("msn", "Getting Address Book\n");
 
@@ -819,19 +913,16 @@
 	else if (LastChanged != NULL)
 		update_str = g_strdup_printf(MSN_GET_ADDRESS_UPDATE_XML, LastChanged);
 
-	token_str = g_markup_escape_text(
-		msn_nexus_get_token_str(session->nexus, MSN_AUTH_CONTACTS), -1);
 	body = g_strdup_printf(MSN_GET_ADDRESS_TEMPLATE,
 		MsnSoapPartnerScenarioText[partner_scenario],
-		token_str,
 		update_str ? update_str : "");
-	g_free(token_str);
 
-	msn_soap_message_send(session,
-		msn_soap_message_new(MSN_GET_ADDRESS_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, FALSE,
-		msn_get_address_cb, session);
+	state = msn_callback_state_new(session);
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_GET_ADDRESS_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_get_address_cb;
+	msn_contact_request(state);
 
 	g_free(update_str);
 	g_free(body);
@@ -841,105 +932,6 @@
  * Contact Operations
  ***************************************************************/
 
-static const char *
-msn_contact_operation_str(MsnCallbackAction action)
-{
-	/* Make sure this is large enough when adding more */
-	static char buf[BUF_LEN];
-	buf[0] = '\0';
-
-	if (action & MSN_ADD_BUDDY)
-		strcat(buf, "Adding Buddy,");
-	if (action & MSN_MOVE_BUDDY)
-		strcat(buf, "Moving Buddy,");
-	if (action & MSN_ACCEPTED_BUDDY)
-		strcat(buf, "Accepted Buddy,");
-	if (action & MSN_DENIED_BUDDY)
-		strcat(buf, "Denied Buddy,");
-	if (action & MSN_ADD_GROUP)
-		strcat(buf, "Adding Group,");
-	if (action & MSN_DEL_GROUP)
-		strcat(buf, "Deleting Group,");
-	if (action & MSN_RENAME_GROUP)
-		strcat(buf, "Renaming Group,");
-	if (action & MSN_UPDATE_INFO)
-		strcat(buf, "Updating Contact Info,");
-
-	return buf;
-}
-
-static gboolean msn_contact_request(MsnCallbackState *state);
-
-static void
-msn_contact_request_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
-	gpointer data)
-{
-	MsnCallbackState *state = data;
-	xmlnode *fault;
-	char *faultcode_str;
-
-	if (resp == NULL) {
-		purple_debug_error("msn",
-		                   "Operation {%s} failed. No response received from server.\n",
-		                   msn_contact_operation_str(state->action));
-		return;
-	}
-
-	fault = xmlnode_get_child(resp->xml, "Body/Fault");
-
-	if (fault == NULL) {
-		/* No errors */
-		if (state->cb)
-			((MsnSoapCallback)state->cb)(req, resp, data);
-		msn_callback_state_free(state);
-		return;
-	}
-
-	faultcode_str = xmlnode_get_data(xmlnode_get_child(fault, "faultcode"));
-
-	if (faultcode_str && g_str_equal(faultcode_str, "q0:BadContextToken")) {
-		purple_debug_info("msn",
-		                  "Contact Operation {%s} failed because of bad token."
-		                  " Updating token now and retrying operation.\n",
-		                  msn_contact_operation_str(state->action));
-		/* Token has expired, so renew it, and try again later */
-		msn_nexus_update_token(state->session->nexus, MSN_AUTH_CONTACTS,
-		                       (GSourceFunc)msn_contact_request, data);
-	}
-	else
-	{
-		if (state->cb) {
-			((MsnSoapCallback)state->cb)(req, resp, data);
-		} else {
-			/* We don't know how to respond to this faultcode, so log it */
-			char *str = xmlnode_to_str(fault, NULL);
-			purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
-			                   msn_contact_operation_str(state->action), str);
-			g_free(str);
-		}
-		msn_callback_state_free(state);
-	}
-
-	g_free(faultcode_str);
-}
-
-static gboolean
-msn_contact_request(MsnCallbackState *state)
-{
-	if (state->token == NULL)
-		state->token = xmlnode_get_child(state->body,
-			"Header/ABAuthHeader/TicketToken");
-	/* delete old & replace with new token */
-	xmlnode_free(state->token->child);
-	xmlnode_insert_data(state->token,
-		msn_nexus_get_token_str(state->session->nexus, MSN_AUTH_CONTACTS), -1);
-	msn_soap_message_send(state->session,
-		msn_soap_message_new(state->post_action, xmlnode_copy(state->body)),
-		MSN_CONTACT_SERVER, state->post_url, FALSE,
-		msn_contact_request_cb, state);
-	return FALSE;
-}
-
 static void
 msn_add_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
 	gpointer data)
--- a/libpurple/protocols/msn/contact.h	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/msn/contact.h	Thu Feb 05 11:47:40 2009 +0000
@@ -52,7 +52,7 @@
 		 "</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest xmlns=\"http://www.msn.com/webservices/AddressBook\">false</ManagedGroupRequest>"\
-			"<TicketToken>%s</TicketToken>"\
+			"<TicketToken>EMPTY</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
@@ -94,7 +94,7 @@
 		"</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
-			"<TicketToken>%s</TicketToken>"\
+			"<TicketToken>EMPTY</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -135,7 +135,7 @@
 		"</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
-			"<TicketToken>%s</TicketToken>"\
+			"<TicketToken>EMPTY</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -619,6 +619,15 @@
 	MSN_UPDATE_INFO     = 0x80
 } MsnCallbackAction;
 
+typedef enum
+{
+	MSN_PS_INITIAL,
+	MSN_PS_SAVE_CONTACT,
+	MSN_PS_PENDING_LIST,
+	MSN_PS_CONTACT_API,
+	MSN_PS_BLOCK_UNBLOCK
+} MsnSoapPartnerScenario;
+
 typedef struct _MsnCallbackState MsnCallbackState;
 
 struct _MsnCallbackState
@@ -636,19 +645,12 @@
 	const gchar *post_action;
 	const gchar *post_url;
 	MsnSoapCallback cb;
+	/* For msn_get_contact_list only */
+	MsnSoapPartnerScenario partner_scenario;
 };
 
 typedef enum
 {
-	MSN_PS_INITIAL,
-	MSN_PS_SAVE_CONTACT,
-	MSN_PS_PENDING_LIST,
-	MSN_PS_CONTACT_API,
-	MSN_PS_BLOCK_UNBLOCK
-} MsnSoapPartnerScenario;
-
-typedef enum
-{
 	MSN_UPDATE_DISPLAY,	/* Real display name */
 	MSN_UPDATE_ALIAS,	/* Aliased display name */
 	MSN_UPDATE_COMMENT
--- a/libpurple/protocols/msn/msg.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/msn/msg.c	Thu Feb 05 11:47:40 2009 +0000
@@ -23,6 +23,7 @@
  */
 #include "msn.h"
 #include "msg.h"
+#include "msnutils.h"
 
 MsnMessage *
 msn_message_new(MsnMsgType type)
@@ -804,3 +805,174 @@
 
 	g_string_free(str, TRUE);
 }
+
+/**************************************************************************
+ * Message Handlers
+ **************************************************************************/
+void
+msn_plain_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	PurpleConnection *gc;
+	const char *body;
+	char *body_str;
+	char *body_enc;
+	char *body_final;
+	size_t body_len;
+	const char *passport;
+	const char *value;
+
+	gc = cmdproc->session->account->gc;
+
+	body = msn_message_get_bin_data(msg, &body_len);
+	body_str = g_strndup(body, body_len);
+	body_enc = g_markup_escape_text(body_str, -1);
+	g_free(body_str);
+
+	passport = msg->remote_user;
+
+	if (!strcmp(passport, "messenger@microsoft.com") &&
+		strstr(body, "immediate security update"))
+	{
+		return;
+	}
+
+#if 0
+	if ((value = msn_message_get_attr(msg, "User-Agent")) != NULL)
+	{
+		purple_debug_misc("msn", "User-Agent = '%s'\n", value);
+	}
+#endif
+
+	if ((value = msn_message_get_attr(msg, "X-MMS-IM-Format")) != NULL)
+	{
+		char *pre, *post;
+
+		msn_parse_format(value, &pre, &post);
+
+		body_final = g_strdup_printf("%s%s%s", pre ? pre : "",
+									 body_enc ? body_enc : "", post ? post : "");
+
+		g_free(pre);
+		g_free(post);
+		g_free(body_enc);
+	}
+	else
+	{
+		body_final = body_enc;
+	}
+
+	if (cmdproc->servconn->type == MSN_SERVCONN_SB) {
+		MsnSwitchBoard *swboard = cmdproc->data;
+
+		swboard->flag |= MSN_SB_FLAG_IM;
+
+		if (swboard->current_users > 1 ||
+			((swboard->conv != NULL) &&
+			 purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
+		{
+			/* If current_users is always ok as it should then there is no need to
+			 * check if this is a chat. */
+			if (swboard->current_users <= 1)
+				purple_debug_misc("msn", "plain_msg: current_users(%d)\n",
+								swboard->current_users);
+
+			serv_got_chat_in(gc, swboard->chat_id, passport, 0, body_final,
+							 time(NULL));
+			if (swboard->conv == NULL)
+			{
+				swboard->conv = purple_find_chat(gc, swboard->chat_id);
+				swboard->flag |= MSN_SB_FLAG_IM;
+			}
+		}
+		else
+		{
+			serv_got_im(gc, passport, body_final, 0, time(NULL));
+			if (swboard->conv == NULL)
+			{
+				swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
+										passport, purple_connection_get_account(gc));
+				swboard->flag |= MSN_SB_FLAG_IM;
+			}
+		}
+
+	} else {
+		serv_got_im(gc, passport, body_final, 0, time(NULL));
+	}
+
+	g_free(body_final);
+}
+
+void
+msn_control_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	PurpleConnection *gc;
+	char *passport;
+
+	gc = cmdproc->session->account->gc;
+	passport = msg->remote_user;
+
+	if (msn_message_get_attr(msg, "TypingUser") == NULL)
+		return;
+
+	if (cmdproc->servconn->type == MSN_SERVCONN_SB) {
+		MsnSwitchBoard *swboard = cmdproc->data;
+
+		if (swboard->current_users == 1)
+		{
+			serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT,
+							PURPLE_TYPING);
+		}
+
+	} else {
+		serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT,
+						PURPLE_TYPING);
+	}
+}
+
+void
+msn_datacast_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	GHashTable *body;
+	const char *id;
+	body = msn_message_get_hashtable_from_body(msg);
+
+	id = g_hash_table_lookup(body, "ID");
+
+	if (!strcmp(id, "1")) {
+		/* Nudge */
+		PurpleAccount *account;
+		const char *user;
+
+		account = cmdproc->session->account;
+		user = msg->remote_user;
+
+		if (cmdproc->servconn->type == MSN_SERVCONN_SB) {
+			MsnSwitchBoard *swboard = cmdproc->data;
+			if (swboard->current_users > 1 ||
+				((swboard->conv != NULL) &&
+				 purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
+				purple_prpl_got_attention_in_chat(account->gc, swboard->chat_id, user, MSN_NUDGE);
+
+			else
+				purple_prpl_got_attention(account->gc, user, MSN_NUDGE);
+
+		} else {
+			purple_prpl_got_attention(account->gc, user, MSN_NUDGE);
+		}
+
+	} else if (!strcmp(id, "2")) {
+		/* Wink */
+
+	} else if (!strcmp(id, "3")) {
+		/* Voiceclip */
+
+	} else if (!strcmp(id, "4")) {
+		/* Action */
+
+	} else {
+		purple_debug_warning("msn", "Got unknown datacast with ID %s.\n", id);
+	}
+
+	g_hash_table_destroy(body);
+}
+
--- a/libpurple/protocols/msn/msg.h	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/msn/msg.h	Thu Feb 05 11:47:40 2009 +0000
@@ -339,4 +339,10 @@
 
 char *msn_message_to_string(MsnMessage *msg);
 
+void msn_plain_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+
+void msn_control_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+
+void msn_datacast_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+
 #endif /* _MSN_MSG_H_ */
--- a/libpurple/protocols/msn/msn.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/msn/msn.c	Thu Feb 05 11:47:40 2009 +0000
@@ -853,11 +853,11 @@
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE,
-			NULL, NULL, FALSE, TRUE, FALSE);
+			NULL, NULL, TRUE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE,
-			NULL, NULL, FALSE, TRUE, FALSE);
+			NULL, NULL, TRUE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_MOBILE,
@@ -1176,6 +1176,7 @@
 	MsnMessage *msg;
 	char *msgformat;
 	char *msgtext;
+	size_t msglen;
 	const char *username;
 
 	purple_debug_info("msn", "send IM {%s} to %s\n", message, who);
@@ -1203,13 +1204,23 @@
 	}
 
 	msn_import_html(message, &msgformat, &msgtext);
+	msglen = strlen(msgtext);
+	if (msglen == 0) {
+		/* Stuff like <hr> will be ignored. Don't send an empty message
+		   if that's all there is. */
+		g_free(msgtext);
+		g_free(msgformat);
+
+		return 0;
+	}
+
 	if (msn_user_is_online(account, who) ||
 		msn_user_is_yahoo(account, who) ||
 		swboard != NULL) {
 		/*User online or have a swboard open because it's invisible
 		 * and sent us a message,then send Online Instant Message*/
  
-		if (strlen(msgtext) + strlen(msgformat) + strlen(VERSION) > 1564)
+		if (msglen + strlen(msgformat) + strlen(VERSION) > 1564)
 		{
 			g_free(msgformat);
 			g_free(msgtext);
--- a/libpurple/protocols/msn/notification.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/msn/notification.c	Thu Feb 05 11:47:40 2009 +0000
@@ -2096,6 +2096,13 @@
 	msn_table_add_msg_type(cbs_table,
 						   "application/x-msmsgssystemmessage",
 						   system_msg);
+	/* generic message handlers */
+	msn_table_add_msg_type(cbs_table, "text/plain",
+						   msn_plain_msg);
+	msn_table_add_msg_type(cbs_table, "text/x-msmsgscontrol",
+						   msn_control_msg);
+	msn_table_add_msg_type(cbs_table, "text/x-msnmsgr-datacast",
+						   msn_datacast_msg);
 }
 
 void
--- a/libpurple/protocols/msn/soap.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/msn/soap.c	Thu Feb 05 11:47:40 2009 +0000
@@ -342,12 +342,14 @@
 	}
 
 	if (fault || body) {
-		MsnSoapRequest *request = conn->current_request;
-		conn->current_request = NULL;
-		request->cb(request->message, response,
-			request->cb_data);
+		if (conn->current_request) {
+			MsnSoapRequest *request = conn->current_request;
+			conn->current_request = NULL;
+			request->cb(request->message, response,
+				request->cb_data);
+			msn_soap_request_destroy(request, FALSE);
+		}
 		msn_soap_message_destroy(response);
-		msn_soap_request_destroy(request, FALSE);
 	}
 
 	return TRUE;
--- a/libpurple/protocols/msn/switchboard.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Thu Feb 05 11:47:40 2009 +0000
@@ -887,113 +887,6 @@
  * Message Handlers
  **************************************************************************/
 static void
-plain_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
-{
-	PurpleConnection *gc;
-	MsnSwitchBoard *swboard;
-	const char *body;
-	char *body_str;
-	char *body_enc;
-	char *body_final;
-	size_t body_len;
-	const char *passport;
-	const char *value;
-
-	gc = cmdproc->session->account->gc;
-	swboard = cmdproc->data;
-
-	body = msn_message_get_bin_data(msg, &body_len);
-	body_str = g_strndup(body, body_len);
-	body_enc = g_markup_escape_text(body_str, -1);
-	g_free(body_str);
-
-	passport = msg->remote_user;
-
-	if (!strcmp(passport, "messenger@microsoft.com") &&
-		strstr(body, "immediate security update"))
-	{
-		return;
-	}
-
-#if 0
-	if ((value = msn_message_get_attr(msg, "User-Agent")) != NULL)
-	{
-		purple_debug_misc("msn", "User-Agent = '%s'\n", value);
-	}
-#endif
-
-	if ((value = msn_message_get_attr(msg, "X-MMS-IM-Format")) != NULL)
-	{
-		char *pre, *post;
-
-		msn_parse_format(value, &pre, &post);
-
-		body_final = g_strdup_printf("%s%s%s", pre ? pre : "",
-									 body_enc ? body_enc : "", post ? post : "");
-
-		g_free(pre);
-		g_free(post);
-		g_free(body_enc);
-	}
-	else
-	{
-		body_final = body_enc;
-	}
-
-	swboard->flag |= MSN_SB_FLAG_IM;
-
-	if (swboard->current_users > 1 ||
-		((swboard->conv != NULL) &&
-		 purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
-	{
-		/* If current_users is always ok as it should then there is no need to
-		 * check if this is a chat. */
-		if (swboard->current_users <= 1)
-			purple_debug_misc("msn", "plain_msg: current_users(%d)\n",
-							swboard->current_users);
-
-		serv_got_chat_in(gc, swboard->chat_id, passport, 0, body_final,
-						 time(NULL));
-		if (swboard->conv == NULL)
-		{
-			swboard->conv = purple_find_chat(gc, swboard->chat_id);
-			swboard->flag |= MSN_SB_FLAG_IM;
-		}
-	}
-	else
-	{
-		serv_got_im(gc, passport, body_final, 0, time(NULL));
-		if (swboard->conv == NULL)
-		{
-			swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
-									passport, purple_connection_get_account(gc));
-			swboard->flag |= MSN_SB_FLAG_IM;
-		}
-	}
-
-	g_free(body_final);
-}
-
-static void
-control_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
-{
-	PurpleConnection *gc;
-	MsnSwitchBoard *swboard;
-	char *passport;
-
-	gc = cmdproc->session->account->gc;
-	swboard = cmdproc->data;
-	passport = msg->remote_user;
-
-	if (swboard->current_users == 1 &&
-		msn_message_get_attr(msg, "TypingUser") != NULL)
-	{
-		serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT,
-						PURPLE_TYPING);
-	}
-}
-
-static void
 clientcaps_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
 {
 #if 0
@@ -1012,49 +905,6 @@
 #endif
 }
 
-static void
-datacast_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
-{
-	GHashTable *body;
-	const char *id;
-	body = msn_message_get_hashtable_from_body(msg);
-
-	id = g_hash_table_lookup(body, "ID");
-
-	if (!strcmp(id, "1")) {
-		/* Nudge */
-		MsnSwitchBoard *swboard;
-		PurpleAccount *account;
-		const char *user;
-
-		swboard = cmdproc->data;
-		account = cmdproc->session->account;
-		user = msg->remote_user;
-
-		if (swboard->current_users > 1 ||
-			((swboard->conv != NULL) &&
-			 purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
-			purple_prpl_got_attention_in_chat(account->gc, swboard->chat_id, user, MSN_NUDGE);
-
-		else
-			purple_prpl_got_attention(account->gc, user, MSN_NUDGE);
-
-	} else if (!strcmp(id, "2")) {
-		/* Wink */
-
-	} else if (!strcmp(id, "3")) {
-		/* Voiceclip */
-
-	} else if (!strcmp(id, "4")) {
-		/* Action */
-
-	} else {
-		purple_debug_warning("msn", "Got unknown datacast with ID %s.\n", id);
-	}
-
-	g_hash_table_destroy(body);
-}
-
 /**************************************************************************
  * Connect stuff
  **************************************************************************/
@@ -1372,9 +1222,9 @@
 
 	/* Register the message type callbacks. */
 	msn_table_add_msg_type(cbs_table, "text/plain",
-						   plain_msg);
+						   msn_plain_msg);
 	msn_table_add_msg_type(cbs_table, "text/x-msmsgscontrol",
-						   control_msg);
+						   msn_control_msg);
 	msn_table_add_msg_type(cbs_table, "text/x-clientcaps",
 						   clientcaps_msg);
 	msn_table_add_msg_type(cbs_table, "text/x-clientinfo",
@@ -1384,9 +1234,9 @@
 	msn_table_add_msg_type(cbs_table, "text/x-mms-emoticon",
 						   msn_emoticon_msg);
 	msn_table_add_msg_type(cbs_table, "text/x-mms-animemoticon",
-	                                           msn_emoticon_msg);
+	                       msn_emoticon_msg);
 	msn_table_add_msg_type(cbs_table, "text/x-msnmsgr-datacast",
-						   datacast_msg);
+						   msn_datacast_msg);
 #if 0
 	msn_table_add_msg_type(cbs_table, "text/x-msmmsginvite",
 						   msn_invite_msg);
--- a/libpurple/protocols/msn/user.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/msn/user.c	Thu Feb 05 11:47:40 2009 +0000
@@ -180,7 +180,7 @@
 gboolean
 msn_user_set_friendly_name(MsnUser *user, const char *name)
 {
-	g_return_if_fail(user != NULL);
+	g_return_val_if_fail(user != NULL, FALSE);
 
 	if (user->friendly_name && name && !strcmp(user->friendly_name, name))
 		return FALSE;
--- a/libpurple/protocols/novell/novell.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/novell/novell.c	Thu Feb 05 11:47:40 2009 +0000
@@ -2976,7 +2976,7 @@
 										   NULL, TRUE, TRUE, FALSE);
 	status_types = g_list_append(status_types, type);
 
-	type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
 	status_types = g_list_append(status_types, type);
 
 	return status_types;
--- a/libpurple/protocols/oscar/oscar.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Thu Feb 05 11:47:40 2009 +0000
@@ -4782,7 +4782,7 @@
 		if (status_html != NULL)
 		{
 			status_text = purple_markup_strip_html(status_html);
-			/* If the status_text is longer than 60 character then truncate it */
+			/* If the status_text is longer than 251 characters then truncate it */
 			if (strlen(status_text) > MAXAVAILMSGLEN)
 			{
 				char *tmp = g_utf8_find_prev_char(status_text, &status_text[MAXAVAILMSGLEN - 2]);
@@ -4799,9 +4799,28 @@
 	}
 	else
 	{
+		char *status_text = NULL;
+		
 		htmlaway = purple_status_get_attr_string(status, "message");
 		if ((htmlaway == NULL) || (*htmlaway == '\0'))
 			htmlaway = purple_status_type_get_name(status_type);
+		
+		/* ICQ 6.x seems to use an available message for all statuses so set one */
+		if (od->icq) 
+		{
+			status_text = purple_markup_strip_html(htmlaway);
+			/* If the status_text is longer than 251 characters then truncate it */
+			if (strlen(status_text) > MAXAVAILMSGLEN)
+			{
+				char *tmp = g_utf8_find_prev_char(status_text, &status_text[MAXAVAILMSGLEN - 2]);
+				strcpy(tmp, "...");
+			}
+			aim_srv_setextrainfo(od, FALSE, 0, TRUE, status_text, NULL);
+		}
+		g_free(status_text);
+
+		/* Set a proper away message for icq too so that they work for old third party clients */
+		
 		away = purple_prpl_oscar_convert_to_infotext(htmlaway, &awaylen, &away_encoding);
 
 		if (awaylen > od->rights.maxawaymsglen)
--- a/libpurple/protocols/oscar/oscar.h	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Thu Feb 05 11:47:40 2009 +0000
@@ -286,6 +286,15 @@
 	"us", "en", \
 }
 
+#define CLIENTINFO_ICQBASIC_14_34_3096 { \
+	"ICQBasic", \
+	0x010a, \
+	0x0014, 0x0034, \
+	0x0000, 0x0c18, \
+	0x0000043d, \
+	"us", "en", \
+}
+
 #define CLIENTINFO_NETSCAPE_7_0_1 { \
 	"Netscape 2000 an approved user of AOL Instant Messenger (SM)", \
 	0x1d0d, \
@@ -312,14 +321,14 @@
 #define CLIENTINFO_PURPLE_ICQ { \
 	"Purple/" VERSION, \
 	0x010a, \
-	0x0006, 0x0000, \
-	0x0000, 0x17ab, \
-	0x00007535, \
+	0x0014, 0x0034, \
+	0x0000, 0x0c18, \
+	0x0000043d, \
 	"us", "en", \
 }
 
 #define CLIENTINFO_AIM_KNOWNGOOD CLIENTINFO_AIM_5_1_3036
-#define CLIENTINFO_ICQ_KNOWNGOOD CLIENTINFO_ICQBASIC_14_34_3000
+#define CLIENTINFO_ICQ_KNOWNGOOD CLIENTINFO_ICQBASIC_14_34_3096
 
 typedef enum
 {
--- a/libpurple/protocols/qq/qq.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/qq/qq.c	Thu Feb 05 11:47:40 2009 +0000
@@ -406,15 +406,15 @@
 	GList *types = NULL;
 
 	status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE,
-			"available", _("Available"), FALSE, TRUE, FALSE);
+			"available", _("Available"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_AWAY,
-			"away", _("Away"), FALSE, TRUE, FALSE);
+			"away", _("Away"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE,
-			"invisible", _("Invisible"), FALSE, TRUE, FALSE);
+			"invisible", _("Invisible"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE,
@@ -422,7 +422,7 @@
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE,
-			"offline", _("Offline"), FALSE, TRUE, FALSE);
+			"offline", _("Offline"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_MOBILE,
--- a/libpurple/protocols/silc/silc.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/silc/silc.c	Thu Feb 05 11:47:40 2009 +0000
@@ -48,19 +48,19 @@
 	PurpleStatusType *type;
 	GList *types = NULL;
 
-	type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, SILCPURPLE_STATUS_ID_AVAILABLE, NULL, FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, SILCPURPLE_STATUS_ID_AVAILABLE, NULL, TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, SILCPURPLE_STATUS_ID_HYPER, _("Hyper Active"), FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, SILCPURPLE_STATUS_ID_HYPER, _("Hyper Active"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_AWAY, NULL, FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_AWAY, NULL, TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, SILCPURPLE_STATUS_ID_BUSY, _("Busy"), FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, SILCPURPLE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_INDISPOSED, _("Indisposed"), FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_INDISPOSED, _("Indisposed"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_PAGE, _("Wake Me Up"), FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_PAGE, _("Wake Me Up"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, SILCPURPLE_STATUS_ID_OFFLINE, NULL, FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, SILCPURPLE_STATUS_ID_OFFLINE, NULL, TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
 
 	return types;
@@ -1357,6 +1357,7 @@
 	char tmp[256];
 	SilcClientEntry client_entry;
 	SilcDList list;
+	gboolean free_list = FALSE;
 
 	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, im->nick,
 						      sg->account);
@@ -1373,6 +1374,8 @@
 							im->nick, FALSE);
 		if (!clients)
 			goto err;
+
+		free_list = TRUE;
 	}
 
 	silc_dlist_start(clients);
@@ -1413,6 +1416,9 @@
 	purple_conversation_write(convo, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
 
  out:
+	if (free_list) {
+		silc_client_list_free(client, conn, clients);
+	}
 	g_free(im->nick);
 	g_free(im->message);
 	silc_free(im);
--- a/libpurple/protocols/silc10/silc.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/protocols/silc10/silc.c	Thu Feb 05 11:47:40 2009 +0000
@@ -39,19 +39,19 @@
 	PurpleStatusType *type;
 	GList *types = NULL;
 
-	type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, SILCPURPLE_STATUS_ID_AVAILABLE, NULL, FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, SILCPURPLE_STATUS_ID_AVAILABLE, NULL, TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, SILCPURPLE_STATUS_ID_HYPER, _("Hyper Active"), FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, SILCPURPLE_STATUS_ID_HYPER, _("Hyper Active"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_AWAY, NULL, FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_AWAY, NULL, TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, SILCPURPLE_STATUS_ID_BUSY, _("Busy"), FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, SILCPURPLE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_INDISPOSED, _("Indisposed"), FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_INDISPOSED, _("Indisposed"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_PAGE, _("Wake Me Up"), FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_PAGE, _("Wake Me Up"), TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
-	type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, SILCPURPLE_STATUS_ID_OFFLINE, NULL, FALSE, TRUE, FALSE);
+	type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, SILCPURPLE_STATUS_ID_OFFLINE, NULL, TRUE, TRUE, FALSE);
 	types = g_list_append(types, type);
 
 	return types;
--- a/libpurple/xmlnode.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/xmlnode.c	Thu Feb 05 11:47:40 2009 +0000
@@ -303,7 +303,7 @@
 	node->prefix = g_strdup(prefix);
 }
 
-const char *xmlnode_get_prefix(xmlnode *node)
+const char *xmlnode_get_prefix(const xmlnode *node)
 {
 	g_return_val_if_fail(node != NULL, NULL);
 	return node->prefix;
@@ -449,11 +449,11 @@
 }
 
 static char *
-xmlnode_to_str_helper(xmlnode *node, int *len, gboolean formatting, int depth)
+xmlnode_to_str_helper(const xmlnode *node, int *len, gboolean formatting, int depth)
 {
 	GString *text = g_string_new("");
 	const char *prefix;
-	xmlnode *c;
+	const xmlnode *c;
 	char *node_name, *esc, *esc2, *tab = NULL;
 	gboolean need_end = FALSE, pretty = formatting;
 
@@ -543,13 +543,13 @@
 }
 
 char *
-xmlnode_to_str(xmlnode *node, int *len)
+xmlnode_to_str(const xmlnode *node, int *len)
 {
 	return xmlnode_to_str_helper(node, len, FALSE, 0);
 }
 
 char *
-xmlnode_to_formatted_str(xmlnode *node, int *len)
+xmlnode_to_formatted_str(const xmlnode *node, int *len)
 {
 	char *xml, *xml_with_declaration;
 
--- a/libpurple/xmlnode.h	Thu Feb 05 11:47:36 2009 +0000
+++ b/libpurple/xmlnode.h	Thu Feb 05 11:47:40 2009 +0000
@@ -243,7 +243,7 @@
  * @param node The node to get the prefix from
  * @return The prefix of this node
  */
-const char *xmlnode_get_prefix(xmlnode *node);
+const char *xmlnode_get_prefix(const xmlnode *node);
 
 /**
  * Gets the parent node.
@@ -263,7 +263,7 @@
  * @return The node represented as a string.  You must
  *         g_free this string when finished using it.
  */
-char *xmlnode_to_str(xmlnode *node, int *len);
+char *xmlnode_to_str(const xmlnode *node, int *len);
 
 /**
  * Returns the node in a string of human readable xml.
@@ -275,7 +275,7 @@
  *         tab and new line characters.  You must
  *         g_free this string when finished using it.
  */
-char *xmlnode_to_formatted_str(xmlnode *node, int *len);
+char *xmlnode_to_formatted_str(const xmlnode *node, int *len);
 
 /**
  * Creates a node from a string of XML.  Calling this on the
--- a/pidgin/gtkdialogs.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/pidgin/gtkdialogs.c	Thu Feb 05 11:47:40 2009 +0000
@@ -198,7 +198,7 @@
 	{N_("Macedonian"),          "mk", "Arangel Angov ", "arangel@linux.net.mk"},
 	{N_("Macedonian"),          "mk", "Ivana Kirkovska", "ivana.kirkovska@gmail.com"},
 	{N_("Macedonian"),          "mk", "Jovan Naumovski", "jovan@lugola.net"},
-	{"Mongolian",               "mn", "gooyo", NULL},
+	{N_("Mongolian"),           "mn", "gooyo", NULL},
 	{N_("Bokmål Norwegian"),    "nb", "Hans Fredrik Nordhaug", "hans@nordhaug.priv.no"},
 	{N_("Nepali"),              "ne", "Shyam Krishna Bal", "shyamkrishna_bal@yahoo.com"},
 	{N_("Dutch, Flemish"),      "nl", "Vincent van Adrighem", "V.vanAdrighem@dirck.mine.nu"},
--- a/pidgin/gtkmedia.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/pidgin/gtkmedia.c	Thu Feb 05 11:47:40 2009 +0000
@@ -33,6 +33,7 @@
 #include "request.h"
 
 #include "gtkmedia.h"
+#include "gtkutils.h"
 
 #ifdef USE_VV
 
@@ -57,10 +58,9 @@
 	GstElement *send_level;
 	GstElement *recv_level;
 
-	GtkWidget *calling;
-	GtkWidget *accept;
-	GtkWidget *reject;
-	GtkWidget *hangup;
+	GtkWidget *menubar;
+	GtkWidget *statusbar;
+
 	GtkWidget *mute;
 
 	GtkWidget *send_progress;
@@ -184,38 +184,123 @@
 	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
+menu_hangup(gpointer data, guint action, GtkWidget *item)
+{
+	PidginMedia *gtkmedia = PIDGIN_MEDIA(data);
+	purple_media_hangup(gtkmedia->priv->media);
+}
+
+static GtkItemFactoryEntry menu_items[] = {
+	{ N_("/_Media"), NULL, NULL, 0, "<Branch>", NULL },
+	{ N_("/Media/_Hangup"), NULL, menu_hangup, 0, "<Item>", NULL },
+};
+
+static gint menu_item_count = sizeof(menu_items) / sizeof(menu_items[0]);
+
+static const char *
+item_factory_translate_func (const char *path, gpointer func_data)
+{
+	return _(path);
+}
+
+static GtkWidget *
+setup_menubar(PidginMedia *window)
+{
+	GtkItemFactory *item_factory;
+	GtkAccelGroup *accel_group;
+	GtkWidget *menu;
+
+	accel_group = gtk_accel_group_new ();
+	gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
+	g_object_unref(accel_group);
+
+	item_factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR,
+			"<main>", accel_group);
+
+	gtk_item_factory_set_translate_func(item_factory,
+			(GtkTranslateFunc)item_factory_translate_func,
+			NULL, NULL);
+
+	gtk_item_factory_create_items(item_factory, menu_item_count,
+			menu_items, window);
+	g_signal_connect(G_OBJECT(accel_group), "accel-changed",
+			G_CALLBACK(pidgin_save_accels_cb), NULL);
+
+	menu = gtk_item_factory_get_widget(item_factory, "<main>");
+
+	gtk_widget_show(menu);
+	return menu;
+}
+
 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, _("Calling..."));
+	gtk_widget_show(media->priv->statusbar);
+
+	media->priv->menubar = setup_menubar(media);
+	gtk_box_pack_start(GTK_BOX(vbox), media->priv->menubar,
+			FALSE, TRUE, 0);
+
 	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));
 
-	media->priv->calling = gtk_label_new("Calling...");
-	media->priv->hangup = gtk_button_new_with_mnemonic("_Hangup");
-	media->priv->accept = gtk_button_new_with_mnemonic("_Accept");
-	media->priv->reject = gtk_button_new_with_mnemonic("_Reject");
 	media->priv->mute = gtk_toggle_button_new_with_mnemonic("_Mute");
 
 	g_signal_connect(media->priv->mute, "toggled",
 			G_CALLBACK(pidgin_media_mute_toggled), media);
 
-	gtk_box_pack_end(GTK_BOX(hbox), media->priv->reject, FALSE, FALSE, 0);
-	gtk_box_pack_end(GTK_BOX(hbox), media->priv->accept, FALSE, FALSE, 0);
-	gtk_box_pack_end(GTK_BOX(hbox), media->priv->hangup, FALSE, FALSE, 0);
 	gtk_box_pack_end(GTK_BOX(hbox), media->priv->mute, FALSE, FALSE, 0);
-	gtk_box_pack_end(GTK_BOX(hbox), media->priv->calling, FALSE, FALSE, 0);
 
-	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);
@@ -285,6 +370,7 @@
 	purple_debug_info("gtkmedia", "pidgin_media_dispose\n");
 
 	if (gtkmedia->priv->media) {
+		purple_request_close_with_handle(gtkmedia);
 		purple_media_remove_output_windows(gtkmedia->priv->media);
 		pidgin_media_disconnect_levels(gtkmedia->priv->media, gtkmedia);
 		g_object_unref(gtkmedia->priv->media);
@@ -366,6 +452,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 +462,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 +520,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 +545,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 +561,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 +586,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);
@@ -506,14 +600,14 @@
 
 	if (type & PURPLE_MEDIA_RECV_AUDIO) {
 		gtkmedia->priv->recv_progress = gtk_progress_bar_new();
-		gtk_widget_set_size_request(gtkmedia->priv->recv_progress, 70, 10);
+		gtk_widget_set_size_request(gtkmedia->priv->recv_progress, 320, 10);
 		gtk_box_pack_end(GTK_BOX(recv_widget),
 				   gtkmedia->priv->recv_progress, FALSE, FALSE, 0);
 		gtk_widget_show(gtkmedia->priv->recv_progress);
 	}
 	if (type & PURPLE_MEDIA_SEND_AUDIO) {
 		gtkmedia->priv->send_progress = gtk_progress_bar_new();
-		gtk_widget_set_size_request(gtkmedia->priv->send_progress, 70, 10);
+		gtk_widget_set_size_request(gtkmedia->priv->send_progress, 320, 10);
 		gtk_box_pack_end(GTK_BOX(send_widget),
 				   gtkmedia->priv->send_progress, FALSE, FALSE, 0);
 		gtk_widget_show(gtkmedia->priv->send_progress);
@@ -551,6 +645,8 @@
 		pidgin_media_emit_message(gtkmedia, message);
 		g_free(message);
 	}
+
+	gtk_widget_show(gtkmedia->priv->display);
 }
 
 static void
@@ -573,15 +669,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);
 	}
 }
 
@@ -608,13 +695,6 @@
 			else
 				pidgin_media_set_state(media, PIDGIN_MEDIA_REQUESTED);
 
-			g_signal_connect_swapped(G_OBJECT(media->priv->accept), "clicked", 
-				 G_CALLBACK(purple_media_accept), media->priv->media);
-			g_signal_connect_swapped(G_OBJECT(media->priv->reject), "clicked",
-				 G_CALLBACK(purple_media_reject), media->priv->media);
-			g_signal_connect_swapped(G_OBJECT(media->priv->hangup), "clicked",
-				 G_CALLBACK(purple_media_hangup), media->priv->media);
-
 			g_signal_connect(G_OBJECT(media->priv->media), "error",
 				G_CALLBACK(pidgin_media_error_cb), media);
 			g_signal_connect(G_OBJECT(media->priv->media), "accepted",
@@ -686,28 +766,6 @@
 pidgin_media_set_state(PidginMedia *gtkmedia, PidginMediaState state)
 {
 	gtkmedia->priv->state = state;
-	switch (state) {
-		case PIDGIN_MEDIA_WAITING:
-			gtk_widget_show(gtkmedia->priv->calling);
-			gtk_widget_hide(gtkmedia->priv->accept);
-			gtk_widget_hide(gtkmedia->priv->reject);
-			gtk_widget_show(gtkmedia->priv->hangup);
-			break;
-		case PIDGIN_MEDIA_REQUESTED:
-			gtk_widget_hide(gtkmedia->priv->calling);
-			gtk_widget_show(gtkmedia->priv->accept);
-			gtk_widget_show(gtkmedia->priv->reject);
-			gtk_widget_hide(gtkmedia->priv->hangup);
-			break;
-		case PIDGIN_MEDIA_ACCEPTED:
-			gtk_widget_show(gtkmedia->priv->hangup);
-			gtk_widget_hide(gtkmedia->priv->calling);
-			gtk_widget_hide(gtkmedia->priv->accept);
-			gtk_widget_hide(gtkmedia->priv->reject);
-			break;
-		default:
-			break;
-	}
 }
 
 static gboolean
@@ -728,7 +786,7 @@
 	if (initiator == FALSE) {
 		gchar *message = g_strdup_printf("%s wishes to start a "
 				"media session with you\n", alias);
-		purple_request_accept_cancel(media, "Media invitation",
+		purple_request_accept_cancel(gtkmedia, "Media invitation",
 				message, NULL, 1, (void*)pc, screenname,
 				NULL, media, purple_media_accept,
 				purple_media_reject);
@@ -739,11 +797,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 */
--- a/pidgin/plugins/win32/winprefs/winprefs.c	Thu Feb 05 11:47:36 2009 +0000
+++ b/pidgin/plugins/win32/winprefs/winprefs.c	Thu Feb 05 11:47:40 2009 +0000
@@ -55,9 +55,8 @@
 static const char *PREF_DBLIST_HEIGHT = "/plugins/gtk/win32/winprefs/dblist_height";
 static const char *PREF_DBLIST_SIDE = "/plugins/gtk/win32/winprefs/dblist_side";
 static const char *PREF_BLIST_ON_TOP = "/plugins/gtk/win32/winprefs/blist_on_top";
+/* Deprecated */
 static const char *PREF_CHAT_BLINK = "/plugins/gtk/win32/winprefs/chat_blink";
-
-/* Deprecated */
 static const char *PREF_DBLIST_ON_TOP = "/plugins/gtk/win32/winprefs/dblist_on_top";
 
 static PurplePlugin *handle = NULL;
@@ -229,17 +228,6 @@
 		blist_set_ontop(FALSE);
 }
 
-static gboolean
-winpidgin_conv_chat_blink(PurpleAccount *account, const char *who, char **message,
-		PurpleConversation *conv, PurpleMessageFlags flags, void *data)
-{
-	if(purple_prefs_get_bool(PREF_CHAT_BLINK))
-		winpidgin_conv_blink(conv, flags);
-
-	return FALSE;
-}
-
-
 /*
  *  EXPORTED FUNCTIONS
  */
@@ -258,10 +246,6 @@
 	purple_signal_connect(pidgin_blist_get_handle(), "gtkblist-created",
 		plugin, PURPLE_CALLBACK(blist_create_cb), NULL);
 
-	purple_signal_connect(pidgin_conversations_get_handle(),
-		"displaying-chat-msg", plugin, PURPLE_CALLBACK(winpidgin_conv_chat_blink),
-		NULL);
-
 	purple_signal_connect((void*)purple_get_core(), "quitting", plugin,
 		PURPLE_CALLBACK(purple_quit_cb), NULL);
 
@@ -336,11 +320,6 @@
 		_("Only when docked"), BLIST_TOP_DOCKED,
 		NULL);
 
-	/* Conversations */
-	vbox = pidgin_make_frame(ret, _("Conversations"));
-	pidgin_prefs_checkbox(_("_Flash window when chat messages are received"),
-							PREF_CHAT_BLINK, vbox);
-
 	gtk_widget_show_all(ret);
 	return ret;
 }
@@ -399,7 +378,6 @@
 	purple_prefs_add_bool(PREF_DBLIST_DOCKED, FALSE);
 	purple_prefs_add_int(PREF_DBLIST_HEIGHT, 0);
 	purple_prefs_add_int(PREF_DBLIST_SIDE, 0);
-	purple_prefs_add_bool(PREF_CHAT_BLINK, FALSE);
 
 	/* Convert old preferences */
 	if(purple_prefs_exists(PREF_DBLIST_ON_TOP)) {
@@ -413,6 +391,7 @@
 		purple_prefs_add_int(PREF_BLIST_ON_TOP, blist_top);
 	} else
 		purple_prefs_add_int(PREF_BLIST_ON_TOP, BLIST_TOP_NEVER);
+	purple_prefs_remove(PREF_CHAT_BLINK);
 }
 
 PURPLE_INIT_PLUGIN(winprefs, init_plugin, info)
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Thu Feb 05 11:47:36 2009 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Thu Feb 05 11:47:40 2009 +0000
@@ -1247,6 +1247,9 @@
   Push $R0
   Push $R1
   Push $R2
+  Push $R3 ; This is only used for the Parameters throughout the function
+
+  ${GetParameters} $R3
 
   IntOp $R1 0 + 0
   retry_runcheck:
@@ -1258,7 +1261,14 @@
   IntCmp $R0 0 +3 ;This could check for ERROR_ALREADY_EXISTS(183), but lets just assume
     MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(INSTALLER_IS_RUNNING) /SD IDCANCEL IDRETRY retry_runcheck
     Abort
+
+  ; Allow installer to run even if pidgin is running via "/NOPIDGINRUNCHECK=1"
+  ; This is useful for testing
+  ClearErrors
+  ${GetOptions} "$R3" "/NOPIDGINRUNCHECK=" $R1
+  IfErrors 0 +2
   Call RunCheck
+
   StrCpy $name "Pidgin ${PIDGIN_VERSION}"
   StrCpy $SPELLCHECK_SEL ""
 
@@ -1312,16 +1322,13 @@
   SetShellVarContext "current"
 
   StrCpy $ISSILENT "/S"
-
-  ; GTK installer has two silent states.. one with Message boxes, one without
+  ; GTK installer has two silent states - one with Message boxes, one without
   ; If pidgin installer was run silently, we want to supress gtk installer msg boxes.
-  IfSilent 0 set_gtk_normal
-      StrCpy $ISSILENT "/NOUI"
-  set_gtk_normal:
+  IfSilent 0 +2
+    StrCpy $ISSILENT "/NOUI"
 
-  ${GetParameters} $R0
   ClearErrors
-  ${GetOptions} "$R0" "/L=" $R1
+  ${GetOptions} "$R3" "/L=" $R1
   IfErrors +3
   StrCpy $LANGUAGE $R1
   Goto skip_lang
@@ -1332,7 +1339,7 @@
     skip_lang:
 
   ClearErrors
-  ${GetOptions} "$R0" "/DS=" $R1
+  ${GetOptions} "$R3" "/DS=" $R1
   IfErrors +8
   SectionGetFlags ${SecDesktopShortcut} $R2
   StrCmp "1" $R1 0 +2
@@ -1343,7 +1350,7 @@
   SectionSetFlags ${SecDesktopShortcut} $R2
 
   ClearErrors
-  ${GetOptions} "$R0" "/SMS=" $R1
+  ${GetOptions} "$R3" "/SMS=" $R1
   IfErrors +8
   SectionGetFlags ${SecStartMenuShortcut} $R2
   StrCmp "1" $R1 0 +2
@@ -1380,6 +1387,7 @@
 
   instdir_done:
 ;LogSet on
+  Pop $R3
   Pop $R2
   Pop $R1
   Pop $R0
@@ -1694,6 +1702,7 @@
   Push $R1
   Push $R2
   Push $R3
+  Push $R4
 
   check:
   ClearErrors
@@ -1714,7 +1723,12 @@
   StrCmp $R3 "success" +3
     StrCpy $R0 $R3
     Goto done
+  ; Use a specific temporary $OUTDIR for each dictionary because the installer doesn't clean up after itself
+  StrCpy $R4 "$OUTDIR"
+  SetOutPath "$TEMP\aspell_dict-$R0"
   ExecWait '"$R1"'
+  SetOutPath "$R4"
+  RMDir /r "$TEMP\aspell_dict-$R0"
   Delete $R1
   Goto check ; Check that it is now installed correctly
 
@@ -1723,6 +1737,7 @@
     StrCpy $R0 ''
 
   done:
+  Pop $R4
   Pop $R3
   Pop $R2
   Pop $R1