changeset 27053:f5369bdd0285

propagate from branch 'im.pidgin.pidgin' (head 6fb44be5a32516ac1d940330c4f7b8d530208a16) to branch 'im.pidgin.cpw.malu.client_type' (head d22d5bca23b57e3cc71ec9b4d23bf4ff4cb44fc6)
author Paul Aurich <paul@darkrain42.org>
date Sat, 06 Jun 2009 06:21:39 +0000
parents 5d4e33e3938a (diff) 68d6c6517ef8 (current diff)
children dd7e7071d46d
files libpurple/protocols/jabber/disco.c libpurple/protocols/jabber/jabber.c
diffstat 60 files changed, 3046 insertions(+), 1015 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Sun May 31 18:39:19 2009 +0000
+++ b/COPYRIGHT	Sat Jun 06 06:21:39 2009 +0000
@@ -333,6 +333,7 @@
 Diego Petten
 Nathan Peterson
 Sebastián E. Peyrott
+Andrea Piccinelli
 Celso Pinto
 Joao Luís Marques Pinto
 Aleksander Piotrowski
@@ -451,6 +452,7 @@
 Mark Tiefenbruck
 Andrew Tinney
 Jeffery To
+Krzysztof Tobola (kreez)
 Warren Togami
 Stu Tomlinson
 Bill Tompkins
--- a/ChangeLog	Sun May 31 18:39:19 2009 +0000
+++ b/ChangeLog	Sat Jun 06 06:21:39 2009 +0000
@@ -18,10 +18,14 @@
 	  from you on MSN.
 	* DNS servers are re-read when DNS queries fail in case the system has
 	  moved to a new network and the old servers are not accessible.
+	* Gadu-Gadu accounts can specify a server to which to connect.
+	  (Krzysztof "kreez" Tobola)
 
 	XMPP:
-	* Voice & Video support with Jingle (XEP-0166, 0167, 0176, & 0177), and
-	  voice support with GTalk and GMail. (Mike "Maiku" Ruprecht)
+	* Voice & Video support with Jingle (XEP-0166, 0167, 0176, & 0177), voice
+	  support with GTalk and voice and video support with the GMail web
+	  client. (Mike "Maiku" Ruprecht)
+	* Added a Service Discovery Browser plugin for Pidgin. (Andrei Mozzhuhin)
 	* Support for in-band bytestreams for file transfers (XEP-0047). (Marcus
 	  Lundblad)
 	* Support for sending and receiving attentions (equivalent to "buzz"
@@ -39,6 +43,8 @@
 	* /affiliate and /role will now list the room members with the specified
 	  affiliation/role if possible. (Andrei Mozzhuhin)
 	* Put section breaks between resources in "Get Info" to improve readability.
+	* Silently remove invalid XML 1.0 entities (e.g. ASCII control characters)
+	  from sent messages.
 	* XHTML markup is only included in outgoing messages when the message
 	  contains formatting.
 	* Show when the user was last logged in when doing "Get Info" on an offline
@@ -48,6 +54,9 @@
 	  chat to avoid getting too many fetch requests).
 	* Fix an issue with Jabber (pre-XMPP) servers and the user's preference
 	  to require SSL not being respected.
+	* Fix an issue where Cyrus SASL DIGEST MD5 authentication might fail if
+	  the username, password, or realm (the JID domain) contain non-ASCII
+	  characters.
 
 	Yahoo:
 	* P2P file transfers.  (Sulabh Mahajan)
@@ -80,12 +89,24 @@
 	  rejoin.
 	* Always set unseen-count and unseen-state on conversations.
 	  (Joshua Stein)
+	* Fix a bug in 'Conversation Colors' plugin for RTL messages.
+	* Pressing the Left and Right arrow keys in the buddy list will expand and
+	  collapse buddy groups or contacts. (Peter Ruibal)
+	* Support saving animated custom smileys as animated images or animated
+	  custom smileys. (Andrea Piccinelli)
 
 	Finch:
 	* The hardware cursor is updated correctly. This will be useful
 	  especially for users of braille terminals, screen readers etc.
 	* Added a TinyURL plugin, which aids copying longer URLs.
 
+	Pidgin GTK+ Theme Control Plugin:
+	* Removed mouse cursor color preferences.
+	* Added "Typing Notification Color" preference.
+	* Added "Disable Typing Notification Text" preference.
+	* Preferences have been reorganized into three tabs for Colors, Fonts, and
+	  Miscellaneous categories.
+
 version 2.5.6 (05/19/2009):
 	libpurple:
 	* Improve sleep behavior by aggregation of longer timeouts on second
--- a/ChangeLog.API	Sun May 31 18:39:19 2009 +0000
+++ b/ChangeLog.API	Sat Jun 06 06:21:39 2009 +0000
@@ -33,11 +33,13 @@
 		* purple_global_proxy_set_info
 		* purple_group_destroy
 		* purple_log_get_activity_score
+		* purple_markup_is_rtl
 		* purple_network_force_online
 		* purple_network_set_stun_server
 		* purple_network_set_turn_server
 		* purple_network_get_stun_ip
 		* purple_network_get_turn_ip
+		* purple_proxy_connect_udp
 		* purple_prpl_get_media_caps
 		* purple_prpl_got_account_actions
 		* purple_prpl_initiate_media
@@ -45,6 +47,8 @@
 		* purple_request_field_get_ui_data
 		* purple_request_field_set_ui_data
 		* purple_strequal
+		* purple_utf8_strip_unprintables
+		* purple_util_fetch_url_request_len_with_account
 		* xmlnode_from_file
 		* xmlnode_get_parent
 		* xmlnode_set_attrib_full
@@ -55,6 +59,13 @@
 		  which was completely non-deterministic.  If you want to remove
 		  the attribute with no namespace, then use NULL with
 		  xmlnode_remove_with_namespace.
+		* Plugins may now emit the jabber-sending-xmlnode signal in order
+		  to send stanzas; this method is preferred to the prpl send_raw
+		  function as other plugins listening to the signal see them.
+		* The conversation-updated signal with a PURPLE_CONV_UPDATE_TYPING
+		  update type is emitted when receiving an IM.  Previously, the
+		  typing state was modified (and the buddy-typing-stopped signal
+		  emitted), but this signal was not emitted.
 
 		Deprecated:
 		* buddy-added and buddy-removed blist signals
@@ -70,6 +81,7 @@
 		* purple_status_set_attr_string
 		* purple_presence_add_status
 		* purple_presence_add_list
+		* purple_util_fetch_url_request_len
 		* xmlnode_set_attrib_with_namespace
 		* xmlnode_set_attrib_with_prefix
 
@@ -84,6 +96,7 @@
 		* pidgin_blist_set_theme
 		* pidgin_blist_get_theme
 		* pidgin_prefs_labeled_password
+		* pidgin_smiley_editor_set_data
 		* pidgin_sound_is_customized
 		* pidgin_utils_init, pidgin_utils_uninit
 		* pidgin_notify_pounce_add
--- a/configure.ac	Sun May 31 18:39:19 2009 +0000
+++ b/configure.ac	Sat Jun 06 06:21:39 2009 +0000
@@ -2476,6 +2476,7 @@
 		   pidgin/pixmaps/emotes/small/16/Makefile
 		   pidgin/plugins/Makefile
 		   pidgin/plugins/cap/Makefile
+		   pidgin/plugins/disco/Makefile
 		   pidgin/plugins/gestures/Makefile
 		   pidgin/plugins/gevolution/Makefile
 		   pidgin/plugins/musicmessaging/Makefile
--- a/libpurple/blist.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/blist.c	Sat Jun 06 06:21:39 2009 +0000
@@ -977,18 +977,26 @@
 	PurpleConversation *conv;
 	PurpleBlistNode *bnode;
 	char *old_alias;
+	char *new_alias = NULL;
 
 	g_return_if_fail(contact != NULL);
 
-	if (!purple_strings_are_different(contact->alias, alias))
+	if ((alias != NULL) && (*alias != '\0'))
+		new_alias = purple_utf8_strip_unprintables(alias);
+
+	if (!purple_strings_are_different(contact->alias, new_alias)) {
+		g_free(new_alias);
 		return;
+	}
 
 	old_alias = contact->alias;
 
-	if ((alias != NULL) && (*alias != '\0'))
-		contact->alias = g_strdup(alias);
-	else
+	if ((new_alias != NULL) && (*new_alias != '\0'))
+		contact->alias = new_alias;
+	else {
 		contact->alias = NULL;
+		g_free(new_alias); /* could be "\0" */
+	}
 
 	purple_blist_schedule_save();
 
@@ -1014,18 +1022,26 @@
 {
 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
 	char *old_alias;
+	char *new_alias = NULL;
 
 	g_return_if_fail(chat != NULL);
 
-	if (!purple_strings_are_different(chat->alias, alias))
+	if ((alias != NULL) && (*alias != '\0'))
+		new_alias = purple_utf8_strip_unprintables(alias);
+
+	if (!purple_strings_are_different(chat->alias, new_alias)) {
+		g_free(new_alias);
 		return;
+	}
 
 	old_alias = chat->alias;
 
-	if ((alias != NULL) && (*alias != '\0'))
-		chat->alias = g_strdup(alias);
-	else
+	if ((new_alias != NULL) && (*new_alias != '\0'))
+		chat->alias = new_alias;
+	else {
 		chat->alias = NULL;
+		g_free(new_alias); /* could be "\0" */
+	}
 
 	purple_blist_schedule_save();
 
@@ -1042,18 +1058,26 @@
 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
 	PurpleConversation *conv;
 	char *old_alias;
+	char *new_alias = NULL;
 
 	g_return_if_fail(buddy != NULL);
 
-	if (!purple_strings_are_different(buddy->alias, alias))
+	if ((alias != NULL) && (*alias != '\0'))
+		new_alias = purple_utf8_strip_unprintables(alias);
+
+	if (!purple_strings_are_different(buddy->alias, new_alias)) {
+		g_free(new_alias);
 		return;
+	}
 
 	old_alias = buddy->alias;
 
-	if ((alias != NULL) && (*alias != '\0'))
+	if ((new_alias != NULL) && (*new_alias != '\0'))
 		buddy->alias = g_strdup(alias);
-	else
+	else {
 		buddy->alias = NULL;
+		g_free(new_alias); /* could be "\0" */
+	}
 
 	purple_blist_schedule_save();
 
@@ -1075,18 +1099,26 @@
 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
 	PurpleConversation *conv;
 	char *old_alias;
+	char *new_alias = NULL;
 
 	g_return_if_fail(buddy != NULL);
 
-	if (!purple_strings_are_different(buddy->server_alias, alias))
+	if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL))
+		new_alias = purple_utf8_strip_unprintables(alias);
+
+	if (!purple_strings_are_different(buddy->server_alias, new_alias)) {
+		g_free(new_alias);
 		return;
+	}
 
 	old_alias = buddy->server_alias;
 
-	if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL))
-		buddy->server_alias = g_strdup(alias);
-	else
+	if ((new_alias != NULL) && (*new_alias != '\0'))
+		buddy->server_alias = new_alias;
+	else {
 		buddy->server_alias = NULL;
+		g_free(new_alias); /* could be "\0"; */
+	}
 
 	purple_blist_schedule_save();
 
@@ -1106,19 +1138,24 @@
 /*
  * TODO: If merging, prompt the user if they want to merge.
  */
-void purple_blist_rename_group(PurpleGroup *source, const char *new_name)
+void purple_blist_rename_group(PurpleGroup *source, const char *name)
 {
 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
 	PurpleGroup *dest;
 	gchar *old_name;
+	gchar *new_name;
 	GList *moved_buddies = NULL;
 	GSList *accts;
 
 	g_return_if_fail(source != NULL);
-	g_return_if_fail(new_name != NULL);
-
-	if (*new_name == '\0' || purple_strequal(new_name, source->name))
+	g_return_if_fail(name != NULL);
+
+	new_name = purple_utf8_strip_unprintables(name);
+
+	if (*new_name == '\0' || purple_strequal(new_name, source->name)) {
+		g_free(new_name);
 		return;
+	}
 
 	dest = purple_find_group(new_name);
 	if (dest != NULL && purple_utf8_strcasecmp(source->name, dest->name) != 0) {
@@ -1160,6 +1197,7 @@
 		old_name = g_strdup(source->name);
 		purple_blist_remove_group(source);
 		source = dest;
+		g_free(new_name);
 	} else {
 		/* A simple rename */
 		PurpleBlistNode *cnode, *bnode;
@@ -1172,7 +1210,7 @@
 		}
 
 		old_name = source->name;
-		source->name = g_strdup(new_name);
+		source->name = new_name;
 	}
 
 	/* Save our changes */
@@ -1184,7 +1222,7 @@
 
 	/* Notify all PRPLs */
 	/* TODO: Is this condition needed?  Seems like it would always be TRUE */
-	if(old_name && purple_strequal(source->name, old_name)) {
+	if(old_name && !purple_strequal(source->name, old_name)) {
 		for (accts = purple_group_get_accounts(source); accts; accts = g_slist_remove(accts, accts->data)) {
 			PurpleAccount *account = accts->data;
 			PurpleConnection *gc = NULL;
@@ -1246,7 +1284,7 @@
 	chat = g_new0(PurpleChat, 1);
 	chat->account = account;
 	if ((alias != NULL) && (*alias != '\0'))
-		chat->alias = g_strdup(alias);
+		chat->alias = purple_utf8_strip_unprintables(alias);
 	chat->components = components;
 	purple_blist_node_initialize_settings((PurpleBlistNode *)chat);
 	((PurpleBlistNode *)chat)->type = PURPLE_BLIST_CHAT_NODE;
@@ -1273,13 +1311,13 @@
 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
 	PurpleBuddy *buddy;
 
-	g_return_val_if_fail(account != NULL, FALSE);
-	g_return_val_if_fail(name != NULL, FALSE);
+	g_return_val_if_fail(account != NULL, NULL);
+	g_return_val_if_fail(name != NULL, NULL);
 
 	buddy = g_new0(PurpleBuddy, 1);
 	buddy->account  = account;
-	buddy->name     = g_strdup(name);
-	buddy->alias    = g_strdup(alias);
+	buddy->name     = purple_utf8_strip_unprintables(name);
+	buddy->alias    = purple_utf8_strip_unprintables(alias);
 	buddy->presence = purple_presence_new_for_buddy(buddy);
 	((PurpleBlistNode *)buddy)->type = PURPLE_BLIST_BUDDY_NODE;
 
@@ -1705,7 +1743,7 @@
 		return group;
 
 	group = g_new0(PurpleGroup, 1);
-	group->name = g_strdup(name);
+	group->name = purple_utf8_strip_unprintables(name);
 	group->totalsize = 0;
 	group->currentsize = 0;
 	group->online = 0;
--- a/libpurple/conversation.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/conversation.c	Sat Jun 06 06:21:39 2009 +0000
@@ -55,7 +55,6 @@
 	im = PURPLE_CONV_IM(c);
 
 	purple_conv_im_set_typing_state(im, PURPLE_NOT_TYPING);
-	purple_conv_im_update_typing(im);
 	purple_conv_im_stop_typing_timeout(im);
 
 	return FALSE;
@@ -1050,6 +1049,8 @@
 								   "buddy-typing-stopped", im->conv->account, im->conv->name);
 				break;
 		}
+
+		purple_conv_im_update_typing(im);
 	}
 }
 
--- a/libpurple/media.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/media.c	Sat Jun 06 06:21:39 2009 +0000
@@ -43,6 +43,7 @@
 #ifdef USE_VV
 
 #include <gst/farsight/fs-conference-iface.h>
+#include <gst/farsight/fs-element-added-notifier.h>
 
 /** @copydoc _PurpleMediaSession */
 typedef struct _PurpleMediaSession PurpleMediaSession;
@@ -2380,6 +2381,18 @@
 	stream->connected_cb_id = purple_timeout_add(0,
 			(GSourceFunc)purple_media_connected_cb, stream);
 }
+
+static void
+purple_media_element_added_cb(FsElementAddedNotifier *self,
+		GstBin *bin, GstElement *element, gpointer user_data)
+{
+	/*
+	 * Hack to make H264 work with Gmail video.
+	 */
+	if (!strncmp(GST_ELEMENT_NAME(element), "x264", 4)) {
+		g_object_set(GST_OBJECT(element), "cabac", FALSE, NULL);
+	}
+}
 #endif  /* USE_VV */
 
 gboolean
@@ -2403,7 +2416,7 @@
 
 	if (!session) {
 		GError *err = NULL;
-		GList *codec_conf = NULL;
+		GList *codec_conf = NULL, *iter = NULL;
 		gchar *filename = NULL;
 		PurpleMediaSessionType session_type;
 		GstElement *src = NULL;
@@ -2420,15 +2433,6 @@
 			return FALSE;
 		}
 
-	/* XXX: SPEEX has a latency of 5 or 6 seconds for me */
-#if 0
-	/* SPEEX is added through the configuration */
-		codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY,
-				"SPEEX", FS_MEDIA_TYPE_AUDIO, 8000));
-		codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY,
-				"SPEEX", FS_MEDIA_TYPE_AUDIO, 16000));
-#endif
-
 		filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL);
 		codec_conf = fs_codec_list_from_keyfile(filename, &err);
 		g_free(filename);
@@ -2445,6 +2449,25 @@
 			g_error_free(err);
 		}
 
+		/*
+		 * Add SPEEX if the configuration file doesn't exist or
+		 * there isn't a speex entry.
+		 */
+		for (iter = codec_conf; iter; iter = g_list_next(iter)) {
+			FsCodec *codec = iter->data;
+			if (!g_ascii_strcasecmp(codec->encoding_name, "speex"))
+				break;
+		}
+
+		if (iter == NULL) {
+			codec_conf = g_list_prepend(codec_conf,
+					fs_codec_new(FS_CODEC_ID_ANY,
+					"SPEEX", FS_MEDIA_TYPE_AUDIO, 8000));
+			codec_conf = g_list_prepend(codec_conf,
+					fs_codec_new(FS_CODEC_ID_ANY,
+					"SPEEX", FS_MEDIA_TYPE_AUDIO, 16000));
+		}
+
 		fs_session_set_codec_preferences(session->session, codec_conf, NULL);
 
 		/*
@@ -2456,6 +2479,19 @@
 			g_object_set(G_OBJECT(session->session),
 					"no-rtcp-timeout", 0, NULL);
 
+		/*
+		 * Hack to make x264 work with Gmail video.
+		 */
+		if (is_nice && !strcmp(sess_id, "google-video")) {
+			FsElementAddedNotifier *notifier =
+					fs_element_added_notifier_new();
+			g_signal_connect(G_OBJECT(notifier), "element-added",
+					G_CALLBACK(purple_media_element_added_cb),
+					stream);
+			fs_element_added_notifier_add(notifier,
+					GST_BIN(media->priv->conference));
+		}
+
 		fs_codec_list_destroy(codec_conf);
 
 		session->id = g_strdup(sess_id);
@@ -2670,7 +2706,8 @@
 	PurpleMediaStream *stream;
 	g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL);
 	stream = purple_media_get_stream(media, sess_id, participant);
-	return purple_media_candidate_list_from_fs(stream->local_candidates);
+	return stream ? purple_media_candidate_list_from_fs(
+			stream->local_candidates) : NULL;
 #else
 	return NULL;
 #endif
--- a/libpurple/plugins/psychic.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/plugins/psychic.c	Sat Jun 06 06:21:39 2009 +0000
@@ -74,6 +74,7 @@
 			      time(NULL));
     }
 
+    /* Necessary because we may be creating a new conversation window. */
     purple_conv_im_set_typing_state(PURPLE_CONV_IM(gconv), PURPLE_TYPING);
   }
 }
--- a/libpurple/plugins/signals-test.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/plugins/signals-test.c	Sat Jun 06 06:21:39 2009 +0000
@@ -661,6 +661,32 @@
 	return FALSE;
 }
 
+static gboolean
+jabber_watched_iq(PurpleConnection *pc, const char *type, const char *id,
+                  const char *from, xmlnode *child)
+{
+	purple_debug_misc("signals test", "jabber watched IQ (type=%s, id=%s, from=%s)\n"
+	                  "child %p name=%s, namespace=%s\n",
+	                  type, id, from, child, child->name,
+	                  xmlnode_get_namespace(child));
+
+	if (g_str_equal(type, "get") || g_str_equal(type, "set")) {
+		/* Send the requisite reply */
+		xmlnode *iq = xmlnode_new("iq");
+		xmlnode_set_attrib(iq, "to", from);
+		xmlnode_set_attrib(iq, "id", id);
+		xmlnode_set_attrib(iq, "type", "result");
+
+		purple_signal_emit(purple_connection_get_prpl(pc),
+		                   "jabber-sending-xmlnode", pc, &iq);
+		if (iq != NULL)
+			xmlnode_free(iq);
+	}
+
+	/* Cookie monster eats IQ stanzas; the prpl shouldn't keep processing */
+	return TRUE;
+}
+
 /**************************************************************************
  * Plugin stuff
  **************************************************************************/
@@ -830,6 +856,16 @@
 		                      PURPLE_CALLBACK(jabber_message_received), NULL);
 		purple_signal_connect(jabber_handle, "jabber-receiving-presence", plugin,
 		                      PURPLE_CALLBACK(jabber_presence_received), NULL);
+
+		/* IQ namespace signals */
+		purple_signal_emit(jabber_handle, "jabber-register-namespace-watcher",
+		                   "bogus_node", "super-duper-namespace");
+		/* The above is equivalent to doing:
+			int result = GPOINTER_TO_INT(purple_plugin_ipc_call(jabber_handle, "register_namespace_watcher", &ok, "bogus_node", "super-duper-namespace"));
+		 */
+
+		purple_signal_connect(jabber_handle, "jabber-watched-iq", plugin,
+		                      PURPLE_CALLBACK(jabber_watched_iq), NULL);
 	}
 
 	return TRUE;
@@ -838,8 +874,19 @@
 static gboolean
 plugin_unload(PurplePlugin *plugin)
 {
+	void *jabber_handle = purple_plugins_find_with_id("prpl-jabber");
+
 	purple_signals_disconnect_by_handle(plugin);
 
+	if (jabber_handle) {
+		/* Unregister watched namespaces */
+		purple_signal_emit(jabber_handle, "jabber-unregister-namespace-watcher",
+		                   "bogus_node", "super-duper-namespace");
+		/* The above is equivalent to doing:
+		   int result = GPOINTER_TO_INT(purple_plugin_ipc_call(jabber_handle, "unregister_namespace_watcher", &ok, "bogus_node", "super-duper-namespace"));
+		 */
+	}
+
 	return TRUE;
 }
 
--- a/libpurple/protocols/gg/buddylist.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/gg/buddylist.c	Sat Jun 06 06:21:39 2009 +0000
@@ -40,57 +40,33 @@
 {
 	GGPInfo *info = gc->proto_data;
 	PurpleAccount *account = purple_connection_get_account(gc);
-
-	PurpleBlistNode *gnode, *cnode, *bnode;
-	PurpleBuddy *buddy;
-	uin_t *userlist = NULL;
-	gchar *types = NULL;
-	int size = 0, ret = 0;
+	GSList *buddies;
+	uin_t *userlist;
+	gchar *types;
+	int i = 0, ret = 0;
+	int size;
 
-	for (gnode = purple_blist_get_root();
-	     gnode != NULL;
-	     gnode = purple_blist_node_get_sibling_next(gnode))
-	{
-		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-
-		for (cnode = purple_blist_node_get_first_child(gnode);
-		     cnode != NULL;
-		     cnode = purple_blist_node_get_sibling_next(cnode))
-		{
-			if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
+	buddies = purple_find_buddies(account, NULL);
 
-			for (bnode = purple_blist_node_get_first_child(cnode);
-			     bnode != NULL;
-			     bnode = purple_blist_node_get_sibling_next(bnode))
-			{
-				const gchar *name = NULL;
-
-				if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-
-				buddy = (PurpleBuddy *)bnode;
+	size = g_slist_length(buddies);
+	userlist = g_new(uin_t, size);
+	types    = g_new(gchar, size);
 
-				if (purple_buddy_get_account(buddy) != account)
-					continue;
-
-				name = purple_buddy_get_name(buddy);
+	for (buddies = purple_find_buddies(account, NULL); buddies;
+			buddies = g_slist_delete_link(buddies, buddies), ++i)
+	{
+		PurpleBuddy *buddy = buddies->data;
+		const gchar *name = purple_buddy_get_name(buddy);
 
-				size++;
-				userlist = (uin_t *) g_renew(uin_t, userlist, size);
-				types    = (gchar *) g_renew(gchar, types, size);
-				userlist[size - 1] = ggp_str_to_uin(name);
-				types[size - 1]    = GG_USER_NORMAL;
-				purple_debug_info("gg", "ggp_buddylist_send: adding %d\n",
-						userlist[size - 1]);
-			}
-		}
+		userlist[i] = ggp_str_to_uin(name);
+		types[i]    = GG_USER_NORMAL;
+		purple_debug_info("gg", "ggp_buddylist_send: adding %d\n",
+		                  userlist[i]);
 	}
 
 	ret = gg_notify_ex(info->session, userlist, types, size);
 	purple_debug_info("gg", "send: ret=%d; size=%d\n", ret, size);
-	
+
 	if (userlist) {
 		g_free(userlist);
 		g_free(types);
@@ -178,105 +154,28 @@
 }
 /* }}} */
 
-/* void ggp_buddylist_offline(PurpleConnection *gc) {{{ */
-void ggp_buddylist_offline(PurpleConnection *gc)
-{
-	PurpleAccount *account = purple_connection_get_account(gc);
-	PurpleBlistNode *gnode, *cnode, *bnode;
-	PurpleBuddy *buddy;
-
-	for (gnode = purple_blist_get_root();
-	     gnode != NULL;
-	     gnode = purple_blist_node_get_sibling_next(gnode))
-	{
-		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-
-		for (cnode = purple_blist_node_get_first_child(gnode);
-		     cnode != NULL;
-		     cnode = purple_blist_node_get_sibling_next(cnode))
-		{
-			if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-
-			for (bnode = purple_blist_node_get_first_child(cnode);
-			     bnode != NULL;
-			     bnode = purple_blist_node_get_sibling_next(bnode))
-			{
-				const gchar *name = NULL;
-
-				if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-
-				buddy = (PurpleBuddy *)bnode;
-				
-				name = purple_buddy_get_name(buddy);
-
-				if (purple_buddy_get_account(buddy) != account)
-					continue;
-
-				purple_prpl_got_user_status(
-					account, name, "offline", NULL);
-
-				purple_debug_info("gg",
-					"ggp_buddylist_offline: gone: %s\n",
-					name);
-			}
-		}
-	}
-}
-/* }}} */
-
 /* char *ggp_buddylist_dump(PurpleAccount *account) {{{ */
 char *ggp_buddylist_dump(PurpleAccount *account)
 {
-	PurpleBlistNode *gnode, *cnode, *bnode;
-	PurpleGroup *group;
-	PurpleBuddy *buddy;
+	GSList *buddies;
 	GString *buddylist = g_string_sized_new(1024);
 	char *ptr;
 
-	for (gnode = purple_blist_get_root();
-	     gnode != NULL;
-	     gnode = purple_blist_node_get_sibling_next(gnode))
-	{
-		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-
-		group = (PurpleGroup *)gnode;
-
-		for (cnode = purple_blist_node_get_first_child(gnode);
-		     cnode != NULL;
-		     cnode = purple_blist_node_get_sibling_next(cnode))
-		{
-			if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
+	for (buddies = purple_find_buddies(account, NULL); buddies;
+			buddies = g_slist_delete_link(buddies, buddies)) {
+		PurpleBuddy *buddy = buddies->data;
+		PurpleGroup *group = purple_buddy_get_group(buddy);
+		const char *bname = purple_buddy_get_name(buddy);
+		const char *gname = purple_group_get_name(group);
+		const char *alias = purple_buddy_get_alias(buddy);
 
-			for (bnode = purple_blist_node_get_first_child(cnode);
-			     bnode != NULL;
-			     bnode = purple_blist_node_get_sibling_next(bnode))
-			{
-				const gchar *name, *alias, *gname;
-
-				if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-
-				buddy = (PurpleBuddy *)bnode;
-				if (purple_buddy_get_account(buddy) != account)
-					continue;
+		if (alias == NULL)
+			alias = bname;
 
-				name = purple_buddy_get_name(buddy);
-				alias = purple_buddy_get_alias(buddy);
-				if(alias == NULL)
-					alias = name;
-				gname = purple_group_get_name(group);
-
-				g_string_append_printf(buddylist,
-						"%s;%s;%s;%s;%s;%s;%s;%s%s\r\n",
-						alias, alias, alias, alias,
-						"", gname, name, "", "");
-			}
-		}
+		g_string_append_printf(buddylist,
+				"%s;%s;%s;%s;%s;%s;%s;%s%s\r\n",
+				alias, alias, alias, alias,
+				"", gname, bname, "", "");
 	}
 
 	ptr = charset_convert(buddylist->str, "UTF-8", "CP1250");
--- a/libpurple/protocols/gg/buddylist.h	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/gg/buddylist.h	Sat Jun 06 06:21:39 2009 +0000
@@ -31,7 +31,7 @@
 ggp_buddylist_send(PurpleConnection *gc);
 
 /**
- * Load buddylist from server into the rooster.
+ * Load buddylist from server into the roster.
  *
  * @param gc PurpleConnection
  * @param buddylist Pointer to the buddylist that will be loaded.
@@ -41,14 +41,6 @@
 ggp_buddylist_load(PurpleConnection *gc, char *buddylist);
 
 /**
- * Set offline status for all buddies.
- *
- * @param gc Connection handler
- */
-void
-ggp_buddylist_offline(PurpleConnection *gc);
-
-/**
  * Get all the buddies in the current account.
  *
  * @param account Current account.
--- a/libpurple/protocols/gg/gg.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/gg/gg.c	Sat Jun 06 06:21:39 2009 +0000
@@ -1759,6 +1759,7 @@
 	PurpleStatus *status;
 	struct gg_login_params *glp;
 	GGPInfo *info;
+	const char *address;
 
 	if (ggp_setup_proxy(account) == -1)
 		return;
@@ -1789,6 +1790,26 @@
 	glp->status = ggp_to_gg_status(status, &glp->status_descr);
 	glp->tls = 0;
 
+	address = purple_account_get_string(account, "gg_server", "");
+	if (address && *address) {
+		struct in_addr *addr = gg_gethostbyname(address);
+
+		purple_debug_info("gg", "Using gg server given by user (%s)\n", address);
+
+		if (addr == NULL) {
+			purple_debug_error("gg", "gg_gethostbyname returned error (%d): %s\n",
+			                   errno, g_strerror(errno));
+			purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, /* should this be a settings error? */
+				_("Unable to resolve server"));
+			return;
+		}
+
+		glp->server_addr = inet_addr(inet_ntoa(*addr));
+		glp->server_port = 8074;
+	} else
+		purple_debug_info("gg", "Trying to retrieve address from gg appmsg service\n");
+
 	info->session = gg_login(glp);
 	if (info->session == NULL) {
 		purple_connection_error_reason (gc,
@@ -1837,8 +1858,6 @@
 	if (gc->inpa > 0)
 		purple_input_remove(gc->inpa);
 
-	ggp_buddylist_offline(gc);
-
 	purple_debug_info("gg", "Connection closed.\n");
 }
 
@@ -2369,6 +2388,11 @@
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 						   option);
 
+	option = purple_account_option_string_new(_("GG server"),
+			"gg_server", "");
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+			option);
+
 	my_protocol = plugin;
 
 	gg_debug_handler = purple_gg_debug_handler;
--- a/libpurple/protocols/irc/msgs.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/irc/msgs.c	Sat Jun 06 06:21:39 2009 +0000
@@ -78,7 +78,7 @@
 {
 	PurpleConnection *gc;
 	PurpleStatus *status;
-	PurpleBlistNode *gnode, *cnode, *bnode;
+	GSList *buddies;
 	PurpleAccount *account;
 
 	if ((gc = purple_account_get_connection(irc->account)) == NULL
@@ -97,33 +97,13 @@
 	}
 
 	/* this used to be in the core, but it's not now */
-	for (gnode = purple_blist_get_root();
-	     gnode;
-	     gnode = purple_blist_node_get_sibling_next(gnode))
+	for (buddies = purple_find_buddies(account, NULL); buddies;
+			buddies = g_slist_delete_link(buddies, buddies))
 	{
-		if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		for(cnode = purple_blist_node_get_first_child(gnode);
-		    cnode;
-		    cnode = purple_blist_node_get_sibling_next(cnode))
-		{
-			if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-			for(bnode = purple_blist_node_get_first_child(cnode);
-			    bnode;
-			    bnode = purple_blist_node_get_sibling_next(bnode))
-			{
-				PurpleBuddy *b;
-				if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-				b = (PurpleBuddy *)bnode;
-				if(purple_buddy_get_account(b) == account) {
-					struct irc_buddy *ib = g_new0(struct irc_buddy, 1);
-					ib->name = g_strdup(purple_buddy_get_name(b));
-					g_hash_table_insert(irc->buddies, ib->name, ib);
-				}
-			}
-		}
+		PurpleBuddy *b = buddies->data;
+		struct irc_buddy *ib = g_new0(struct irc_buddy, 1);
+		ib->name = g_strdup(purple_buddy_get_name(b));
+		g_hash_table_insert(irc->buddies, ib->name, ib);
 	}
 
 	irc_blist_timeout(irc);
--- a/libpurple/protocols/jabber/auth.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c	Sat Jun 06 06:21:39 2009 +0000
@@ -989,7 +989,20 @@
 			response = xmlnode_new("response");
 			xmlnode_set_namespace(response, "urn:ietf:params:xml:ns:xmpp-sasl");
 			if (clen > 0) {
-				enc_out = purple_base64_encode((unsigned char*)c_out, clen);
+				/* Cyrus SASL 2.1.22 appears to contain code to add the charset
+				 * to the response but there is no possibility it will be executed.
+				 * My reading of the digestmd5 plugin indicates the username and
+				 * realm are always encoded in UTF-8 (they seem to be the values
+				 * we pass in), so we need to ensure charset=utf-8 is set.
+				 */
+				if (strstr(c_out, ",charset="))
+					enc_out = purple_base64_encode((unsigned char*)c_out, clen);
+				else {
+					char *tmp = g_strdup_printf("%s,charset=utf-8", c_out);
+					enc_out = purple_base64_encode((unsigned char*)tmp, clen + 14);
+					g_free(tmp);
+				}
+
 				xmlnode_insert_data(response, enc_out, -1);
 				g_free(enc_out);
 			}
--- a/libpurple/protocols/jabber/disco.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Sat Jun 06 06:21:39 2009 +0000
@@ -1,5 +1,5 @@
 /*
- * purple - Jabber Protocol Plugin
+ * purple - Jabber Service Discovery
  *
  * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
  *
@@ -22,6 +22,7 @@
 #include "internal.h"
 #include "prefs.h"
 #include "debug.h"
+#include "request.h"
 
 #include "adhoccommands.h"
 #include "buddy.h"
@@ -40,6 +41,11 @@
 	JabberDiscoInfoCallback *callback;
 };
 
+struct _jabber_disco_items_cb_data {
+	gpointer data;
+	JabberDiscoItemsCallback *callback;
+};
+
 #define SUPPORT_FEATURE(x) { \
 	feature = xmlnode_new_child(query, "feature"); \
 	xmlnode_set_attrib(feature, "var", x); \
@@ -148,14 +154,26 @@
 			 */
 			xmlnode *feature = xmlnode_new_child(query, "feature");
 			xmlnode_set_attrib(feature, "var", "http://www.google.com/xmpp/protocol/voice/v1");
+		} else if (g_str_equal(node, CAPS0115_NODE "#" "video-v1")) {
+			/*
+			 * HUGE HACK! We advertise this ext (see jabber_presence_create_js
+			 * where we add <c/> to the <presence/>) for the Google Talk
+			 * clients that don't actually check disco#info features.
+			 *
+			 * This specific feature is redundant but is what
+			 * node='http://mail.google.com/xmpp/client/caps', ver='1.1'
+			 * advertises as 'video-v1'.
+			 */
+			xmlnode *feature = xmlnode_new_child(query, "feature");
+			xmlnode_set_attrib(feature, "var", "http://www.google.com/xmpp/protocol/video/v1");
 #endif
 		} else {
 			xmlnode *error, *inf;
-				
+
 			/* XXX: gross hack, implement jabber_iq_set_type or something */
 			xmlnode_set_attrib(iq->node, "type", "error");
 			iq->type = JABBER_IQ_ERROR;
-			
+
 			error = xmlnode_new_child(query, "error");
 			xmlnode_set_attrib(error, "code", "404");
 			xmlnode_set_attrib(error, "type", "cancel");
@@ -164,13 +182,42 @@
 		}
 		g_free(node_uri);
 		jabber_iq_send(iq);
-	} else if(type == JABBER_IQ_RESULT) {
+	} else if (type == JABBER_IQ_SET) {
+		/* wtf? seriously. wtf‽ */
+		JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
+		xmlnode *error, *bad_request;
+
+		/* Free the <query/> */
+		xmlnode_free(xmlnode_get_child(iq->node, "query"));
+		/* Add an error */
+		error = xmlnode_new_child(iq->node, "error");
+		xmlnode_set_attrib(error, "type", "modify");
+		bad_request = xmlnode_new_child(error, "bad-request");
+		xmlnode_set_namespace(bad_request, "urn:ietf:params:xml:ns:xmpp-stanzas");
+
+		jabber_iq_set_id(iq, id);
+		xmlnode_set_attrib(iq->node, "to", from);
+
+		jabber_iq_send(iq);
+	}
+}
+
+static void jabber_disco_info_cb(JabberStream *js, const char *from,
+                                 JabberIqType type, const char *id,
+                                 xmlnode *packet, gpointer data)
+{
+	struct _jabber_disco_info_cb_data *jdicd = data;
+	xmlnode *query;
+
+	query = xmlnode_get_child_with_namespace(packet, "query",
+				"http://jabber.org/protocol/disco#info");
+
+	if (type == JABBER_IQ_RESULT && query) {
 		xmlnode *child;
 		JabberID *jid;
 		JabberBuddy *jb;
 		JabberBuddyResource *jbr = NULL;
 		JabberCapabilities capabilities = JABBER_CAP_NONE;
-		struct _jabber_disco_info_cb_data *jdicd;
 
 		if((jid = jabber_id_new(from))) {
 			if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE)))
@@ -181,7 +228,7 @@
 		if(jbr)
 			capabilities = jbr->capabilities;
 
-		for(child = in_query->child; child; child = child->next) {
+		for(child = query->child; child; child = child->next) {
 			if(child->type != XMLNODE_TYPE_TAG)
 				continue;
 
@@ -233,6 +280,8 @@
 					capabilities |= JABBER_CAP_IQ_REGISTER;
 				else if(!strcmp(var, "urn:xmpp:ping"))
 					capabilities |= JABBER_CAP_PING;
+				else if(!strcmp(var, "http://jabber.org/protocol/disco#items"))
+					capabilities |= JABBER_CAP_ITEMS;
 				else if(!strcmp(var, "http://jabber.org/protocol/commands")) {
 					capabilities |= JABBER_CAP_ADHOC;
 				}
@@ -248,19 +297,12 @@
 		if(jbr)
 			jbr->capabilities = capabilities;
 
-		if((jdicd = g_hash_table_lookup(js->disco_callbacks, from))) {
-			jdicd->callback(js, from, capabilities, jdicd->data);
-			g_hash_table_remove(js->disco_callbacks, from);
-		}
-	} else if(type == JABBER_IQ_ERROR) {
+		jdicd->callback(js, from, capabilities, jdicd->data);
+	} else { /* type == JABBER_IQ_ERROR or query == NULL */
 		JabberID *jid;
 		JabberBuddy *jb;
 		JabberBuddyResource *jbr = NULL;
 		JabberCapabilities capabilities = JABBER_CAP_NONE;
-		struct _jabber_disco_info_cb_data *jdicd;
-
-		if(!(jdicd = g_hash_table_lookup(js->disco_callbacks, from)))
-			return;
 
 		if((jid = jabber_id_new(from))) {
 			if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE)))
@@ -272,7 +314,6 @@
 			capabilities = jbr->capabilities;
 
 		jdicd->callback(js, from, capabilities, jdicd->data);
-		g_hash_table_remove(js->disco_callbacks, from);
 	}
 }
 
@@ -517,10 +558,10 @@
 	jdicd->data = data;
 	jdicd->callback = callback;
 
-	g_hash_table_insert(js->disco_callbacks, g_strdup(who), jdicd);
-
 	iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info");
 	xmlnode_set_attrib(iq->node, "to", who);
 
+	jabber_iq_set_callback(iq, jabber_disco_info_cb, jdicd);
 	jabber_iq_send(iq);
 }
+
--- a/libpurple/protocols/jabber/disco.h	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/disco.h	Sat Jun 06 06:21:39 2009 +0000
@@ -1,5 +1,5 @@
 /**
- * @file disco.h service discovery handlers
+ * @file disco.h Jabber Service Discovery
  *
  * purple
  *
@@ -24,9 +24,18 @@
 
 #include "jabber.h"
 
+typedef struct _JabberDiscoItem {
+	const char *jid;  /* MUST */
+	const char *node; /* SHOULD */
+	const char *name; /* MAY */
+} JabberDiscoItem;
+
 typedef void (JabberDiscoInfoCallback)(JabberStream *js, const char *who,
 		JabberCapabilities capabilities, gpointer data);
 
+typedef void (JabberDiscoItemsCallback)(JabberStream *js, const char *jid,
+		const char *node, GSList *items, gpointer data);
+
 void jabber_disco_info_parse(JabberStream *js, const char *from,
                              JabberIqType type, const char *id, xmlnode *in_query);
 void jabber_disco_items_parse(JabberStream *js, const char *from,
--- a/libpurple/protocols/jabber/google.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Sat Jun 06 06:21:39 2009 +0000
@@ -36,6 +36,9 @@
 
 #ifdef USE_VV
 
+#define NS_GOOGLE_VIDEO "http://www.google.com/session/video"
+#define NS_GOOGLE_PHONE "http://www.google.com/session/phone"
+
 typedef struct {
 	char *id;
 	char *initiator;
@@ -55,6 +58,7 @@
 	PurpleMedia *media;
 	JabberStream *js;
 	char *remote_jid;
+	gboolean video;
 } GoogleSession;
 
 static gboolean
@@ -104,9 +108,13 @@
 google_session_send_candidates(PurpleMedia *media, gchar *session_id,
 		gchar *participant, GoogleSession *session)
 {
-	GList *candidates = purple_media_get_local_candidates(session->media, "google-voice",
-							      session->remote_jid);
+	GList *candidates = purple_media_get_local_candidates(
+			session->media, session_id, session->remote_jid);
 	PurpleMediaCandidate *transport;
+	gboolean video = FALSE;
+
+	if (!strcmp(session_id, "google-video"))
+		video = TRUE;
 
 	for (;candidates;candidates = candidates->next) {
 		JabberIq *iq;
@@ -114,11 +122,10 @@
 		PurpleMediaCandidateType type;
 		xmlnode *sess;
 		xmlnode *candidate;
+		guint component_id;
 		transport = (PurpleMediaCandidate*)(candidates->data);
-
-		if (purple_media_candidate_get_component_id(transport)
-				!= PURPLE_MEDIA_COMPONENT_RTP)
-			continue;
+		component_id = purple_media_candidate_get_component_id(
+				transport);
 
 		iq = jabber_iq_new(session->js, JABBER_IQ_SET);
 		sess = google_session_create_xmlnode(session, "candidates");
@@ -139,7 +146,11 @@
 
 		xmlnode_set_attrib(candidate, "address", ip);
 		xmlnode_set_attrib(candidate, "port", port);
-		xmlnode_set_attrib(candidate, "name", "rtp");
+		xmlnode_set_attrib(candidate, "name",
+				component_id == PURPLE_MEDIA_COMPONENT_RTP ?
+				video ? "video_rtp" : "rtp" :
+				component_id == PURPLE_MEDIA_COMPONENT_RTCP ?
+				video ? "video_rtcp" : "rtcp" : "none");
 		xmlnode_set_attrib(candidate, "username", username);
 		/*
 		 * As of this writing, Farsight 2 in Google compatibility
@@ -205,13 +216,38 @@
 			google_session_send_candidates(session->media,
 					"google-voice", session->remote_jid,
 					session);
+			google_session_send_candidates(session->media,
+					"google-video", session->remote_jid,
+					session);
 			xmlnode_set_attrib(iq->node, "to", session->remote_jid);
 			xmlnode_set_attrib(iq->node, "from", me);
 			sess = google_session_create_xmlnode(session, "accept");
 		}
 		xmlnode_insert_child(iq->node, sess);
 		desc = xmlnode_new_child(sess, "description");
-		xmlnode_set_namespace(desc, "http://www.google.com/session/phone");
+		if (session->video)
+			xmlnode_set_namespace(desc, NS_GOOGLE_VIDEO);
+		else
+			xmlnode_set_namespace(desc, NS_GOOGLE_PHONE);
+
+		codecs = purple_media_get_codecs(media, "google-video");
+
+		for (iter = codecs; iter; iter = g_list_next(iter)) {
+			PurpleMediaCodec *codec = (PurpleMediaCodec*)iter->data;
+			gchar *id = g_strdup_printf("%d",
+					purple_media_codec_get_id(codec));
+			gchar *encoding_name =
+					purple_media_codec_get_encoding_name(codec);
+			payload = xmlnode_new_child(desc, "payload-type");
+			xmlnode_set_attrib(payload, "id", id);
+			xmlnode_set_attrib(payload, "name", encoding_name);
+			xmlnode_set_attrib(payload, "width", "320");
+			xmlnode_set_attrib(payload, "height", "200");
+			xmlnode_set_attrib(payload, "framerate", "30");
+			g_free(encoding_name);
+			g_free(id);
+		}
+		purple_media_codec_list_free(codecs);
 
 		codecs = purple_media_get_codecs(media, "google-voice");
 
@@ -224,8 +260,17 @@
 			gchar *clock_rate = g_strdup_printf("%d",
 					purple_media_codec_get_clock_rate(codec));
 			payload = xmlnode_new_child(desc, "payload-type");
+			if (session->video)
+				xmlnode_set_namespace(payload, NS_GOOGLE_PHONE);
 			xmlnode_set_attrib(payload, "id", id);
-			xmlnode_set_attrib(payload, "name", encoding_name);
+			/*
+			 * Hack to make Gmail accept speex as the codec.
+			 * It shouldn't have to be case sensitive.
+			 */
+			if (purple_strequal(encoding_name, "SPEEX"))
+				xmlnode_set_attrib(payload, "name", "speex");
+			else
+				xmlnode_set_attrib(payload, "name", encoding_name);
 			xmlnode_set_attrib(payload, "clockrate", clock_rate);
 			g_free(clock_rate);
 			g_free(encoding_name);
@@ -235,10 +280,14 @@
 
 		jabber_iq_send(iq);
 
-		if (is_initiator)
+		if (is_initiator) {
 			google_session_send_candidates(session->media,
 					"google-voice", session->remote_jid,
 					session);
+			google_session_send_candidates(session->media,
+					"google-video", session->remote_jid,
+					session);
+		}
 
 		g_signal_handlers_disconnect_by_func(G_OBJECT(session->media),
 				G_CALLBACK(google_session_ready), session);
@@ -339,6 +388,9 @@
 	session->js = js;
 	session->remote_jid = jid;
 
+	if (type & PURPLE_MEDIA_VIDEO)
+		session->video = TRUE;
+
 	session->media = purple_media_manager_create_media(
 			purple_media_manager_get(),
 			purple_connection_get_account(js->gc),
@@ -349,8 +401,12 @@
 	params = jabber_google_session_get_params(js, &num_params);
 
 	if (purple_media_add_stream(session->media, "google-voice",
-				session->remote_jid, PURPLE_MEDIA_AUDIO,
-				TRUE, "nice", num_params, params) == FALSE) {
+			session->remote_jid, PURPLE_MEDIA_AUDIO,
+			TRUE, "nice", num_params, params) == FALSE ||
+			(session->video && purple_media_add_stream(
+			session->media, "google-video",
+			session->remote_jid, PURPLE_MEDIA_VIDEO,
+			TRUE, "nice", num_params, params) == FALSE)) {
 		purple_media_error(session->media, "Error adding stream.");
 		purple_media_stream_info(session->media,
 				PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE);
@@ -378,10 +434,10 @@
 google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id)
 {
 	JabberIq *result;
-	GList *codecs = NULL;
+	GList *codecs = NULL, *video_codecs = NULL;
 	xmlnode *desc_element, *codec_element;
 	PurpleMediaCodec *codec;
-	const char *id, *encoding_name,  *clock_rate;
+	const char *xmlns;
 	GParameter *params;
 	guint num_params;
 
@@ -390,6 +446,19 @@
 		return;
 	}
 
+	desc_element = xmlnode_get_child(sess, "description");
+	xmlns = xmlnode_get_namespace(desc_element);
+
+	if (purple_strequal(xmlns, NS_GOOGLE_PHONE))
+		session->video = FALSE;
+	else if (purple_strequal(xmlns, NS_GOOGLE_VIDEO))
+		session->video = TRUE;
+	else {
+		purple_debug_error("jabber", "Received initiate with "
+				"invalid namespace %s.\n", xmlns);
+		return;
+	}
+
 	session->media = purple_media_manager_create_media(
 			purple_media_manager_get(),
 			purple_connection_get_account(js->gc),
@@ -401,7 +470,11 @@
 
 	if (purple_media_add_stream(session->media, "google-voice",
 			session->remote_jid, PURPLE_MEDIA_AUDIO, FALSE,
-			"nice", num_params, params) == FALSE) {
+			"nice", num_params, params) == FALSE ||
+			(session->video && purple_media_add_stream(
+			session->media, "google-video",
+			session->remote_jid, PURPLE_MEDIA_VIDEO,
+			FALSE, "nice", num_params, params) == FALSE)) {
 		purple_media_error(session->media, "Error adding stream.");
 		purple_media_stream_info(session->media,
 				PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE);
@@ -412,23 +485,55 @@
 
 	g_free(params);
 
-	desc_element = xmlnode_get_child(sess, "description");
+	for (codec_element = xmlnode_get_child(desc_element, "payload-type");
+	     codec_element; codec_element = codec_element->next) {
+		const char *id, *encoding_name,  *clock_rate,
+				*width, *height, *framerate;
+		gboolean video;
+		if (codec_element->name &&
+				strcmp(codec_element->name, "payload-type"))
+			continue;
 
-	for (codec_element = xmlnode_get_child(desc_element, "payload-type");
-	     codec_element;
-	     codec_element = xmlnode_get_next_twin(codec_element)) {
+		xmlns = xmlnode_get_namespace(codec_element);
 		encoding_name = xmlnode_get_attrib(codec_element, "name");
 		id = xmlnode_get_attrib(codec_element, "id");
-		clock_rate = xmlnode_get_attrib(codec_element, "clockrate");
+
+		if (!session->video ||
+				(xmlns && !strcmp(xmlns, NS_GOOGLE_PHONE))) {
+			clock_rate = xmlnode_get_attrib(
+					codec_element, "clockrate");
+			video = FALSE;
+		} else {
+			width = xmlnode_get_attrib(codec_element, "width");
+			height = xmlnode_get_attrib(codec_element, "height");
+			framerate = xmlnode_get_attrib(
+					codec_element, "framerate");
+			clock_rate = "90000";
+			video = TRUE;
+		}
 
 		if (id) {
-			codec = purple_media_codec_new(atoi(id), encoding_name, PURPLE_MEDIA_AUDIO,
-					     clock_rate ? atoi(clock_rate) : 0);
-			codecs = g_list_append(codecs, codec);
+			codec = purple_media_codec_new(atoi(id), encoding_name,
+					video ?	PURPLE_MEDIA_VIDEO :
+					PURPLE_MEDIA_AUDIO,
+					clock_rate ? atoi(clock_rate) : 0);
+			if (video)
+				video_codecs = g_list_append(
+						video_codecs, codec);
+			else
+				codecs = g_list_append(codecs, codec);
 		}
 	}
 
-	purple_media_set_remote_codecs(session->media, "google-voice", session->remote_jid, codecs);
+	if (codecs)
+		purple_media_set_remote_codecs(session->media, "google-voice",
+				session->remote_jid, codecs);
+	if (video_codecs)
+		purple_media_set_remote_codecs(session->media, "google-video",
+				session->remote_jid, video_codecs);
+
+	purple_media_codec_list_free(codecs);
+	purple_media_codec_list_free(video_codecs);
 
 	g_signal_connect_swapped(G_OBJECT(session->media), "accepted",
 			G_CALLBACK(google_session_ready), session);
@@ -442,8 +547,6 @@
 	g_signal_connect(G_OBJECT(session->media), "stream-info",
 			G_CALLBACK(google_session_stream_info_cb), session);
 
-	purple_media_codec_list_free(codecs);
-
 	result = jabber_iq_new(js, JABBER_IQ_RESULT);
 	jabber_iq_set_id(result, iq_id);
 	xmlnode_set_attrib(result->node, "to", session->remote_jid);
@@ -454,19 +557,22 @@
 google_session_handle_candidates(JabberStream  *js, GoogleSession *session, xmlnode *sess, const char *iq_id)
 {
 	JabberIq *result;
-	GList *list = NULL;
+	GList *list = NULL, *video_list = NULL;
 	xmlnode *cand;
 	static int name = 0;
 	char n[4];
 
-	for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) {
+	for (cand = xmlnode_get_child(sess, "candidate"); cand;
+			cand = xmlnode_get_next_twin(cand)) {
 		PurpleMediaCandidate *info;
+		const gchar *cname = xmlnode_get_attrib(cand, "name");
 		const gchar *type = xmlnode_get_attrib(cand, "type");
 		const gchar *protocol = xmlnode_get_attrib(cand, "protocol");
 		const gchar *address = xmlnode_get_attrib(cand, "address");
 		const gchar *port = xmlnode_get_attrib(cand, "port");
+		guint component_id;
 
-		if (type && address && port) {
+		if (cname && type && address && port) {
 			PurpleMediaCandidateType candidate_type;
 
 			g_snprintf(n, sizeof(n), "S%d", name++);
@@ -480,7 +586,13 @@
 			else
 				candidate_type = PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
 
-			info = purple_media_candidate_new(n, PURPLE_MEDIA_COMPONENT_RTP,
+			if (purple_strequal(cname, "rtcp") ||
+					purple_strequal(cname, "video_rtcp"))
+				component_id = PURPLE_MEDIA_COMPONENT_RTCP;
+			else
+				component_id = PURPLE_MEDIA_COMPONENT_RTP;
+
+			info = purple_media_candidate_new(n, component_id,
 					candidate_type,
 					purple_strequal(protocol, "udp") ?
 							PURPLE_MEDIA_NETWORK_PROTOCOL_UDP :
@@ -489,12 +601,23 @@
 					atoi(port));
 			g_object_set(info, "username", xmlnode_get_attrib(cand, "username"),
 					"password", xmlnode_get_attrib(cand, "password"), NULL);
-			list = g_list_append(list, info);
+			if (!strncmp(cname, "video_", 6))
+				video_list = g_list_append(video_list, info);
+			else
+				list = g_list_append(list, info);
 		}
 	}
 
-	purple_media_add_remote_candidates(session->media, "google-voice", session->remote_jid, list);
+	if (list)
+		purple_media_add_remote_candidates(
+				session->media, "google-voice",
+				session->remote_jid, list);
+	if (video_list)
+		purple_media_add_remote_candidates(
+				session->media, "google-video",
+				session->remote_jid, video_list);
 	purple_media_candidate_list_free(list);
+	purple_media_candidate_list_free(video_list);
 
 	result = jabber_iq_new(js, JABBER_IQ_RESULT);
 	jabber_iq_set_id(result, iq_id);
@@ -506,28 +629,57 @@
 google_session_handle_accept(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id)
 {
 	xmlnode *desc_element = xmlnode_get_child(sess, "description");
-	xmlnode *codec_element = xmlnode_get_child(desc_element, "payload-type");
-	GList *codecs = NULL;
+	xmlnode *codec_element = xmlnode_get_child(
+			desc_element, "payload-type");
+	GList *codecs = NULL, *video_codecs = NULL;
 	JabberIq *result = NULL;
+	const gchar *xmlns = xmlnode_get_namespace(desc_element);
+	gboolean video = (xmlns && !strcmp(xmlns, NS_GOOGLE_VIDEO));
+
+	for (; codec_element; codec_element = codec_element->next) {
+		const gchar *xmlns, *encoding_name, *id,
+				*clock_rate, *width, *height, *framerate;
+		gboolean video_codec = FALSE;
+
+		if (!purple_strequal(codec_element->name, "payload-type"))
+			continue;
 
-	for (; codec_element; codec_element =
-			xmlnode_get_next_twin(codec_element)) {
-		const gchar *encoding_name =
-				xmlnode_get_attrib(codec_element, "name");
-		const gchar *id = xmlnode_get_attrib(codec_element, "id");
-		const gchar *clock_rate =
-				xmlnode_get_attrib(codec_element, "clockrate");
+		xmlns = xmlnode_get_namespace(codec_element);
+		encoding_name =	xmlnode_get_attrib(codec_element, "name");
+		id = xmlnode_get_attrib(codec_element, "id");
+
+		if (!video || purple_strequal(xmlns, NS_GOOGLE_PHONE))
+			clock_rate = xmlnode_get_attrib(
+					codec_element, "clockrate");
+		else {
+			clock_rate = "90000";
+			width = xmlnode_get_attrib(codec_element, "width");
+			height = xmlnode_get_attrib(codec_element, "height");
+			framerate = xmlnode_get_attrib(
+					codec_element, "framerate");
+			video_codec = TRUE;
+		}
 
 		if (id && encoding_name) {
-			PurpleMediaCodec *codec = purple_media_codec_new(atoi(id),
-					encoding_name, PURPLE_MEDIA_AUDIO,
+			PurpleMediaCodec *codec = purple_media_codec_new(
+					atoi(id), encoding_name,
+					video_codec ? PURPLE_MEDIA_VIDEO :
+					PURPLE_MEDIA_AUDIO,
 					clock_rate ? atoi(clock_rate) : 0);
-			codecs = g_list_append(codecs, codec);
+			if (video_codec)
+				video_codecs = g_list_append(
+						video_codecs, codec);
+			else
+				codecs = g_list_append(codecs, codec);
 		}
 	}
 
-	purple_media_set_remote_codecs(session->media, "google-voice",
-			session->remote_jid, codecs);
+	if (codecs)
+		purple_media_set_remote_codecs(session->media, "google-voice",
+				session->remote_jid, codecs);
+	if (video_codecs)
+		purple_media_set_remote_codecs(session->media, "google-video",
+				session->remote_jid, video_codecs);
 
 	purple_media_stream_info(session->media, PURPLE_MEDIA_INFO_ACCEPT,
 			NULL, NULL, FALSE);
--- a/libpurple/protocols/jabber/google.h	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/google.h	Sat Jun 06 06:21:39 2009 +0000
@@ -28,6 +28,7 @@
 #include "media.h"
 
 #define GOOGLE_VOICE_CAP "http://www.google.com/xmpp/protocol/voice/v1"
+#define GOOGLE_VIDEO_CAP "http://www.google.com/xmpp/protocol/video/v1"
 #define GOOGLE_JINGLE_INFO_NAMESPACE "google:jingleinfo"
 
 void jabber_gmail_init(JabberStream *js);
--- a/libpurple/protocols/jabber/jabber.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sat Jun 06 06:21:39 2009 +0000
@@ -450,22 +450,25 @@
 	return len;
 }
 
-void jabber_send(JabberStream *js, xmlnode *packet)
+void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet,
+                           gpointer unused)
 {
 	char *txt;
 	int len;
 
-	purple_signal_emit(jabber_plugin, "jabber-sending-xmlnode", js->gc, &packet);
-
-	/* if we get NULL back, we're done processing */
-	if(NULL == packet)
+	if (NULL == packet)
 		return;
 
-	txt = xmlnode_to_str(packet, &len);
-	jabber_send_raw(js, txt, len);
+	txt = xmlnode_to_str(*packet, &len);
+	jabber_send_raw(purple_connection_get_protocol_data(pc), txt, len);
 	g_free(txt);
 }
 
+void jabber_send(JabberStream *js, xmlnode *packet)
+{
+	purple_signal_emit(jabber_plugin, "jabber-sending-xmlnode", js->gc, &packet);
+}
+
 static gboolean jabber_keepalive_timeout(PurpleConnection *gc)
 {
 	JabberStream *js = gc->proto_data;
@@ -767,8 +770,6 @@
 	js->fd = -1;
 	js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
 			g_free, g_free);
-	js->disco_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
-			g_free, g_free);
 	js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal,
 			g_free, (GDestroyNotify)jabber_buddy_free);
 	js->chats = g_hash_table_new_full(g_str_hash, g_str_equal,
@@ -1090,6 +1091,24 @@
 	jabber_iq_send(iq);
 }
 
+static const struct {
+	const char *name;
+	const char *label;
+} registration_fields[] = {
+	{ "email",   N_("Email") },
+	{ "nick",    N_("Nickname") },
+	{ "first",   N_("First name") },
+	{ "last",    N_("Last name") },
+	{ "address", N_("Address") },
+	{ "city",    N_("City") },
+	{ "state",   N_("State") },
+	{ "zip",     N_("Postal code") },
+	{ "phone",   N_("Phone") },
+	{ "url",     N_("URL") },
+	{ "date",    N_("Date") },
+	{ NULL, NULL }
+};
+
 void jabber_register_parse(JabberStream *js, const char *from, JabberIqType type,
                            const char *id, xmlnode *query)
 {
@@ -1097,18 +1116,15 @@
 	PurpleRequestFields *fields;
 	PurpleRequestFieldGroup *group;
 	PurpleRequestField *field;
-	xmlnode *x, *y;
+	xmlnode *x, *y, *node;
 	char *instructions;
 	JabberRegisterCBData *cbdata;
 	gboolean registered = FALSE;
+	int i;
 
 	if (type != JABBER_IQ_RESULT)
 		return;
 
-	if (!from)
-		from = js->serverFQDN;
-	g_return_if_fail(from != NULL);
-
 	if(js->registration) {
 		/* get rid of the login thingy */
 		purple_connection_set_state(js->gc, PURPLE_CONNECTED);
@@ -1157,74 +1173,53 @@
 	group = purple_request_field_group_new(NULL);
 	purple_request_fields_add_group(fields, group);
 
-	if(js->registration)
-		field = purple_request_field_string_new("username", _("Username"), js->user->node, FALSE);
-	else
-		field = purple_request_field_string_new("username", _("Username"), NULL, FALSE);
-
-	purple_request_field_group_add_field(group, field);
-
-	if(js->registration)
-		field = purple_request_field_string_new("password", _("Password"),
-									purple_connection_get_password(js->gc), FALSE);
-	else
-		field = purple_request_field_string_new("password", _("Password"), NULL, FALSE);
-
-	purple_request_field_string_set_masked(field, TRUE);
-	purple_request_field_group_add_field(group, field);
-
-	if(xmlnode_get_child(query, "name")) {
+	if((node = xmlnode_get_child(query, "username"))) {
+		char *data = xmlnode_get_data(node);
+		if(js->registration)
+			field = purple_request_field_string_new("username", _("Username"), data ? data : js->user->node, FALSE);
+		else
+			field = purple_request_field_string_new("username", _("Username"), data, FALSE);
+
+		purple_request_field_group_add_field(group, field);
+		g_free(data);
+	}
+	if((node = xmlnode_get_child(query, "password"))) {
+		if(js->registration)
+			field = purple_request_field_string_new("password", _("Password"),
+										purple_connection_get_password(js->gc), FALSE);
+		else {
+			char *data = xmlnode_get_data(node);
+			field = purple_request_field_string_new("password", _("Password"), data, FALSE);
+			g_free(data);
+		}
+
+		purple_request_field_string_set_masked(field, TRUE);
+		purple_request_field_group_add_field(group, field);
+	}
+
+	if((node = xmlnode_get_child(query, "name"))) {
 		if(js->registration)
 			field = purple_request_field_string_new("name", _("Name"),
 													purple_account_get_alias(js->gc->account), FALSE);
-		else
-			field = purple_request_field_string_new("name", _("Name"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
-	}
-	if(xmlnode_get_child(query, "email")) {
-		field = purple_request_field_string_new("email", _("Email"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
-	}
-	if(xmlnode_get_child(query, "nick")) {
-		field = purple_request_field_string_new("nick", _("Nickname"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
-	}
-	if(xmlnode_get_child(query, "first")) {
-		field = purple_request_field_string_new("first", _("First name"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
-	}
-	if(xmlnode_get_child(query, "last")) {
-		field = purple_request_field_string_new("last", _("Last name"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
-	}
-	if(xmlnode_get_child(query, "address")) {
-		field = purple_request_field_string_new("address", _("Address"), NULL, FALSE);
+		else {
+			char *data = xmlnode_get_data(node);
+			field = purple_request_field_string_new("name", _("Name"), data, FALSE);
+			g_free(data);
+		}
 		purple_request_field_group_add_field(group, field);
 	}
-	if(xmlnode_get_child(query, "city")) {
-		field = purple_request_field_string_new("city", _("City"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
-	}
-	if(xmlnode_get_child(query, "state")) {
-		field = purple_request_field_string_new("state", _("State"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
-	}
-	if(xmlnode_get_child(query, "zip")) {
-		field = purple_request_field_string_new("zip", _("Postal code"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
+
+	for (i = 0; registration_fields[i].name != NULL; ++i) {
+		if ((node = xmlnode_get_child(query, registration_fields[i].name))) {
+			char *data = xmlnode_get_data(node);
+			field = purple_request_field_string_new(registration_fields[i].name,
+			                                        _(registration_fields[i].label),
+			                                        data, FALSE);
+			purple_request_field_group_add_field(group, field);
+			g_free(data);
+		}
 	}
-	if(xmlnode_get_child(query, "phone")) {
-		field = purple_request_field_string_new("phone", _("Phone"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
-	}
-	if(xmlnode_get_child(query, "url")) {
-		field = purple_request_field_string_new("url", _("URL"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
-	}
-	if(xmlnode_get_child(query, "date")) {
-		field = purple_request_field_string_new("date", _("Date"), NULL, FALSE);
-		purple_request_field_group_add_field(group, field);
-	}
+
 	if(registered) {
 		field = purple_request_field_bool_new("unregister", _("Unregister"), FALSE);
 		purple_request_field_group_add_field(group, field);
@@ -1297,8 +1292,6 @@
 	js->registration = TRUE;
 	js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
 			g_free, g_free);
-	js->disco_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
-			g_free, g_free);
 	js->user = jabber_id_new(purple_account_get_username(account));
 	js->next_id = g_random_int();
 	js->old_length = 0;
@@ -1487,8 +1480,6 @@
 
 	if(js->iq_callbacks)
 		g_hash_table_destroy(js->iq_callbacks);
-	if(js->disco_callbacks)
-		g_hash_table_destroy(js->disco_callbacks);
 	if(js->buddies)
 		g_hash_table_destroy(js->buddies);
 	if(js->chats)
@@ -2972,7 +2963,7 @@
 	return (caps & (PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION));
 }
 
-static gboolean
+gboolean
 jabber_video_enabled(JabberStream *js, const char *namespace)
 {
 	PurpleMediaManager *manager = purple_media_manager_get();
@@ -3212,8 +3203,12 @@
 				caps |= PURPLE_MEDIA_CAPS_MODIFY_SESSION |
 						PURPLE_MEDIA_CAPS_CHANGE_DIRECTION;
 		}
-		if (jabber_resource_has_capability(jbr, GOOGLE_VOICE_CAP))
+		if (jabber_resource_has_capability(jbr, GOOGLE_VOICE_CAP)) {
 			caps |= PURPLE_MEDIA_CAPS_AUDIO;
+			if (jabber_resource_has_capability(jbr,
+					GOOGLE_VIDEO_CAP))
+				caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO;
+		}
 		return caps;
 	}
 
@@ -3493,6 +3488,21 @@
 							 purple_marshal_VOID__POINTER,
 							 NULL, 1,
 							 purple_value_new(PURPLE_TYPE_STRING));
+
+	/* Modifying these? Look at libxmpp.c:load_plugin for the signal versions */
+	purple_plugin_ipc_register(plugin, "register_namespace_watcher",
+	                           PURPLE_CALLBACK(jabber_iq_signal_register),
+	                           purple_marshal_VOID__POINTER_POINTER,
+	                           NULL, 2,
+	                           purple_value_new(PURPLE_TYPE_STRING),  /* node */
+	                           purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
+
+	purple_plugin_ipc_register(plugin, "unregister_namespace_watcher",
+	                           PURPLE_CALLBACK(jabber_iq_signal_unregister),
+	                           purple_marshal_VOID__POINTER_POINTER,
+	                           NULL, 2,
+	                           purple_value_new(PURPLE_TYPE_STRING),  /* node */
+	                           purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
 }
 
 void
--- a/libpurple/protocols/jabber/jabber.h	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sat Jun 06 06:21:39 2009 +0000
@@ -44,6 +44,8 @@
 	JABBER_CAP_ADHOC		  = 1 << 12,
 	JABBER_CAP_BLOCKING       = 1 << 13,
 
+	JABBER_CAP_ITEMS          = 1 << 14,
+
 	JABBER_CAP_RETRIEVED      = 1 << 31
 } JabberCapabilities;
 
@@ -53,12 +55,12 @@
 #include <glib.h>
 #include "circbuffer.h"
 #include "connection.h"
+#include "dnsquery.h"
 #include "dnssrv.h"
 #include "media.h"
 #include "mediamanager.h"
 #include "roomlist.h"
 #include "sslconn.h"
-#include "dnsquery.h"
 
 #include "iq.h"
 #include "jutil.h"
@@ -153,7 +155,6 @@
 	GList *user_directories;
 
 	GHashTable *iq_callbacks;
-	GHashTable *disco_callbacks;
 	int next_id;
 
 	GList *bs_proxies;
@@ -301,6 +302,8 @@
 void jabber_process_packet(JabberStream *js, xmlnode **packet);
 void jabber_send(JabberStream *js, xmlnode *data);
 void jabber_send_raw(JabberStream *js, const char *data, int len);
+void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet,
+                           gpointer unused);
 
 void jabber_stream_set_state(JabberStream *js, JabberStreamState state);
 
@@ -366,6 +369,7 @@
 GList *jabber_actions(PurplePlugin *plugin, gpointer context);
 
 gboolean jabber_audio_enabled(JabberStream *js, const char *unused);
+gboolean jabber_video_enabled(JabberStream *js, const char *unused);
 gboolean jabber_initiate_media(PurpleAccount *account, const char *who,
 		PurpleMediaSessionType type);
 PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who);
--- a/libpurple/protocols/jabber/libxmpp.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Sat Jun 06 06:21:39 2009 +0000
@@ -34,6 +34,7 @@
 #include "iq.h"
 #include "jabber.h"
 #include "chat.h"
+#include "disco.h"
 #include "message.h"
 #include "roster.h"
 #include "si.h"
@@ -117,6 +118,7 @@
 	jabber_unregister_account,		/* unregister_user */
 	jabber_send_attention,			/* send_attention */
 	jabber_attention_types,			/* attention_types */
+
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL, /* get_account_text_table */
 	jabber_initiate_media,          /* initiate_media */
@@ -135,6 +137,14 @@
 			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
 			purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
 
+	/*
+	 * Do not remove this or the plugin will fail. Completely. You have been
+	 * warned!
+	 */
+	purple_signal_connect_priority(plugin, "jabber-sending-xmlnode",
+			plugin, PURPLE_CALLBACK(jabber_send_signal_cb),
+			NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
+
 	purple_signal_register(plugin, "jabber-sending-text",
 			     purple_marshal_VOID__POINTER_POINTER, NULL, 2,
 			     purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
@@ -168,6 +178,7 @@
 			purple_value_new(PURPLE_TYPE_STRING), /* from */
 			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); /* child */
 
+	/* Modifying these? Look at jabber_init_plugin for the ipc versions */
 	purple_signal_register(plugin, "jabber-register-namespace-watcher",
 			purple_marshal_VOID__POINTER_POINTER,
 			NULL, 2,
--- a/libpurple/protocols/jabber/message.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/message.c	Sat Jun 06 06:21:39 2009 +0000
@@ -1190,7 +1190,9 @@
 			jm->typing_style |= JM_TS_JEP_0022;
 	}
 
-	purple_markup_html_to_xhtml(msg, &xhtml, &jm->body);
+	tmp = purple_utf8_strip_unprintables(msg);
+	purple_markup_html_to_xhtml(tmp, &xhtml, &jm->body);
+	g_free(tmp);
 	tmp = jabber_message_smileyfy_xhtml(jm, xhtml);
 	if (tmp) {
 		g_free(xhtml);
@@ -1231,7 +1233,9 @@
 	jm->to = g_strdup_printf("%s@%s", chat->room, chat->server);
 	jm->id = jabber_get_next_id(jm->js);
 
+	tmp = purple_utf8_strip_unprintables(msg);
 	purple_markup_html_to_xhtml(msg, &xhtml, &jm->body);
+	g_free(tmp);
 	tmp = jabber_message_smileyfy_xhtml(jm, xhtml);
 	if (tmp) {
 		g_free(xhtml);
--- a/libpurple/protocols/jabber/presence.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sat Jun 06 06:21:39 2009 +0000
@@ -245,6 +245,7 @@
 {
 	xmlnode *show, *status, *presence, *pri, *c;
 	const char *show_string = NULL;
+	gboolean audio_enabled, video_enabled;
 
 	presence = xmlnode_new("presence");
 
@@ -300,9 +301,18 @@
 	 * just assume that if we specify a 'voice-v1' ext (ignoring that
 	 * these are to be assigned no semantic value), we support receiving voice
 	 * calls.
+	 *
+	 * Ditto for 'video-v1'.
 	 */
-	if (jabber_audio_enabled(js, NULL /* unused */))
+	audio_enabled = jabber_audio_enabled(js, NULL /* unused */);
+	video_enabled = jabber_video_enabled(js, NULL /* unused */);
+
+	if (audio_enabled && video_enabled)
+		xmlnode_set_attrib(c, "ext", "voice-v1 video-v1");
+	else if (audio_enabled)
 		xmlnode_set_attrib(c, "ext", "voice-v1");
+	else if (video_enabled)
+		xmlnode_set_attrib(c, "ext", "video-v1");
 #endif
 
 	return presence;
--- a/libpurple/protocols/jabber/roster.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/jabber/roster.c	Sat Jun 06 06:21:39 2009 +0000
@@ -282,9 +282,23 @@
 		return;
 
 	if(grps) {
+		GString *out = g_string_new(NULL);
 		groups = grps;
+
+		for (l = groups; l; l = l->next) {
+			out = g_string_append(out, (const char *)l->data);
+			if (l->next)
+				out = g_string_append(out, ", ");
+		}
+
+		purple_debug_info("jabber", "jabber_roster_update(%s): [Source: grps]: groups: %s\n",
+		                  name, out->str);
+		g_string_free(out, TRUE);
+
 	} else {
 		GSList *buddies = purple_find_buddies(js->gc->account, name);
+		GString *out = g_string_new(NULL);
+
 		if(!buddies)
 			return;
 		while(buddies) {
@@ -293,6 +307,15 @@
 			groups = g_slist_append(groups, (char *)purple_group_get_name(g));
 			buddies = g_slist_remove(buddies, b);
 		}
+		for (l = groups; l; l = l->next) {
+			out = g_string_append(out, (const char *)l->data);
+			if (l->next)
+				out = g_string_append(out, ", ");
+		}
+
+		purple_debug_info("jabber", "jabber_roster_update(%s): [Source: local blist]: groups: %s\n",
+		                  name, out->str);
+		g_string_free(out, TRUE);
 	}
 
 	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster");
@@ -340,6 +363,9 @@
 
 	jb = jabber_buddy_find(js, name, FALSE);
 
+	purple_debug_info("jabber", "jabber_roster_add_buddy(): Adding %s\n",
+	                  name);
+
 	jabber_roster_update(js, who, NULL);
 
 	my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
@@ -369,6 +395,9 @@
 	if(b != NULL) {
 		purple_blist_alias_buddy(b, alias);
 
+		purple_debug_info("jabber", "jabber_roster_alias_change(): Aliased %s to %s\n",
+				name, alias ? alias : "(null)");
+
 		jabber_roster_update(gc->proto_data, name, NULL);
 	}
 }
@@ -395,6 +424,10 @@
 			groups = g_slist_append(groups, (char*)gname);
 		buddies = g_slist_remove(buddies, b);
 	}
+
+	purple_debug_info("jabber", "jabber_roster_group_change(): Moving %s from %s to %s\n",
+	                  name, old_group, new_group);
+
 	jabber_roster_update(gc->proto_data, name, groups);
 	g_slist_free(groups);
 }
@@ -428,6 +461,9 @@
 			buddies = g_slist_remove(buddies, tmpbuddy);
 		}
 
+		purple_debug_info("jabber", "jabber_roster_remove_buddy(): Removing %s from %s\n",
+		                  purple_buddy_get_name(buddy), purple_group_get_name(group));
+
 		jabber_roster_update(gc->proto_data, name, groups);
 		g_slist_free(groups);
 	} else {
@@ -439,6 +475,9 @@
 		xmlnode_set_attrib(item, "jid", name);
 		xmlnode_set_attrib(item, "subscription", "remove");
 
+		purple_debug_info("jabber", "jabber_roster_remove_buddy(): Removing %s\n",
+		                  purple_buddy_get_name(buddy));
+
 		jabber_iq_send(iq);
 	}
 }
--- a/libpurple/protocols/msn/msn.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/msn/msn.c	Sat Jun 06 06:21:39 2009 +0000
@@ -1281,7 +1281,7 @@
 			imdata->gc = gc;
 			imdata->who = who;
 			imdata->msg = body_str;
-			imdata->flags = flags;
+			imdata->flags = flags & ~PURPLE_MESSAGE_SEND;
 			imdata->when = time(NULL);
 			purple_timeout_add(0, msn_send_me_im, imdata);
 		}
--- a/libpurple/protocols/msn/session.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/msn/session.c	Sat Jun 06 06:21:39 2009 +0000
@@ -261,9 +261,9 @@
 static void
 msn_session_sync_users(MsnSession *session)
 {
-	PurpleBlistNode *gnode, *cnode, *bnode;
 	PurpleConnection *gc = purple_account_get_connection(session->account);
 	GList *to_remove = NULL;
+	GSList *buddies;
 
 	g_return_if_fail(gc != NULL);
 
@@ -271,60 +271,36 @@
 	 * being logged in. This no longer happens, so we manually iterate
 	 * over the whole buddy list to identify sync issues.
 	 */
-	for (gnode = purple_blist_get_root(); gnode;
-			gnode = purple_blist_node_get_sibling_next(gnode)) {
-		PurpleGroup *group = (PurpleGroup *)gnode;
-		const char *group_name;
-		if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		group_name = purple_group_get_name(group);
-		for(cnode = purple_blist_node_get_first_child(gnode);
-				cnode;
-				cnode = purple_blist_node_get_sibling_next(cnode)) {
-			if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-			for(bnode = purple_blist_node_get_first_child(cnode);
-					bnode;
-					bnode = purple_blist_node_get_sibling_next(bnode)) {
-				PurpleBuddy *b;
-				if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-				b = (PurpleBuddy *)bnode;
-				if(purple_buddy_get_account(b) == purple_connection_get_account(gc)) {
-					MsnUser *remote_user;
-					gboolean found = FALSE;
-
-					remote_user = msn_userlist_find_user(session->userlist, purple_buddy_get_name(b));
+	for (buddies = purple_find_buddies(session->account, NULL); buddies;
+			buddies = g_slist_delete_link(buddies, buddies)) {
+		PurpleBuddy *buddy = buddies->data;
+		const gchar *buddy_name = purple_buddy_get_name(buddy);
+		const gchar *group_name = purple_group_get_name(purple_buddy_get_group(buddy));
+		MsnUser *remote_user;
+		gboolean found = FALSE;
 
-					if ((remote_user != NULL) && (remote_user->list_op & MSN_LIST_FL_OP))
-					{
-						GList *l;
-
-						for (l = remote_user->group_ids; l != NULL; l = l->next)
-						{
-							const char *name = msn_userlist_find_group_name(remote_user->userlist, l->data);
-							if (name && !g_ascii_strcasecmp(group_name, name))
-							{
-								found = TRUE;
-								break;
-							}
-						}
-					}
+		remote_user = msn_userlist_find_user(session->userlist, buddy_name);
+		if (remote_user && remote_user->list_op & MSN_LIST_FL_OP) {
+			GList *l;
+			for (l = remote_user->group_ids; l; l = l->next) {
+				const char *name = msn_userlist_find_group_name(remote_user->userlist, l->data);
+				if (name && !g_ascii_strcasecmp(group_name, name)) {
+					found = TRUE;
+					break;
+				}
+			}
 
-					/* We don't care if they're in a different group, as long as they're on the
-					 * list somewhere. If we check for the group, we cause pain, agony and
-					 * suffering for people who decide to re-arrange their buddy list elsewhere.
-					 */
-					if (!found)
-					{
-						if ((remote_user == NULL) || !(remote_user->list_op & MSN_LIST_FL_OP)) {
-							/* The user is not on the server list */
-							msn_show_sync_issue(session, purple_buddy_get_name(b), group_name);
-						} else {
-							/* The user is not in that group on the server list */
-							to_remove = g_list_prepend(to_remove, b);
-						}
-					}
+			/* We don't care if they're in a different group, as long as they're on the
+			 * list somewhere. If we check for the group, we cause pain, agony and
+			 * suffering for people who decide to re-arrange their buddy list elsewhere.
+			 */
+			if (!found) {
+				if ((remote_user == NULL) || !(remote_user->list_op & MSN_LIST_FL_OP)) {
+					/* The user is not on the server list */
+					msn_show_sync_issue(session, buddy_name, group_name);
+				} else {
+					/* The user is not in that group on the server list */
+					to_remove = g_list_prepend(to_remove, buddy);
 				}
 			}
 		}
--- a/libpurple/protocols/msn/userlist.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/msn/userlist.c	Sat Jun 06 06:21:39 2009 +0000
@@ -783,7 +783,6 @@
 void
 msn_userlist_load(MsnSession *session)
 {
-	PurpleBlistNode *gnode, *cnode, *bnode;
 	PurpleAccount *account = session->account;
 	PurpleConnection *gc = purple_account_get_connection(account);
 	GSList *l;
@@ -791,34 +790,14 @@
 
 	g_return_if_fail(gc != NULL);
 
-	for (gnode = purple_blist_get_root(); gnode;
-			gnode = purple_blist_node_get_sibling_next(gnode))
-	{
-		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		for (cnode = purple_blist_node_get_first_child(gnode);
-				cnode;
-				cnode = purple_blist_node_get_sibling_next(cnode))
-		{
-			if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-			for (bnode = purple_blist_node_get_first_child(cnode);
-					bnode;
-					bnode = purple_blist_node_get_sibling_next(bnode))
-			{
-				PurpleBuddy *b;
-				if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-				b = (PurpleBuddy *)bnode;
-				if (purple_buddy_get_account(b) == account)
-				{
-					user = msn_userlist_find_add_user(session->userlist,
-						purple_buddy_get_name(b), NULL);
-					purple_buddy_set_protocol_data(b, user);
-					msn_user_set_op(user, MSN_LIST_FL_OP);
-				}
-			}
-		}
+	for (l = purple_find_buddies(account, NULL); l != NULL;
+			l = g_slist_delete_link(l, l)) {
+		PurpleBuddy *buddy = l->data;
+
+		user = msn_userlist_find_add_user(session->userlist,
+			purple_buddy_get_name(buddy), NULL);
+		purple_buddy_set_protocol_data(buddy, user);
+		msn_user_set_op(user, MSN_LIST_FL_OP);
 	}
 	for (l = session->account->permit; l != NULL; l = l->next)
 	{
--- a/libpurple/protocols/msnp9/session.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/msnp9/session.c	Sat Jun 06 06:21:39 2009 +0000
@@ -221,68 +221,51 @@
 static void
 msn_session_sync_users(MsnSession *session)
 {
-	PurpleBlistNode *gnode, *cnode, *bnode;
 	PurpleConnection *gc = purple_account_get_connection(session->account);
 	GList *to_remove = NULL;
+	GSList *buddies;
 
 	g_return_if_fail(gc != NULL);
 
 	/* The core used to use msn_add_buddy to add all buddies before
 	 * being logged in. This no longer happens, so we manually iterate
 	 * over the whole buddy list to identify sync issues. */
+	for (buddies = purple_find_buddies(session->account, NULL); buddies;
+			buddies = g_slist_delete_link(buddies, buddies)) {
+		PurpleBuddy *buddy = buddies->data;
+		const char *buddy_name = purple_buddy_get_name(buddy);
+		const char *group_name = purple_group_get_name(purple_buddy_get_group(buddy));
+		MsnUser *remote_user;
+		gboolean found = FALSE;
 
-	for (gnode = purple_blist_get_root(); gnode; gnode = gnode->next) {
-		PurpleGroup *group = (PurpleGroup *)gnode;
-		const char *group_name = group->name;
-		if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		for(cnode = gnode->child; cnode; cnode = cnode->next) {
-			if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-			for(bnode = cnode->child; bnode; bnode = bnode->next) {
-				PurpleBuddy *b;
-				if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-				b = (PurpleBuddy *)bnode;
-				if(purple_buddy_get_account(b) == purple_connection_get_account(gc)) {
-					MsnUser *remote_user;
-					gboolean found = FALSE;
+		remote_user = msn_userlist_find_user(session->userlist, buddy_name);
 
-					remote_user = msn_userlist_find_user(session->userlist, purple_buddy_get_name(b));
+		if (remote_user && remote_user->list_op & MSN_LIST_FL_OP) {
+			int group_id;
+			GList *l;
 
-					if ((remote_user != NULL) && (remote_user->list_op & MSN_LIST_FL_OP))
-					{
-						int group_id;
-						GList *l;
+			group_id = msn_userlist_find_group_id(remote_user->userlist,
+					group_name);
 
-						group_id = msn_userlist_find_group_id(remote_user->userlist,
-								group_name);
-
-						for (l = remote_user->group_ids; l != NULL; l = l->next)
-						{
-							if (group_id == GPOINTER_TO_INT(l->data))
-							{
-								found = TRUE;
-								break;
-							}
-						}
-
-					}
+			for (l = remote_user->group_ids; l; l = l->next) {
+				if (group_id == GPOINTER_TO_INT(l->data)) {
+					found = TRUE;
+					break;
+				}
+			}
 
-					/* We don't care if they're in a different group, as long as they're on the
-					 * list somewhere. If we check for the group, we cause pain, agony and
-					 * suffering for people who decide to re-arrange their buddy list elsewhere.
-					 */
-					if (!found)
-					{
-						if ((remote_user == NULL) || !(remote_user->list_op & MSN_LIST_FL_OP)) {
-							/* The user is not on the server list */
-							msn_show_sync_issue(session, purple_buddy_get_name(b), group_name);
-						} else {
-							/* The user is not in that group on the server list */
-							to_remove = g_list_prepend(to_remove, b);
-						}
-					}
+			/* We don't care if they're in a different group, as long as they're on the
+			 * list somewhere. If we check for the group, we cause pain, agony and
+			 * suffering for people who decide to re-arrange their buddy list elsewhere.
+			 */
+			if (!found)
+			{
+				if ((remote_user == NULL) || !(remote_user->list_op & MSN_LIST_FL_OP)) {
+					/* The user is not on the server list */
+					msn_show_sync_issue(session, buddy_name, group_name);
+				} else {
+					/* The user is not in that group on the server list */
+					to_remove = g_list_prepend(to_remove, buddy);
 				}
 			}
 		}
--- a/libpurple/protocols/myspace/myspace.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Sat Jun 06 06:21:39 2009 +0000
@@ -1118,10 +1118,6 @@
 	guint buddy_count;
 
 	body = msim_msg_get_dictionary(reply, "body");
-	if (!body) {
-		/* No friends. Not an error. */
-		return;
-	}
 
 	buddy_count = 0;
 
@@ -1477,28 +1473,22 @@
  * @return TRUE if successful.
  */
 static gboolean
-msim_incoming_im(MsimSession *session, MsimMessage *msg)
+msim_incoming_im(MsimSession *session, MsimMessage *msg, const gchar *username)
 {
-	gchar *username, *msg_msim_markup, *msg_purple_markup;
+	gchar *msg_msim_markup, *msg_purple_markup;
 	gchar *userid;
 	time_t time_received;
 	PurpleConversation *conv;
 
-	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-	g_return_val_if_fail(msg != NULL, FALSE);
-
-	username = msim_msg_get_string(msg, "_username");
 	/* I know this isn't really a string... but we need it to be one for
 	 * purple_find_conversation_with_account(). */
 	userid = msim_msg_get_string(msg, "f");
-	g_return_val_if_fail(username != NULL, FALSE);
 
 	purple_debug_info("msim_incoming_im", "UserID is %s", userid);
 
 	if (msim_is_userid(username)) {
 		purple_debug_info("msim", "Ignoring message from spambot (%s) on account %s\n",
 				username, purple_account_get_username(session->account));
-		g_free(username);
 		return FALSE;
 	}
 
@@ -1523,14 +1513,13 @@
 
 	serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received);
 
-	g_free(username);
 	g_free(msg_purple_markup);
 
 	return TRUE;
 }
 
 /**
- * Handle an incoming action message.
+ * Handle an incoming action message or an IM.
  *
  * @param session
  * @param msg
@@ -1538,7 +1527,7 @@
  * @return TRUE if successful.
  */
 static gboolean
-msim_incoming_action(MsimSession *session, MsimMessage *msg)
+msim_incoming_action_or_im(MsimSession *session, MsimMessage *msg)
 {
 	gchar *msg_text, *username;
 	gboolean rc;
@@ -1552,7 +1541,8 @@
 	username = msim_msg_get_string(msg, "_username");
 	g_return_val_if_fail(username != NULL, FALSE);
 
-	purple_debug_info("msim", "msim_incoming_action: action <%s> from <%s>\n",
+	purple_debug_info("msim",
+			"msim_incoming_action_or_im: action <%s> from <%s>\n",
 			msg_text, username);
 
 	if (g_str_equal(msg_text, "%typing%")) {
@@ -1566,13 +1556,16 @@
 	} else if (strstr(msg_text, "!!!GroupCount=")) {
 		/* TODO: support group chats. I think the number in msg_text has
 		 * something to do with the 'gid' field. */
-		purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text);
+		purple_debug_info("msim",
+				"msim_incoming_action_or_im: "
+				"TODO: implement #4691, group chats: %s\n", msg_text);
 
 		rc = TRUE;
 	} else if (strstr(msg_text, "!!!Offline=")) {
 		/* TODO: support group chats. This one might mean a user
 		 * went offline or exited the chat. */
-		purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text);
+		purple_debug_info("msim", "msim_incoming_action_or_im: "
+				"TODO: implement #4691, group chats: %s\n", msg_text);
 
 		rc = TRUE;
 	} else if (msim_msg_get_integer(msg, "aid") != 0) {
@@ -1583,9 +1576,7 @@
 
 		rc = TRUE;
 	} else {
-		msim_unrecognized(session, msg,
-				"got to msim_incoming_action but unrecognized value for 'msg'");
-		rc = FALSE;
+		rc = msim_incoming_im(session, msg, username);
 	}
 
 	g_free(msg_text);
@@ -1670,10 +1661,9 @@
 	switch (bm) {
 		case MSIM_BM_STATUS:
 			return msim_incoming_status(session, msg);
-		case MSIM_BM_INSTANT:
-			return msim_incoming_im(session, msg);
-		case MSIM_BM_ACTION:
-			return msim_incoming_action(session, msg);
+		case MSIM_BM_INSTANT_ACTION_OR_IM:
+		case MSIM_BM_DELAYABLE_ACTION_OR_IM:
+			return msim_incoming_action_or_im(session, msg);
 		case MSIM_BM_MEDIA:
 			return msim_incoming_media(session, msg);
 		case MSIM_BM_UNOFFICIAL_CLIENT:
@@ -1681,7 +1671,8 @@
 		default:
 			/* Not really an IM, but show it for informational
 			 * purposes during development. */
-			return msim_incoming_im(session, msg);
+			/* TODO: This is probably wrong */
+			return msim_incoming_action_or_im(session, msg);
 	}
 }
 
@@ -2294,7 +2285,7 @@
 
 	message_msim = html_to_msim_markup(session, message);
 
-	if (msim_send_bm(session, who, message_msim, MSIM_BM_INSTANT)) {
+	if (msim_send_bm(session, who, message_msim, MSIM_BM_DELAYABLE_ACTION_OR_IM)) {
 		/* Return 1 to have Purple show this IM as being sent, 0 to not. I always
 		 * return 1 even if the message could not be sent, since I don't know if
 		 * it has failed yet--because the IM is only sent after the userid is
@@ -2347,7 +2338,7 @@
 	}
 
 	purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str);
-	msim_send_bm(session, name, typing_str, MSIM_BM_ACTION);
+	msim_send_bm(session, name, typing_str, MSIM_BM_INSTANT_ACTION_OR_IM);
 	return 0;
 }
 
--- a/libpurple/protocols/myspace/myspace.h	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/myspace/myspace.h	Sat Jun 06 06:21:39 2009 +0000
@@ -127,12 +127,12 @@
 #define MSIM_FINAL_STRING           "\\final\\" /**< Message end marker */
 
 /* Messages */
-#define MSIM_BM_INSTANT             1
-#define MSIM_BM_STATUS              100
-#define MSIM_BM_ACTION              121
-#define MSIM_BM_MEDIA               122
-#define MSIM_BM_PROFILE             124
-#define MSIM_BM_UNOFFICIAL_CLIENT   200
+#define MSIM_BM_DELAYABLE_ACTION_OR_IM  1
+#define MSIM_BM_STATUS                  100
+#define MSIM_BM_INSTANT_ACTION_OR_IM    121
+#define MSIM_BM_MEDIA                   122
+#define MSIM_BM_PROFILE                 124
+#define MSIM_BM_UNOFFICIAL_CLIENT       200
 
 /* Authentication algorithm for login2 */
 #define MSIM_AUTH_ALGORITHM         196610
--- a/libpurple/protocols/myspace/zap.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/myspace/zap.c	Sat Jun 06 06:21:39 2009 +0000
@@ -109,7 +109,7 @@
 	/* Construct and send the actual zap command. */
 	zap_string = g_strdup_printf("!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", code);
 
-	if (!msim_send_bm(session, username, zap_string, MSIM_BM_ACTION)) {
+	if (!msim_send_bm(session, username, zap_string, MSIM_BM_INSTANT_ACTION_OR_IM)) {
 		purple_debug_info("msim_send_zap",
 				"msim_send_bm failed: zapping %s with %s\n",
 				username, zap_string);
--- a/libpurple/protocols/oscar/oscar.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Sat Jun 06 06:21:39 2009 +0000
@@ -6541,47 +6541,35 @@
 {
 	PurpleConnection *gc = (PurpleConnection *) action->context;
 	OscarData *od = purple_connection_get_protocol_data(gc);
-	gchar *nombre, *text, *tmp;
-	PurpleBlistNode *gnode, *cnode, *bnode;
+	gchar *text, *tmp;
+	GSList *buddies;
 	PurpleAccount *account;
 	int num=0;
 
 	text = g_strdup("");
 	account = purple_connection_get_account(gc);
 
-	for (gnode = purple_blist_get_root(); gnode;
-			gnode = purple_blist_node_get_sibling_next(gnode)) {
-		PurpleGroup *group = (PurpleGroup *)gnode;
-		const char *gname;
-		if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		gname = purple_group_get_name(group);
-		for (cnode = purple_blist_node_get_first_child(gnode);
-				cnode;
-				cnode = purple_blist_node_get_sibling_next(cnode)) {
-			if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-			for (bnode = purple_blist_node_get_first_child(cnode);
-					bnode;
-					bnode = purple_blist_node_get_sibling_next(bnode)) {
-				PurpleBuddy *buddy = (PurpleBuddy *)bnode;
-				const char *bname;
-				if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-				bname = purple_buddy_get_name(buddy);
-				if (purple_buddy_get_account(buddy) == account && aim_ssi_waitingforauth(od->ssi.local, gname, bname)) {
-					if (purple_buddy_get_alias_only(buddy))
-						nombre = g_strdup_printf(" %s (%s)", bname, purple_buddy_get_alias_only(buddy));
-					else
-						nombre = g_strdup_printf(" %s", bname);
-					tmp = g_strdup_printf("%s%s<br>", text, nombre);
-					g_free(text);
-					text = tmp;
-					g_free(nombre);
-					num++;
-				}
-			}
+	buddies = purple_find_buddies(account, NULL);
+	while (buddies) {
+		PurpleBuddy *buddy;
+		const gchar *bname, *gname;
+
+		buddy = buddies->data;
+		bname = purple_buddy_get_name(buddy);
+		gname = purple_group_get_name(purple_buddy_get_group(buddy));
+		if (aim_ssi_waitingforauth(od->ssi.local, gname, bname)) {
+			const gchar *alias = purple_buddy_get_alias_only(buddy);
+			if (alias)
+				tmp = g_strdup_printf("%s %s (%s)<br>", text, bname, alias);
+			else
+				tmp = g_strdup_printf("%s %s<br>", text, bname);
+			g_free(text);
+			text = tmp;
+
+			num++;
 		}
+
+		buddies = g_slist_delete_link(buddies, buddies);
 	}
 
 	if (!num) {
--- a/libpurple/protocols/silc/buddy.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/silc/buddy.c	Sat Jun 06 06:21:39 2009 +0000
@@ -1407,35 +1407,16 @@
 
 void silcpurple_send_buddylist(PurpleConnection *gc)
 {
-	PurpleBlistNode *gnode, *cnode, *bnode;
-	PurpleBuddy *buddy;
+	GSList *buddies;
 	PurpleAccount *account;
 
 	account = purple_connection_get_account(gc);
 
-	for (gnode = purple_blist_get_root();
-			gnode != NULL;
-			gnode = purple_blist_node_get_sibling_next(gnode))
+	for (buddies = purple_find_buddies(account, NULL); buddies;
+			buddies = g_slist_delete_link(buddies, buddies))
 	{
-		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		for (cnode = purple_blist_node_get_first_child(gnode);
-				cnode != NULL;
-				cnode = purple_blist_node_get_sibling_next(cnode))
-		{
-			if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-			for (bnode = purple_blist_node_get_first_child(cnode);
-					bnode != NULL;
-					bnode = purple_blist_node_get_sibling_next(bnode))
-			{
-				if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-				buddy = (PurpleBuddy *)bnode;
-				if (purple_buddy_get_account(buddy) == account)
-					silcpurple_add_buddy_i(gc, buddy, TRUE);
-			}
-		}
+		PurpleBuddy *buddy = buddies->data;
+		silcpurple_add_buddy_i(gc, buddy, TRUE);
 	}
 }
 
--- a/libpurple/protocols/silc/ops.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/silc/ops.c	Sat Jun 06 06:21:39 2009 +0000
@@ -839,7 +839,7 @@
 
 			b = NULL;
 			if (public_key) {
-				PurpleBlistNode *gnode, *cnode, *bnode;
+				GSList *buddies;
 				const char *f;
 
 				pk = silc_pkcs_public_key_encode(public_key, &pk_len);
@@ -857,29 +857,13 @@
 				silc_free(pk);
 
 				/* Find buddy by associated public key */
-				for (gnode = purple_blist_get_root(); gnode;
-				     gnode = purple_blist_node_get_sibling_next(gnode)) {
-					if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-						continue;
-					for (cnode = purple_blist_node_get_first_child(gnode);
-							cnode;
-							cnode = purple_blist_node_get_sibling_next(cnode)) {
-						if( !PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-							continue;
-						for (bnode = purple_blist_node_get_first_child(cnode);
-								bnode;
-								bnode = purple_blist_node_get_sibling_next(bnode)) {
-							if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-								continue;
-							b = (PurpleBuddy *)bnode;
-							if (purple_buddy_get_account(b) != account)
-								continue;
-							f = purple_blist_node_get_string(bnode, "public-key");
-							if (f && !strcmp(f, buf))
-								goto cont;
-							b = NULL;
-						}
-					}
+				for (buddies = purple_find_buddies(account, NULL); buddies;
+						buddies = g_slist_delete_link(buddies, buddies)) {
+					b = buddies->data;
+					f = purple_blist_node_get_string(PURPLE_BLIST_NODE(b), "public-key");
+					if (purple_strequal(f, buf))
+						goto cont;
+					b = NULL;
 				}
 			}
 		cont:
--- a/libpurple/protocols/simple/simple.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/simple/simple.c	Sat Jun 06 06:21:39 2009 +0000
@@ -213,27 +213,18 @@
 }
 
 static void simple_get_buddies(PurpleConnection *gc) {
-	PurpleBlistNode *gnode, *cnode, *bnode;
+	GSList *buddies;
 	PurpleAccount *account;
 
 	purple_debug_info("simple", "simple_get_buddies\n");
 
 	account = purple_connection_get_account(gc);
-	for(gnode = purple_blist_get_root(); gnode;
-			gnode = purple_blist_node_get_sibling_next(gnode)) {
-		if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) continue;
-		for(cnode = purple_blist_node_get_first_child(gnode);
-				cnode;
-				cnode = purple_blist_node_get_sibling_next(cnode)) {
-			if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) continue;
-			for(bnode = purple_blist_node_get_first_child(cnode);
-					bnode;
-					bnode = purple_blist_node_get_sibling_next(bnode)) {
-				if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) continue;
-				if(purple_buddy_get_account((PurpleBuddy*)bnode) == account)
-					simple_add_buddy(gc, (PurpleBuddy*)bnode, (PurpleGroup *)gnode);
-			}
-		}
+	buddies = purple_find_buddies(account, NULL);
+	while (buddies) {
+		PurpleBuddy *buddy = buddies->data;
+		simple_add_buddy(gc, buddy, purple_buddy_get_group(buddy));
+
+		buddies = g_slist_delete_link(buddies, buddies);
 	}
 }
 
--- a/libpurple/protocols/yahoo/yahoo.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Sat Jun 06 06:21:39 2009 +0000
@@ -147,7 +147,6 @@
 static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
 {
 	PurpleAccount *account = purple_connection_get_account(gc);
-	struct yahoo_data *yd = gc->proto_data;
 	GSList *l = pkt->hash;
 	YahooFriend *f = NULL;
 	char *name = NULL;
@@ -168,29 +167,7 @@
 
 		switch (pair->key) {
 		case 0: /* we won't actually do anything with this */
-			break;
-		case 1: /* we don't get the full buddy list here. */
-			if (!yd->logged_in) {
-				purple_connection_set_display_name(gc, pair->value);
-				purple_connection_set_state(gc, PURPLE_CONNECTED);
-				yd->logged_in = TRUE;
-				if (yd->picture_upload_todo) {
-					yahoo_buddy_icon_upload(gc, yd->picture_upload_todo);
-					yd->picture_upload_todo = NULL;
-				}
-				yahoo_set_status(account, purple_account_get_active_status(account));
-
-				/* this requests the list. i have a feeling that this is very evil
-				 *
-				 * scs.yahoo.com sends you the list before this packet without  it being
-				 * requested
-				 *
-				 * do_import(gc, NULL);
-				 * newpkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YAHOO_STATUS_OFFLINE, 0);
-				 * yahoo_packet_send_and_free(newpkt, yd);
-				 */
-
-				}
+		case 1: /* we won't actually do anything with this */
 			break;
 		case 8: /* how many online buddies we have */
 			break;
@@ -577,6 +554,18 @@
 	}
 
 	g_hash_table_foreach(ht, yahoo_do_group_cleanup, NULL);
+	
+	/* Now that we have processed the buddy list, we can say yahoo has connected */
+	purple_connection_set_display_name(gc, purple_normalize(account, purple_account_get_username(account)));
+	purple_connection_set_state(gc, PURPLE_CONNECTED);
+	yd->logged_in = TRUE;
+	if (yd->picture_upload_todo) {
+		yahoo_buddy_icon_upload(gc, yd->picture_upload_todo);
+		yd->picture_upload_todo = NULL;
+	}
+	yahoo_set_status(account, purple_account_get_active_status(account));
+	purple_debug_info("yahoo","Authentication: Connection established\n");
+
 	g_hash_table_destroy(ht);
 	g_free(norm_bud);
 	g_free(temp);
@@ -1584,7 +1573,8 @@
 
 	to_y64(base64_string, md5_digest, 16);
 
-	pkt = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, YAHOO_STATUS_WEBLOGIN, yd->session_id);
+	purple_debug_info("yahoo", "yahoo status: %d\n", yd->current_status);
+	pkt = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->current_status, yd->session_id);
 	if(yd->jp) {
 		yahoo_packet_hash(pkt, "ssssssss",
 					1, name,
--- a/libpurple/protocols/zephyr/zephyr.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/protocols/zephyr/zephyr.c	Sat Jun 06 06:21:39 2009 +0000
@@ -1254,45 +1254,32 @@
 
 #ifdef WIN32
 
-static gint check_loc(gpointer_data)
+static gint check_loc(gpointer data)
 {
-	PurpleBlistNode *gnode, *cnode, *bnode;
+	GSList *buddies;
 	ZLocations_t locations;
+	PurpleConnection *gc = data;
+	zephyr_account *zephyr = gc->proto_data;
+	PurpleAccount *account = purple_connection_get_account(gc);
 	int numlocs;
 	int one = 1;
 
-	for (gnode = purple_blist_get_root(); gnode;
-			gnode = purple_blist_node_get_sibling_next(gnode)) {
-		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		for (cnode = purple_blist_node_get_first_child(gnode);
-				cnode;
-				cnode = purple_blist_node_get_sibling_next(cnode)) {
-			if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-			for (bnode = purple_blist_node_get_first_child(cnode);
-					bnode;
-					bnode = purple_blist_node_get_sibling_next(bnode)) {
-				PurpleBuddy *b = (PurpleBuddy *) bnode;
-
-				if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-				if (purple_buddy_get_account(b)->gc == zgc) {
-					char *chk;
-					const char *bname = purple_buddy_get_name(b);
-					chk = local_zephyr_normalize(bname);
-					ZLocateUser(chk,&numlocs, ZAUTH);
-					if (numlocs) {
-						int i;
-						for(i=0;i<numlocs;i++) {
-							ZGetLocations(&locations,&one);
-							serv_got_update(zgc,bname,1,0,0,0,0);
-						}
-					}
-				}
+	for (buddies = purple_find_buddies(account, NULL); buddies;
+			buddies = g_slist_delete_link(buddies, buddies)) {
+		PurpleBuddy *b = buddies->data;
+		char *chk;
+		const char *bname = purple_buddy_get_name(b);
+		chk = local_zephyr_normalize(bname);
+		ZLocateUser(chk,&numlocs, ZAUTH);
+		if (numlocs) {
+			int i;
+			for(i=0;i<numlocs;i++) {
+				ZGetLocations(&locations,&one);
+				serv_got_update(zgc,bname,1,0,0,0,0);
 			}
 		}
 	}
+
 	return TRUE;
 }
 
@@ -1300,7 +1287,7 @@
 
 static gint check_loc(gpointer data)
 {
-	PurpleBlistNode *gnode, *cnode, *bnode;
+	GSList *buddies;
 	ZAsyncLocateData_t ald;
 	PurpleConnection *gc = (PurpleConnection *)data;
 	zephyr_account *zephyr = gc->proto_data;
@@ -1312,65 +1299,49 @@
 		ald.version = NULL;
 	}
 
-	for (gnode = purple_blist_get_root(); gnode;
-			gnode = purple_blist_node_get_sibling_next(gnode)) {
-		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		for (cnode = purple_blist_node_get_first_child(gnode);
-				cnode;
-				cnode = purple_blist_node_get_sibling_next(cnode)) {
-			if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-			for (bnode = purple_blist_node_get_first_child(cnode);
-					bnode;
-					bnode = purple_blist_node_get_sibling_next(bnode)) {
-				PurpleBuddy *b = (PurpleBuddy *) bnode;
+	for (buddies = purple_find_buddies(account, NULL); buddies;
+			buddies = g_slist_delete_link(buddies, buddies)) {
+		PurpleBuddy *b = buddies->data;
 
-				if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-				if (purple_buddy_get_account(b) == account) {
-					const char *chk;
-					const char *name = purple_buddy_get_name(b);
+		const char *chk;
+		const char *name = purple_buddy_get_name(b);
 
-					chk = local_zephyr_normalize(zephyr,name);
-					purple_debug_info("zephyr","chk: %s b->name %s\n",chk,name);
-					/* XXX add real error reporting */
-					/* doesn't matter if this fails or not; we'll just move on to the next one */
-					if (use_zeph02(zephyr)) {
+		chk = local_zephyr_normalize(zephyr,name);
+		purple_debug_info("zephyr","chk: %s b->name %s\n",chk,name);
+		/* XXX add real error reporting */
+		/* doesn't matter if this fails or not; we'll just move on to the next one */
+		if (use_zeph02(zephyr)) {
 #ifdef WIN32
-						int numlocs;
-						int one=1;
-						ZLocateUser(chk,&numlocs,ZAUTH);
-						if (numlocs) {
-							int i;
-							for(i=0;i<numlocs;i++) {
-								ZGetLocations(&locations,&one);
-								if (nlocs>0) 
-									purple_prpl_got_user_status(account,name,"available",NULL);
-								else 
-									purple_prpl_got_user_status(account,name,"offline",NULL);
-							}
-						}
-#else
-						ZRequestLocations(chk, &ald, UNACKED, ZAUTH);
-						g_free(ald.user);
-						g_free(ald.version);
-#endif /* WIN32 */
-					} else 
-						if (use_tzc(zephyr)) {
-							gchar *zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n",chk);
-							size_t len = strlen(zlocstr);
-							size_t result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,len);
-							if (result != len) {
-								purple_debug_error("zephyr", "Unable to write a message: %s\n", g_strerror(errno));
-							}
-							g_free(zlocstr);
-						}
+			int numlocs;
+			int one=1;
+			ZLocateUser(chk,&numlocs,ZAUTH);
+			if (numlocs) {
+				int i;
+				for(i=0;i<numlocs;i++) {
+					ZGetLocations(&locations,&one);
+					if (nlocs>0) 
+						purple_prpl_got_user_status(account,name,"available",NULL);
+					else 
+						purple_prpl_got_user_status(account,name,"offline",NULL);
 				}
 			}
-		}
+#else
+			ZRequestLocations(chk, &ald, UNACKED, ZAUTH);
+			g_free(ald.user);
+			g_free(ald.version);
+#endif /* WIN32 */
+		} else 
+			if (use_tzc(zephyr)) {
+				gchar *zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n",chk);
+				size_t len = strlen(zlocstr);
+				size_t result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,len);
+				if (result != len) {
+					purple_debug_error("zephyr", "Unable to write a message: %s\n", g_strerror(errno));
+				}
+				g_free(zlocstr);
+			}
 	}
-	
+
 	return TRUE;
 }
 
@@ -1955,8 +1926,7 @@
 
 static void write_anyone(PurpleConnection *gc)
 {
-	PurpleBlistNode *gnode, *cnode, *bnode;
-	PurpleBuddy *b;
+	GSList *buddies;
 	char *fname;
 	FILE *fd;
 	PurpleAccount *account;
@@ -1969,29 +1939,12 @@
 	}
 
 	account = purple_connection_get_account(gc);
-	for (gnode = purple_blist_get_root();
-			gnode;
-			gnode = purple_blist_node_get_sibling_next(gnode)) {
-		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		for (cnode = purple_blist_node_get_first_child(gnode);
-				cnode;
-				cnode = purple_blist_node_get_sibling_next(cnode)) {
-			if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-			for (bnode = purple_blist_node_get_first_child(cnode);
-					bnode;
-					bnode = purple_blist_node_get_sibling_next(bnode)) {
-				if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-				b = (PurpleBuddy *) bnode;
-				if (purple_buddy_get_account(b) == account) {
-					gchar *stripped_user = zephyr_strip_local_realm(zephyr, purple_buddy_get_name(b));
-					fprintf(fd, "%s\n", stripped_user);
-					g_free(stripped_user);
-				}
-			}
-		}
+	for (buddies = purple_find_buddies(account, NULL); buddies;
+			buddies = g_slist_delete_link(buddies, buddies)) {
+		PurpleBuddy *b = buddies->data;
+		gchar *stripped_user = zephyr_strip_local_realm(zephyr, purple_buddy_get_name(b));
+		fprintf(fd, "%s\n", stripped_user);
+		g_free(stripped_user);
 	}
 
 	fclose(fd);
--- a/libpurple/proxy.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/proxy.c	Sat Jun 06 06:21:39 2009 +0000
@@ -47,6 +47,7 @@
 	gchar *host;
 	int port;
 	int fd;
+	int socket_type;
 	guint inpa;
 	PurpleProxyInfo *gpi;
 	PurpleDnsQueryData *query_data;
@@ -676,6 +677,68 @@
 }
 
 static void
+proxy_connect_udp_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
+{
+	int flags;
+
+	purple_debug_info("proxy", "UDP Connecting to %s:%d with no proxy\n",
+			connect_data->host, connect_data->port);
+
+	connect_data->fd = socket(addr->sa_family, SOCK_DGRAM, 0);
+	if (connect_data->fd < 0)
+	{
+		purple_proxy_connect_data_disconnect_formatted(connect_data,
+				_("Unable to create socket:\n%s"), g_strerror(errno));
+		return;
+	}
+
+	flags = fcntl(connect_data->fd, F_GETFL);
+	fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
+#ifndef _WIN32
+	fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
+#endif
+
+	if (connect(connect_data->fd, addr, addrlen) != 0)
+	{
+		if ((errno == EINPROGRESS) || (errno == EINTR))
+		{
+			purple_debug_info("proxy", "UDP Connection in progress\n");
+			connect_data->inpa = purple_input_add(connect_data->fd,
+					PURPLE_INPUT_WRITE, socket_ready_cb, connect_data);
+		}
+		else
+		{
+			purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
+		}
+	}
+	else
+	{
+		/*
+		 * The connection happened IMMEDIATELY... strange, but whatever.
+		 */
+		int error = ETIMEDOUT;
+		int ret;
+
+		purple_debug_info("proxy", "UDP Connected immediately.\n");
+
+		ret = purple_input_get_error(connect_data->fd, &error);
+		if ((ret != 0) || (error != 0))
+		{
+			if (ret != 0)
+				error = errno;
+			purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
+			return;
+		}
+
+		/*
+		 * We want to call the "connected" callback eventually, but we
+		 * don't want to call it before we return, just in case.
+		 */
+		purple_timeout_add(10, clean_connect, connect_data);
+	}
+}
+
+static void
 proxy_connect_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
 {
 	int flags;
@@ -2042,6 +2105,12 @@
 #endif
 	purple_debug_info("proxy", "Attempting connection to %s\n", ipaddr);
 
+	if (connect_data->socket_type == SOCK_DGRAM) {
+		proxy_connect_udp_none(connect_data, addr, addrlen);
+		g_free(addr);
+		return;
+	}
+
 	switch (purple_proxy_info_get_type(connect_data->gpi)) {
 		case PURPLE_PROXY_NONE:
 			proxy_connect_none(connect_data, addr, addrlen);
@@ -2193,6 +2262,7 @@
 
 	connect_data = g_new0(PurpleProxyConnectData, 1);
 	connect_data->fd = -1;
+	connect_data->socket_type = SOCK_STREAM;
 	connect_data->handle = handle;
 	connect_data->connect_cb = connect_cb;
 	connect_data->data = data;
@@ -2243,6 +2313,71 @@
 	return connect_data;
 }
 
+PurpleProxyConnectData *
+purple_proxy_connect_udp(void *handle, PurpleAccount *account,
+				   const char *host, int port,
+				   PurpleProxyConnectFunction connect_cb, gpointer data)
+{
+	const char *connecthost = host;
+	int connectport = port;
+	PurpleProxyConnectData *connect_data;
+
+	g_return_val_if_fail(host       != NULL, NULL);
+	g_return_val_if_fail(port       >  0,    NULL);
+	g_return_val_if_fail(connect_cb != NULL, NULL);
+
+	connect_data = g_new0(PurpleProxyConnectData, 1);
+	connect_data->fd = -1;
+	connect_data->socket_type = SOCK_DGRAM;
+	connect_data->handle = handle;
+	connect_data->connect_cb = connect_cb;
+	connect_data->data = data;
+	connect_data->host = g_strdup(host);
+	connect_data->port = port;
+	connect_data->gpi = purple_proxy_get_setup(account);
+
+	if ((purple_proxy_info_get_type(connect_data->gpi) != PURPLE_PROXY_NONE) &&
+		(purple_proxy_info_get_host(connect_data->gpi) == NULL ||
+		 purple_proxy_info_get_port(connect_data->gpi) <= 0)) {
+
+		purple_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid."));
+		purple_proxy_connect_data_destroy(connect_data);
+		return NULL;
+	}
+
+	switch (purple_proxy_info_get_type(connect_data->gpi))
+	{
+		case PURPLE_PROXY_NONE:
+			break;
+
+		case PURPLE_PROXY_HTTP:
+		case PURPLE_PROXY_SOCKS4:
+		case PURPLE_PROXY_SOCKS5:
+		case PURPLE_PROXY_USE_ENVVAR:
+			purple_debug_info("proxy", "Ignoring Proxy type (%d) for UDP.\n",
+			                  purple_proxy_info_get_type(connect_data->gpi));
+			break;
+
+		default:
+			purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n",
+			                   purple_proxy_info_get_type(connect_data->gpi));
+			purple_proxy_connect_data_destroy(connect_data);
+			return NULL;
+	}
+
+	connect_data->query_data = purple_dnsquery_a(connecthost,
+			connectport, connection_host_resolved, connect_data);
+	if (connect_data->query_data == NULL)
+	{
+		purple_proxy_connect_data_destroy(connect_data);
+		return NULL;
+	}
+
+	handles = g_slist_prepend(handles, connect_data);
+
+	return connect_data;
+}
+
 /*
  * Combine some of this code with purple_proxy_connect()
  */
@@ -2260,6 +2395,7 @@
 
 	connect_data = g_new0(PurpleProxyConnectData, 1);
 	connect_data->fd = -1;
+	connect_data->socket_type = SOCK_STREAM;
 	connect_data->handle = handle;
 	connect_data->connect_cb = connect_cb;
 	connect_data->data = data;
--- a/libpurple/proxy.h	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/proxy.h	Sat Jun 06 06:21:39 2009 +0000
@@ -257,6 +257,35 @@
 			PurpleProxyConnectFunction connect_cb, gpointer data);
 
 /**
+ * Makes a connection to the specified host and port.  Note that this
+ * function name can be misleading--although it is called "proxy
+ * connect," it is used for establishing any outgoing UDP connection,
+ * whether through a proxy or not.
+ *
+ * @param handle     A handle that should be associated with this
+ *                   connection attempt.  The handle can be used
+ *                   to cancel the connection attempt using the
+ *                   purple_proxy_connect_cancel_with_handle()
+ *                   function.
+ * @param account    The account making the connection.
+ * @param host       The destination host.
+ * @param port       The destination port.
+ * @param connect_cb The function to call when the connection is
+ *                   established.  If the connection failed then
+ *                   fd will be -1 and error message will be set
+ *                   to something descriptive (hopefully).
+ * @param data       User-defined data.
+ *
+ * @return NULL if there was an error, or a reference to an
+ *         opaque data structure that can be used to cancel
+ *         the pending connection, if needed.
+ */
+PurpleProxyConnectData *purple_proxy_connect_udp(void *handle,
+			PurpleAccount *account,
+			const char *host, int port,
+			PurpleProxyConnectFunction connect_cb, gpointer data);
+
+/**
  * Makes a connection through a SOCKS5 proxy.
  *
  * @param handle     A handle that should be associated with this
--- a/libpurple/server.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/server.c	Sat Jun 06 06:21:39 2009 +0000
@@ -728,7 +728,6 @@
 		im = PURPLE_CONV_IM(conv);
 
 		purple_conv_im_set_typing_state(im, state);
-		purple_conv_im_update_typing(im);
 	} else {
 		switch (state)
 		{
@@ -766,7 +765,6 @@
 
 		purple_conv_im_stop_typing_timeout(im);
 		purple_conv_im_set_typing_state(im, PURPLE_NOT_TYPING);
-		purple_conv_im_update_typing(im);
 	}
 	else
 	{
--- a/libpurple/util.c	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/util.c	Sat Jun 06 06:21:39 2009 +0000
@@ -1041,6 +1041,35 @@
 	return ret;
 }
 
+gboolean purple_markup_is_rtl(const char *html)
+{
+	GData *attributes;
+	const gchar *start, *end;
+	gboolean res = FALSE;
+
+	if (purple_markup_find_tag("span", html, &start, &end, &attributes))
+	{
+		/* tmp is a member of attributes and is free with g_datalist_clear call */
+		const char *tmp = g_datalist_get_data(&attributes, "dir");
+		if (tmp && !g_ascii_strcasecmp(tmp, "RTL"))
+			res = TRUE;
+		if (!res)
+		{
+			tmp = g_datalist_get_data(&attributes, "style");
+			if (tmp)
+			{
+				char *tmp2 = purple_markup_get_css_property(tmp, "direction");
+				if (tmp2 && !g_ascii_strcasecmp(tmp2, "RTL"))
+					res = TRUE;
+				g_free(tmp2);
+			}
+
+		}
+		g_datalist_clear(&attributes);
+	}
+	return res;
+}
+
 gboolean
 purple_markup_find_tag(const char *needle, const char *haystack,
 					 const char **start, const char **end, GData **attributes)
@@ -4395,6 +4424,37 @@
 	return g_string_free(workstr, FALSE);
 }
 
+gchar *
+purple_utf8_strip_unprintables(const gchar *str)
+{
+	gchar *workstr, *iter;
+
+	if (str == NULL)
+		/* Act like g_strdup */
+		return NULL;
+
+	g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
+
+	workstr = iter = g_new(gchar, strlen(str) + 1);
+	while (*str) {
+		gunichar c = g_utf8_get_char(str);
+		const gchar *next = g_utf8_next_char(str);
+		size_t len = next - str;
+
+		if (g_unichar_isprint(c)) {
+			memcpy(iter, str, len);
+			iter += len;
+		}
+
+		str = next;
+	}
+
+	/* nul-terminate the new string */
+	*iter = '\0';
+
+	return workstr;
+}
+
 /*
  * This function is copied from g_strerror() but changed to use
  * gai_strerror().
--- a/libpurple/util.h	Sun May 31 18:39:19 2009 +0000
+++ b/libpurple/util.h	Sat Jun 06 06:21:39 2009 +0000
@@ -506,8 +506,6 @@
  *
  * @return The text with HTML entities literalized.  You must g_free
  *         this string when finished with it.
- *
- * @see purple_escape_html
  */
 char *purple_unescape_html(const char *html);
 
@@ -581,6 +579,16 @@
  */
 char * purple_markup_get_css_property(const gchar *style, const gchar *opt);
 
+/**
+ * Check if the given HTML contains RTL text.
+ *
+ * @param html  The HTML text.
+ *
+ * @return  TRUE if the text contains RTL text, FALSE otherwise.
+ *
+ * @since 2.6.0
+ */
+gboolean purple_markup_is_rtl(const char *html);
 
 /*@}*/
 
@@ -1240,6 +1248,22 @@
 gchar *purple_utf8_salvage(const char *str);
 
 /**
+ * Removes unprintable characters from a UTF-8 string. These characters
+ * (in particular low-ASCII characters) are invalid in XML 1.0 and thus
+ * are not allowed in XMPP and are rejected by libxml2 by default. This
+ * function uses g_unichar_isprint to determine what characters should
+ * be stripped. The returned string must be freed by the caller.
+ *
+ * @param str A valid UTF-8 string.
+ *
+ * @return A newly allocated UTF-8 string without the unprintable characters.
+ * @since 2.6.0
+ *
+ * @see g_unichar_isprint
+ */
+gchar *purple_utf8_strip_unprintables(const gchar *str);
+
+/**
  * Return the UTF-8 version of gai_strerror().  It calls gai_strerror()
  * then converts the result to UTF-8.  This function is analogous to
  * g_strerror().
--- a/pidgin/gtkblist.c	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/gtkblist.c	Sat Jun 06 06:21:39 2009 +0000
@@ -1611,8 +1611,9 @@
 {
 	PurpleBlistNode *node;
 	GValue val;
-	GtkTreeIter iter;
+	GtkTreeIter iter, parent;
 	GtkTreeSelection *sel;
+	GtkTreePath *path;
 
 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
 	if(!gtk_tree_selection_get_selected(sel, NULL, &iter))
@@ -1636,8 +1637,62 @@
 		}
 		if(buddy)
 			pidgin_retrieve_user_info(buddy->account->gc, buddy->name);
-	} else if (event->keyval == GDK_F2) {
-		gtk_blist_menu_alias_cb(tv, node);
+	} else {
+		switch (event->keyval) {
+			case GDK_F2:
+				gtk_blist_menu_alias_cb(tv, node);
+				break;
+
+			case GDK_Left:
+				path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
+				if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
+					/* Collapse the Group */
+					gtk_tree_view_collapse_row(GTK_TREE_VIEW(tv), path);
+					gtk_tree_path_free(path);
+					return TRUE;
+				} else {
+					/* Select the Parent */
+					if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path)) {
+						if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(gtkblist->treemodel), &parent, &iter)) {
+							gtk_tree_path_free(path);
+							path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent);
+							gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
+							gtk_tree_path_free(path);
+							return TRUE;
+						}
+					}
+				}
+				gtk_tree_path_free(path);
+				break;
+
+			case GDK_Right:
+				path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
+				if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
+					/* Expand the Group */
+					if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+						pidgin_blist_expand_contact_cb(NULL, node);
+						gtk_tree_path_free(path);
+						return TRUE;
+					} else if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+						gtk_tree_view_expand_row(GTK_TREE_VIEW(tv), path, FALSE);
+						gtk_tree_path_free(path);
+						return TRUE;
+					}
+				} else {
+					/* Select the First Child */
+					if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &parent, path)) {
+						if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent, 0)) {
+							gtk_tree_path_free(path);
+							path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter);
+							gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE);
+							gtk_tree_path_free(path);
+							return TRUE;
+						}
+					}
+				}
+				gtk_tree_path_free(path);
+				break;
+		}
 	}
 
 	return FALSE;
@@ -5556,9 +5611,12 @@
 	gtkblist = PIDGIN_BLIST(list);
 	priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
 
+	if (priv->current_theme)
+		g_object_unref(priv->current_theme);
+
 	theme_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/theme");
 	if (theme_name && *theme_name)
-		priv->current_theme = PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name, "blist"));
+		priv->current_theme = g_object_ref(PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name, "blist")));
 	else
 		priv->current_theme = NULL;
 
@@ -6164,10 +6222,9 @@
 
 	if (count > 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"))
 		show = TRUE;
-	else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) { /* Or chat? */
-		if (buddy_is_displayable((PurpleBuddy*)node))
-			show = TRUE;
-	} else if (!show_offline && PURPLE_BLIST_NODE_IS_GROUP(node)) {
+	else if (PURPLE_BLIST_NODE_IS_BUDDY(node) && buddy_is_displayable((PurpleBuddy*)node)) { /* Or chat? */
+		show = TRUE;
+	} else if (!show_offline) {
 		show = pidgin_blist_group_has_show_offline_buddy(group);
 	}
 
@@ -6691,6 +6748,8 @@
 	gtkblist->arrow_cursor = NULL;
 
 	priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
+	if (priv->current_theme)
+		g_object_unref(priv->current_theme);
 	g_free(priv);
 
 	g_free(gtkblist);
@@ -7261,7 +7320,10 @@
 	else
 		purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme", "");
 
-	priv->current_theme = theme;
+	if (priv->current_theme)
+		g_object_unref(priv->current_theme);
+
+	priv->current_theme = theme ? g_object_ref(theme) : NULL;
 
 	pidgin_blist_build_layout(list);
 
--- a/pidgin/gtkconv.c	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/gtkconv.c	Sat Jun 06 06:21:39 2009 +0000
@@ -5629,38 +5629,6 @@
 #endif
 }
 
-/* Returns true if the given HTML contains RTL text */
-static gboolean
-html_is_rtl(const char *html)
-{
-	GData *attributes;
-	const gchar *start, *end;
-	gboolean res = FALSE;
-
-	if (purple_markup_find_tag("span", html, &start, &end, &attributes))
-	{
-		/* tmp is a member of attributes and is free with g_datalist_clear call */
-		const char *tmp = g_datalist_get_data(&attributes, "dir");
-		if (tmp && !g_ascii_strcasecmp(tmp, "RTL"))
-			res = TRUE;
-		if (!res)
-		{
-			tmp = g_datalist_get_data(&attributes, "style");
-			if (tmp)
-			{
-				char *tmp2 = purple_markup_get_css_property(tmp, "direction");
-				if (tmp2 && !g_ascii_strcasecmp(tmp2, "RTL"))
-					res = TRUE;
-				g_free(tmp2);
-			}
-
-		}
-		g_datalist_clear(&attributes);
-	}
-	return res;
-}
-
-
 static void
 pidgin_conv_write_conv(PurpleConversation *conv, const char *name, const char *alias,
 						const char *message, PurpleMessageFlags flags,
@@ -5822,7 +5790,7 @@
 	}
 
 	/* Bi-Directional support - set timestamp direction using unicode characters */
-	is_rtl_message = html_is_rtl(message);
+	is_rtl_message = purple_markup_is_rtl(message);
 	/* Enforce direction only if message is RTL - doesn't effect LTR users */
 	if (is_rtl_message)
 		str_embed_direction_chars(&mdate);
--- a/pidgin/gtkdialogs.c	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/gtkdialogs.c	Sat Jun 06 06:21:39 2009 +0000
@@ -74,7 +74,7 @@
 static const struct developer developers[] = {
 	{"Daniel 'datallah' Atallah",	NULL, NULL},
 	{"Paul 'darkrain42' Aurich",	NULL, NULL },
-	{"John 'rekkanoryo' Bailey",	N_("bug master"), "rekkanoryo@pidgin.im"},
+	{"John 'rekkanoryo' Bailey",	N_("bug master"), NULL},
 	{"Ethan 'Paco-Paco' Blanton",	NULL, NULL},
 	{"Hylke Bons",			N_("artist"), "h.bons@student.rug.nl"},
 	{"Thomas Butter",				NULL, NULL},
--- a/pidgin/gtkimhtml.c	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/gtkimhtml.c	Sat Jun 06 06:21:39 2009 +0000
@@ -3867,12 +3867,15 @@
 }
 
 static void
-gtk_imhtml_custom_smiley_save(GtkWidget *w, GtkIMHtmlImage *image)
-{
+gtk_imhtml_custom_smiley_save(GtkWidget *w, GtkIMHtmlImageSave *save)
+{
+	GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image;
+	
 	/* Create an add dialog */
 	PidginSmiley *editor = pidgin_smiley_edit(NULL, NULL);
 	pidgin_smiley_editor_set_shortcut(editor, image->filename);
 	pidgin_smiley_editor_set_image(editor, image->pixbuf);
+	pidgin_smiley_editor_set_data(editor, save->data, save->datasize);
 }
 
 /*
@@ -3907,7 +3910,7 @@
 				item = gtk_image_menu_item_new_with_mnemonic(_("_Add Custom Smiley..."));
 				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
 				g_signal_connect(G_OBJECT(item), "activate",
-								 G_CALLBACK(gtk_imhtml_custom_smiley_save), image);
+								 G_CALLBACK(gtk_imhtml_custom_smiley_save), save);
 				gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
 			}
 
--- a/pidgin/gtknotify.c	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/gtknotify.c	Sat Jun 06 06:21:39 2009 +0000
@@ -120,7 +120,7 @@
 {
 	PIDGIN_NOTIFY_MAIL,
 	PIDGIN_NOTIFY_POUNCE,
-        PIDGIN_NOTIFY_TYPES
+	PIDGIN_NOTIFY_TYPES
 } PidginNotifyType;
 
 static PidginNotifyDialog *mail_dialog = NULL;
@@ -1379,7 +1379,6 @@
 
 	spec_dialog = g_new0(PidginNotifyDialog, 1);
 	spec_dialog->dialog = dialog;
-	spec_dialog->open_button = button;
 
 	spec_dialog->treemodel = treemodel;
 	spec_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(spec_dialog->treemodel));
@@ -1399,6 +1398,7 @@
 
 		button = gtk_dialog_add_button(GTK_DIALOG(dialog),
 						 PIDGIN_STOCK_OPEN_MAIL, GTK_RESPONSE_YES);
+		spec_dialog->open_button = button;
 
 		gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(spec_dialog->treeview), FALSE);
 
@@ -1496,7 +1496,7 @@
 		mail_dialog = spec_dialog;
 	else if (type == PIDGIN_NOTIFY_POUNCE) {
 		pounce_dialog = spec_dialog;
-        }
+	}
 
 	return spec_dialog->dialog;
 
--- a/pidgin/gtkprefs.c	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/gtkprefs.c	Sat Jun 06 06:21:39 2009 +0000
@@ -1163,14 +1163,15 @@
 static void
 prefs_set_blist_theme_cb(GtkComboBox *combo_box, gpointer user_data)
 {
-	PidginBlistTheme *theme;
+	PidginBlistTheme *theme = NULL;
 	GtkTreeIter iter;
 	gchar *name = NULL;
 
 	g_return_if_fail(gtk_combo_box_get_active_iter(combo_box, &iter));
 	gtk_tree_model_get(GTK_TREE_MODEL(prefs_blist_themes), &iter, 2, &name, -1);
 
-	theme = PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(name, "blist"));
+	if (name && *name)
+		theme = PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(name, "blist"));
 	g_free(name);
 
 	pidgin_blist_set_theme(theme);
--- a/pidgin/gtksmiley.c	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/gtksmiley.c	Sat Jun 06 06:21:39 2009 +0000
@@ -47,6 +47,8 @@
 	GtkWidget *smiley_image;
 	gchar *filename;
 	GdkPixbuf *custom_pixbuf;
+	gpointer data; /** @since 2.6.0 */
+	gsize datasize; /** @since 2.6.0 */
 };
 
 typedef struct
@@ -277,7 +279,6 @@
 		purple_debug_info("gtksmiley", "adding a new smiley\n");
 
 		if (s->filename == NULL) {
-			/* Get the smiley from the custom pixbuf */
 			gchar *buffer = NULL;
 			gsize size = 0;
 			gchar *filename;
@@ -296,8 +297,16 @@
 				}
 			}
 
-			gdk_pixbuf_save_to_buffer(s->custom_pixbuf, &buffer, &size,
-				"png", NULL, "compression", "9", NULL, NULL);
+			if (s->data && s->datasize) {
+				/* Cached data & size in memory */
+				buffer = s->data;
+				size = s->datasize;
+			}
+			else {
+				/* Get the smiley from the custom pixbuf */
+				gdk_pixbuf_save_to_buffer(s->custom_pixbuf, &buffer, &size,
+					"png", NULL, "compression", "9", NULL, NULL);
+			}
 			filename = purple_util_get_image_filename(buffer, size);
 			s->filename = g_build_filename(dirname, filename, NULL);
 			purple_util_write_data_to_file_absolute(s->filename, buffer, size);
@@ -465,6 +474,13 @@
 		gtk_image_set_from_pixbuf(GTK_IMAGE(editor->smiley_image), image);
 }
 
+void
+pidgin_smiley_editor_set_data(PidginSmiley *editor, gpointer *data, gsize datasize)
+{
+	editor->data = data;
+	editor->datasize = datasize;
+}
+
 /******************************************************************************
  * Delete smiley
  *****************************************************************************/
--- a/pidgin/gtksmiley.h	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/gtksmiley.h	Sat Jun 06 06:21:39 2009 +0000
@@ -100,4 +100,15 @@
  */
 void pidgin_smiley_editor_set_image(PidginSmiley *editor, GdkPixbuf *image);
 
+/**
+ * Sets the image data in a smiley add dialog
+ *
+ * @param editor A smiley editor dialog
+ * @param data A pointer to smiley's data
+ * @param datasize The size of smiley's data
+ *
+ * @since 2.6.0
+ */
+void pidgin_smiley_editor_set_data(PidginSmiley *editor, gpointer *data, gsize datasize);
+
 #endif /* PIDGIN_GTKSMILEY_H */
--- a/pidgin/gtkutils.c	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/gtkutils.c	Sat Jun 06 06:21:39 2009 +0000
@@ -525,7 +525,7 @@
 	GtkWidget *item = gtk_menu_get_active(GTK_MENU(menu));
 	if (p_item)
 		(*p_item) = item;
-	return g_object_get_data(G_OBJECT(item), "aop_per_item_data");
+	return item ? g_object_get_data(G_OBJECT(item), "aop_per_item_data") : NULL;
 }
 
 static void
--- a/pidgin/plugins/Makefile.am	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/plugins/Makefile.am	Sat Jun 06 06:21:39 2009 +0000
@@ -1,4 +1,4 @@
-DIST_SUBDIRS = cap gestures gevolution musicmessaging perl ticker
+DIST_SUBDIRS = cap disco gestures gevolution musicmessaging perl ticker
 
 if BUILD_GEVOLUTION
 GEVOLUTION_DIR = gevolution
@@ -26,6 +26,7 @@
 	$(GEVOLUTION_DIR) \
 	$(MUSICMESSAGING_DIR) \
 	$(PERL_DIR) \
+	disco \
 	ticker
 
 plugindir = $(libdir)/pidgin
--- a/pidgin/plugins/convcolors.c	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/plugins/convcolors.c	Sat Jun 06 06:21:39 2009 +0000
@@ -101,6 +101,7 @@
 	gboolean bold, italic, underline;
 	int f;
 	const char *color;
+	gboolean rtl = FALSE;
 
 	for (i = 0; formats[i].prefix; i++)
 		if (flags & formats[i].flag)
@@ -126,6 +127,7 @@
 	bold = (f & FONT_BOLD);
 	italic = (f & FONT_ITALIC);
 	underline = (f & FONT_UNDERLINE);
+	rtl = purple_markup_is_rtl(*displaying);
 
 	if (purple_prefs_get_bool(PREF_IGNORE))
 	{
@@ -156,11 +158,13 @@
 	}
 
 	t = *displaying;
-	*displaying = g_strdup_printf("%s%s%s%s%s%s%s",
+	*displaying = g_strdup_printf("%s%s%s%s%s%s%s%s%s",
 						bold ? "<B>" : "</B>",
 						italic ? "<I>" : "</I>",
 						underline ? "<U>" : "</U>",
-						t, 
+						rtl ? "<SPAN style=\"direction:rtl;text-align:right;\">" : "",
+						t,
+						rtl ? "</SPAN>" : "",
 						bold ? "</B>" : "<B>",
 						italic ? "</I>" : "<I>",
 						underline ? "</U>" : "<U>"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/disco/Makefile.am	Sat Jun 06 06:21:39 2009 +0000
@@ -0,0 +1,23 @@
+plugindir = $(libdir)/pidgin
+
+xmppdisco_la_LDFLAGS = -module -avoid-version
+
+if PLUGINS
+
+plugin_LTLIBRARIES = xmppdisco.la
+
+xmppdisco_la_SOURCES = \
+	gtkdisco.c \
+	xmppdisco.c
+
+xmppdisco_la_LIBADD = $(GTK_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+	-DDATADIR=\"$(datadir)\" \
+	-I$(top_srcdir)/libpurple \
+	-I$(top_builddir)/libpurple \
+	-I$(top_srcdir)/pidgin \
+	$(DEBUG_CFLAGS) \
+	$(GTK_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/disco/gtkdisco.c	Sat Jun 06 06:21:39 2009 +0000
@@ -0,0 +1,592 @@
+/**
+ * @file gtkdisco.c GTK+ Service Discovery UI
+ * @ingroup pidgin
+ */
+
+/* pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+#include "debug.h"
+#include "gtkutils.h"
+#include "pidgin.h"
+#include "request.h"
+
+#include "gtkdisco.h"
+#include "xmppdisco.h"
+
+GList *dialogs = NULL;
+
+struct _menu_cb_info {
+	PidginDiscoList *list;
+	XmppDiscoService *service;
+};
+
+enum {
+	PIXBUF_COLUMN = 0,
+	NAME_COLUMN,
+	DESCRIPTION_COLUMN,
+	SERVICE_COLUMN,
+	NUM_OF_COLUMNS
+};
+
+static void
+pidgin_disco_list_destroy(PidginDiscoList *list)
+{
+	g_hash_table_destroy(list->services);
+	if (list->dialog && list->dialog->discolist == list)
+		list->dialog->discolist = NULL;
+
+	if (list->tree) {
+		gtk_widget_destroy(list->tree);
+		list->tree = NULL;
+	}
+
+	g_free((gchar*)list->server);
+	g_free(list);
+}
+
+PidginDiscoList *pidgin_disco_list_ref(PidginDiscoList *list)
+{
+	g_return_val_if_fail(list != NULL, NULL);
+
+	++list->ref;
+    purple_debug_misc("xmppdisco", "reffing list, ref count now %d\n", list->ref);
+
+	return list;
+}
+
+void pidgin_disco_list_unref(PidginDiscoList *list)
+{
+	g_return_if_fail(list != NULL);
+
+	--list->ref;
+
+	purple_debug_misc("xmppdisco", "unreffing list, ref count now %d\n", list->ref);
+	if (list->ref == 0)
+		pidgin_disco_list_destroy(list);
+}
+
+void pidgin_disco_list_set_in_progress(PidginDiscoList *list, gboolean in_progress)
+{
+	PidginDiscoDialog *dialog = list->dialog;
+
+	if (!dialog)
+		return;
+
+	list->in_progress = in_progress;
+
+	if (in_progress) {
+		gtk_widget_set_sensitive(dialog->account_widget, FALSE);
+		gtk_widget_set_sensitive(dialog->stop_button, TRUE);
+		gtk_widget_set_sensitive(dialog->browse_button, FALSE);
+	} else {
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog->progress), 0.0);
+
+		gtk_widget_set_sensitive(dialog->account_widget, TRUE);
+
+		gtk_widget_set_sensitive(dialog->stop_button, FALSE);
+		gtk_widget_set_sensitive(dialog->browse_button, TRUE);
+/*
+		gtk_widget_set_sensitive(dialog->register_button, FALSE);
+		gtk_widget_set_sensitive(dialog->add_button, FALSE);
+*/
+	}
+}
+
+static void pidgin_disco_create_tree(PidginDiscoList *pdl);
+
+static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
+				     PidginDiscoDialog *dialog)
+{
+	dialog->account = account;
+	gtk_widget_set_sensitive(dialog->browse_button, account != NULL);
+}
+
+static void register_button_cb(GtkButton *button, PidginDiscoDialog *dialog)
+{
+	struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "disco-info");
+
+	xmpp_disco_service_register(info->service);
+}
+
+static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server)
+{
+	pidgin_disco_list_set_in_progress(pdl, FALSE);
+	pidgin_disco_list_unref(pdl);
+}
+
+static void discolist_ok_cb(PidginDiscoList *pdl, const char *server)
+{
+	gtk_widget_set_sensitive(pdl->dialog->browse_button, TRUE);
+
+	if (!server || !*server) {
+		purple_notify_error(my_plugin, _("Invalid Server"), _("Invalid Server"),
+		                    NULL);
+
+		pidgin_disco_list_set_in_progress(pdl, FALSE);
+		pidgin_disco_list_unref(pdl);
+		return;
+	}
+
+	pdl->server = g_strdup(server);
+	pidgin_disco_list_set_in_progress(pdl, TRUE);
+	xmpp_disco_start(pdl);
+}
+
+static void browse_button_cb(GtkButton *button, PidginDiscoDialog *dialog)
+{
+	PurpleConnection *pc;
+	PidginDiscoList *pdl;
+	const char *username;
+	const char *at, *slash;
+	char *server = NULL;
+
+	pc = purple_account_get_connection(dialog->account);
+	if (!pc)
+		return;
+
+	gtk_widget_set_sensitive(dialog->browse_button, FALSE);
+	gtk_widget_set_sensitive(dialog->add_button, FALSE);
+	gtk_widget_set_sensitive(dialog->register_button, FALSE);
+
+	if (dialog->discolist != NULL) {
+		if (dialog->discolist->tree) {
+			gtk_widget_destroy(dialog->discolist->tree);
+			dialog->discolist->tree = NULL;
+		}
+		pidgin_disco_list_unref(dialog->discolist);
+	}
+
+	pdl = dialog->discolist = g_new0(PidginDiscoList, 1);
+	pdl->services = g_hash_table_new_full(NULL, NULL, NULL,
+			(GDestroyNotify)gtk_tree_row_reference_free);
+	pdl->pc = pc;
+	/* We keep a copy... */
+	pidgin_disco_list_ref(pdl);
+
+	pdl->dialog = dialog;
+	pidgin_disco_create_tree(pdl);
+
+	if (dialog->account_widget)
+		gtk_widget_set_sensitive(dialog->account_widget, FALSE);
+
+	username = purple_account_get_username(dialog->account);
+	at = g_utf8_strchr(username, -1, '@');
+	slash = g_utf8_strchr(username, -1, '/');
+	if (at && !slash) {
+		server = g_strdup_printf("%s", at + 1);
+	} else if (at && slash && at + 1 < slash) {
+		server = g_strdup_printf("%.*s", (int)(slash - (at + 1)), at + 1);
+	}
+
+	if (server == NULL)
+		/* This shouldn't ever happen since the account is connected */
+		server = g_strdup("jabber.org");
+
+	purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"),
+			_("Select an XMPP server to query"),
+			server, FALSE, FALSE, NULL,
+			_("Find Services"), PURPLE_CALLBACK(discolist_ok_cb),
+			_("Cancel"), PURPLE_CALLBACK(discolist_cancel_cb),
+			purple_connection_get_account(pc), NULL, NULL, pdl);
+
+	g_free(server);
+}
+
+static void add_room_to_blist_cb(GtkButton *button, PidginDiscoDialog *dialog)
+{
+	struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "disco-info");
+	PurpleAccount *account;
+	const char *name;
+
+	g_return_if_fail(info != NULL);
+
+	account = purple_connection_get_account(info->list->pc);
+	name = info->service->name;
+
+	if (info->service->type == XMPP_DISCO_SERVICE_TYPE_CHAT)
+		purple_blist_request_add_chat(account, NULL, NULL, name);
+	else
+		purple_blist_request_add_buddy(account, name, NULL, NULL);
+}
+
+static void
+selection_changed_cb(GtkTreeSelection *selection, PidginDiscoList *pdl)
+{
+	XmppDiscoService *service;
+	GtkTreeIter iter;
+	GValue val;
+	static struct _menu_cb_info *info;
+	PidginDiscoDialog *dialog = pdl->dialog;
+
+	if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
+		val.g_type = 0;
+		gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), &iter, SERVICE_COLUMN, &val);
+		service = g_value_get_pointer(&val);
+		if (!service) {
+			gtk_widget_set_sensitive(dialog->add_button, FALSE);
+			gtk_widget_set_sensitive(dialog->register_button, FALSE);
+			return;
+		}
+
+		info = g_new0(struct _menu_cb_info, 1);
+		info->list = dialog->discolist;
+		info->service = service;
+
+		g_object_set_data_full(G_OBJECT(dialog->add_button), "disco-info",
+		                       info, g_free);
+		g_object_set_data(G_OBJECT(dialog->register_button), "disco-info", info);
+
+		gtk_widget_set_sensitive(dialog->add_button, service->flags & XMPP_DISCO_ADD);
+		gtk_widget_set_sensitive(dialog->register_button, service->flags & XMPP_DISCO_REGISTER);
+	} else {
+		gtk_widget_set_sensitive(dialog->add_button, FALSE);
+		gtk_widget_set_sensitive(dialog->register_button, FALSE);
+	}
+}
+
+static void
+row_expanded_cb(GtkTreeView *tree, GtkTreeIter *arg1, GtkTreePath *rg2,
+                gpointer user_data)
+{
+	PidginDiscoList *pdl;
+	XmppDiscoService *service;
+	GValue val;
+
+	pdl = user_data;
+
+	val.g_type = 0;
+	gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), arg1, SERVICE_COLUMN,
+	                         &val);
+	service = g_value_get_pointer(&val);
+	xmpp_disco_service_expand(service);
+}
+
+static void
+destroy_win_cb(GtkWidget *window, gpointer d)
+{
+	PidginDiscoDialog *dialog = d;
+	PidginDiscoList *list = dialog->discolist;
+
+	if (list) {
+		list->dialog = NULL;
+
+		if (list->in_progress)
+			list->in_progress = FALSE;
+
+		pidgin_disco_list_unref(list);
+	}
+
+	dialogs = g_list_remove(dialogs, d);
+	g_free(dialog);
+}
+
+static void stop_button_cb(GtkButton *button, PidginDiscoDialog *dialog)
+{
+	pidgin_disco_list_set_in_progress(dialog->discolist, FALSE);
+}
+
+static void close_button_cb(GtkButton *button, PidginDiscoDialog *dialog)
+{
+	GtkWidget *window = dialog->window;
+
+	gtk_widget_destroy(window);
+}
+
+static gboolean account_filter_func(PurpleAccount *account)
+{
+	return purple_strequal(purple_account_get_protocol_id(account), XMPP_PLUGIN_ID);
+}
+
+static void pidgin_disco_create_tree(PidginDiscoList *pdl)
+{
+	GtkCellRenderer *text_renderer, *pixbuf_renderer;
+	GtkTreeViewColumn *column;
+	GtkTreeSelection *selection;
+
+	pdl->model = gtk_tree_store_new(NUM_OF_COLUMNS,
+			GDK_TYPE_PIXBUF,	/* PIXBUF_COLUMN */
+			G_TYPE_STRING,		/* NAME_COLUMN */
+			G_TYPE_STRING,		/* DESCRIPTION_COLUMN */
+			G_TYPE_POINTER		/* SERVICE_COLUMN */
+	);
+
+	pdl->tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(pdl->model));
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(pdl->tree), TRUE);
+
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pdl->tree));
+	g_signal_connect(G_OBJECT(selection), "changed",
+					 G_CALLBACK(selection_changed_cb), pdl);
+
+	g_object_unref(pdl->model);
+
+	gtk_container_add(GTK_CONTAINER(pdl->dialog->sw), pdl->tree);
+	gtk_widget_show(pdl->tree);
+
+	text_renderer = gtk_cell_renderer_text_new();
+	pixbuf_renderer = gtk_cell_renderer_pixbuf_new();
+
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(column, _("Name"));
+
+	gtk_tree_view_column_pack_start(column,  pixbuf_renderer, FALSE);
+	gtk_tree_view_column_set_attributes(column, pixbuf_renderer,
+			"pixbuf", PIXBUF_COLUMN, NULL);
+
+	gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
+	gtk_tree_view_column_set_attributes(column, text_renderer,
+			"text", NAME_COLUMN, NULL);
+
+	gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
+	                                GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), NAME_COLUMN);
+	gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(pdl->tree), column);
+
+	column = gtk_tree_view_column_new_with_attributes(_("Description"), text_renderer,
+				"text", DESCRIPTION_COLUMN, NULL);
+	gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
+	                                GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), DESCRIPTION_COLUMN);
+	gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(pdl->tree), column);
+
+	g_signal_connect(G_OBJECT(pdl->tree), "row-expanded", G_CALLBACK(row_expanded_cb), pdl);
+}
+
+void pidgin_disco_signed_off_cb(PurpleConnection *pc)
+{
+	GList *node;
+
+	for (node = dialogs; node; node = node->next) {
+		PidginDiscoDialog *dialog = node->data;
+		PidginDiscoList *list = dialog->discolist;
+
+		if (list && list->pc == pc) {
+			if (list->in_progress)
+				pidgin_disco_list_set_in_progress(list, FALSE);
+
+			if (list->tree) {
+				gtk_widget_destroy(list->tree);
+				list->tree = NULL;
+			}
+
+			pidgin_disco_list_unref(list);
+			dialog->discolist = NULL;
+
+			gtk_widget_set_sensitive(dialog->browse_button,
+					pidgin_account_option_menu_get_selected(dialog->account_widget) != NULL);
+
+			gtk_widget_set_sensitive(dialog->register_button, FALSE);
+			gtk_widget_set_sensitive(dialog->add_button, FALSE);
+		}
+	}
+}
+
+void pidgin_disco_dialogs_destroy_all(void)
+{
+	while (dialogs) {
+		PidginDiscoDialog *dialog = dialogs->data;
+
+		gtk_widget_destroy(dialog->window);
+		/* destroy_win_cb removes the dialog from the list */
+	}
+}
+
+PidginDiscoDialog *pidgin_disco_dialog_new(void)
+{
+	PidginDiscoDialog *dialog;
+	GtkWidget *window, *vbox, *vbox2, *bbox;
+
+	dialog = g_new0(PidginDiscoDialog, 1);
+	dialogs = g_list_prepend(dialogs, dialog);
+
+	/* Create the window. */
+	dialog->window = window = pidgin_create_dialog(_("Service Discovery"), PIDGIN_HIG_BORDER, "service discovery", TRUE);
+
+	g_signal_connect(G_OBJECT(window), "destroy",
+					 G_CALLBACK(destroy_win_cb), dialog);
+
+	/* Create the parent vbox for everything. */
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER);
+
+	vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(vbox), vbox2);
+	gtk_widget_show(vbox2);
+
+	/* accounts dropdown list */
+	dialog->account_widget = pidgin_account_option_menu_new(NULL, FALSE,
+	                         G_CALLBACK(dialog_select_account_cb), account_filter_func, dialog);
+	dialog->account = pidgin_account_option_menu_get_selected(dialog->account_widget);
+	pidgin_add_widget_to_vbox(GTK_BOX(vbox2), _("_Account:"), NULL, dialog->account_widget, TRUE, NULL);
+
+	/* scrolled window */
+	dialog->sw = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(dialog->sw),
+	                                    GTK_SHADOW_IN);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dialog->sw),
+	                               GTK_POLICY_AUTOMATIC,
+	                               GTK_POLICY_AUTOMATIC);
+	gtk_box_pack_start(GTK_BOX(vbox2), dialog->sw, TRUE, TRUE, 0);
+	gtk_widget_set_size_request(dialog->sw, -1, 250);
+	gtk_widget_show(dialog->sw);
+
+	/* progress bar */
+	dialog->progress = gtk_progress_bar_new();
+	gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(dialog->progress), 0.1);
+	gtk_box_pack_start(GTK_BOX(vbox2), dialog->progress, FALSE, FALSE, 0);
+	gtk_widget_show(dialog->progress);
+
+	/* button box */
+	bbox = pidgin_dialog_get_action_area(GTK_DIALOG(window));
+	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+
+	/* stop button */
+	dialog->stop_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP,
+	                 G_CALLBACK(stop_button_cb), dialog);
+	gtk_widget_set_sensitive(dialog->stop_button, FALSE);
+
+	/* browse button */
+	dialog->browse_button = pidgin_pixbuf_button_from_stock(_("_Browse"), GTK_STOCK_REFRESH,
+	                                                    PIDGIN_BUTTON_HORIZONTAL);
+	gtk_box_pack_start(GTK_BOX(bbox), dialog->browse_button, FALSE, FALSE, 0);
+	g_signal_connect(G_OBJECT(dialog->browse_button), "clicked",
+	                 G_CALLBACK(browse_button_cb), dialog);
+	gtk_widget_set_sensitive(dialog->browse_button, dialog->account != NULL);
+	gtk_widget_show(dialog->browse_button);
+
+	/* register button */
+	dialog->register_button = pidgin_dialog_add_button(GTK_DIALOG(dialog->window), _("Register"),
+	                 G_CALLBACK(register_button_cb), dialog);
+	gtk_widget_set_sensitive(dialog->register_button, FALSE);
+
+	/* add button */
+	dialog->add_button = pidgin_pixbuf_button_from_stock(_("_Add"), GTK_STOCK_ADD,
+	                                                    PIDGIN_BUTTON_HORIZONTAL);
+	gtk_box_pack_start(GTK_BOX(bbox), dialog->add_button, FALSE, FALSE, 0);
+	g_signal_connect(G_OBJECT(dialog->add_button), "clicked",
+	                 G_CALLBACK(add_room_to_blist_cb), dialog);
+	gtk_widget_set_sensitive(dialog->add_button, FALSE);
+	gtk_widget_show(dialog->add_button);
+
+	/* close button */
+	dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE,
+					 G_CALLBACK(close_button_cb), dialog);
+
+	/* show the dialog window and return the dialog */
+	gtk_widget_show(dialog->window);
+
+	return dialog;
+}
+
+void pidgin_disco_add_service(PidginDiscoList *pdl, XmppDiscoService *service, XmppDiscoService *parent)
+{
+	PidginDiscoDialog *dialog;
+	GtkTreeIter iter, parent_iter, child;
+	char *filename = NULL;
+	GdkPixbuf *pixbuf = NULL;
+	gboolean append = TRUE;
+
+	dialog = pdl->dialog;
+	g_return_if_fail(dialog != NULL);
+
+	if (service != NULL)
+		purple_debug_info("xmppdisco", "Adding service \"%s\"\n", service->name);
+	else
+		purple_debug_info("xmppdisco", "Service \"%s\" has no childrens\n", parent->name);
+
+	gtk_progress_bar_pulse(GTK_PROGRESS_BAR(dialog->progress));
+
+	if (parent) {
+		GtkTreeRowReference *rr;
+		GtkTreePath *path;
+
+		rr = g_hash_table_lookup(pdl->services, parent);
+		path = gtk_tree_row_reference_get_path(rr);
+		if (path) {
+			gtk_tree_model_get_iter(GTK_TREE_MODEL(pdl->model), &parent_iter, path);
+			gtk_tree_path_free(path);
+
+			if (gtk_tree_model_iter_children(GTK_TREE_MODEL(pdl->model), &child,
+			                                 &parent_iter)) {
+				PidginDiscoList *tmp;
+				gtk_tree_model_get(GTK_TREE_MODEL(pdl->model), &child,
+				                   SERVICE_COLUMN, &tmp, -1);
+				if (!tmp)
+					append = FALSE;
+			}
+		}
+	}
+
+	if (service == NULL) {
+		if (parent != NULL && !append)
+			gtk_tree_store_remove(pdl->model, &child);
+		return;
+	}
+
+	if (append)
+		gtk_tree_store_append(pdl->model, &iter, (parent ? &parent_iter : NULL));
+	else
+		iter = child;
+
+	if (service->flags & XMPP_DISCO_BROWSE) {
+		GtkTreeRowReference *rr;
+		GtkTreePath *path;
+
+		gtk_tree_store_append(pdl->model, &child, &iter);
+
+		path = gtk_tree_model_get_path(GTK_TREE_MODEL(pdl->model), &iter);
+		rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(pdl->model), path);
+		g_hash_table_insert(pdl->services, service, rr);
+		gtk_tree_path_free(path);
+	}
+
+	if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY && service->gateway_type) {
+		char *tmp = g_strconcat(service->gateway_type, ".png", NULL);
+		filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "22", tmp, NULL);
+		g_free(tmp);
+#if 0
+	} else if (service->type == XMPP_DISCO_SERVICE_TYPE_USER) {
+		filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", "22", "person.png", NULL);
+#endif
+	} else if (service->type == XMPP_DISCO_SERVICE_TYPE_CHAT)
+		filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", "22", "chat.png", NULL);
+
+	if (filename) {
+		pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+		g_free(filename);
+	}
+
+	gtk_tree_store_set(pdl->model, &iter,
+			PIXBUF_COLUMN, pixbuf,
+			NAME_COLUMN, service->name,
+			DESCRIPTION_COLUMN, service->description,
+			SERVICE_COLUMN, service,
+			-1);
+
+	if (pixbuf)
+		g_object_unref(pixbuf);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/disco/gtkdisco.h	Sat Jun 06 06:21:39 2009 +0000
@@ -0,0 +1,79 @@
+/* pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301 USA
+ */
+
+#ifndef PIDGIN_XMPP_DISCO_UI_H
+#define PIDGIN_XMPP_DISCO_UI_H
+
+typedef struct _PidginDiscoDialog PidginDiscoDialog;
+typedef struct _PidginDiscoList PidginDiscoList;
+
+#include "xmppdisco.h"
+
+struct _PidginDiscoDialog {
+	GtkWidget *window;
+	GtkWidget *account_widget;
+
+	GtkWidget *sw;
+	GtkWidget *progress;
+
+	GtkWidget *stop_button;
+	GtkWidget *browse_button;
+	GtkWidget *register_button;
+	GtkWidget *add_button;
+	GtkWidget *close_button;
+
+	PurpleAccount *account;
+	PidginDiscoList *discolist;
+};
+
+struct _PidginDiscoList {
+	PurpleConnection *pc;
+	gboolean in_progress;
+	const gchar *server;
+
+	gint ref;
+	guint fetch_count;
+
+	PidginDiscoDialog *dialog;
+	GtkTreeStore *model;
+	GtkWidget *tree;
+	GHashTable *services;
+};
+
+/**
+ * Shows a new service discovery dialog.
+ */
+PidginDiscoDialog *pidgin_disco_dialog_new(void);
+
+/**
+ * Destroy all the open dialogs (called when unloading the plugin).
+ */
+void pidgin_disco_dialogs_destroy_all(void);
+void pidgin_disco_signed_off_cb(PurpleConnection *pc);
+
+void pidgin_disco_add_service(PidginDiscoList *list, XmppDiscoService *service,
+                              XmppDiscoService *parent);
+
+PidginDiscoList *pidgin_disco_list_ref(PidginDiscoList *list);
+void pidgin_disco_list_unref(PidginDiscoList *list);
+
+void pidgin_disco_list_set_in_progress(PidginDiscoList *list, gboolean in_progress);
+#endif /* PIDGIN_XMPP_DISCO_UI_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/disco/xmppdisco.c	Sat Jun 06 06:21:39 2009 +0000
@@ -0,0 +1,665 @@
+/*
+ * Purple - XMPP Service Disco Browser
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301 USA
+ *
+ */
+
+/* TODO list (a little bit of a brain dump):
+	* Support more actions than "register" and "add" based on context.
+		- Subscribe to pubsub nodes (just...because?)
+		- Execute ad-hoc commands
+		- Change 'Register' to 'Unregister' if we're registered?
+		- Administer MUCs
+	* Enumerate pubsub node contents.
+		- PEP too? (useful development tool at times)
+	* See if we can better handle the ad-hoc commands that ejabberd returns
+	  when disco'ing a server as an administrator:
+from disco#items:
+	<item jid='darkrain42.org' node='announce' name='Announcements'/>
+disco#info:
+	<iq from='darkrain42.org' type='result'>
+		<query xmlns='http://jabber.org/protocol/disco#info' node='announce'/>
+	</iq>
+	* For services that are a JID w/o a node, handle fetching ad-hoc commands?
+*/
+
+#include "internal.h"
+#include "pidgin.h"
+
+#include "debug.h"
+#include "signals.h"
+#include "version.h"
+
+#include "gtkconv.h"
+#include "gtkimhtml.h"
+#include "gtkplugin.h"
+
+#include "xmppdisco.h"
+#include "gtkdisco.h"
+
+/* Variables */
+PurplePlugin *my_plugin = NULL;
+static GHashTable *iq_callbacks = NULL;
+static gboolean iq_listening = FALSE;
+
+typedef void (*XmppIqCallback)(PurpleConnection *pc, const char *type,
+                              const char *id, const char *from, xmlnode *iq,
+							  gpointer data);
+
+struct xmpp_iq_cb_data
+{
+	gpointer context;
+	PurpleConnection *pc;
+	XmppIqCallback cb;
+};
+
+struct item_data {
+	PidginDiscoList *list;
+	XmppDiscoService *parent;
+	char *name;
+	char *node; /* disco#info replies don't always include the node */
+};
+
+static char*
+generate_next_id()
+{
+	static guint32 index = 0;
+
+	if (index == 0) {
+		do {
+			index = g_random_int();
+		} while (index == 0);
+	}
+
+	return g_strdup_printf("purpledisco%x", index++);
+}
+
+static gboolean
+remove_iq_callbacks_by_pc(gpointer key, gpointer value, gpointer user_data)
+{
+	struct xmpp_iq_cb_data *cb_data = value;
+
+	if (cb_data && cb_data->pc == user_data) {
+		/*
+		 * This is a hack. All the IQ callback datas in this code are
+		 * the same structure so that we can free them here. Ideally they'd
+		 * be objects and this would be polymorphic. That's overkill, here.
+		 */
+		struct item_data *item_data = cb_data->context;
+
+		if (item_data) {
+			pidgin_disco_list_unref(item_data->list);
+			g_free(item_data->name);
+			g_free(item_data->node);
+			g_free(item_data);
+		}
+
+		return TRUE;
+	} else
+		return FALSE;
+}
+
+static gboolean
+xmpp_iq_received(PurpleConnection *pc, const char *type, const char *id,
+                 const char *from, xmlnode *iq)
+{
+	struct xmpp_iq_cb_data *cb_data;
+
+	cb_data = g_hash_table_lookup(iq_callbacks, id);
+	if (!cb_data)
+		return FALSE;
+
+	cb_data->cb(cb_data->pc, type, id, from, iq, cb_data->context);
+
+	g_hash_table_remove(iq_callbacks, id);
+	if (g_hash_table_size(iq_callbacks) == 0) {
+		PurplePlugin *prpl = purple_connection_get_prpl(pc);
+		iq_listening = FALSE;
+		purple_signal_disconnect(prpl, "jabber-receiving-iq", my_plugin,
+		                         PURPLE_CALLBACK(xmpp_iq_received));
+	}
+
+	/* Om nom nom nom */
+	return TRUE;
+}
+
+static void
+xmpp_iq_register_callback(PurpleConnection *pc, gchar *id, gpointer data,
+                          XmppIqCallback cb)
+{
+	struct xmpp_iq_cb_data *cbdata = g_new0(struct xmpp_iq_cb_data, 1);
+
+	cbdata->context = data;
+	cbdata->cb = cb;
+	cbdata->pc = pc;
+
+	g_hash_table_insert(iq_callbacks, id, cbdata);
+
+	if (!iq_listening) {
+    	PurplePlugin *prpl = purple_plugins_find_with_id(XMPP_PLUGIN_ID);
+		iq_listening = TRUE;
+		purple_signal_connect(prpl, "jabber-receiving-iq", my_plugin,
+		                      PURPLE_CALLBACK(xmpp_iq_received), NULL);
+	}
+}
+
+static void
+xmpp_disco_info_do(PurpleConnection *pc, gpointer cbdata, const char *jid,
+                   const char *node, XmppIqCallback cb)
+{
+	xmlnode *iq, *query;
+	char *id = generate_next_id();
+
+	iq = xmlnode_new("iq");
+	xmlnode_set_attrib(iq, "type", "get");
+	xmlnode_set_attrib(iq, "to", jid);
+	xmlnode_set_attrib(iq, "id", id);
+
+	query = xmlnode_new_child(iq, "query");
+	xmlnode_set_namespace(query, NS_DISCO_INFO);
+	if (node)
+		xmlnode_set_attrib(query, "node", node);
+
+	/* Steals id */
+	xmpp_iq_register_callback(pc, id, cbdata, cb);
+
+	purple_signal_emit(purple_connection_get_prpl(pc), "jabber-sending-xmlnode",
+	                   pc, &iq);
+	if (iq != NULL)
+		xmlnode_free(iq);
+}
+
+static void
+xmpp_disco_items_do(PurpleConnection *pc, gpointer cbdata, const char *jid,
+                    const char *node, XmppIqCallback cb)
+{
+	xmlnode *iq, *query;
+	char *id = generate_next_id();
+
+	iq = xmlnode_new("iq");
+	xmlnode_set_attrib(iq, "type", "get");
+	xmlnode_set_attrib(iq, "to", jid);
+	xmlnode_set_attrib(iq, "id", id);
+
+	query = xmlnode_new_child(iq, "query");
+	xmlnode_set_namespace(query, NS_DISCO_ITEMS);
+	if (node)
+		xmlnode_set_attrib(query, "node", node);
+
+	/* Steals id */
+	xmpp_iq_register_callback(pc, id, cbdata, cb);
+
+	purple_signal_emit(purple_connection_get_prpl(pc), "jabber-sending-xmlnode",
+	                   pc, &iq);
+	if (iq != NULL)
+		xmlnode_free(iq);
+}
+
+static XmppDiscoServiceType
+disco_service_type_from_identity(xmlnode *identity)
+{
+	const char *category, *type;
+
+	if (!identity)
+		return XMPP_DISCO_SERVICE_TYPE_OTHER;
+
+	category = xmlnode_get_attrib(identity, "category");
+	type = xmlnode_get_attrib(identity, "type");
+
+	if (!category)
+		return XMPP_DISCO_SERVICE_TYPE_OTHER;
+
+	if (g_str_equal(category, "conference"))
+		return XMPP_DISCO_SERVICE_TYPE_CHAT;
+	else if (g_str_equal(category, "directory"))
+		return XMPP_DISCO_SERVICE_TYPE_DIRECTORY;
+	else if (g_str_equal(category, "gateway"))
+		return XMPP_DISCO_SERVICE_TYPE_GATEWAY;
+	else if (g_str_equal(category, "pubsub")) {
+		if (!type || g_str_equal(type, "collection"))
+			return XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION;
+		else if (g_str_equal(type, "leaf"))
+			return XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF;
+		else if (g_str_equal(type, "service"))
+			return XMPP_DISCO_SERVICE_TYPE_OTHER;
+		else {
+			purple_debug_warning("xmppdisco", "Unknown pubsub type '%s'\n", type);
+			return XMPP_DISCO_SERVICE_TYPE_OTHER;
+		}
+	}
+
+	return XMPP_DISCO_SERVICE_TYPE_OTHER;
+}
+
+static const struct {
+    const char *from;
+    const char *to;
+} disco_type_mappings[] = {
+    { "gadu-gadu", "gadu-gadu" }, /* the prpl is prpl-gg, but list_icon returns "gadu-gadu" */
+    { "sametime",  "meanwhile" },
+    { "myspaceim", "myspace" },
+    { "xmpp",      "jabber" }, /* prpl-jabber (mentioned in case the prpl is renamed so this line will match) */
+    { NULL,        NULL }
+};
+
+static const gchar *
+disco_type_from_string(const gchar *str)
+{
+    int i = 0;
+
+    g_return_val_if_fail(str != NULL, "");
+
+    for ( ; disco_type_mappings[i].from; ++i) {
+        if (!strcasecmp(str, disco_type_mappings[i].from))
+            return disco_type_mappings[i].to;
+    }
+
+    /* fallback to the string itself */
+    return str;
+}
+
+static void
+got_info_cb(PurpleConnection *pc, const char *type, const char *id,
+            const char *from, xmlnode *iq, gpointer data)
+{
+	struct item_data *item_data = data;
+	PidginDiscoList *list = item_data->list;
+	xmlnode *query;
+
+	--list->fetch_count;
+
+	if (!list->in_progress)
+		goto out;
+
+	if (g_str_equal(type, "result") &&
+			(query = xmlnode_get_child(iq, "query"))) {
+		xmlnode *identity = xmlnode_get_child(query, "identity");
+		XmppDiscoService *service;
+		xmlnode *feature;
+
+		service = g_new0(XmppDiscoService, 1);
+		service->list = item_data->list;
+		purple_debug_info("xmppdisco", "parent for %s is %p\n", from, item_data->parent);
+		service->parent = item_data->parent;
+		service->flags = XMPP_DISCO_ADD;
+		service->type = disco_service_type_from_identity(identity);
+
+		if (item_data->node) {
+			if (item_data->name) {
+				service->name = item_data->name;
+				item_data->name = NULL;
+			} else
+				service->name = g_strdup(item_data->node);
+
+			service->node = item_data->node;
+			item_data->node = NULL;
+
+			if (service->type == XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION)
+				service->flags |= XMPP_DISCO_BROWSE;
+		} else
+			service->name = g_strdup(from);
+
+		if (item_data->name) {
+			service->description = item_data->name;
+			item_data->name = NULL;
+		} else if (identity)
+			service->description = g_strdup(xmlnode_get_attrib(identity, "name"));
+
+		/* TODO: Overlap with service->name a bit */
+		service->jid = g_strdup(from);
+
+		for (feature = xmlnode_get_child(query, "feature"); feature;
+				feature = xmlnode_get_next_twin(feature)) {
+			const char *var;
+			if (!(var = xmlnode_get_attrib(feature, "var")))
+				continue;
+
+			if (g_str_equal(var, NS_REGISTER))
+				service->flags |= XMPP_DISCO_REGISTER;
+			else if (g_str_equal(var, NS_DISCO_ITEMS))
+				service->flags |= XMPP_DISCO_BROWSE;
+			else if (g_str_equal(var, NS_MUC)) {
+				service->flags |= XMPP_DISCO_BROWSE;
+				service->type = XMPP_DISCO_SERVICE_TYPE_CHAT;
+			}
+		}
+
+		if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY)
+			service->gateway_type = g_strdup(disco_type_from_string(
+					xmlnode_get_attrib(identity, "type")));
+
+		pidgin_disco_add_service(list, service, service->parent);
+	}
+
+out:
+	if (list->fetch_count == 0)
+		pidgin_disco_list_set_in_progress(list, FALSE);
+
+	g_free(item_data->name);
+	g_free(item_data->node);
+	g_free(item_data);
+	pidgin_disco_list_unref(list);
+}
+
+static void
+got_items_cb(PurpleConnection *pc, const char *type, const char *id,
+             const char *from, xmlnode *iq, gpointer data)
+{
+	struct item_data *item_data = data;
+	PidginDiscoList *list = item_data->list;
+	xmlnode *query;
+	gboolean has_items = FALSE;
+
+	--list->fetch_count;
+
+	if (!list->in_progress)
+		goto out;
+
+	if (g_str_equal(type, "result") &&
+			(query = xmlnode_get_child(iq, "query"))) {
+		xmlnode *item;
+
+		for (item = xmlnode_get_child(query, "item"); item;
+				item = xmlnode_get_next_twin(item)) {
+			const char *jid = xmlnode_get_attrib(item, "jid");
+			const char *name = xmlnode_get_attrib(item, "name");
+			const char *node = xmlnode_get_attrib(item, "node");
+
+			has_items = TRUE;
+
+			if (item_data->parent->type == XMPP_DISCO_SERVICE_TYPE_CHAT) {
+				/* This is a hacky first-order approximation. Any MUC
+				 * component that has a >1 level hierarchy (a Yahoo MUC
+				 * transport component probably does) will violate this.
+				 *
+				 * On the other hand, this is better than querying all the
+				 * chats at conference.jabber.org to enumerate them.
+				 */
+				XmppDiscoService *service = g_new0(XmppDiscoService, 1);
+				service->list = item_data->list;
+				service->parent = item_data->parent;
+				service->flags = XMPP_DISCO_ADD;
+				service->type = XMPP_DISCO_SERVICE_TYPE_CHAT;
+
+				service->name = g_strdup(name);
+				service->jid = g_strdup(jid);
+				service->node = g_strdup(node);
+				pidgin_disco_add_service(list, service, item_data->parent);
+			} else {
+				struct item_data *item_data2 = g_new0(struct item_data, 1);
+
+				item_data2->list = item_data->list;
+				item_data2->parent = item_data->parent;
+				item_data2->name = g_strdup(name);
+				item_data2->node = g_strdup(node);
+
+				++list->fetch_count;
+				pidgin_disco_list_ref(list);
+				xmpp_disco_info_do(pc, item_data2, jid, node, got_info_cb);
+			}
+		}
+	}
+
+	if (!has_items)
+		pidgin_disco_add_service(list, NULL, item_data->parent);
+
+out:
+	if (list->fetch_count == 0)
+		pidgin_disco_list_set_in_progress(list, FALSE);
+
+	g_free(item_data);
+	pidgin_disco_list_unref(list);
+}
+
+static void
+server_items_cb(PurpleConnection *pc, const char *type, const char *id,
+                const char *from, xmlnode *iq, gpointer data)
+{
+	struct item_data *cb_data = data;
+	PidginDiscoList *list = cb_data->list;
+	xmlnode *query;
+
+	g_free(cb_data);
+	--list->fetch_count;
+
+	if (g_str_equal(type, "result") &&
+			(query = xmlnode_get_child(iq, "query"))) {
+		xmlnode *item;
+
+		for (item = xmlnode_get_child(query, "item"); item;
+				item = xmlnode_get_next_twin(item)) {
+			const char *jid = xmlnode_get_attrib(item, "jid");
+			const char *name = xmlnode_get_attrib(item, "name");
+			const char *node = xmlnode_get_attrib(item, "node");
+			struct item_data *item_data;
+
+			if (!jid)
+				continue;
+
+			item_data = g_new0(struct item_data, 1);
+			item_data->list = list;
+			item_data->name = g_strdup(name);
+			item_data->node = g_strdup(node);
+
+			++list->fetch_count;
+			pidgin_disco_list_ref(list);
+			xmpp_disco_info_do(pc, item_data, jid, node, got_info_cb);
+		}
+	}
+
+	if (list->fetch_count == 0)
+		pidgin_disco_list_set_in_progress(list, FALSE);
+
+	pidgin_disco_list_unref(list);
+}
+
+static void
+server_info_cb(PurpleConnection *pc, const char *type, const char *id,
+               const char *from, xmlnode *iq, gpointer data)
+{
+	struct item_data *cb_data = data;
+	PidginDiscoList *list = cb_data->list;
+	xmlnode *query;
+	gboolean items = FALSE;
+
+	--list->fetch_count;
+
+	if (g_str_equal(type, "result") &&
+			(query = xmlnode_get_child(iq, "query"))) {
+		xmlnode *feature;
+
+		for (feature = xmlnode_get_child(query, "feature"); feature;
+				feature = xmlnode_get_next_twin(feature)) {
+			const char *var = xmlnode_get_attrib(feature, "var");
+			if (purple_strequal(var, NS_DISCO_ITEMS)) {
+				items = TRUE;
+				break;
+			}
+		}
+	}
+
+	if (items) {
+		xmpp_disco_items_do(pc, cb_data, from, NULL /* node */, server_items_cb);
+		++list->fetch_count;
+		pidgin_disco_list_ref(list);
+	} else {
+		purple_notify_error(my_plugin, _("Error"),
+		                    _("Server does not support service discovery"),
+		                   NULL);
+		pidgin_disco_list_set_in_progress(list, FALSE);
+		g_free(cb_data);
+	}
+
+	pidgin_disco_list_unref(list);
+}
+
+void xmpp_disco_start(PidginDiscoList *list)
+{
+	struct item_data *cb_data;
+
+	g_return_if_fail(list != NULL);
+
+	++list->fetch_count;
+	pidgin_disco_list_ref(list);
+
+	cb_data = g_new0(struct item_data, 1);
+	cb_data->list = list;
+
+	xmpp_disco_info_do(list->pc, cb_data, list->server, NULL, server_info_cb);
+}
+
+void xmpp_disco_service_expand(XmppDiscoService *service)
+{
+	struct item_data *item_data;
+
+	g_return_if_fail(service != NULL);
+
+	if (service->expanded)
+		return;
+
+	item_data = g_new0(struct item_data, 1);
+	item_data->list = service->list;
+	item_data->parent = service;
+
+	++service->list->fetch_count;
+	pidgin_disco_list_ref(service->list);
+
+	pidgin_disco_list_set_in_progress(service->list, TRUE);
+
+	xmpp_disco_items_do(service->list->pc, item_data, service->jid, service->node,
+	                    got_items_cb);
+	service->expanded = TRUE;
+}
+
+void xmpp_disco_service_register(XmppDiscoService *service)
+{
+	xmlnode *iq, *query;
+	char *id = generate_next_id();
+
+	iq = xmlnode_new("iq");
+	xmlnode_set_attrib(iq, "type", "get");
+	xmlnode_set_attrib(iq, "to", service->jid);
+	xmlnode_set_attrib(iq, "id", id);
+
+	query = xmlnode_new_child(iq, "query");
+	xmlnode_set_namespace(query, NS_REGISTER);
+
+	purple_signal_emit(purple_connection_get_prpl(service->list->pc),
+			"jabber-sending-xmlnode", service->list->pc, &iq);
+	if (iq != NULL)
+		xmlnode_free(iq);
+	g_free(id);
+}
+
+static void
+create_dialog(PurplePluginAction *action)
+{
+	pidgin_disco_dialog_new();
+}
+
+static GList *
+actions(PurplePlugin *plugin, gpointer context)
+{
+	GList *l = NULL;
+	PurplePluginAction *action = NULL;
+
+	action = purple_plugin_action_new(_("XMPP Service Discovery"),
+	                                  create_dialog);
+	l = g_list_prepend(l, action);
+
+	return l;
+}
+
+static void
+signed_off_cb(PurpleConnection *pc, gpointer unused)
+{
+	/* Deal with any dialogs */
+	pidgin_disco_signed_off_cb(pc);
+
+	/* Remove all the IQ callbacks for this connection */
+	g_hash_table_foreach_remove(iq_callbacks, remove_iq_callbacks_by_pc, pc);
+}
+
+static gboolean
+plugin_load(PurplePlugin *plugin)
+{
+    PurplePlugin *xmpp_prpl;
+
+	my_plugin = plugin;
+
+    xmpp_prpl = purple_plugins_find_with_id(XMPP_PLUGIN_ID);
+    if (NULL == xmpp_prpl)
+        return FALSE;
+
+	purple_signal_connect(purple_connections_get_handle(), "signing-off",
+	                      plugin, PURPLE_CALLBACK(signed_off_cb), NULL);
+
+	iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+	return TRUE;
+}
+
+static gboolean
+plugin_unload(PurplePlugin *plugin)
+{
+	g_hash_table_destroy(iq_callbacks);
+	iq_callbacks = NULL;
+
+	purple_signals_disconnect_by_handle(plugin);
+	pidgin_disco_dialogs_destroy_all();
+
+	return TRUE;
+}
+
+static PurplePluginInfo info =
+{
+	PURPLE_PLUGIN_MAGIC,
+	PURPLE_MAJOR_VERSION,
+	PURPLE_MINOR_VERSION,
+	PURPLE_PLUGIN_STANDARD,
+	PIDGIN_PLUGIN_TYPE,
+	0,
+	NULL,
+	PURPLE_PRIORITY_DEFAULT,
+	"gtk-xmppdisco",
+	N_("XMPP Service Discovery"),
+	DISPLAY_VERSION,
+	N_("Allows browsing and registering services."),
+	N_("This plugin is useful for registering with legacy transports or other "
+	   "XMPP services."),
+	"Paul Aurich <paul@darkrain42.org>",
+	PURPLE_WEBSITE,
+	plugin_load,
+	plugin_unload,
+	NULL,               /**< destroy    */
+	NULL,               /**< ui_info    */
+	NULL,               /**< extra_info */
+	NULL,               /**< prefs_info */
+	actions,
+
+	/* padding */
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(xmppdisco, init_plugin, info)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/disco/xmppdisco.h	Sat Jun 06 06:21:39 2009 +0000
@@ -0,0 +1,107 @@
+/* pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301 USA
+ */
+
+#ifndef PIDGIN_XMPP_DISCO_H
+#define PIDGIN_XMPP_DISCO_H
+
+typedef struct _XmppDiscoService XmppDiscoService;
+
+#include "gtkdisco.h"
+
+#define XMPP_PLUGIN_ID      "prpl-jabber"
+#define NS_DISCO_INFO       "http://jabber.org/protocol/disco#info"
+#define NS_DISCO_ITEMS      "http://jabber.org/protocol/disco#items"
+#define NS_MUC              "http://jabber.org/protocol/muc"
+#define NS_REGISTER         "jabber:iq:register"
+
+#include "plugin.h"
+extern PurplePlugin *my_plugin;
+
+/**
+ * The types of services.
+ */
+typedef enum
+{
+    XMPP_DISCO_SERVICE_TYPE_UNSET,
+    /**
+     * A registerable gateway to another protocol. An example would be
+     * XMPP legacy transports.
+     */
+    XMPP_DISCO_SERVICE_TYPE_GATEWAY,
+
+    /**
+     * A directory (e.g. allows the user to search for other users).
+     */
+    XMPP_DISCO_SERVICE_TYPE_DIRECTORY,
+
+    /**
+     * A chat (multi-user conversation).
+     */
+    XMPP_DISCO_SERVICE_TYPE_CHAT,
+
+	/**
+	 * A pubsub collection (contains nodes)
+	 */
+	XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION,
+
+	/**
+	 * A pubsub leaf (contains stuff, not nodes).
+	 */
+	XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF,
+
+	/**
+     * Something else. Do we need more categories?
+     */
+    XMPP_DISCO_SERVICE_TYPE_OTHER
+} XmppDiscoServiceType;
+
+/**
+ * The flags of services.
+ */
+typedef enum
+{
+    XMPP_DISCO_NONE          = 0x0000,
+    XMPP_DISCO_ADD           = 0x0001, /**< Supports an 'add' operation */
+    XMPP_DISCO_BROWSE        = 0x0002, /**< Supports browsing */
+    XMPP_DISCO_REGISTER      = 0x0004  /**< Supports a 'register' operation */
+} XmppDiscoServiceFlags;
+
+struct _XmppDiscoService {
+	PidginDiscoList *list;
+	gchar *name;
+	gchar *description;
+
+	gchar *gateway_type;
+	XmppDiscoServiceType type;
+	XmppDiscoServiceFlags flags;
+
+	XmppDiscoService *parent;
+	gchar *jid;
+	gchar *node;
+	gboolean expanded;
+};
+
+void xmpp_disco_start(PidginDiscoList *list);
+
+void xmpp_disco_service_expand(XmppDiscoService *service);
+void xmpp_disco_service_register(XmppDiscoService *service);
+
+#endif /* PIDGIN_XMPP_DISCO_H */
--- a/pidgin/plugins/pidginrc.c	Sun May 31 18:39:19 2009 +0000
+++ b/pidgin/plugins/pidginrc.c	Sat Jun 06 06:21:39 2009 +0000
@@ -28,34 +28,31 @@
 static guint pref_callback;
 
 static const gchar *color_prefs[] = {
-	"/plugins/gtk/purplerc/color/GtkWidget::cursor-color",
-	"/plugins/gtk/purplerc/color/GtkWidget::secondary-cursor-color",
 	"/plugins/gtk/purplerc/color/GtkIMHtml::hyperlink-color",
 	"/plugins/gtk/purplerc/color/GtkIMHtml::hyperlink-visited-color",
 	"/plugins/gtk/purplerc/color/GtkIMHtml::send-name-color",
 	"/plugins/gtk/purplerc/color/GtkIMHtml::receive-name-color",
 	"/plugins/gtk/purplerc/color/GtkIMHtml::highlight-name-color",
-	"/plugins/gtk/purplerc/color/GtkIMHtml::action-name-color"
+	"/plugins/gtk/purplerc/color/GtkIMHtml::action-name-color",
+	"/plugins/gtk/purplerc/color/GtkIMHtml::typing-ntofication-color"
 };
 static const gchar *color_prefs_set[] = {
-	"/plugins/gtk/purplerc/set/color/GtkWidget::cursor-color",
-	"/plugins/gtk/purplerc/set/color/GtkWidget::secondary-cursor-color",
 	"/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-color",
 	"/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-visited-color",
 	"/plugins/gtk/purplerc/set/color/GtkIMHtml::send-name-color",
 	"/plugins/gtk/purplerc/set/color/GtkIMHtml::receive-name-color",
 	"/plugins/gtk/purplerc/set/color/GtkIMHtml::highlight-name-color",
-	"/plugins/gtk/purplerc/set/color/GtkIMHtml::action-name-color"
+	"/plugins/gtk/purplerc/set/color/GtkIMHtml::action-name-color",
+	"/plugins/gtk/purplerc/set/color/GtkIMHtml::typing-notification-color"
 };
 static const gchar *color_names[] = {
-	N_("Cursor Color"),
-	N_("Secondary Cursor Color"),
 	N_("Hyperlink Color"),
 	N_("Visited Hyperlink Color"),
 	N_("Sent Message Name Color"),
 	N_("Received Message Name Color"),
 	N_("Highlighted Message Name Color"),
-	N_("Action Message Name Color")
+	N_("Action Message Name Color"),
+	N_("Typing Notification Color")
 };
 static GtkWidget *color_widgets[G_N_ELEMENTS(color_prefs)];
 
@@ -126,6 +123,10 @@
 
 	g_string_append(style_string, "style \"purplerc_style\"\n{");
 
+	if(purple_prefs_get_bool("/plugins/gtk/purplerc/set/disable-typing-notification")) {
+		g_string_append(style_string, "\tGtkIMHtml::typing-notification-enable = 0\n");
+	}
+
 	for (i = 0; i < G_N_ELEMENTS(color_prefs); i++) {
 		if (purple_prefs_get_bool(color_prefs_set[i])) {
 			const gchar *pref;
@@ -349,103 +350,27 @@
 }
 
 static GtkWidget *
-purplerc_get_config_frame(PurplePlugin *plugin)
+purplerc_make_interface_vbox(void)
 {
-	/* Note: Intentionally not using the size group argument to the
-	 * pidgin_prefs_labeled_* functions they only add the text label to
-	 * the size group not the whole thing, which isn't what I want. */
+	GtkWidget *vbox = NULL, *hbox = NULL, *check = NULL;
+	GtkSizeGroup *labelsg = NULL;
 	gint i;
-	gchar *tmp;
-	GtkWidget *check = NULL, *widget = NULL;
-	GtkWidget *ret = NULL, *hbox = NULL, *frame = NULL;
-	GtkSizeGroup *labelsg = NULL, *widgetsg = NULL, *buttonsg = NULL;
-#ifndef _WIN32
-	const gchar *homepath = "$HOME";
-#else
-	const gchar *homepath = "\%APPDATA\%";
-#endif
-
-	ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
-	gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
-
-	labelsg  = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
-	widgetsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
-	buttonsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
-
-	frame = pidgin_make_frame(ret, _("General"));
-	/* interface font */
-	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
-	gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0);
-
-	check = pidgin_prefs_checkbox(_("GTK+ Interface Font"),
-	                              "/plugins/gtk/purplerc/set/gtk-font-name",
-	                              hbox);
-	gtk_size_group_add_widget(labelsg, check);
-
-	widget = pidgin_pixbuf_button_from_stock("", GTK_STOCK_SELECT_FONT,
-	                                         PIDGIN_BUTTON_HORIZONTAL);
-	gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
-	gtk_size_group_add_widget(widgetsg, widget);
-	gtk_widget_set_sensitive(widget,
-	                         purple_prefs_get_bool("/plugins/gtk/purplerc/set/gtk-font-name"));
-	g_signal_connect(G_OBJECT(check), "toggled",
-	                 G_CALLBACK(pidgin_toggle_sensitive), widget);
-	g_signal_connect(G_OBJECT(widget), "clicked",
-	                 G_CALLBACK(purplerc_set_font), GINT_TO_POINTER(-1));
 
-	/* key theme name */
-	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
-	gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0);
-
-	check = pidgin_prefs_checkbox(_("GTK+ Text Shortcut Theme"),
-	                              "/plugins/gtk/purplerc/set/gtk-key-theme-name",
-	                              hbox);
-	gtk_size_group_add_widget(labelsg, check);
-
-	widget = pidgin_prefs_labeled_entry(hbox, "",
-	                                    "/plugins/gtk/purplerc/gtk-key-theme-name",
-	                                    NULL);
-	/*
-	gtk_size_group_add_widget(widgetsg, widget);
-	*/
-	gtk_widget_set_sensitive(widget,
-	                         purple_prefs_get_bool("/plugins/gtk/purplerc/set/gtk-key-theme-name"));
-	g_signal_connect(G_OBJECT(check), "toggled",
-	                 G_CALLBACK(pidgin_toggle_sensitive), widget);
+	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+	labelsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
-	/*
-	for (i = 0; i < G_N_ELEMENTS(widget_bool_prefs); i++) {
-		hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
-		gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0);
-
-		check = pidgin_prefs_checkbox(_(widget_bool_names[i]),
-		                              widget_bool_prefs_set[i], hbox);
-		gtk_size_group_add_widget(labelsg, check);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BORDER);
 
-		widget_bool_widgets[i] = pidgin_prefs_checkbox("", widget_bool_prefs[i], hbox);
-		*
-		gtk_size_group_add_widget(widgetsb, widget_bool_widgets[i]);
-		*
-		gtk_widget_set_sensitive(widget_bool_widgets[i],
-		                         purple_prefs_get_bool(widget_bool_prefs_set[i]));
-		g_signal_connect(G_OBJECT(check), "toggled",
-		                 G_CALLBACK(pidgin_toggle_sensitive),
-		                 widget_bool_widgets[i]);
-	}
-	*/
-
-	frame = pidgin_make_frame(ret, _("Interface colors"));
-	/* imhtml stuff */
 	for (i = 0; i < G_N_ELEMENTS(color_prefs); i++) {
 		hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
-		gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0);
+		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
 
 		check = pidgin_prefs_checkbox(_(color_names[i]),
 		                              color_prefs_set[i], hbox);
 		gtk_size_group_add_widget(labelsg, check);
 
-		color_widgets[i] = pidgin_pixbuf_button_from_stock("", GTK_STOCK_SELECT_COLOR, PIDGIN_BUTTON_HORIZONTAL);
-		gtk_size_group_add_widget(widgetsg, color_widgets[i]);
+		color_widgets[i] = pidgin_pixbuf_button_from_stock("",
+				GTK_STOCK_SELECT_COLOR, PIDGIN_BUTTON_HORIZONTAL);
 		gtk_box_pack_start(GTK_BOX(hbox), color_widgets[i], FALSE,
 		                   FALSE, 0);
 		gtk_widget_set_sensitive(color_widgets[i],
@@ -458,39 +383,51 @@
 		                 GINT_TO_POINTER(i));
 	}
 
-	frame = pidgin_make_frame(ret, _("Widget Sizes"));
-	/* widget size stuff */
-	for (i = 0; i < G_N_ELEMENTS(widget_size_prefs); i++) {
-		hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
-		gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0);
+	g_object_unref(labelsg);
+
+	return vbox;
+}
 
-		check = pidgin_prefs_checkbox(_(widget_size_names[i]),
-		                              widget_size_prefs_set[i], hbox);
-		gtk_size_group_add_widget(labelsg, check);
+static GtkWidget *
+purplerc_make_fonts_vbox(void)
+{
+	GtkWidget *vbox = NULL, *hbox = NULL, *check = NULL, *widget = NULL;
+	GtkSizeGroup *labelsg = NULL;
+	int i;
+
+	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+	labelsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BORDER);
 
-		widget_size_widgets[i] = pidgin_prefs_labeled_spin_button(hbox, "", widget_size_prefs[i], 0, 50, NULL);
-		/*
-		gtk_size_group_add_widget(widgetsg, widget_size_widgets[i]);
-		*/
-		gtk_widget_set_sensitive(widget_size_widgets[i],
-		                         purple_prefs_get_bool(widget_size_prefs_set[i]));
-		g_signal_connect(G_OBJECT(check), "toggled",
-		                 G_CALLBACK(pidgin_toggle_sensitive),
-		                 widget_size_widgets[i]);
-	}
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+	check = pidgin_prefs_checkbox(_("GTK+ Interface Font"),
+	                              "/plugins/gtk/purplerc/set/gtk-font-name",
+	                              hbox);
+	gtk_size_group_add_widget(labelsg, check);
 
-	frame = pidgin_make_frame(ret, _("Fonts"));
-	/* imhtml font stuff */
+	widget = pidgin_pixbuf_button_from_stock("", GTK_STOCK_SELECT_FONT,
+	                                         PIDGIN_BUTTON_HORIZONTAL);
+	gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
+	gtk_widget_set_sensitive(widget,
+	                         purple_prefs_get_bool("/plugins/gtk/purplerc/set/gtk-font-name"));
+	g_signal_connect(G_OBJECT(check), "toggled",
+	                 G_CALLBACK(pidgin_toggle_sensitive), widget);
+	g_signal_connect(G_OBJECT(widget), "clicked",
+	                 G_CALLBACK(purplerc_set_font), GINT_TO_POINTER(-1));
+
 	for (i = 0; i < G_N_ELEMENTS(font_prefs); i++) {
 		hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
-		gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0);
+		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
 
 		check = pidgin_prefs_checkbox(_(font_names[i]),
 		                              font_prefs_set[i], hbox);
 		gtk_size_group_add_widget(labelsg, check);
 
-		font_widgets[i] = pidgin_pixbuf_button_from_stock("", GTK_STOCK_SELECT_FONT, PIDGIN_BUTTON_HORIZONTAL);
-		gtk_size_group_add_widget(widgetsg, font_widgets[i]);
+		font_widgets[i] = pidgin_pixbuf_button_from_stock("",
+				GTK_STOCK_SELECT_FONT, PIDGIN_BUTTON_HORIZONTAL);
 		gtk_box_pack_start(GTK_BOX(hbox), font_widgets[i], FALSE,
 		                   FALSE, 0);
 		gtk_widget_set_sensitive(font_widgets[i],
@@ -503,6 +440,127 @@
 		                 GINT_TO_POINTER(i));
 	}
 
+	g_object_unref(labelsg);
+
+	return vbox;
+}
+
+static GtkWidget *
+purplerc_make_misc_vbox(void)
+{
+	/* Note: Intentionally not using the size group argument to the
+	 * pidgin_prefs_labeled_* functions they only add the text label to
+	 * the size group not the whole thing, which isn't what I want. */
+	GtkWidget *vbox = NULL, *hbox = NULL, *check = NULL, *widget = NULL;
+	GtkSizeGroup *labelsg = NULL;
+	int i;
+
+	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+	labelsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BORDER);
+
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+	check = pidgin_prefs_checkbox(_("GTK+ Text Shortcut Theme"),
+	                              "/plugins/gtk/purplerc/set/gtk-key-theme-name",
+	                              hbox);
+	gtk_size_group_add_widget(labelsg, check);
+
+	widget = pidgin_prefs_labeled_entry(hbox, "",
+	                                    "/plugins/gtk/purplerc/gtk-key-theme-name",
+	                                    NULL);
+	gtk_widget_set_sensitive(widget,
+	                         purple_prefs_get_bool("/plugins/gtk/purplerc/set/gtk-key-theme-name"));
+	g_signal_connect(G_OBJECT(check), "toggled",
+	                 G_CALLBACK(pidgin_toggle_sensitive), widget);
+
+	for (i = 0; i < G_N_ELEMENTS(widget_size_prefs); i++) {
+		hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+		check = pidgin_prefs_checkbox(_(widget_size_names[i]),
+		                              widget_size_prefs_set[i], hbox);
+		gtk_size_group_add_widget(labelsg, check);
+
+		widget_size_widgets[i] = pidgin_prefs_labeled_spin_button(hbox, "", widget_size_prefs[i], 0, 50, NULL);
+		gtk_widget_set_sensitive(widget_size_widgets[i],
+		                         purple_prefs_get_bool(widget_size_prefs_set[i]));
+		g_signal_connect(G_OBJECT(check), "toggled",
+		                 G_CALLBACK(pidgin_toggle_sensitive),
+		                 widget_size_widgets[i]);
+	}
+
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+	check = pidgin_prefs_checkbox(_("Disable Typing Notification Text"),
+			"/plugins/gtk/purplerc/set/disable-typing-notification", hbox);
+
+	/* Widget boolean stuff */
+	/*
+	for (i = 0; i < G_N_ELEMENTS(widget_bool_prefs); i++) {
+		hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+		check = pidgin_prefs_checkbox(_(widget_bool_names[i]),
+		                              widget_bool_prefs_set[i], hbox);
+		gtk_size_group_add_widget(labelsg, check);
+
+		widget_bool_widgets[i] = pidgin_prefs_checkbox("", widget_bool_prefs[i], hbox);
+
+		gtk_widget_set_sensitive(widget_bool_widgets[i],
+		                         purple_prefs_get_bool(widget_bool_prefs_set[i]));
+		g_signal_connect(G_OBJECT(check), "toggled",
+		                 G_CALLBACK(pidgin_toggle_sensitive),
+		                 widget_bool_widgets[i]);
+	}
+	*/
+
+	g_object_unref(labelsg);
+
+	return vbox;
+}
+
+static GtkWidget *
+purplerc_get_config_frame(PurplePlugin *plugin)
+{
+	gchar *tmp;
+	GtkWidget *check = NULL, *label = NULL;
+	GtkWidget *ret = NULL, *hbox = NULL, *frame = NULL, *note = NULL;
+#ifndef _WIN32
+	const gchar *homepath = "$HOME";
+#else
+	const gchar *homepath = "\%APPDATA\%";
+#endif
+
+	ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+	note = gtk_notebook_new();
+	label = gtk_label_new(NULL);
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
+
+	gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
+
+	tmp = g_strdup_printf("<span weight=\"bold\">%s</span>", _("GTK+ Theme Control Settings"));
+	gtk_label_set_markup(GTK_LABEL(label), tmp);
+	g_free(tmp);
+
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(ret), hbox, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(ret), note, FALSE, FALSE, 0);
+
+	label = gtk_label_new(_("Colors"));
+	gtk_notebook_insert_page(GTK_NOTEBOOK(note), purplerc_make_interface_vbox(), label, -1);
+
+	label = gtk_label_new(_("Fonts"));
+	gtk_notebook_insert_page(GTK_NOTEBOOK(note), purplerc_make_fonts_vbox(), label, -1);
+
+	label = gtk_label_new(_("Miscellaneous"));
+	gtk_notebook_insert_page(GTK_NOTEBOOK(note), purplerc_make_misc_vbox(), label, -1);
+
+	gtk_box_pack_start(GTK_BOX(ret), gtk_hseparator_new(), TRUE, TRUE, 0);
+
 	frame = pidgin_make_frame(ret, _("Gtkrc File Tools"));
 
 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
@@ -512,22 +570,17 @@
 	                      homepath, G_DIR_SEPARATOR_S ".purple" G_DIR_SEPARATOR_S);
 	check = gtk_button_new_with_label(tmp);
 	g_free(tmp);
-	gtk_box_pack_start(GTK_BOX(hbox), check, FALSE, FALSE, 0);
-	gtk_size_group_add_widget(buttonsg, check);
+	gtk_box_pack_start(GTK_BOX(hbox), check, TRUE, TRUE, 0);
 	g_signal_connect(G_OBJECT(check), "clicked",
 	                 G_CALLBACK(purplerc_write), NULL);
 
 	check = gtk_button_new_with_label(_("Re-read gtkrc files"));
-	gtk_box_pack_start(GTK_BOX(hbox), check, FALSE, FALSE, 0);
-	gtk_size_group_add_widget(buttonsg, check);
+	gtk_box_pack_start(GTK_BOX(hbox), check, TRUE, TRUE, 0);
 	g_signal_connect(G_OBJECT(check), "clicked",
 	                 G_CALLBACK(purplerc_reread), NULL);
 
 	gtk_widget_show_all(ret);
 
-	g_object_unref(labelsg);
-	g_object_unref(widgetsg);
-	g_object_unref(buttonsg);
 
 	return ret;
 }
@@ -621,6 +674,15 @@
 		purple_prefs_add_bool(widget_bool_prefs_set[i], FALSE);
 	}
 	*/
+
+	purple_prefs_add_bool("/plugins/gtk/purplerc/disable-typing-notification", FALSE);
+	purple_prefs_add_bool("/plugins/gtk/purplerc/set/disable-typing-notification", FALSE);
+
+	/* remove old cursor color prefs */
+	purple_prefs_remove("/plugins/gtk/purplerc/color/GtkWidget::cursor-color");
+	purple_prefs_remove("/plugins/gtk/purplerc/color/GtkWidget::secondary-cursor-color");
+	purple_prefs_remove("/plugins/gtk/purplerc/set/color/GtkWidget::cursor-color");
+	purple_prefs_remove("/plugins/gtk/purplerc/set/color/GtkWidget::secondary-cursor-color");
 }
 
 PURPLE_INIT_PLUGIN(purplerc, purplerc_init, purplerc_info)
--- a/po/de.po	Sun May 31 18:39:19 2009 +0000
+++ b/po/de.po	Sat Jun 06 06:21:39 2009 +0000
@@ -11,9 +11,9 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-05-15 19:56+0200\n"
-"PO-Revision-Date: 2009-05-15 19:56+0200\n"
-"Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n"
+"POT-Creation-Date: 2009-06-01 12:31+0200\n"
+"PO-Revision-Date: 2009-06-01 12:31+0200\n"
+"Last-Translator: Björn Voigt <bjoern@cs.tu-berlin.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -1788,6 +1788,8 @@
 msgid "+++ %s signed off"
 msgstr "+++ %s hat sich abgemeldet"
 
+#. Unknown error
+#. Unknown error!
 msgid "Unknown error"
 msgstr "Unbekannter Fehler"
 
@@ -3257,8 +3259,8 @@
 msgid "_Password:"
 msgstr "_Passwort:"
 
-msgid "IRC nicks may not contain whitespace"
-msgstr "IRC-Nicknamen dürfen keine Leerzeichen enthalten"
+msgid "IRC nick and server may not contain whitespace"
+msgstr "IRC- Server und -Nicknamen dürfen keine Leerzeichen enthalten"
 
 #. 1. connect to server
 #. connect to the server
@@ -3741,6 +3743,10 @@
 msgid "Server does not use any supported authentication method"
 msgstr "Der Server benutzt keine der unterstützten Authentifizierungsmethoden"
 
+msgid "You require encryption, but it is not available on this server."
+msgstr ""
+"Sie fordern Verschlüsselung, aber diese ist auf dem Server nicht verfügbar."
+
 msgid "Invalid challenge from server"
 msgstr "Ungültige Challenge vom Server"
 
@@ -3748,15 +3754,13 @@
 msgstr "SASL-Fehler"
 
 msgid "The BOSH connection manager terminated your session."
-msgstr ""
-
-#, fuzzy
+msgstr "Der BOSH-Verbindungsmanager hat Ihre Sitzung beendet."
+
 msgid "No session ID given"
-msgstr "Kein Grund angegeben"
-
-#, fuzzy
+msgstr "Keine Sitzungs-ID angegeben"
+
 msgid "Unsupported version of BOSH protocol"
-msgstr "Nicht-unterstützte Version"
+msgstr "Nicht-unterstützte Version des BOSH-Protokolls"
 
 msgid "Unable to establish a connection with the server"
 msgstr "Die Verbindung mit dem Server konnte nicht hergestellt werden"
@@ -4006,7 +4010,7 @@
 msgid "Resource"
 msgstr "Ressource"
 
-#, fuzzy, c-format
+#, c-format
 msgid "%s ago"
 msgstr "vor %s"
 
@@ -4194,10 +4198,6 @@
 msgid "Roles:"
 msgstr "Funktion"
 
-msgid "You require encryption, but it is not available on this server."
-msgstr ""
-"Sie fordern Verschlüsselung, aber diese ist auf dem Server nicht verfügbar."
-
 msgid "Ping timeout"
 msgstr "Ping-Zeitüberschreitung"
 
@@ -5424,17 +5424,6 @@
 msgstr "Windows Live ID-Authentifikation:Ungültige Antwort"
 
 #, c-format
-msgid "%s is not a valid group."
-msgstr "%s ist keine gültige Gruppe."
-
-msgid "Unknown error."
-msgstr "Unbekannter Fehler."
-
-#, c-format
-msgid "%s on %s (%s)"
-msgstr "%s auf %s (%s)"
-
-#, c-format
 msgid "%s just sent you a Nudge!"
 msgstr "%s hat Sie gerade angestoßen!"
 
@@ -5475,15 +5464,12 @@
 msgid "Service Temporarily Unavailable."
 msgstr "Dienst momentan nicht verfügbar."
 
+msgid "Unknown error."
+msgstr "Unbekannter Fehler."
+
 msgid "Mobile message was not sent because it was too long."
 msgstr "Mobile Nachricht wurde nicht gesendet, da sie zu lang war."
 
-msgid "Unable to rename group"
-msgstr "Kann die Gruppe nicht umbenennen"
-
-msgid "Unable to delete group"
-msgstr "Kann die Gruppe nicht löschen"
-
 #, c-format
 msgid ""
 "The MSN server will shut down for maintenance in %d minute. You will "
@@ -5647,14 +5633,6 @@
 "Nachricht konnte nicht gesendet werden, da ein unbekannter Fehler "
 "aufgetreten ist:"
 
-#, c-format
-msgid "%s has added you to his or her buddy list."
-msgstr "Der Benutzer %s hat Sie zu seiner Buddy-Liste hinzugefügt."
-
-#, c-format
-msgid "%s has removed you from his or her buddy list."
-msgstr "Der Benutzer %s hat Sie von seiner Buddy-Liste gelöscht."
-
 msgid "Delete Buddy from Address Book?"
 msgstr "Buddy aus dem Adressbuch löschen?"
 
@@ -5684,6 +5662,28 @@
 msgstr "MSN-Protokoll-Plugin"
 
 #, c-format
+msgid "%s is not a valid group."
+msgstr "%s ist keine gültige Gruppe."
+
+#, c-format
+msgid "%s on %s (%s)"
+msgstr "%s auf %s (%s)"
+
+msgid "Unable to rename group"
+msgstr "Kann die Gruppe nicht umbenennen"
+
+msgid "Unable to delete group"
+msgstr "Kann die Gruppe nicht löschen"
+
+#, c-format
+msgid "%s has added you to his or her buddy list."
+msgstr "Der Benutzer %s hat Sie zu seiner Buddy-Liste hinzugefügt."
+
+#, c-format
+msgid "%s has removed you from his or her buddy list."
+msgstr "Der Benutzer %s hat Sie von seiner Buddy-Liste gelöscht."
+
+#, c-format
 msgid "No such user: %s"
 msgstr "Kein solcher Benutzer: %s"
 
@@ -6557,6 +6557,9 @@
 msgid "iChat AV"
 msgstr "iChat AV"
 
+msgid "Live Video"
+msgstr "Live-Video"
+
 msgid "Camera"
 msgstr "Kamera"
 
@@ -9556,6 +9559,30 @@
 msgid "Add buddy rejected"
 msgstr "Hinzufügen des Buddys zurückgewiesen"
 
+#. Some error in the received stream
+msgid "Received invalid data"
+msgstr "Ungültige Daten empfangen"
+
+#. Password incorrect
+msgid "Incorrect Password"
+msgstr "Falsches Passwort"
+
+#. security lock from too many failed login attempts
+msgid "Account locked: Too many failed login attempts"
+msgstr "Konto gesperrt: Zu viele erfolglose Login-Versuche"
+
+#. the username does not exist
+msgid "Username does not exist"
+msgstr "Benutzername existiert nicht"
+
+#. indicates a lock of some description
+msgid "Account locked: See the debug log"
+msgstr "Konto gesperrt: Sehen Sie in den Debug-Mitschnitt"
+
+#. username or password missing
+msgid "Username or password missing"
+msgstr "Benutzername oder Passwort fehlt"
+
 #, c-format
 msgid ""
 "The Yahoo server has requested the use of an unrecognized authentication "
@@ -10465,8 +10492,9 @@
 msgid "Please update the necessary fields."
 msgstr "Bitte aktualisieren Sie die erforderlichen Felder."
 
-msgid "Room _List"
-msgstr "Ra_umliste"
+#, fuzzy
+msgid "A_ccount"
+msgstr "Konto"
 
 msgid ""
 "Please enter the appropriate information about the chat you would like to "
@@ -10475,8 +10503,8 @@
 "Bitte geben Sie geeignete Informationen über den Chat ein, den Sie betreten "
 "wollen.\n"
 
-msgid "_Account:"
-msgstr "_Konto:"
+msgid "Room _List"
+msgstr "Ra_umliste"
 
 msgid "_Block"
 msgstr "_Sperren"
@@ -10596,7 +10624,7 @@
 msgstr "/Buddys/Anzeigen/_Offline-Buddys"
 
 msgid "/Buddies/Show/_Empty Groups"
-msgstr "/Buddys/Anzeigen/_leere Gruppen"
+msgstr "/Buddys/Anzeigen/_Leere Gruppen"
 
 msgid "/Buddies/Show/Buddy _Details"
 msgstr "/Buddys/Anzeigen/Buddy-_Details"
@@ -10837,7 +10865,7 @@
 msgstr "/Buddys/Anzeigen/Offline-Buddys"
 
 msgid "/Buddies/Show/Empty Groups"
-msgstr "/Buddys/Anzeigen/leere Gruppen"
+msgstr "/Buddys/Anzeigen/Leere Gruppen"
 
 msgid "/Buddies/Show/Buddy Details"
 msgstr "/Buddys/Anzeigen/Buddy-Details"
@@ -11410,6 +11438,9 @@
 msgid "Hungarian"
 msgstr "Ungarisch"
 
+msgid "Armenian"
+msgstr "Armenisch"
+
 msgid "Indonesian"
 msgstr "Indonesisch"
 
@@ -11506,6 +11537,9 @@
 msgid "Swedish"
 msgstr "Schwedisch"
 
+msgid "Swahili"
+msgstr "Swahili"
+
 msgid "Tamil"
 msgstr "Tamilisch"
 
@@ -12365,6 +12399,9 @@
 msgid "Pounce on Whom"
 msgstr "Bei wem alarmieren"
 
+msgid "_Account:"
+msgstr "_Konto:"
+
 msgid "_Buddy name:"
 msgstr "_Buddy-Name:"
 
@@ -13025,6 +13062,9 @@
 msgid "Custom Smiley Manager"
 msgstr "Verwaltung für benutzerdefinierte Smileys"
 
+msgid "Select Buddy Icon"
+msgstr "Buddy-Icon auswählen"
+
 msgid "Click to change your buddyicon for this account."
 msgstr "Klicken Sie hier, um Ihr Buddy-Icon für dieses Konto zu ändern."
 
@@ -14183,19 +14223,3 @@
 msgid "This plugin is useful for debbuging XMPP servers or clients."
 msgstr ""
 "Dieses Plugin ist nützlich zur Fehlersuche in XMPP-Servern oder -Clients."
-
-#~ msgid "Live Video"
-#~ msgstr "Live-Video"
-
-#~ msgid "Invite message"
-#~ msgstr "Einladungsnachricht"
-
-#~ msgid ""
-#~ "Please enter the name of the user you wish to invite,\n"
-#~ "along with an optional invite message."
-#~ msgstr ""
-#~ "Bitte geben Sie den Benutzernamen der Person ein, die Sie einladen "
-#~ "möchten zusammen mit einer optionalen Einladungsnachricht."
-
-#~ msgid "ST_UN server:"
-#~ msgstr "ST_UN Server:"