changeset 31292:47b6eda87723

propagate from branch 'im.pidgin.pidgin' (head 07d0765c444a097af45c2650f54323afb900a07b) to branch 'im.pidgin.soc.2010.msn-tlc' (head f3998422a4724ab424e4e2328f58fc0504856557)
author masca@cpw.pidgin.im
date Mon, 19 Jul 2010 21:11:32 +0000
parents e874875a74a7 (diff) c7fa7c7aca7d (current diff)
children 6814678f3c63
files libpurple/protocols/msn/dialog.c libpurple/protocols/msn/dialog.h libpurple/protocols/msn/notification.c libpurple/protocols/msn/session.c libpurple/protocols/msn/session.h libpurple/protocols/msn/slplink.c libpurple/protocols/msn/switchboard.c libpurple/protocols/msn/sync.c libpurple/protocols/msn/sync.h
diffstat 52 files changed, 751 insertions(+), 297 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Jul 19 21:05:06 2010 +0000
+++ b/ChangeLog	Mon Jul 19 21:11:32 2010 +0000
@@ -1,11 +1,17 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
 version 2.7.2 (??/??/????):
+	General:
+	* Use silent build rules for automake >1.11. You can enable verbose
+	  builds with the --disable-silent-rules configure option, or using
+	  make V=1.
+
 	libpurple:
 	* Fix the TURN server settings (broken in 2.7.0).
 
 	Pidgin:
 	* Re-focus the input area after clicking the attention toolbar button.
+	* Re-arrange media window to make it more netbook-friendly.
 
 	Finch:
 	* Rebindable 'suggest-next-page' and 'suggest-prev-page' actions for
@@ -21,17 +27,24 @@
 	  a fallback to legacy IQ authentication (broken in 2.7.0).
 	* Fix a crash when receiving custom emoticons that don't adhere to
 	  the specification.
+	* When initiating a file transfer, don't show resources that are certain
+	  to not support file transfers in the resource selection dialog.
+	* Fix connecting to servers using BOSH and authenticating with
+	  DIGEST-MD5 when libpurple was built with Cyrus SASL support.
 
 	Yahoo/Yahoo JAPAN:
 	* Renamed "Use account proxy for SSL connections" to "Use account proxy
 	  for HTTP and HTTPS requests" and tied the option to HTTP requests too.
-	* Properly detect HTTP proxy server use when the HTTP proxy is the global
-	  proxy server, an account-level non-HTTP proxy server is configured, and
-	  the "Use account proxy for HTTP and HTTPS requests" account option is
-	  turned off.  This fixes connecting for some HTTP proxy servers.
-	* Fall back to connecting to scsa.msg.yahoo.com (not configurable) if the
-	  HTTP-based connect server lookup fails.  This does not work for Yahoo
-	  JAPAN accounts.
+	* Properly detect HTTP proxy server use when the HTTP proxy is the
+	  global proxy server, an account-level non-HTTP proxy server is
+	  configured, and the "Use account proxy for HTTP and HTTPS requests"
+	  account option is turned off.  This fixes connecting for some HTTP
+	  proxy servers.
+	* Fall back to connecting to scsa.msg.yahoo.com (not configurable) if
+	  the HTTP-based connect server lookup fails.  This does not work for
+	  Yahoo JAPAN accounts.
+	* Fix file transfers that get stuck with "Waiting for transfer to
+	  begin".
 
 version 2.7.1 (05/29/2010):
 	General:
--- a/ChangeLog.API	Mon Jul 19 21:05:06 2010 +0000
+++ b/ChangeLog.API	Mon Jul 19 21:11:32 2010 +0000
@@ -7,6 +7,23 @@
 		  called for a protocol that doesn't support the underlying
 		  calls and the caller does not specify a failure callback.
 
+	Perl:
+		Added:
+		* Exposed log-subsystem signals.
+
+	Pidgin:
+		Changed:
+		* Changing the visibility (gtk_widget_hide/show) of
+		  the widgets in the GtkIMHtmlToolbar should now affect
+		  the visibility of the entries in the 'lean' view
+		  (the default toolbar view).
+
+	libgnt:
+		Added:
+		* gnt_tree_row_get_key, gnt_tree_row_get_next,
+		  gnt_tree_row_get_prev, gnt_tree_row_get_child and
+		  gnt_tree_row_get_parent.
+
 version 2.7.1 (05/29/2010):
 	* No changes
 
--- a/Makefile.am	Mon Jul 19 21:05:06 2010 +0000
+++ b/Makefile.am	Mon Jul 19 21:11:32 2010 +0000
@@ -100,18 +100,18 @@
 # creates, and also make sure that the shell command exits
 # successfully; the rm -f ensures both
 package_revision_raw.txt:
-	REAL_BLDDIR=$$PWD/$(top_builddir); \
+	$(AM_V_GEN)REAL_BLDDIR=$$PWD/$(top_builddir); \
 	(cd $(srcdir) && $$REAL_BLDDIR/mtn --root=. automate get_base_revision_id) 2>/dev/null >$@ \
 	|| (cd $(srcdir) && mtn --root=. automate get_base_revision_id) 2>/dev/null >$@ \
 	|| rm -f $@
 package_revision.h: package_revision_raw.txt
-	if test -f $<; then \
+	$(AM_V_GEN)if test -f $<; then \
 	  echo "#define REVISION \"`cat $<`\"" > $@; \
 	fi
-	if test ! -f $@ -a -f $(srcdir)/$@; then \
+	$(AM_V_at)if test ! -f $@ -a -f $(srcdir)/$@; then \
 	  cp $(srcdir)/$@ $@; \
 	fi
-	test -f $@ || echo "#define REVISION \"unknown\"" > $@
+	$(AM_V_at)test -f $@ || echo "#define REVISION \"unknown\"" > $@
 
 # This is a magic directive copy-and-pasted, then modified, from the
 # automake 1.9 manual, section 13.4, "Checking the distribution".
--- a/configure.ac	Mon Jul 19 21:05:06 2010 +0000
+++ b/configure.ac	Mon Jul 19 21:11:32 2010 +0000
@@ -52,10 +52,10 @@
           [purple_major_version.purple_minor_version.purple_micro_version])
 m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix]))
 
-m4_define([gnt_lt_current], [7])
+m4_define([gnt_lt_current], [8])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [7])
-m4_define([gnt_micro_version], [2])
+m4_define([gnt_minor_version], [8])
+m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
@@ -75,6 +75,8 @@
 AC_CANONICAL_HOST
 AC_CONFIG_HEADERS([config.h])
 AM_INIT_AUTOMAKE([1.9 -Wno-portability dist-bzip2])
+dnl TODO: Always use AM_SILENT_RULES when we depend on automake >= 1.11
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
 
 PURPLE_MAJOR_VERSION=purple_major_version
 PURPLE_MINOR_VERSION=purple_minor_version
@@ -660,6 +662,10 @@
 		for location in $ac_ncurses_includes $NCURSES_HEADERS /usr/include/ncursesw /usr/include
 		do
 			f="$location/ncurses.h"
+			orig_CFLAGS="$CFLAGS"
+			orig_CPPFLAGS="$CPPFLAGS"
+			CFLAGS="$CFLAGS -I$location"
+			CPPFLAGS="$CPPFLAGS -I$location"
 			AC_CHECK_HEADER($f,[
 				AC_MSG_CHECKING([if $f supports wide characters])
 				AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
@@ -678,9 +684,13 @@
 					fi
 
 					found_ncurses_h=yes
+					CFLAGS="$orig_CFLAGS"
+					CPPFLAGS="$orig_CPPFLAGS"
 					AC_MSG_RESULT([yes])
 					break
 				], [
+					CFLAGS="$orig_CFLAGS"
+					CPPFLAGS="$orig_CPPFLAGS"
 					AC_MSG_RESULT([no])
 				])
 			])
--- a/doc/finch.1.in	Mon Jul 19 21:05:06 2010 +0000
+++ b/doc/finch.1.in	Mon Jul 19 21:11:32 2010 +0000
@@ -59,7 +59,7 @@
 Display the version information window.
 
 .SH GNT Shortcuts
-You can use the following shortcuts (see the "\*QWidget Actions\*U" section for a more complete list):
+You can use the following shortcuts (see the "Widget Actions" section for a more complete list):
 .TP
 .B Alt \+ a
 Bring up a list of available actions. You can use this list to access the
@@ -507,6 +507,10 @@
 .br
 a-/ = help-for-widget
 .br
+a-c-j = window-scroll-down
+.br
+a-c-k = window-scroll-up
+.br
 # The following action is still incomplete, and doesn't have a default binding
 .br
 # switch-window-n
--- a/finch/finch.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/finch/finch.c	Mon Jul 19 21:11:32 2010 +0000
@@ -252,6 +252,7 @@
 	gboolean opt_version = FALSE;
 	char *opt_config_dir_arg = NULL;
 	gboolean debug_enabled = FALSE;
+	struct stat st;
 
 	struct option long_options[] = {
 		{"config",   required_argument, NULL, 'c'},
@@ -361,6 +362,8 @@
 	purple_idle_set_ui_ops(finch_idle_get_ui_ops());
 
 	path = g_build_filename(purple_user_dir(), "plugins", NULL);
+	if (!g_stat(path, &st))
+		g_mkdir(path, S_IRUSR | S_IWUSR | S_IXUSR);
 	purple_plugins_add_search_path(path);
 	g_free(path);
 
--- a/finch/gntaccount.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/finch/gntaccount.c	Mon Jul 19 21:11:32 2010 +0000
@@ -125,7 +125,8 @@
 
 	if (value == NULL || *value == '\0')
 	{
-		purple_notify_error(NULL, _("Error"), _("Account was not added"),
+		purple_notify_error(NULL, _("Error"),
+				dialog->account ? _("Account was not modified") : _("Account was not added"),
 				_("Username of an account must be non-empty."));
 		return;
 	}
@@ -160,8 +161,28 @@
 		account = dialog->account;
 
 		/* Protocol */
-		purple_account_set_protocol_id(account, purple_plugin_get_id(plugin));
-		purple_account_set_username(account, username->str);
+		if (purple_account_is_disconnected(account)) {
+			purple_account_set_protocol_id(account, purple_plugin_get_id(plugin));
+			purple_account_set_username(account, username->str);
+		} else {
+			const char *old = purple_account_get_protocol_id(account);
+			char *oldprpl;
+			if (strcmp(old, purple_plugin_get_id(plugin))) {
+				purple_notify_error(NULL, _("Error"), _("Account was not modified"),
+						_("The account's protocol cannot be changed while it is connected to the server."));
+				return;
+			}
+
+			oldprpl = g_strdup(purple_normalize(account, purple_account_get_username(account)));
+			if (g_utf8_collate(oldprpl, purple_normalize(account, username->str))) {
+				purple_notify_error(NULL, _("Error"), _("Account was not modified"),
+						_("The account's username cannot be changed while it is connected to the server."));
+				g_free(oldprpl);
+				return;
+			}
+			g_free(oldprpl);
+			purple_account_set_username(account, username->str);
+		}
 	}
 	g_string_free(username, TRUE);
 
@@ -245,6 +266,17 @@
 		}
 	}
 
+	/* In case of a new account, the 'Accounts' window is updated from the account-added
+	 * callback. In case of changes in an existing account, we need to explicitly do it
+	 * here.
+	 */
+	if (dialog->account != NULL && accounts.window) {
+		gnt_tree_change_text(GNT_TREE(accounts.tree), dialog->account,
+				0, purple_account_get_username(dialog->account));
+		gnt_tree_change_text(GNT_TREE(accounts.tree), dialog->account,
+				1, purple_account_get_protocol_name(dialog->account));
+	}
+
 	gnt_widget_destroy(dialog->window);
 }
 
@@ -603,7 +635,7 @@
 	button = gnt_button_new(_("Cancel"));
 	gnt_box_add_widget(GNT_BOX(hbox), button);
 	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gnt_widget_destroy), window);
-	
+
 	button = gnt_button_new(_("Save"));
 	gnt_box_add_widget(GNT_BOX(hbox), button);
 	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(save_account_cb), dialog);
--- a/finch/gntdebug.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/finch/gntdebug.c	Mon Jul 19 21:11:32 2010 +0000
@@ -77,7 +77,10 @@
 		}
 		return;
 	}
-	pipe(pipes);
+	if (pipe(pipes)) {
+		readhandle = -1;
+		return;
+	};
 	dup2(pipes[1], STDERR_FILENO);
 
 	stderrch = g_io_channel_unix_new(pipes[0]);
--- a/finch/gntrequest.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/finch/gntrequest.c	Mon Jul 19 21:11:32 2010 +0000
@@ -349,10 +349,9 @@
 			}
 			else if (type == PURPLE_REQUEST_FIELD_LIST)
 			{
-				GList *list = NULL;
+				GList *list = NULL, *iter;
 				if (purple_request_field_list_get_multi_select(field))
 				{
-					GList *iter;
 					GntWidget *tree = FINCH_GET_DATA(field);
 
 					iter = purple_request_field_list_get_items(field);
@@ -361,14 +360,23 @@
 						const char *text = iter->data;
 						gpointer key = purple_request_field_list_get_data(field, text);
 						if (gnt_tree_get_choice(GNT_TREE(tree), key))
-							list = g_list_prepend(list, key);
+							list = g_list_prepend(list, (gpointer)text);
 					}
 				}
 				else
 				{
 					GntWidget *combo = FINCH_GET_DATA(field);
 					gpointer data = gnt_combo_box_get_selected_data(GNT_COMBO_BOX(combo));
-					list = g_list_append(list, data);
+
+					iter = purple_request_field_list_get_items(field);
+					for (; iter; iter = iter->next) {
+						const char *text = iter->data;
+						gpointer key = purple_request_field_list_get_data(field, text);
+						if (key == data) {
+							list = g_list_prepend(list, (gpointer)text);
+							break;
+						}
+					}
 				}
 
 				purple_request_field_list_set_selected(field, list);
@@ -815,7 +823,7 @@
 	for (list = purple_request_fields_get_groups(allfields); list; list = list->next) {
 		PurpleRequestFieldGroup *group = list->data;
 		GList *fields = purple_request_field_group_get_fields(group);
-		
+
 		for (; fields ; fields = fields->next) {
 			PurpleRequestField *field = fields->data;
 			PurpleRequestFieldType type = purple_request_field_get_type(field);
@@ -826,6 +834,7 @@
 			switch (type) {
 				case PURPLE_REQUEST_FIELD_LIST:
 					val = purple_request_field_list_get_selected(field)->data;
+					val = purple_request_field_list_get_data(field, val);
 					break;
 				case PURPLE_REQUEST_FIELD_BOOLEAN:
 					val = GINT_TO_POINTER(purple_request_field_bool_get_value(field));
--- a/finch/gntsound.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/finch/gntsound.c	Mon Jul 19 21:11:32 2010 +0000
@@ -104,7 +104,8 @@
 	{PURPLE_SOUND_CHAT_YOU_SAY, N_("You talk in chat"), "send_chat_msg", "send.wav", NULL},
 	{PURPLE_SOUND_CHAT_SAY,     N_("Others talk in chat"), "chat_msg_recv", "receive.wav", NULL},
 	{PURPLE_SOUND_POUNCE_DEFAULT, NULL, "pounce_default", "alert.wav", NULL},
-	{PURPLE_SOUND_CHAT_NICK,    N_("Someone says your username in chat"), "nick_said", "alert.wav", NULL}
+	{PURPLE_SOUND_CHAT_NICK,    N_("Someone says your username in chat"), "nick_said", "alert.wav", NULL},
+	{PURPLE_SOUND_GOT_ATTENTION, N_("Attention received"), "got_attention", "alert.wav", NULL}
 };
 
 const char *
@@ -273,6 +274,14 @@
 		play_conv_event(conv, event);
 }
 
+static void
+got_attention_cb(PurpleAccount *account, const char *who,
+	PurpleConversation *conv, guint type, PurpleSoundEventID event)
+{
+	play_conv_event(conv, event);
+}
+
+
 /*
  * We mute sounds for the 10 seconds after you log in so that
  * you don't get flooded with sounds when the blist shows all
@@ -300,34 +309,22 @@
 static void
 initialize_profile(const char *name, PurplePrefType type, gconstpointer val, gpointer null)
 {
+	FinchSoundEvent *event;
 	if (purple_prefs_exists(make_pref("")))
 		return;
 
 	purple_prefs_add_none(make_pref(""));
 	purple_prefs_add_none(make_pref("/enabled"));
 	purple_prefs_add_none(make_pref("/file"));
-	purple_prefs_add_bool(make_pref("/enabled/login"), FALSE);
-	purple_prefs_add_path(make_pref("/file/login"), "");
-	purple_prefs_add_bool(make_pref("/enabled/logout"), FALSE);
-	purple_prefs_add_path(make_pref("/file/logout"), "");
-	purple_prefs_add_bool(make_pref("/enabled/im_recv"), FALSE);
-	purple_prefs_add_path(make_pref("/file/im_recv"), "");
-	purple_prefs_add_bool(make_pref("/enabled/first_im_recv"), FALSE);
-	purple_prefs_add_path(make_pref("/file/first_im_recv"), "");
-	purple_prefs_add_bool(make_pref("/enabled/send_im"), FALSE);
-	purple_prefs_add_path(make_pref("/file/send_im"), "");
-	purple_prefs_add_bool(make_pref("/enabled/join_chat"), FALSE);
-	purple_prefs_add_path(make_pref("/file/join_chat"), "");
-	purple_prefs_add_bool(make_pref("/enabled/left_chat"), FALSE);
-	purple_prefs_add_path(make_pref("/file/left_chat"), "");
-	purple_prefs_add_bool(make_pref("/enabled/send_chat_msg"), FALSE);
-	purple_prefs_add_path(make_pref("/file/send_chat_msg"), "");
-	purple_prefs_add_bool(make_pref("/enabled/chat_msg_recv"), FALSE);
-	purple_prefs_add_path(make_pref("/file/chat_msg_recv"), "");
-	purple_prefs_add_bool(make_pref("/enabled/nick_said"), FALSE);
-	purple_prefs_add_path(make_pref("/file/nick_said"), "");
-	purple_prefs_add_bool(make_pref("/enabled/pounce_default"), FALSE);
-	purple_prefs_add_path(make_pref("/file/pounce_default"), "");
+
+	for (event = sounds; event - sounds < PURPLE_NUM_SOUNDS; event++) {
+		char pref[512];
+		g_snprintf(pref, sizeof(pref), "/enabled/%s", event->pref);
+		purple_prefs_add_bool(make_pref(pref), FALSE);
+		g_snprintf(pref, sizeof(pref), "/file/%s", event->pref);
+		purple_prefs_add_path(make_pref(pref), "");
+	}
+
 	purple_prefs_add_bool(make_pref("/conv_focus"), FALSE);
 	purple_prefs_add_bool(make_pref("/mute"), FALSE);
 	purple_prefs_add_path(make_pref("/command"), "");
@@ -336,6 +333,25 @@
 }
 
 static void
+update_profiles(void)
+{
+	GList *list = finch_sound_get_profiles();
+	for (; list; list = g_list_delete_link(list, list)) {
+		char pname[512];
+
+		/* got_attention was added in libpurple 2.7.0 */
+		g_snprintf(pname, sizeof(pname), FINCH_PREFS_ROOT "/sound/profiles/%s%s",
+				(char *)list->data, "/enabled/got_attention");
+		purple_prefs_add_bool(pname, FALSE);
+		g_snprintf(pname, sizeof(pname), FINCH_PREFS_ROOT "/sound/profiles/%s%s",
+				(char *)list->data, "/file/got_attention");
+		purple_prefs_add_path(pname, "");
+
+		g_free(list->data);
+	}
+}
+
+static void
 finch_sound_init(void)
 {
 	void *gnt_sound_handle = finch_sound_get_handle();
@@ -356,7 +372,7 @@
 	purple_prefs_connect_callback(gnt_sound_handle, FINCH_PREFS_ROOT "/sound/actprofile", initialize_profile, NULL);
 	purple_prefs_trigger_callback(FINCH_PREFS_ROOT "/sound/actprofile");
 
-	
+
 #ifdef USE_GSTREAMER
 	purple_debug_info("sound", "Initializing sound output drivers.\n");
 #if (GST_VERSION_MAJOR > 0 || \
@@ -399,6 +415,11 @@
 	purple_signal_connect(conv_handle, "received-chat-msg",
 						gnt_sound_handle, PURPLE_CALLBACK(chat_msg_received_cb),
 						GINT_TO_POINTER(PURPLE_SOUND_CHAT_SAY));
+	purple_signal_connect(conv_handle, "got-attention",
+						gnt_sound_handle, PURPLE_CALLBACK(got_attention_cb),
+						GINT_TO_POINTER(PURPLE_SOUND_GOT_ATTENTION));
+
+	update_profiles();
 }
 
 static void
@@ -577,7 +598,8 @@
 	if ((event == PURPLE_SOUND_BUDDY_ARRIVE) && mute_login_sounds)
 		return;
 
-	if (event >= PURPLE_NUM_SOUNDS) {
+	if (event >= PURPLE_NUM_SOUNDS ||
+			event >= G_N_ELEMENTS(sounds)) {
 		purple_debug_error("sound", "got request for unknown sound: %d\n", event);
 		return;
 	}
--- a/finch/libgnt/configure.ac	Mon Jul 19 21:05:06 2010 +0000
+++ b/finch/libgnt/configure.ac	Mon Jul 19 21:11:32 2010 +0000
@@ -24,9 +24,9 @@
 # Make sure to update ../../configure.ac with libgnt version changes.
 #
 
-m4_define([gnt_lt_current], [7])
+m4_define([gnt_lt_current], [8])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [7])
+m4_define([gnt_minor_version], [8])
 m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
@@ -265,6 +265,10 @@
 	for location in $ac_ncurses_includes /usr/include/ncursesw /usr/include
 	do
 		f="$location/ncurses.h"
+		orig_CFLAGS="$CFLAGS"
+		orig_CPPFLAGS="$CPPFLAGS"
+		CFLAGS="$CFLAGS -I$location"
+		CPPFLAGS="$CPPFLAGS -I$location"
 		AC_CHECK_HEADER($f,[
 			AC_MSG_CHECKING([if $f supports wide characters])
 			AC_TRY_COMPILE([
@@ -283,9 +287,13 @@
 				fi
 
 				found_ncurses_h=yes
+				CFLAGS="$orig_CFLAGS"
+				CPPFLAGS="$orig_CPPFLAGS"
 				AC_MSG_RESULT([yes])
 				break
 			], [
+				CFLAGS="$orig_CFLAGS"
+				CPPFLAGS="$orig_CPPFLAGS"
 				AC_MSG_RESULT([no])
 			])
 		])
--- a/finch/libgnt/gntcombobox.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/finch/libgnt/gntcombobox.c	Mon Jul 19 21:11:32 2010 +0000
@@ -150,12 +150,11 @@
 gnt_combo_box_key_pressed(GntWidget *widget, const char *text)
 {
 	GntComboBox *box = GNT_COMBO_BOX(widget);
-	if (GNT_WIDGET_IS_FLAG_SET(box->dropdown->parent, GNT_WIDGET_MAPPED))
-	{
-		if (text[1] == 0)
-		{
-			switch (text[0])
-			{
+	gboolean showing = !!GNT_WIDGET_IS_FLAG_SET(box->dropdown->parent, GNT_WIDGET_MAPPED);
+
+	if (showing) {
+		if (text[1] == 0) {
+			switch (text[0]) {
 				case '\r':
 				case '\t':
 				case '\n':
@@ -166,8 +165,42 @@
 					return TRUE;
 			}
 		}
-		if (gnt_widget_key_pressed(box->dropdown, text))
-			return TRUE;
+	}
+
+	if (gnt_widget_key_pressed(box->dropdown, text)) {
+		if (!showing)
+			popup_dropdown(box);
+		return TRUE;
+	}
+
+	{
+#define SEARCH_IN_RANGE(start, end) do { \
+		GntTreeRow *row; \
+		for (row = start; row != end; \
+				row = gnt_tree_row_get_next(tree, row)) { \
+			gpointer key = gnt_tree_row_get_key(tree, row); \
+			GList *list = gnt_tree_get_row_text_list(tree, key); \
+			gboolean found = FALSE; \
+			found = (list->data && g_ascii_strncasecmp(text, list->data, len) == 0); \
+			g_list_foreach(list, (GFunc)g_free, NULL); \
+			g_list_free(list); \
+			if (found) { \
+				if (!showing) \
+					popup_dropdown(box); \
+				gnt_tree_set_selected(tree, key); \
+				return TRUE; \
+			} \
+		} \
+} while (0)
+
+		int len = strlen(text);
+		GntTree *tree = GNT_TREE(box->dropdown);
+		GntTreeRow *current = tree->current;
+
+		SEARCH_IN_RANGE(gnt_tree_row_get_next(tree, current), NULL);
+		SEARCH_IN_RANGE(tree->top, current);
+
+#undef SEARCH_IN_RANGE
 	}
 
 	return FALSE;
--- a/finch/libgnt/gnttree.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/finch/libgnt/gnttree.c	Mon Jul 19 21:11:32 2010 +0000
@@ -1926,3 +1926,33 @@
 	return (row && row->parent) ? row->parent->key : NULL;
 }
 
+gpointer gnt_tree_row_get_key(GntTree *tree, GntTreeRow *row)
+{
+	g_return_val_if_fail(row && row->tree == tree, NULL);
+	return row->key;
+}
+
+GntTreeRow * gnt_tree_row_get_next(GntTree *tree, GntTreeRow *row)
+{
+	g_return_val_if_fail(row && row->tree == tree, NULL);
+	return row->next;
+}
+
+GntTreeRow * gnt_tree_row_get_prev(GntTree *tree, GntTreeRow *row)
+{
+	g_return_val_if_fail(row && row->tree == tree, NULL);
+	return row->prev;
+}
+
+GntTreeRow * gnt_tree_row_get_child(GntTree *tree, GntTreeRow *row)
+{
+	g_return_val_if_fail(row && row->tree == tree, NULL);
+	return row->child;
+}
+
+GntTreeRow * gnt_tree_row_get_parent(GntTree *tree, GntTreeRow *row)
+{
+	g_return_val_if_fail(row && row->tree == tree, NULL);
+	return row->parent;
+}
+
--- a/finch/libgnt/gnttree.h	Mon Jul 19 21:05:06 2010 +0000
+++ b/finch/libgnt/gnttree.h	Mon Jul 19 21:11:32 2010 +0000
@@ -222,6 +222,61 @@
 GList * gnt_tree_get_row_text_list(GntTree *tree, gpointer key);
 
 /**
+ * Get the key of a row.
+ *
+ * @param tree   The tree
+ * @param row    The GntTreeRow object
+ *
+ * @return The key of the row.
+ * @since 2.8.0 (gnt), 2.7.2 (pidgin)
+ */
+gpointer gnt_tree_row_get_key(GntTree *tree, GntTreeRow *row);
+
+/**
+ * Get the next row.
+ *
+ * @param tree The tree
+ * @param row  The GntTreeRow object
+ *
+ * @return The next row.
+ * @since 2.8.0 (gnt), 2.7.2 (pidgin)
+ */
+GntTreeRow * gnt_tree_row_get_next(GntTree *tree, GntTreeRow *row);
+
+/**
+ * Get the previous row.
+ *
+ * @param tree The tree
+ * @param row  The GntTreeRow object
+ *
+ * @return The previous row.
+ * @since 2.8.0 (gnt), 2.7.2 (pidgin)
+ */
+GntTreeRow * gnt_tree_row_get_prev(GntTree *tree, GntTreeRow *row);
+
+/**
+ * Get the child row.
+ *
+ * @param tree The tree
+ * @param row  The GntTreeRow object
+ *
+ * @return The child row.
+ * @since 2.8.0 (gnt), 2.7.2 (pidgin)
+ */
+GntTreeRow * gnt_tree_row_get_child(GntTree *tree, GntTreeRow *row);
+
+/**
+ * Get the parent row.
+ *
+ * @param tree The tree
+ * @param row  The GntTreeRow object
+ *
+ * @return The parent row.
+ * @since 2.8.0 (gnt), 2.7.2 (pidgin)
+ */
+GntTreeRow * gnt_tree_row_get_parent(GntTree *tree, GntTreeRow *row);
+
+/**
  * Get a list of text of the current row.
  *
  * @param tree  The tree
--- a/finch/libgnt/gntutils.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/finch/libgnt/gntutils.c	Mon Jul 19 21:11:32 2010 +0000
@@ -385,7 +385,6 @@
 	const char *name;
 	char *content;
 	xmlNode *ch;
-	gboolean processed = FALSE;
 	char *url = NULL;
 	gboolean insert_nl_s = FALSE, insert_nl_e = FALSE;
 
@@ -426,17 +425,14 @@
 
 	for (ch = node->children; ch; ch = ch->next) {
 		if (ch->type == XML_ELEMENT_NODE) {
-			processed = TRUE;
 			util_parse_html_to_tv(ch, tv, flag);
+		} else if (ch->type == XML_TEXT_NODE) {
+			content = (char*)xmlNodeGetContent(ch);
+			gnt_text_view_append_text_with_flags(tv, content, flag);
+			xmlFree(content);
 		}
 	}
 
-	if (!processed) {
-		content = (char*)xmlNodeGetContent(node);
-		gnt_text_view_append_text_with_flags(tv, content, flag);
-		xmlFree(content);
-	}
-
 	if (url) {
 		char *href = g_strdup_printf(" (%s)", url);
 		gnt_text_view_append_text_with_flags(tv, href, flag);
--- a/libpurple/Makefile.am	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/Makefile.am	Mon Jul 19 21:11:32 2010 +0000
@@ -170,13 +170,11 @@
 purple_builtheaders = purple.h version.h marshallers.h
 
 marshallers.h: marshallers.list
-	@echo "Generating marshallers.h"
-	$(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --header > marshallers.h
+	$(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --header > marshallers.h
 
 marshallers.c: marshallers.list marshallers.h
-	@echo "Generating marshallers.c"
-	echo "#include \"marshallers.h\"" > marshallers.c
-	$(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --body >> marshallers.c
+	$(AM_V_GEN)echo "#include \"marshallers.h\"" > marshallers.c
+	$(AM_V_at)$(GLIB_GENMARSHAL) --prefix=purple_smarshal $(srcdir)/marshallers.list --body >> marshallers.c
 
 if ENABLE_DBUS
 
@@ -212,16 +210,16 @@
 	$(srcdir)/protocols/jabber/libxmpp.c
 
 dbus-types.c: dbus-analyze-types.py $(purple_build_coreheaders)
-	cat $(purple_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --pattern=PURPLE_DBUS_DEFINE_TYPE\(%s\) > $@
+	$(AM_V_GEN)cat $(purple_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --pattern=PURPLE_DBUS_DEFINE_TYPE\(%s\) > $@
 
 dbus-types.h: dbus-analyze-types.py $(purple_build_coreheaders)
-	cat $(purple_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --pattern=PURPLE_DBUS_DECLARE_TYPE\(%s\) > $@
+	$(AM_V_GEN)cat $(purple_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --pattern=PURPLE_DBUS_DECLARE_TYPE\(%s\) > $@
 
 dbus-bindings.c: dbus-analyze-functions.py $(dbus_exported)
-	cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py > $@
+	$(AM_V_GEN)cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py > $@
 
 dbus-signals.c: dbus-analyze-signals.py $(dbus_signals)
-	cat $(dbus_signals) | $(PYTHON) $(srcdir)/dbus-analyze-signals.py > $@
+	$(AM_V_GEN)cat $(dbus_signals) | $(PYTHON) $(srcdir)/dbus-analyze-signals.py > $@
 
 dbus-server.$(OBJEXT): dbus-bindings.c dbus-signals.c dbus-types.c dbus-types.h
 dbus-server.lo: dbus-bindings.c dbus-signals.c dbus-types.c dbus-types.h
@@ -236,11 +234,11 @@
 libpurple_client_la_LIBADD = $(DBUS_LIBS)
 
 purple-client-bindings.c: dbus-analyze-functions.py $(dbus_exported)
-	cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py --client > $@
+	$(AM_V_GEN)cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py --client > $@
 
 purple-client-bindings.h: dbus-analyze-types.py dbus-analyze-functions.py $(purple_coreheaders) $(addprefix media/, $(purple_mediaheaders)) $(purple_builtheaders) $(dbus_exported)
-	cat $(purple_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --keyword=enum --verbatim > $@
-	cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py --client --headers >> $@
+	$(AM_V_GEN)cat $(purple_build_coreheaders) | $(PYTHON) $(srcdir)/dbus-analyze-types.py --keyword=enum --verbatim > $@
+	$(AM_V_at)cat $(dbus_build_exported) | $(PYTHON) $(srcdir)/dbus-analyze-functions.py --client --headers >> $@
 
 $(libpurple_client_la_OBJECTS): purple-client-bindings.h purple-client-bindings.c
 
--- a/libpurple/certificate.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/certificate.c	Mon Jul 19 21:11:32 2010 +0000
@@ -99,7 +99,8 @@
 			         "that can verify it is currently trusted.");
 			break;
 		case PURPLE_CERTIFICATE_NOT_ACTIVATED:
-			return _("The certificate is not valid yet.");
+			return _("The certificate is not valid yet.  Check that your "
+			         "computer's date and time are accurate.");
 			break;
 		case PURPLE_CERTIFICATE_EXPIRED:
 			return _("The certificate has expired and should not be "
--- a/libpurple/connection.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/connection.c	Mon Jul 19 21:11:32 2010 +0000
@@ -516,7 +516,8 @@
 	account = data;
 	gc = purple_account_get_connection(account);
 
-	gc->disconnect_timeout = 0;
+	if (gc != NULL)
+		gc->disconnect_timeout = 0;
 
 	password = g_strdup(purple_account_get_password(account));
 	purple_account_disconnect(account);
--- a/libpurple/plugins/Makefile.mingw	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/plugins/Makefile.mingw	Mon Jul 19 21:11:32 2010 +0000
@@ -55,7 +55,7 @@
 	$(MAKE) -C $(SSL_PLUGIN) -f $(MINGW_MAKEFILE) install
 	cp *.dll $(PURPLE_INSTALL_PLUGINS_DIR)
 
-.c.dll:
+%.dll: %.c $(PURPLE_CONFIG_H) $(PURPLE_VERSION_H)
 	$(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@.o -c $<
 	$(CC) -shared $@.o $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $@
 
--- a/libpurple/plugins/perl/common/Log.xs	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/plugins/perl/common/Log.xs	Mon Jul 19 21:11:32 2010 +0000
@@ -27,6 +27,9 @@
 		newCONSTSUB(flags_stash, (char *)civ->name, newSViv(civ->iv));
 }
 
+Purple::Handle
+purple_log_get_handle()
+
 int
 purple_log_common_sizer(log)
 	Purple::Log log
--- a/libpurple/protocols/jabber/auth_cyrus.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/jabber/auth_cyrus.c	Mon Jul 19 21:11:32 2010 +0000
@@ -36,7 +36,7 @@
 {
 	purple_connection_error_reason(purple_account_get_connection(account),
 		PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
-		_("Server requires plaintext authentication over an unencrypted stream"));
+		_("Server may require plaintext authentication over an unencrypted stream"));
 }
 
 static void start_cyrus_wrapper(JabberStream *js)
@@ -240,8 +240,9 @@
 				 * it in plaintext, see if we can turn on
 				 * plaintext auth
 				 */
+				/* XXX Should we just check for PLAIN/LOGIN being offered mechanisms? */
 				} else if (!plaintext) {
-					char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection.  Allow this and continue authentication?"),
+					char *msg = g_strdup_printf(_("%s may require plaintext authentication over an unencrypted connection.  Allow this and continue authentication?"),
 							purple_account_get_username(account));
 					purple_request_yes_no(js->gc, _("Plaintext Authentication"),
 							_("Plaintext Authentication"),
--- a/libpurple/protocols/jabber/bosh.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/jabber/bosh.c	Mon Jul 19 21:11:32 2010 +0000
@@ -195,6 +195,11 @@
 	g_free(path);
 	conn->pipelining = TRUE;
 
+	if (purple_ip_address_is_valid(host))
+		js->serverFQDN = g_strdup(js->user->domain);
+	else
+		js->serverFQDN = g_strdup(host);
+
 	if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) {
 		purple_debug_info("jabber", "Ignoring unexpected username and password "
 		                            "in BOSH URL.\n");
--- a/libpurple/protocols/jabber/caps.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/jabber/caps.c	Mon Jul 19 21:11:32 2010 +0000
@@ -29,6 +29,7 @@
 #include "iq.h"
 #include "presence.h"
 #include "util.h"
+#include "xdata.h"
 
 #define JABBER_CAPS_FILENAME "xmpp-caps.xml"
 
@@ -41,20 +42,14 @@
 static GHashTable *nodetable = NULL; /* char *node -> JabberCapsNodeExts */
 static guint       save_timer = 0;
 
-/**
- *	Processes a query-node and returns a JabberCapsClientInfo object with all relevant info.
- *
- *	@param 	query 	A query object.
- *	@return 		A JabberCapsClientInfo object.
- */
-static JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query);
-
 /* Free a GList of allocated char* */
 static void
 free_string_glist(GList *list)
 {
-	g_list_foreach(list, (GFunc)g_free, NULL);
-	g_list_free(list);
+	while (list) {
+		g_free(list->data);
+		list = g_list_delete_link(list, list);
+	}
 }
 
 static JabberCapsNodeExts*
@@ -238,15 +233,15 @@
 	if(!capsdata)
 		return;
 
-	if (strcmp(capsdata->name, "capabilities") != 0) {
+	if (!g_str_equal(capsdata->name, "capabilities")) {
 		xmlnode_free(capsdata);
 		return;
 	}
 
-	for(client = capsdata->child; client; client = client->next) {
-		if(client->type != XMLNODE_TYPE_TAG)
+	for (client = capsdata->child; client; client = client->next) {
+		if (client->type != XMLNODE_TYPE_TAG)
 			continue;
-		if(!strcmp(client->name, "client")) {
+		if (g_str_equal(client->name, "client")) {
 			JabberCapsClientInfo *value = g_new0(JabberCapsClientInfo, 1);
 			JabberCapsTuple *key = (JabberCapsTuple*)&value->tuple;
 			xmlnode *child;
@@ -259,15 +254,15 @@
 			if (key->hash == NULL)
 				exts = jabber_caps_find_exts_by_node(key->node);
 
-			for(child = client->child; child; child = child->next) {
-				if(child->type != XMLNODE_TYPE_TAG)
+			for (child = client->child; child; child = child->next) {
+				if (child->type != XMLNODE_TYPE_TAG)
 					continue;
-				if(!strcmp(child->name,"feature")) {
+				if (g_str_equal(child->name, "feature")) {
 					const char *var = xmlnode_get_attrib(child, "var");
 					if(!var)
 						continue;
 					value->features = g_list_append(value->features,g_strdup(var));
-				} else if(!strcmp(child->name,"identity")) {
+				} else if (g_str_equal(child->name, "identity")) {
 					const char *category = xmlnode_get_attrib(child, "category");
 					const char *type = xmlnode_get_attrib(child, "type");
 					const char *name = xmlnode_get_attrib(child, "name");
@@ -284,15 +279,15 @@
 					id->lang = g_strdup(lang);
 
 					value->identities = g_list_append(value->identities,id);
-				} else if(!strcmp(child->name,"x")) {
+				} else if (g_str_equal(child->name, "x")) {
 					/* TODO: See #7814 -- this might cause problems if anyone
 					 * ever actually specifies forms. In fact, for this to
 					 * work properly, that bug needs to be fixed in
 					 * xmlnode_from_str, not the output version... */
 					value->forms = g_list_append(value->forms, xmlnode_copy(child));
-				} else if (!strcmp(child->name, "ext") && key->hash != NULL) {
+				} else if (g_str_equal(child->name, "ext") && key->hash != NULL) {
 					purple_debug_warning("jabber", "Ignoring exts when reading new-style caps\n");
-				} else if (!strcmp(child->name, "ext")) {
+				} else if (g_str_equal(child->name, "ext")) {
 					/* TODO: Do we care about reading in the identities listed here? */
 					const char *identifier = xmlnode_get_attrib(child, "identifier");
 					xmlnode *node;
@@ -304,7 +299,7 @@
 					for (node = child->child; node; node = node->next) {
 						if (node->type != XMLNODE_TYPE_TAG)
 							continue;
-						if (!strcmp(node->name, "feature")) {
+						if (g_str_equal(node->name, "feature")) {
 							const char *var = xmlnode_get_attrib(node, "var");
 							if (!var)
 								continue;
@@ -463,13 +458,13 @@
 		 * size in jabber_caps_calculate_hash is large enough. The cipher API
 		 * doesn't seem to offer a "Get the hash size" function(?).
 		 */
-		if (!strcmp(userdata->hash, "sha-1")) {
+		if (g_str_equal(userdata->hash, "sha-1")) {
 			hash = jabber_caps_calculate_hash(info, "sha1");
-		} else if (!strcmp(userdata->hash, "md5")) {
+		} else if (g_str_equal(userdata->hash, "md5")) {
 			hash = jabber_caps_calculate_hash(info, "md5");
 		}
 
-		if (!hash || strcmp(hash, userdata->ver)) {
+		if (!hash || !g_str_equal(hash, userdata->ver)) {
 			purple_debug_warning("jabber", "Could not validate caps info from "
 			                     "%s. Expected %s, got %s\n",
 			                     xmlnode_get_attrib(packet, "from"),
@@ -709,44 +704,6 @@
 }
 
 static gint
-jabber_identity_compare(gconstpointer a, gconstpointer b)
-{
-	const JabberIdentity *ac;
-	const JabberIdentity *bc;
-	gint cat_cmp;
-	gint typ_cmp;
-
-	ac = a;
-	bc = b;
-
-	if ((cat_cmp = strcmp(ac->category, bc->category)) == 0) {
-		if ((typ_cmp = strcmp(ac->type, bc->type)) == 0) {
-			if (!ac->lang && !bc->lang) {
-				return 0;
-			} else if (ac->lang && !bc->lang) {
-				return 1;
-			} else if (!ac->lang && bc->lang) {
-				return -1;
-			} else {
-				return strcmp(ac->lang, bc->lang);
-			}
-		} else {
-			return typ_cmp;
-		}
-	} else {
-		return cat_cmp;
-	}
-}
-
-static gchar *jabber_caps_get_formtype(const xmlnode *x) {
-	xmlnode *formtypefield;
-	formtypefield = xmlnode_get_child(x, "field");
-	while (formtypefield && strcmp(xmlnode_get_attrib(formtypefield, "var"), "FORM_TYPE")) formtypefield = xmlnode_get_next_twin(formtypefield);
-	formtypefield = xmlnode_get_child(formtypefield, "value");
-	return xmlnode_get_data(formtypefield);;
-}
-
-static gint
 jabber_xdata_compare(gconstpointer a, gconstpointer b)
 {
 	const xmlnode *aformtypefield = a;
@@ -755,8 +712,8 @@
 	char *bformtype;
 	int result;
 
-	aformtype = jabber_caps_get_formtype(aformtypefield);
-	bformtype = jabber_caps_get_formtype(bformtypefield);
+	aformtype = jabber_x_data_get_formtype(aformtypefield);
+	bformtype = jabber_x_data_get_formtype(bformtypefield);
 
 	result = strcmp(aformtype, bformtype);
 	g_free(aformtype);
@@ -764,20 +721,21 @@
 	return result;
 }
 
-static JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query)
+JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query)
 {
 	xmlnode *child;
 	JabberCapsClientInfo *info;
 
-	if (!query || strcmp(query->xmlns, NS_DISCO_INFO))
-		return 0;
+	if (!query || !g_str_equal(query->name, "query") ||
+			!purple_strequal(query->xmlns, NS_DISCO_INFO))
+		return NULL;
 
 	info = g_new0(JabberCapsClientInfo, 1);
 
 	for(child = query->child; child; child = child->next) {
 		if (child->type != XMLNODE_TYPE_TAG)
 			continue;
-		if (!strcmp(child->name,"identity")) {
+		if (g_str_equal(child->name, "identity")) {
 			/* parse identity */
 			const char *category = xmlnode_get_attrib(child, "category");
 			const char *type = xmlnode_get_attrib(child, "type");
@@ -795,13 +753,13 @@
 			id->lang = g_strdup(lang);
 
 			info->identities = g_list_append(info->identities, id);
-		} else if (!strcmp(child->name, "feature")) {
+		} else if (g_str_equal(child->name, "feature")) {
 			/* parse feature */
 			const char *var = xmlnode_get_attrib(child, "var");
 			if (var)
 				info->features = g_list_prepend(info->features, g_strdup(var));
-		} else if (!strcmp(child->name, "x")) {
-			if (child->xmlns && !strcmp(child->xmlns, "jabber:x:data")) {
+		} else if (g_str_equal(child->name, "x")) {
+			if (purple_strequal(child->xmlns, "jabber:x:data")) {
 				/* x-data form */
 				xmlnode *dataform = xmlnode_copy(child);
 				info->forms = g_list_append(info->forms, dataform);
@@ -848,9 +806,12 @@
 static void 
 append_escaped_string(PurpleCipherContext *context, const gchar *str)
 {
-	char *tmp = g_markup_escape_text(str, -1);
-	purple_cipher_context_append(context, (const guchar *)tmp, strlen(tmp));
-	g_free(tmp);
+	if (str && *str) {
+		char *tmp = g_markup_escape_text(str, -1);
+		purple_cipher_context_append(context, (const guchar *)tmp, strlen(tmp));
+		g_free(tmp);
+	}
+
 	purple_cipher_context_append(context, (const guchar *)"<", 1);
 }
 
@@ -904,7 +865,7 @@
 	/* concat x-data forms to the verification string */
 	for(node = info->forms; node; node = node->next) {
 		xmlnode *data = (xmlnode *)node->data;
-		gchar *formtype = jabber_caps_get_formtype(data);
+		gchar *formtype = jabber_x_data_get_formtype(data);
 		GList *fields = jabber_caps_xdata_get_fields(data);
 
 		/* append FORM_TYPE's field value to the verification string */
@@ -965,6 +926,10 @@
 	}
 
 	info.features = features;
+	/* TODO: This copy can go away, I think, since jabber_identities
+	 * is pre-sorted, so the sort in calculate_hash should be idempotent.
+	 * However, I want to test that. --darkrain
+	 */
 	info.identities = g_list_copy(jabber_identities);
 	info.forms = NULL;
 
@@ -989,7 +954,7 @@
 	for (node = accounts; node; node = node->next) {
 		PurpleAccount *account = node->data;
 		const char *prpl_id = purple_account_get_protocol_id(account);
-		if (!strcmp("prpl-jabber", prpl_id) && purple_account_is_connected(account)) {
+		if (g_str_equal("prpl-jabber", prpl_id) && purple_account_is_connected(account)) {
 			PurpleConnection *gc = purple_account_get_connection(account);
 			jabber_presence_send(gc->proto_data, TRUE);
 		}
--- a/libpurple/protocols/jabber/caps.h	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/jabber/caps.h	Mon Jul 19 21:11:32 2010 +0000
@@ -115,4 +115,15 @@
  */
 void jabber_caps_broadcast_change(void);
 
+/**
+ * Parse the <query/> element from an IQ stanza into a JabberCapsClientInfo
+ * struct.
+ *
+ * Exposed for tests
+ *
+ * @param query The 'query' element from an IQ reply stanza.
+ * @returns A JabberCapsClientInfo struct, or NULL on error
+ */
+JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query);
+
 #endif /* PURPLE_JABBER_CAPS_H_ */
--- a/libpurple/protocols/jabber/jabber.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Mon Jul 19 21:11:32 2010 +0000
@@ -1962,20 +1962,53 @@
 	}
 }
 
-void jabber_add_identity(const gchar *category, const gchar *type, const gchar *lang, const gchar *name) {
+gint
+jabber_identity_compare(gconstpointer a, gconstpointer b)
+{
+	const JabberIdentity *ac;
+	const JabberIdentity *bc;
+	gint cat_cmp;
+	gint typ_cmp;
+
+	ac = a;
+	bc = b;
+
+	if ((cat_cmp = strcmp(ac->category, bc->category)) == 0) {
+		if ((typ_cmp = strcmp(ac->type, bc->type)) == 0) {
+			if (!ac->lang && !bc->lang) {
+				return 0;
+			} else if (ac->lang && !bc->lang) {
+				return 1;
+			} else if (!ac->lang && bc->lang) {
+				return -1;
+			} else {
+				return strcmp(ac->lang, bc->lang);
+			}
+		} else {
+			return typ_cmp;
+		}
+	} else {
+		return cat_cmp;
+	}
+}
+
+void jabber_add_identity(const gchar *category, const gchar *type,
+                         const gchar *lang, const gchar *name)
+{
 	GList *identity;
 	JabberIdentity *ident;
+
 	/* both required according to XEP-0030 */
 	g_return_if_fail(category != NULL);
 	g_return_if_fail(type != NULL);
 
-	for(identity = jabber_identities; identity; identity = identity->next) {
-		JabberIdentity *ident = (JabberIdentity*)identity->data;
-		if (!strcmp(ident->category, category) &&
-		    !strcmp(ident->type, type) &&
-		    ((!ident->lang && !lang) || (ident->lang && lang && !strcmp(ident->lang, lang)))) {
+	/* Check if this identity is already there... */
+	for (identity = jabber_identities; identity; identity = identity->next) {
+		JabberIdentity *id = identity->data;
+		if (g_str_equal(id->category, category) &&
+			g_str_equal(id->type, type) &&
+			purple_strequal(id->lang, lang))
 			return;
-		}
 	}
 
 	ident = g_new0(JabberIdentity, 1);
@@ -1983,7 +2016,8 @@
 	ident->type = g_strdup(type);
 	ident->lang = g_strdup(lang);
 	ident->name = g_strdup(name);
-	jabber_identities = g_list_prepend(jabber_identities, ident);
+	jabber_identities = g_list_insert_sorted(jabber_identities, ident,
+	                                         jabber_identity_compare);
 }
 
 static void jabber_identities_destroy(void)
@@ -3440,8 +3474,7 @@
 			for (iter = jb->resources; iter ; iter = g_list_next(iter)) {
 				JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data;
 
-				if (jabber_resource_has_capability(jbr,
-						"http://jabber.org/protocol/si/profile/file-transfer")
+				if (jabber_resource_has_capability(jbr, NS_SI_FILE_TRANSFER)
 			    	&& (jabber_resource_has_capability(jbr,
 			    			NS_BYTESTREAMS)
 			        	|| jabber_resource_has_capability(jbr, NS_IBB))) {
@@ -3743,7 +3776,7 @@
 	jabber_add_feature("http://jabber.org/protocol/muc", 0);
 	jabber_add_feature("http://jabber.org/protocol/muc#user", 0);
 	jabber_add_feature("http://jabber.org/protocol/si", 0);
-	jabber_add_feature("http://jabber.org/protocol/si/profile/file-transfer", 0);
+	jabber_add_feature(NS_SI_FILE_TRANSFER, 0);
 	jabber_add_feature(NS_XHTML_IM, 0);
 	jabber_add_feature(NS_PING, 0);
 
--- a/libpurple/protocols/jabber/jabber.h	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Mon Jul 19 21:11:32 2010 +0000
@@ -303,6 +303,9 @@
 
 /* what kind of additional features as returned from disco#info are supported? */
 extern GList *jabber_features;
+/* A sorted list of identities advertised.  Use jabber_add_identity to add
+ * so it remains sorted.
+ */
 extern GList *jabber_identities;
 
 void jabber_stream_features_parse(JabberStream *js, xmlnode *packet);
@@ -342,6 +345,11 @@
 void jabber_add_identity(const gchar *category, const gchar *type, const gchar *lang, const gchar *name);
 
 /**
+ * GCompareFunc for JabberIdentity structs.
+ */
+gint jabber_identity_compare(gconstpointer a, gconstpointer b);
+
+/**
  * Returns true if this connection is over a secure (SSL) stream. Use this
  * instead of checking js->gsc because BOSH stores its PurpleSslConnection
  * members in its own data structure.
--- a/libpurple/protocols/jabber/namespaces.h	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/jabber/namespaces.h	Mon Jul 19 21:11:32 2010 +0000
@@ -61,6 +61,9 @@
 #define NS_AVATAR_1_1_DATA      "urn:xmpp:avatar:data"
 #define NS_AVATAR_1_1_METADATA  "urn:xmpp:avatar:metadata"
 
+/* XEP-0096 SI File Transfer */
+#define NS_SI_FILE_TRANSFER 	"http://jabber.org/protocol/si/profile/file-transfer"
+
 /* XEP-0124 Bidirectional-streams Over Synchronous HTTP (BOSH) */
 #define NS_BOSH "http://jabber.org/protocol/httpbind"
 
--- a/libpurple/protocols/jabber/si.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/jabber/si.c	Mon Jul 19 21:11:32 2010 +0000
@@ -1262,12 +1262,10 @@
 	xmlnode_set_namespace(si, "http://jabber.org/protocol/si");
 	jsx->stream_id = jabber_get_next_id(jsx->js);
 	xmlnode_set_attrib(si, "id", jsx->stream_id);
-	xmlnode_set_attrib(si, "profile",
-			"http://jabber.org/protocol/si/profile/file-transfer");
+	xmlnode_set_attrib(si, "profile", NS_SI_FILE_TRANSFER);
 
 	file = xmlnode_new_child(si, "file");
-	xmlnode_set_namespace(file,
-			"http://jabber.org/protocol/si/profile/file-transfer");
+	xmlnode_set_namespace(file, NS_SI_FILE_TRANSFER);
 	xmlnode_set_attrib(file, "name", xfer->filename);
 	g_snprintf(buf, sizeof(buf), "%" G_GSIZE_FORMAT, xfer->size);
 	xmlnode_set_attrib(file, "size", buf);
@@ -1488,7 +1486,7 @@
 
 		if (jabber_resource_has_capability(jbr, NS_IBB))
 			jsx->stream_method |= STREAM_METHOD_IBB;
-		if (jabber_resource_has_capability(jbr, "http://jabber.org/protocol/si/profile/file-transfer")) {
+		if (jabber_resource_has_capability(jbr, NS_SI_FILE_TRANSFER)) {
 			jabber_si_xfer_send_request(xfer);
 			return;
 		}
@@ -1523,7 +1521,8 @@
 		JabberBuddy *jb;
 		JabberBuddyResource *jbr = NULL;
 		char *resource;
-
+		GList *resources = NULL;
+		
 		if(NULL != (resource = jabber_get_resource(xfer->who))) {
 			/* they've specified a resource, no need to ask or
 			 * default or anything, just do it */
@@ -1535,7 +1534,22 @@
 
 		jb = jabber_buddy_find(jsx->js, xfer->who, TRUE);
 
-		if(!jb || !jb->resources) {
+		if (jb) {
+			GList *l;
+
+			for (l = jb->resources ; l ; l = g_list_next(l)) {
+				jbr = l->data;
+
+				if (!jabber_resource_know_capabilities(jbr) ||
+				    (jabber_resource_has_capability(jbr, NS_SI_FILE_TRANSFER)
+				     && (jabber_resource_has_capability(jbr, NS_BYTESTREAMS)
+				         || jabber_resource_has_capability(jbr, NS_IBB)))) {
+					resources = g_list_append(resources, jbr);
+				}
+			}
+		}
+		
+		if (!resources) {
 			/* no resources online, we're trying to send to someone
 			 * whose presence we're not subscribed to, or
 			 * someone who is offline.  Let's inform the user */
@@ -1551,13 +1565,11 @@
 
 			purple_notify_error(jsx->js->gc, _("File Send Failed"), _("File Send Failed"), msg);
 			g_free(msg);
-		} else if(!jb->resources->next) {
+		} else if (g_list_length(resources) == 1) {
 			/* only 1 resource online (probably our most common case)
 			 * so no need to ask who to send to */
-			jbr = jb->resources->data;
-
+			jbr = resources->data;
 			do_transfer_send(xfer, jbr->name);
-
 		} else {
 			/* we've got multiple resources, we need to pick one to send to */
 			GList *l;
@@ -1565,11 +1577,9 @@
 			PurpleRequestFields *fields = purple_request_fields_new();
 			PurpleRequestField *field = purple_request_field_choice_new("resource", _("Resource"), 0);
 			PurpleRequestFieldGroup *group = purple_request_field_group_new(NULL);
-
-			for(l = jb->resources; l; l = l->next)
-			{
+			
+			for(l = resources; l; l = l->next) {
 				jbr = l->data;
-
 				purple_request_field_choice_add(field, jbr->name);
 			}
 
@@ -1583,6 +1593,8 @@
 
 			g_free(msg);
 		}
+
+		g_list_free(resources);
 	} else {
 		xmlnode *si, *feature, *x, *field, *value;
 
@@ -1695,7 +1707,7 @@
 	size_t filesize = 0;
 
 	if(!(profile = xmlnode_get_attrib(si, "profile")) ||
-			strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer"))
+			strcmp(profile, NS_SI_FILE_TRANSFER))
 		return;
 
 	if(!(stream_id = xmlnode_get_attrib(si, "id")))
--- a/libpurple/protocols/jabber/xdata.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/jabber/xdata.c	Mon Jul 19 21:11:32 2010 +0000
@@ -411,4 +411,30 @@
 	return handle;
 }
 
+gchar *
+jabber_x_data_get_formtype(const xmlnode *form)
+{
+	xmlnode *field;
 
+	g_return_val_if_fail(form != NULL, NULL);
+
+	for (field = xmlnode_get_child((xmlnode *)form, "field"); field;
+			field = xmlnode_get_next_twin(field)) {
+		const char *var = xmlnode_get_attrib(field, "var");
+		if (purple_strequal(var, "FORM_TYPE")) {
+			xmlnode *value = xmlnode_get_child(field, "value");
+			if (value)
+				return xmlnode_get_data(value);
+			else
+				/* An interesting corner case... Looking for a second
+				 * FORM_TYPE would be more considerate, but I'm in favor
+				 * of not helping broken clients.
+				 */
+				return NULL;
+		}
+	}
+
+	/* Erm, none found :( */
+	return NULL;
+}
+
--- a/libpurple/protocols/jabber/xdata.h	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/jabber/xdata.h	Mon Jul 19 21:11:32 2010 +0000
@@ -37,4 +37,19 @@
 void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data);
 void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data);
 
+/*
+ * Return the form type (the CDATA of the value child of the FORM_TYPE
+ * field entry.
+ * E.g., for the following, "http://jabber.org/protocol/muc#roominfo".
+ * <x xmlns='jabber:x:data' type='result'>
+ *  <field var='FORM_TYPE' type='hidden'>
+ *    <value>http://jabber.org/protocol/muc#roominfo</value>
+ *  </field>
+ * </x>
+ *
+ * @param form The xmlnode for the form (the 'x' element)
+ * @returns The FORM_TYPE.  Must be freed by caller.
+ */
+gchar *jabber_x_data_get_formtype(const xmlnode *form);
+
 #endif /* PURPLE_JABBER_XDATA_H_ */
--- a/libpurple/protocols/msn/notification.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/msn/notification.c	Mon Jul 19 21:11:32 2010 +0000
@@ -431,7 +431,7 @@
 
 /* add contact to xmlnode */
 static void
-msn_add_contact_xml(MsnSession *session, xmlnode *mlNode,const char *passport, MsnListOp list_op, MsnNetwork networkId)
+msn_add_contact_xml(xmlnode *mlNode, const char *passport, MsnListOp list_op, MsnNetwork networkId)
 {
 	xmlnode *d_node,*c_node;
 	char **tokens;
@@ -565,8 +565,8 @@
 
 		adl_node = xmlnode_new("ml");
 		xmlnode_set_attrib(adl_node, "l", "1");
-		msn_add_contact_xml(session, adl_node, passport,
-				user->list_op & MSN_LIST_OP_MASK, network);
+		msn_add_contact_xml(adl_node, passport,
+		                    user->list_op & MSN_LIST_OP_MASK, network);
 		payload = xmlnode_to_str(adl_node, &payload_len);
 		msn_notification_post_adl(session->notification->cmdproc, payload, payload_len);
 		g_free(payload);
@@ -622,8 +622,9 @@
 		}
 
 		if (user->networkid != MSN_NETWORK_UNKNOWN) {
-			msn_add_contact_xml(session, adl_node, user->passport,
-				user->list_op & MSN_LIST_OP_MASK, user->networkid);
+			msn_add_contact_xml(adl_node, user->passport,
+			                    user->list_op & MSN_LIST_OP_MASK,
+			                    user->networkid);
 
 			/* each ADL command may contain up to 150 contacts */
 			if (++adl_count % 150 == 0) {
@@ -652,8 +653,7 @@
 				purple_debug_info("msn", "Adding FQY address, count is %d\n",
 				                  session->adl_fqy);
 
-			msn_add_contact_xml(session, fqy_node, user->passport,
-				0, user->networkid);
+			msn_add_contact_xml(fqy_node, user->passport, 0, user->networkid);
 
 			/* each FQY command may contain up to 150 contacts, probably */
 			if (++fqy_count % 150 == 0) {
@@ -696,6 +696,8 @@
 	xmlnode_free(adl_node);
 	xmlnode_free(fqy_node);
 
+	msn_session_activate_login_timeout(session);
+
 	pc = purple_account_get_connection(session->account);
 	display_name = purple_connection_get_display_name(pc);
 	if (display_name
@@ -2255,8 +2257,7 @@
 	node = xmlnode_new("ml");
 	node->child = NULL;
 
-	msn_add_contact_xml(session, node, passport,
-	                    addrem->list_op, network);
+	msn_add_contact_xml(node, passport, addrem->list_op, network);
 
 	payload = xmlnode_to_str(node, &payload_len);
 	xmlnode_free(node);
@@ -2286,8 +2287,7 @@
 	adl_node = xmlnode_new("ml");
 	adl_node->child = NULL;
 
-	msn_add_contact_xml(notification->session, adl_node, user->passport,
-	                    list_op, user->networkid);
+	msn_add_contact_xml(adl_node, user->passport, list_op, user->networkid);
 
 	payload = xmlnode_to_str(adl_node, &payload_len);
 	xmlnode_free(adl_node);
@@ -2325,8 +2325,7 @@
 	rml_node = xmlnode_new("ml");
 	rml_node->child = NULL;
 
-	msn_add_contact_xml(notification->session, rml_node, user->passport,
-	                    list_op, user->networkid);
+	msn_add_contact_xml(rml_node, user->passport, list_op, user->networkid);
 
 	payload = xmlnode_to_str(rml_node, &payload_len);
 	xmlnode_free(rml_node);
--- a/libpurple/protocols/msn/session.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/msn/session.c	Mon Jul 19 21:11:32 2010 +0000
@@ -134,6 +134,11 @@
 	if (!session->connected)
 		return;
 
+	if (session->login_timeout) {
+		purple_timeout_remove(session->login_timeout);
+		session->login_timeout = 0;
+	}
+
 	session->connected = FALSE;
 
 	while (session->switches != NULL)
@@ -261,6 +266,28 @@
 	return swboard;
 }
 
+static gboolean
+msn_login_timeout_cb(gpointer data)
+{
+	MsnSession *session = data;
+	/* This forces the login process to finish, even though we haven't heard
+	   a response for our FQY requests yet. We'll at least end up online to the
+	   people we've already added. The rest will follow later. */
+	msn_session_finish_login(session);
+	session->login_timeout = 0;
+	return FALSE;
+}
+
+void
+msn_session_activate_login_timeout(MsnSession *session)
+{
+	if (!session->logged_in) {
+		session->login_timeout =
+			purple_timeout_add_seconds(MSN_LOGIN_FQY_TIMEOUT,
+			                           msn_login_timeout_cb, session);
+	}
+}
+
 static void
 msn_session_sync_users(MsnSession *session)
 {
@@ -365,6 +392,9 @@
 			msg = g_strdup_printf(_("Unable to authenticate: %s"),
 								  (info == NULL ) ?
 								  _("Unknown error") : info);
+			/* Clear the password if it isn't being saved */
+			if (!purple_account_get_remember_password(session->account))
+				purple_account_set_password(session->account, NULL);
 			break;
 		case MSN_ERROR_BAD_BLIST:
 			reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
--- a/libpurple/protocols/msn/session.h	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/msn/session.h	Mon Jul 19 21:11:32 2010 +0000
@@ -59,6 +59,8 @@
 
 #define MSN_LOGIN_STEPS MSN_LOGIN_STEP_END
 
+#define MSN_LOGIN_FQY_TIMEOUT 30
+
 #include "nexus.h"
 #include "notification.h"
 #include "oim.h"
@@ -78,6 +80,7 @@
 	gboolean connected;
 	gboolean logged_in; /**< A temporal flag to ignore local buddy list adds. */
 	int      adl_fqy; /**< A count of ADL/FQY so status is only changed once. */
+	guint    login_timeout; /**< Timeout to force status change if ADL/FQY fail. */
 	gboolean destroying; /**< A flag that states if the session is being destroyed. */
 	gboolean http_method;
 
@@ -208,7 +211,16 @@
 						   const char *info);
 
 /**
- * Sets the current step in the login proccess.
+ * Starts a timeout to initiate finishing login. Sometimes the server ignores
+ * our FQY requests, so this forces ourselves online eventually.
+ *
+ * @param session The MSN session.
+ */
+void
+msn_session_activate_login_timeout(MsnSession *session);
+
+/**
+ * Sets the current step in the login process.
  *
  * @param session The MSN session.
  * @param step The current step.
--- a/libpurple/protocols/msn/slplink.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/msn/slplink.c	Mon Jul 19 21:11:32 2010 +0000
@@ -221,11 +221,16 @@
 		slplink->swboard = NULL;
 	}
 
-	/* The slplink has no slpcalls in it, release it from the DC. */
-	if (slplink->slp_calls == NULL && slplink->dc != NULL) {
-		slplink->dc->slplink = NULL;
-		msn_dc_destroy(slplink->dc);
-		slplink->dc = NULL;
+	if (slplink->dc != NULL) {
+		if ((slplink->dc->state != DC_STATE_ESTABLISHED && slplink->dc->slpcall == slpcall)
+		 || (slplink->slp_calls == NULL)) {
+			/* The DC is not established and its corresponding slpcall is dead,
+			 * or the slplink has no slpcalls in it and no longer needs the DC.
+			 */
+			slplink->dc->slplink = NULL;
+			msn_dc_destroy(slplink->dc);
+			slplink->dc = NULL;
+		}
 	}
 }
 
--- a/libpurple/protocols/msn/switchboard.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Mon Jul 19 21:11:32 2010 +0000
@@ -647,8 +647,13 @@
 	else if ((swboard->current_users > 1) ||
 			 (purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
 	{
+		GList *passport;
 		/* This is a switchboard used for a chat */
 		purple_conv_chat_remove_user(PURPLE_CONV_CHAT(swboard->conv), user, NULL);
+
+		passport = g_list_find_custom(swboard->users, user, (GCompareFunc)strcmp);
+		g_free(passport->data);
+		swboard->users = g_list_delete_link(swboard->users, passport);
 		swboard->current_users--;
 		if (swboard->current_users == 0)
 			msn_switchboard_destroy(swboard);
--- a/libpurple/protocols/oscar/oscar.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Mon Jul 19 21:11:32 2010 +0000
@@ -2667,10 +2667,10 @@
 					g_free(rtfmsg);
 				}
 			}
-		} else if (args->info.rtfmsg.msgtype == 26)
+		} else if (args->info.rtfmsg.msgtype == 26) {
 			purple_debug_info("oscar", "Sending X-Status Reply\n");
 			icq_relay_xstatus(od, userinfo->bn, args->cookie);
-
+		}
 	}
 	else
 	{
--- a/libpurple/protocols/oscar/util.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/oscar/util.c	Mon Jul 19 21:11:32 2010 +0000
@@ -38,8 +38,8 @@
 static const char * const msgerrreason[] = {
 	N_("Invalid error"),
 	N_("Invalid SNAC"),
-	N_("Rate to host"),
-	N_("Rate to client"),
+	N_("Server rate limit exceeded"),
+	N_("Client rate limit exceeded"),
 	N_("Not logged in"),
 	N_("Service unavailable"),
 	N_("Service not defined"),
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Mon Jul 19 21:11:32 2010 +0000
@@ -983,8 +983,8 @@
 	struct yahoo_xfer_data *xd;
 	struct sockaddr_in *addr;
 	struct yahoo_packet *pkt;
-	long actaddr;
-	long a,b,c,d;
+	unsigned long actaddr;
+	unsigned char a,b,c,d;
 	PurpleConnection *gc;
 	PurpleAccount *account;
 	YahooData *yd;
@@ -1018,19 +1018,19 @@
 	/* TODO:actually, u must try with addr no.1 , if its not working addr no.2 ..... */
 	addr = hosts->data;
 	actaddr = addr->sin_addr.s_addr;
-	d = actaddr % 256;
-	actaddr = (actaddr - d) / 256;
-	c = actaddr % 256;
-	actaddr = (actaddr - c) / 256;
-	b = actaddr % 256;
-	actaddr = (actaddr - b) / 256;
-	a = actaddr;
+	d = actaddr & 0xff;
+	actaddr >>= 8;
+	c = actaddr & 0xff;
+	actaddr >>= 8;
+	b = actaddr & 0xff;
+	actaddr >>= 8;
+	a = actaddr & 0xff;
 	if(yd->jp)
 		xd->port = YAHOOJP_XFER_RELAY_PORT;
 	else
 		xd->port = YAHOO_XFER_RELAY_PORT;
 
-	url = g_strdup_printf("%ld.%ld.%ld.%ld", d, c, b, a);
+	url = g_strdup_printf("%u.%u.%u.%u", d, c, b, a);
 
 	/* Free the address... */
 	g_free(hosts->data);
--- a/libpurple/tests/Makefile.am	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/tests/Makefile.am	Mon Jul 19 21:11:32 2010 +0000
@@ -10,6 +10,7 @@
         check_libpurple.c \
 	    tests.h \
 		test_cipher.c \
+		test_jabber_caps.c \
 		test_jabber_jutil.c \
 		test_jabber_scram.c \
 		test_qq.c \
--- a/libpurple/tests/check_libpurple.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/tests/check_libpurple.c	Mon Jul 19 21:11:32 2010 +0000
@@ -84,6 +84,7 @@
 	sr = srunner_create (master_suite());
 
 	srunner_add_suite(sr, cipher_suite());
+	srunner_add_suite(sr, jabber_caps_suite());
 	srunner_add_suite(sr, jabber_jutil_suite());
 	srunner_add_suite(sr, jabber_scram_suite());
 	srunner_add_suite(sr, qq_suite());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_jabber_caps.c	Mon Jul 19 21:11:32 2010 +0000
@@ -0,0 +1,54 @@
+#include <string.h>
+
+#include "tests.h"
+#include "../xmlnode.h"
+#include "../protocols/jabber/caps.h"
+
+START_TEST(test_parse_invalid)
+{
+	xmlnode *query;
+
+	fail_unless(NULL == jabber_caps_parse_client_info(NULL));
+
+	/* Something other than a disco#info query */
+	query = xmlnode_new("foo");
+	fail_unless(NULL == jabber_caps_parse_client_info(query));
+	xmlnode_free(query);
+
+	query = xmlnode_new("query");
+	fail_unless(NULL == jabber_caps_parse_client_info(query));
+	xmlnode_set_namespace(query, "jabber:iq:last");
+	fail_unless(NULL == jabber_caps_parse_client_info(query));
+	xmlnode_free(query);
+}
+END_TEST
+
+#define assert_caps_calculate_match(hash_func, hash, str) { \
+	xmlnode *query = xmlnode_from_str((str), -1); \
+	JabberCapsClientInfo *info = jabber_caps_parse_client_info(query); \
+	gchar *got_hash = jabber_caps_calculate_hash(info, (hash_func)); \
+	assert_string_equal_free((hash), got_hash); \
+}
+
+START_TEST(test_calculate_caps)
+{
+	assert_caps_calculate_match("sha1", "GNjxthSckUNvAIoCCJFttjl6VL8=",
+	"<query xmlns='http://jabber.org/protocol/disco#info' node='http://tkabber.jabber.ru/#GNjxthSckUNvAIoCCJFttjl6VL8='><identity category='client' type='pc' name='Tkabber'/><x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='software'><value>Tkabber</value></field><field var='software_version'><value> ( 8.5.5 )</value></field><field var='os'><value>ATmega640-16AU</value></field><field var='os_version'><value/></field></x><feature var='games:board'/><feature var='google:mail:notify'/><feature var='http://jabber.org/protocol/activity'/><feature var='http://jabber.org/protocol/bytestreams'/><feature var='http://jabber.org/protocol/chatstates'/><feature var='http://jabber.org/protocol/commands'/><feature var='http://jabber.org/protocol/commands'/><feature var='http://jabber.org/protocol/disco#info'/><feature var='http://jabber.org/protocol/disco#items'/><feature var='http://jabber.org/protocol/feature-neg'/><feature var='http://jabber.org/protocol/geoloc'/><feature var='http://jabber.org/protocol/ibb'/><feature var='http://jabber.org/protocol/iqibb'/><feature var='http://jabber.org/protocol/mood'/><feature var='http://jabber.org/protocol/muc'/><feature var='http://jabber.org/protocol/mute#ancestor'/><feature var='http://jabber.org/protocol/mute#editor'/><feature var='http://jabber.org/protocol/rosterx'/><feature var='http://jabber.org/protocol/si'/><feature var='http://jabber.org/protocol/si/profile/file-transfer'/><feature var='http://jabber.org/protocol/tune'/><feature var='jabber:iq:avatar'/><feature var='jabber:iq:browse'/><feature var='jabber:iq:dtcp'/><feature var='jabber:iq:filexfer'/><feature var='jabber:iq:ibb'/><feature var='jabber:iq:inband'/><feature var='jabber:iq:jidlink'/><feature var='jabber:iq:last'/><feature var='jabber:iq:oob'/><feature var='jabber:iq:privacy'/><feature var='jabber:iq:time'/><feature var='jabber:iq:version'/><feature var='jabber:x:data'/><feature var='jabber:x:event'/><feature var='jabber:x:oob'/><feature var='urn:xmpp:ping'/><feature var='urn:xmpp:receipts'/><feature var='urn:xmpp:time'/></query>");
+}
+END_TEST
+
+Suite *
+jabber_caps_suite(void)
+{
+	Suite *s = suite_create("Jabber Caps Functions");
+
+	TCase *tc = tcase_create("Parsing invalid ndoes");
+	tcase_add_test(tc, test_parse_invalid);
+	suite_add_tcase(s, tc);
+
+	tc = tcase_create("Calculating from XMLnode");
+	tcase_add_test(tc, test_calculate_caps);
+	suite_add_tcase(s, tc);
+
+	return s;
+}
--- a/libpurple/tests/tests.h	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/tests/tests.h	Mon Jul 19 21:11:32 2010 +0000
@@ -9,6 +9,7 @@
 /* remember to add the suite to the runner in check_libpurple.c */
 Suite * master_suite(void);
 Suite * cipher_suite(void);
+Suite * jabber_caps_suite(void);
 Suite * jabber_jutil_suite(void);
 Suite * jabber_scram_suite(void);
 Suite * qq_suite(void);
--- a/libpurple/upnp.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/upnp.c	Mon Jul 19 21:11:32 2010 +0000
@@ -403,6 +403,11 @@
 		lookup_internal_ip();
 	}
 
+	if (dd->inpa > 0)
+		purple_input_remove(dd->inpa);
+	if (dd->tima > 0)
+		purple_timeout_remove(dd->tima);
+
 	g_free(dd);
 }
 
@@ -506,6 +511,8 @@
 
 	if (dd->inpa)
 		purple_input_remove(dd->inpa);
+	if (dd->tima > 0)
+		purple_timeout_remove(dd->tima);
 	dd->inpa = 0;
 	dd->tima = 0;
 
@@ -610,7 +617,7 @@
 
 	/* We have already done all our retries. Make sure that the callback
 	 * doesn't get called before the original function returns */
-	purple_timeout_add(10, purple_upnp_discover_timeout, dd);
+	dd->tima = purple_timeout_add(10, purple_upnp_discover_timeout, dd);
 }
 
 void
@@ -647,7 +654,7 @@
 			"purple_upnp_discover(): Failed In sock creation\n");
 		/* Short circuit the retry attempts */
 		dd->retry_count = NUM_UDP_ATTEMPTS;
-		purple_timeout_add(10, purple_upnp_discover_timeout, dd);
+		dd->tima = purple_timeout_add(10, purple_upnp_discover_timeout, dd);
 		return;
 	}
 
@@ -659,7 +666,7 @@
 			"purple_upnp_discover(): Failed In gethostbyname\n");
 		/* Short circuit the retry attempts */
 		dd->retry_count = NUM_UDP_ATTEMPTS;
-		purple_timeout_add(10, purple_upnp_discover_timeout, dd);
+		dd->tima = purple_timeout_add(10, purple_upnp_discover_timeout, dd);
 		return;
 	}
 
--- a/libpurple/util.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/util.c	Mon Jul 19 21:11:32 2010 +0000
@@ -3423,7 +3423,7 @@
 			   char **ret_path, char **ret_user, char **ret_passwd)
 {
 	gboolean is_https = FALSE;
-	char scan_info[255];
+	const char * scan_info;
 	char port_str[6];
 	int f;
 	const char *at, *slash;
@@ -3431,11 +3431,12 @@
 	char host[256], path[256], user[256], passwd[256];
 	int port = 0;
 	/* hyphen at end includes it in control set */
-	static const char addr_ctrl[] = "A-Za-z0-9.-";
-	static const char port_ctrl[] = "0-9";
-	static const char page_ctrl[] = "A-Za-z0-9.~_/:*!@&%%?=+^-";
-	static const char user_ctrl[] = "A-Za-z0-9.~_/*!&%%?=+^-";
-	static const char passwd_ctrl[] = "A-Za-z0-9.~_/*!&%%?=+^-";
+
+#define ADDR_CTRL "A-Za-z0-9.-"
+#define PORT_CTRL "0-9"
+#define PAGE_CTRL "A-Za-z0-9.~_/:*!@&%%?=+^-"
+#define USER_CTRL "A-Za-z0-9.~_/*!&%%?=+^-"
+#define PASSWD_CTRL "A-Za-z0-9.~_/*!&%%?=+^-"
 
 	g_return_val_if_fail(url != NULL, FALSE);
 
@@ -3455,37 +3456,32 @@
 	/* Only care about @ char BEFORE the first / */
 	at = strchr(url, '@');
 	slash = strchr(url, '/');
-	if ((at != NULL) &&
-			(((slash != NULL) && (strlen(at) > strlen(slash))) ||
-			(slash == NULL))) {
-		g_snprintf(scan_info, sizeof(scan_info),
-					"%%255[%s]:%%255[%s]^@", user_ctrl, passwd_ctrl);
+	f = 0;
+	if (at && (!slash || at < slash)) {
+		scan_info = "%255[" USER_CTRL "]:%255[" PASSWD_CTRL "]^@";
 		f = sscanf(url, scan_info, user, passwd);
 
-		if (f ==1 ) {
+		if (f == 1) {
 			/* No passwd, possibly just username supplied */
-			g_snprintf(scan_info, sizeof(scan_info),
-						"%%255[%s]^@", user_ctrl);
+			scan_info = "%255[" USER_CTRL "]^@";
 			f = sscanf(url, scan_info, user);
-			*passwd = '\0';
 		}
 
 		url = at+1; /* move pointer after the @ char */
-	} else {
+	}
+
+	if (f < 1) {
 		*user = '\0';
 		*passwd = '\0';
-	}
-
-	g_snprintf(scan_info, sizeof(scan_info),
-			   "%%255[%s]:%%5[%s]/%%255[%s]", addr_ctrl, port_ctrl, page_ctrl);
-
+	} else if (f == 1)
+		*passwd = '\0';
+
+	scan_info = "%255[" ADDR_CTRL "]:%5[" PORT_CTRL "]/%255[" PAGE_CTRL "]";
 	f = sscanf(url, scan_info, host, port_str, path);
 
 	if (f == 1)
 	{
-		g_snprintf(scan_info, sizeof(scan_info),
-				   "%%255[%s]/%%255[%s]",
-				   addr_ctrl, page_ctrl);
+		scan_info = "%255[" ADDR_CTRL "]/%255[" PAGE_CTRL "]";
 		f = sscanf(url, scan_info, host, path);
 		/* Use the default port */
 		if (is_https)
@@ -3509,6 +3505,12 @@
 	if (ret_passwd != NULL) *ret_passwd = g_strdup(passwd);
 
 	return ((*host != '\0') ? TRUE : FALSE);
+
+#undef ADDR_CTRL
+#undef PORT_CTRL
+#undef PAGE_CTRL
+#undef USER_CTRL
+#undef PASSWD_CTRL
 }
 
 /**
--- a/libpurple/xmlnode.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/xmlnode.c	Mon Jul 19 21:11:32 2010 +0000
@@ -223,7 +223,7 @@
 
 
 const char *
-xmlnode_get_attrib(xmlnode *node, const char *attr)
+xmlnode_get_attrib(const xmlnode *node, const char *attr)
 {
 	xmlnode *x;
 
@@ -240,9 +240,9 @@
 }
 
 const char *
-xmlnode_get_attrib_with_namespace(xmlnode *node, const char *attr, const char *xmlns)
+xmlnode_get_attrib_with_namespace(const xmlnode *node, const char *attr, const char *xmlns)
 {
-	xmlnode *x;
+	const xmlnode *x;
 
 	g_return_val_if_fail(node != NULL, NULL);
 	g_return_val_if_fail(attr != NULL, NULL);
--- a/libpurple/xmlnode.h	Mon Jul 19 21:05:06 2010 +0000
+++ b/libpurple/xmlnode.h	Mon Jul 19 21:11:32 2010 +0000
@@ -205,7 +205,7 @@
  *
  * @return The value of the attribute.
  */
-const char *xmlnode_get_attrib(xmlnode *node, const char *attr);
+const char *xmlnode_get_attrib(const xmlnode *node, const char *attr);
 
 /**
  * Gets a namespaced attribute from a node
@@ -216,7 +216,7 @@
  *
  * @return The value of the attribute/
  */
-const char *xmlnode_get_attrib_with_namespace(xmlnode *node, const char *attr, const char *xmlns);
+const char *xmlnode_get_attrib_with_namespace(const xmlnode *node, const char *attr, const char *xmlns);
 
 /**
  * Removes an attribute from a node.
--- a/pidgin/gtkblist.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/pidgin/gtkblist.c	Mon Jul 19 21:11:32 2010 +0000
@@ -3601,11 +3601,12 @@
 		purple_request_field_list_add_selected(f, _("None"));
 
 	/* TODO: rlaager wants this sorted. */
-	/* The connection is checked for PURPLE_CONNECTION_SUPPORT_MOODS flag before
-	 * this function is called for a non-null account. So using
-	 * PURPLE_PROTOCOL_PLUGIN_HAS_FUNC isn't necessary here */
-	for (mood = account ? prpl_info->get_moods(account) : global_moods;
-	     mood->mood != NULL ; mood++) {
+	/* TODO: darkrain wants it sorted post-translation */
+	if (account && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_moods))
+		mood = prpl_info->get_moods(account);
+	else
+		mood = global_moods;
+	for ( ; mood->mood != NULL ; mood++) {
 		char *path;
 
 		if (mood->mood == NULL || mood->description == NULL)
--- a/pidgin/gtkimhtmltoolbar.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Mon Jul 19 21:11:32 2010 +0000
@@ -1276,6 +1276,15 @@
 }
 
 static void
+button_visibility_changed(GtkWidget *button, gpointer dontcare, GtkWidget *item)
+{
+	if (GTK_WIDGET_VISIBLE(button))
+		gtk_widget_hide(item);
+	else
+		gtk_widget_show(item);
+}
+
+static void
 button_sensitiveness_changed(GtkWidget *button, gpointer dontcare, GtkWidget *item)
 {
 	gtk_widget_set_sensitive(item, GTK_WIDGET_IS_SENSITIVE(button));
@@ -1323,6 +1332,7 @@
 	GtkWidget *insert_menu;
 	GtkWidget *menuitem;
 	GtkWidget *sep;
+	GObject *wide_attention_button;
 	int i;
 	struct {
 		const char *label;
@@ -1393,6 +1403,8 @@
 		gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), menuitem);
 		g_signal_connect(G_OBJECT(old), "notify::sensitive",
 				G_CALLBACK(button_sensitiveness_changed), menuitem);
+		g_signal_connect(G_OBJECT(old), "notify::visible",
+				G_CALLBACK(button_visibility_changed), menuitem);
 		gtk_container_foreach(GTK_CONTAINER(menuitem), (GtkCallback)enable_markup, NULL);
 	}
 
@@ -1425,12 +1437,16 @@
 	gtk_menu_shell_append(GTK_MENU_SHELL(insert_menu), menuitem);
 	g_signal_connect(G_OBJECT(toolbar->image), "notify::sensitive",
 			G_CALLBACK(button_sensitiveness_changed), menuitem);
+	g_signal_connect(G_OBJECT(toolbar->image), "notify::visible",
+			G_CALLBACK(button_visibility_changed), menuitem);
 
 	menuitem = gtk_menu_item_new_with_mnemonic(_("_Link"));
 	g_signal_connect_swapped(G_OBJECT(menuitem), "activate", G_CALLBACK(gtk_button_clicked), toolbar->link);
 	gtk_menu_shell_append(GTK_MENU_SHELL(insert_menu), menuitem);
 	g_signal_connect(G_OBJECT(toolbar->link), "notify::sensitive",
 			G_CALLBACK(button_sensitiveness_changed), menuitem);
+	g_signal_connect(G_OBJECT(toolbar->link), "notify::visible",
+			G_CALLBACK(button_visibility_changed), menuitem);
 
 	menuitem = gtk_menu_item_new_with_mnemonic(_("_Horizontal rule"));
 	g_signal_connect(G_OBJECT(menuitem), "activate"	, G_CALLBACK(insert_hr_cb), toolbar);
@@ -1467,29 +1483,30 @@
 	gtk_widget_show_all(sep);
 
 	/* Attention */
+	wide_attention_button = g_object_get_data(G_OBJECT(toolbar), "attention");
+
 	attention_button = gtk_button_new();
 	gtk_button_set_relief(GTK_BUTTON(attention_button), GTK_RELIEF_NONE);
 	bbox = gtk_hbox_new(FALSE, 3);
 	gtk_container_add(GTK_CONTAINER(attention_button), bbox);
-	image = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION, 
+	image = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION,
 		gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
 	gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
 	label = gtk_label_new_with_mnemonic(_("_Attention!"));
 	gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(box), attention_button, FALSE, FALSE, 0);
-	g_signal_connect_swapped(G_OBJECT(attention_button), "clicked", 
-		G_CALLBACK(gtk_button_clicked),
-	    g_object_get_data(G_OBJECT(toolbar), "attention"));
+	g_signal_connect_swapped(G_OBJECT(attention_button), "clicked",
+		G_CALLBACK(gtk_button_clicked), wide_attention_button);
 	gtk_widget_show_all(attention_button);
-	
-	g_signal_connect(G_OBJECT(g_object_get_data(G_OBJECT(toolbar), "attention")),
-	        "notify::sensitive",
+
+	g_signal_connect(wide_attention_button, "notify::sensitive",
 			G_CALLBACK(button_sensitiveness_changed), attention_button);
+	g_signal_connect(wide_attention_button, "notify::visible",
+			G_CALLBACK(button_visibility_changed), attention_button);
 
 	/* set attention button to be greyed out until we get a conversation */
-	gtk_widget_set_sensitive(g_object_get_data(G_OBJECT(toolbar), "attention"),
-		FALSE);
-	
+	gtk_widget_set_sensitive(GTK_WIDGET(wide_attention_button), FALSE);
+
 	gtk_box_pack_start(GTK_BOX(hbox), box, FALSE, FALSE, 0);
 	g_object_set_data(G_OBJECT(hbox), "lean-view", box);
 	gtk_widget_show(box);
--- a/pidgin/gtkmain.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/pidgin/gtkmain.c	Mon Jul 19 21:11:32 2010 +0000
@@ -97,7 +97,6 @@
  */
 static const int catch_sig_list[] = {
 	SIGSEGV,
-	SIGHUP,
 	SIGINT,
 	SIGTERM,
 	SIGQUIT,
@@ -223,9 +222,6 @@
 	}
 
 	switch (sig) {
-	case SIGHUP:
-		purple_debug_warning("sighandler", "Caught signal %d\n", sig);
-		break;
 #if defined(USE_GSTREAMER) && !defined(GST_CAN_DISABLE_FORKING)
 /* By default, gstreamer forks when you initialize it, and waitpids for the
  * child.  But if libpurple reaps the child rather than leaving it to
@@ -550,6 +546,7 @@
 	gboolean debug_enabled;
 	gboolean migration_failed = FALSE;
 	GList *active_accounts;
+	struct stat st;
 
 	struct option long_options[] = {
 		{"config",       required_argument, NULL, 'c'},
@@ -853,6 +850,8 @@
 	 * in user's home directory.
 	 */
 	search_path = g_build_filename(purple_user_dir(), "plugins", NULL);
+	if (!g_stat(search_path, &st))
+		g_mkdir(search_path, S_IRUSR | S_IWUSR | S_IXUSR);
 	purple_plugins_add_search_path(search_path);
 	g_free(search_path);
 	purple_plugins_add_search_path(LIBDIR);
--- a/pidgin/gtkmedia.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/pidgin/gtkmedia.c	Mon Jul 19 21:11:32 2010 +0000
@@ -333,7 +333,7 @@
 	gtk_box_pack_start(GTK_BOX(vbox), media->priv->menubar,
 			FALSE, TRUE, 0);
 
-	media->priv->display = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	media->priv->display = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	gtk_container_set_border_width(GTK_CONTAINER(media->priv->display),
 			PIDGIN_HIG_BOX_SPACE);
 	gtk_box_pack_start(GTK_BOX(vbox), media->priv->display,
@@ -629,29 +629,29 @@
 		gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display),
 				recv_widget, TRUE, TRUE, 0);
 		gtk_widget_show(recv_widget);
-	} else
+	} else {
 		recv_widget = gtkmedia->priv->recv_widget;
+	}
 	if (gtkmedia->priv->send_widget == NULL
 			&& type & (PURPLE_MEDIA_SEND_VIDEO |
 			PURPLE_MEDIA_SEND_AUDIO)) {
 		send_widget = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 		gtk_box_pack_start(GTK_BOX(gtkmedia->priv->display),
-				send_widget, TRUE, TRUE, 0);
+				send_widget, FALSE, TRUE, 0);
 		button_widget = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
-		gtk_box_pack_end(GTK_BOX(send_widget), button_widget,
-				FALSE, FALSE, 0);
-		gtk_widget_show(GTK_WIDGET(button_widget));
+		gtk_box_pack_end(GTK_BOX(recv_widget), button_widget,
+				FALSE, TRUE, 0);
 		gtk_widget_show(send_widget);
 
 		/* Hold button */
 		gtkmedia->priv->hold =
 				gtk_toggle_button_new_with_mnemonic("_Hold");
+		gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->hold,
+				FALSE, FALSE, 0);
+		gtk_widget_show(gtkmedia->priv->hold);
 		g_signal_connect(gtkmedia->priv->hold, "toggled",
 				G_CALLBACK(pidgin_media_hold_toggled),
 				gtkmedia);
-		gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->hold,
-				FALSE, FALSE, 0);
-		gtk_widget_show(gtkmedia->priv->hold);
 	} else {
 		send_widget = gtkmedia->priv->send_widget;
 		button_widget = gtkmedia->priv->button_widget;
@@ -663,7 +663,7 @@
 		GtkWidget *remote_video;
 		GdkColor color = {0, 0, 0, 0};
 
-		aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE);
+		aspect = gtk_aspect_frame_new(NULL, 0, 0, 4.0/3.0, FALSE);
 		gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN);
 		gtk_box_pack_start(GTK_BOX(recv_widget), aspect, TRUE, TRUE, 0);
 
@@ -683,15 +683,16 @@
 
 		gtkmedia->priv->remote_video = remote_video;
 	}
+
 	if (type & PURPLE_MEDIA_SEND_VIDEO) {
 		PidginMediaRealizeData *data;
 		GtkWidget *aspect;
 		GtkWidget *local_video;
 		GdkColor color = {0, 0, 0, 0};
 
-		aspect = gtk_aspect_frame_new(NULL, 0.5, 0.5, 4.0/3.0, FALSE);
+		aspect = gtk_aspect_frame_new(NULL, 0, 0, 4.0/3.0, TRUE);
 		gtk_frame_set_shadow_type(GTK_FRAME(aspect), GTK_SHADOW_IN);
-		gtk_box_pack_start(GTK_BOX(send_widget), aspect, TRUE, TRUE, 0);
+		gtk_box_pack_start(GTK_BOX(send_widget), aspect, FALSE, TRUE, 0);
 
 		data = g_new0(PidginMediaRealizeData, 1);
 		data->gtkmedia = gtkmedia;
@@ -703,44 +704,43 @@
 		g_signal_connect(G_OBJECT(local_video), "realize",
 				G_CALLBACK(realize_cb), data);
 		gtk_container_add(GTK_CONTAINER(aspect), local_video);
-		gtk_widget_set_size_request (GTK_WIDGET(local_video), 160, 120);
+		gtk_widget_set_size_request (GTK_WIDGET(local_video), 80, 60);
 
 		gtk_widget_show(local_video);
 		gtk_widget_show(aspect);
 
 		gtkmedia->priv->pause =
 				gtk_toggle_button_new_with_mnemonic(_("_Pause"));
+		gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->pause,
+				FALSE, FALSE, 0);
+		gtk_widget_show(gtkmedia->priv->pause);
 		g_signal_connect(gtkmedia->priv->pause, "toggled",
 				G_CALLBACK(pidgin_media_pause_toggled),
 				gtkmedia);
-		gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->pause,
-				FALSE, FALSE, 0);
-		gtk_widget_show(gtkmedia->priv->pause);
 
 		gtkmedia->priv->local_video = local_video;
 	}
-
 	if (type & PURPLE_MEDIA_RECV_AUDIO) {
 		gtk_box_pack_end(GTK_BOX(recv_widget),
 				pidgin_media_add_audio_widget(gtkmedia,
 				PURPLE_MEDIA_RECV_AUDIO), FALSE, FALSE, 0);
 	}
+
 	if (type & PURPLE_MEDIA_SEND_AUDIO) {
 		gtkmedia->priv->mute =
 				gtk_toggle_button_new_with_mnemonic("_Mute");
+		gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->mute,
+				FALSE, FALSE, 0);
+		gtk_widget_show(gtkmedia->priv->mute);
 		g_signal_connect(gtkmedia->priv->mute, "toggled",
 				G_CALLBACK(pidgin_media_mute_toggled),
 				gtkmedia);
-		gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->mute,
-				FALSE, FALSE, 0);
-		gtk_widget_show(gtkmedia->priv->mute);
 
-		gtk_box_pack_end(GTK_BOX(send_widget),
+		gtk_box_pack_end(GTK_BOX(recv_widget),
 				pidgin_media_add_audio_widget(gtkmedia,
 				PURPLE_MEDIA_SEND_AUDIO), FALSE, FALSE, 0);
 	}
 
-
 	if (type & PURPLE_MEDIA_AUDIO &&
 			gtkmedia->priv->level_handler_id == 0) {
 		gtkmedia->priv->level_handler_id = g_signal_connect(
@@ -752,8 +752,10 @@
 		gtkmedia->priv->send_widget = send_widget;
 	if (recv_widget != NULL)
 		gtkmedia->priv->recv_widget = recv_widget;
-	if (button_widget != NULL)
+	if (button_widget != NULL) {
 		gtkmedia->priv->button_widget = button_widget;
+		gtk_widget_show(GTK_WIDGET(button_widget));
+	}
 
 	if (purple_media_is_initiator(media, sid, NULL) == FALSE) {
 		if (gtkmedia->priv->timeout_id != 0)
--- a/pidgin/gtkprefs.c	Mon Jul 19 21:05:06 2010 +0000
+++ b/pidgin/gtkprefs.c	Mon Jul 19 21:11:32 2010 +0000
@@ -2223,6 +2223,7 @@
 
 	gtk_widget_set_sensitive(hbox,
 			!strcmp(method, "automatic") ||
+			!strcmp(method, "alsa") ||
 			!strcmp(method, "esd"));
 }
 #endif /* USE_GSTREAMER */
--- a/pidgin/plugins/Makefile.mingw	Mon Jul 19 21:05:06 2010 +0000
+++ b/pidgin/plugins/Makefile.mingw	Mon Jul 19 21:11:32 2010 +0000
@@ -73,7 +73,7 @@
 	$(MAKE) -C $(WINPREFS_PLUGIN) -f $(MINGW_MAKEFILE) install
 	cp *.dll $(PIDGIN_INSTALL_PLUGINS_DIR)
 
-.c.dll:
+%.dll: %.c $(PURPLE_CONFIG_H) $(PURPLE_VERSION_H)
 	$(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@.o -c $<
 	$(CC) -shared $@.o $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $@