changeset 21924:e9005670e279

merge of 'f6430c7013d08f95c60248eeb22c752a0107499b' and 'f84b5a4f3cdf2a2a92ff24e79e6aa6c2e0c8c1aa'
author Etan Reisner <pidgin@unreliablesource.net>
date Sat, 22 Dec 2007 23:18:08 +0000
parents d7f1231cc21a (diff) da103e8cdb33 (current diff)
children 2b6e6dd24a37
files libpurple/protocols/bonjour/mdns_howl.c
diffstat 112 files changed, 5070 insertions(+), 2206 deletions(-) [+]
line wrap: on
line diff
--- a/.mtn-ignore	Sat Dec 22 17:54:30 2007 +0000
+++ b/.mtn-ignore	Sat Dec 22 23:18:08 2007 +0000
@@ -33,6 +33,7 @@
 pidgin.spec$
 pidgin-.*.tar.gz
 pidgin-.*.tar.bz2
+pidgin-*.*.*-win32bin$
 pidgin/pidgin$
 pidgin/pixmaps/emotes/default/24/theme
 pidgin/pixmaps/emotes/none/theme
@@ -51,6 +52,7 @@
 libpurple/plugins/perl/common/const-c.inc
 libpurple/plugins/perl/common/const-xs.inc
 libpurple/plugins/perl/common/lib
+libpurple/purple.h$
 libpurple/purple-client-bindings.c
 libpurple/purple-client-bindings.h
 libpurple/purple-client-example
--- a/AUTHORS	Sat Dec 22 17:54:30 2007 +0000
+++ b/AUTHORS	Sat Dec 22 23:18:08 2007 +0000
@@ -35,6 +35,7 @@
 Megan 'Cae' Schneider - support/QA
 Evan Schoenberg - Developer
 Kevin 'SimGuy' Stange - Developer & Webmaster
+Will 'resiak' Thompson - Developer
 Stu 'nosnilmot' Tomlinson - Developer
 Nathan 'faceprint' Walp - Developer
 
@@ -42,8 +43,8 @@
 -------------------
 Dennis 'EvilDennisR' Ristuccia
 Peter 'Fmoo' Ruibal
+Elliott 'QuLogic' Sales de Andrade
 Gabriel 'Nix' Schulhof
-Will 'resiak' Thompson
 
 Retired Developers:
 ------------------
--- a/COPYRIGHT	Sat Dec 22 17:54:30 2007 +0000
+++ b/COPYRIGHT	Sat Dec 22 23:18:08 2007 +0000
@@ -309,6 +309,7 @@
 Tim Ringenbach
 Dennis Ristuccia
 Lee Roach
+Eion Robb
 Rhett Robinson
 Luciano Miguel Ferreira Rocha
 Andrew Rodland
@@ -326,6 +327,7 @@
 Andrew Sayman
 Alceste Scalas
 Carsten Schaar
+Jonathan Schleifer <js-pidgin@webkeks.org>
 Matteo Settenvini
 Colin Seymour
 Luke Schierer
--- a/ChangeLog	Sat Dec 22 17:54:30 2007 +0000
+++ b/ChangeLog	Sat Dec 22 23:18:08 2007 +0000
@@ -1,9 +1,17 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.3.2 (??/??/????):
+version 2.4.0 (??/??/????):
 	libpurple:
 	* Fixed various problems with loss of status messages when going
 	  or returning from idle on MySpaceIM.
+	* Eliminated unmaintained Howl backend implementation for the
+	  Bonjour protocol.  Avahi (or Apple's Bonjour runtime on win32) is
+	  now required to use Bonjour.
+
+	Pidgin:
+	* Added the ability to theme conversation name colors (red and blue)
+	  through your GTK+ theme, and exposed those theme settings to the 
+	  Pidgin GTK+ Theme Control plugin (Dustin Howett)
 
 	Finch:
 	* Color is used in the buddylist to indicate status, and the conversation
@@ -13,6 +21,27 @@
 	  request dialog. M-d will properly delete-forward-word, and M-f has been
 	  fixed to imitate readline's behavior.
 
+version 2.3.1 (12/7/2007):
+	http://developer.pidgin.im/query?status=closed&milestone=2.3.1
+		NOTE: Due to the way this release was made, it is possible that
+		      bugs marked as fixed in 2.3.1 will not be fixed until the
+		      next release.
+	
+	* Fixed a number of MSN bugs introduced in 2.3.0, resolving problems
+	  connecting to MSN and random local display name changes
+	* Going idle on MySpaceIM will no longer clear your status and message.
+	* Idle MySpaceIM buddies should now appear online at login.
+	* Fixed crashes in XMPP when discovering a client's capabilities
+	* Don't set the current tune title if it's NULL (XMPP/Google Talk)
+	* Don't allow buddies to be manually added to Bonjour
+	* Don't advertise IPv6 on Bonjour because we don't support it
+	* Compile fixes for FreeBSD and Solaris
+	* Update QQ client version so some accounts can connect again
+	* Do not allow ISON requests to stack in IRC, preventing flooding IRC
+	  servers when temporary network outages are restored
+	* Plug several leaks in the perl plugin loader
+	* Prevent autoaccept plugin overwriting existing files
+
 version 2.3.0 (11/24/2007):
 	http://developer.pidgin.im/query?status=closed&milestone=2.3.0
 		NOTE: Some bugs marked fixed in 2.2.1, 2.2.2 or 2.2.3 may not
--- a/ChangeLog.API	Sat Dec 22 17:54:30 2007 +0000
+++ b/ChangeLog.API	Sat Dec 22 23:18:08 2007 +0000
@@ -1,6 +1,29 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.3.2 (??/??/????):
+version 2.4.0 (??/??/????):
+	libpurple:
+		Added:
+		* purple_certificate_add_ca_search_path. (Florian Quèze)
+		* purple_gai_strerror.
+		* purple_major_version, purple_minor_version,
+		  purple_micro_version variables are exported by version.h,
+		  giving the version of libpurple in use at runtime.
+
+	Pidgin:
+		Added:
+		* pidgin_create_dialog to create a window that closes on escape. Also
+		  added utility functions pidgin_dialog_get_vbox_with_properties,
+		  pidgin_dialog_get_vbox, pidgin_dialog_get_action_area to access the
+		  contents in the created dialog. (Peter 'fmoo' Ruibal)
+		* pidgin_dialog_add_button to add buttons to a dialog created by
+		  pidgin_create_dialog.
+		* GTK_IMHTML_NO_SMILEY for GtkIMHtmlOptions means not to look for
+		  smileys in the text. (Florian 'goutnet' Delizy)
+		* pidgin_auto_parent_window to make a window transient for a suitable
+		  parent window.
+		* pidgin_tooltip_setup_for_treeview, pidgin_tooltip_destroy and
+		  pidgin_tooltip_show to simplify the process of drawing tooltips.
+
 	Finch:
 		libgnt:
 		* Added gnt_tree_set_row_color to set the color for a row in a tree.
--- a/Makefile.am	Sat Dec 22 17:54:30 2007 +0000
+++ b/Makefile.am	Sat Dec 22 23:18:08 2007 +0000
@@ -30,10 +30,12 @@
 distcheck-hook: libpurple/plugins/perl/common/Purple.pm pidgin/plugins/perl/common/Pidgin.pm
 #	cp libpurple/plugins/perl/common/Gaim.pm $(distdir)/libpurple/plugins/perl/common
 
+if ENABLE_GTK
 appsdir = $(datadir)/applications
 apps_in_files = pidgin.desktop.in
 apps_DATA = $(apps_in_files:.desktop.in=.desktop)
 @INTLTOOL_DESKTOP_RULE@
+endif
 
 if ENABLE_GTK
 GTK_DIR=pidgin
@@ -51,7 +53,7 @@
 	@doxygen
 if HAVE_XSLTPROC
 	@echo "Generating devhelp index..."
-	@xsltproc doxy2devhelp.xsl doc/xml/index.xml > doc/html/pidgin.devhelp
+	@xsltproc $(top_srcdir)/doxy2devhelp.xsl doc/xml/index.xml > doc/html/pidgin.devhelp
 	@echo "(Symlink doc/html to ~/.local/share/gtk-doc/html/pidgin to make devhelp see the documentation)"
 else
 	@echo "Not generating devhelp index: xsltproc was not found by configure"
--- a/NEWS	Sat Dec 22 17:54:30 2007 +0000
+++ b/NEWS	Sat Dec 22 23:18:08 2007 +0000
@@ -1,5 +1,19 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+2.3.1 (12/7/2007):
+	Stu: I'm sorry for the MSN problems and the plugin crashes in 2.3.0.
+	Hopefully this will redeem us. This fixes a number of bugs. I'm a
+	bit late but I'd like to welcome John to the team. Enjoy!
+
+	Luke: I've done absolutely nothing in the last 2 weeks, except watch
+	others commit bug and, more, leak fixes.  People should be noticing
+	remarkably fewer memory leaks now than 2 or more releases ago.
+
+	Kevin: I'm not quite sure what happened to our MySpaceIM Summer of
+	Code student, but I fixed a few MySpace bugs with idle and status.
+	I will try to fix some of the other more significant bugs, after I
+	figure out the protocol, especially including grouping issues.
+
 2.3.0 (11/20/2007):
 	Luke: While this does not have the new MSN code, rest assured that
 	we are working on it and that it is nearing release.  This contains
--- a/configure.ac	Sat Dec 22 17:54:30 2007 +0000
+++ b/configure.ac	Sat Dec 22 23:18:08 2007 +0000
@@ -43,19 +43,19 @@
 #
 # Make sure to update finch/libgnt/configure.ac with libgnt version changes.
 #
-m4_define([purple_lt_current], [3])
+m4_define([purple_lt_current], [4])
 m4_define([purple_major_version], [2])
-m4_define([purple_minor_version], [3])
-m4_define([purple_micro_version], [1])
+m4_define([purple_minor_version], [4])
+m4_define([purple_micro_version], [0])
 m4_define([purple_version_suffix], [devel])
 m4_define([purple_version],
           [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], [3])
+m4_define([gnt_lt_current], [4])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [3])
-m4_define([gnt_micro_version], [1])
+m4_define([gnt_minor_version], [4])
+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])
@@ -722,59 +722,6 @@
 AC_SUBST(AVAHI_CFLAGS)
 AC_SUBST(AVAHI_LIBS)
 
-AM_CONDITIONAL(MDNS_AVAHI, test "x$avahiincludes" = "xyes" -a "x$avahilibs" = "xyes")
-
-dnl #######################################################################
-dnl # Check for Howl headers (for Bonjour)
-dnl #######################################################################
-AC_ARG_WITH(howl-includes, [AC_HELP_STRING([--with-howl-includes=DIR], [compile the Bonjour plugin against the Howl includes in DIR])], [ac_howl_includes="$withval"], [ac_howl_includes="no"])
-AC_ARG_WITH(howl-libs, [AC_HELP_STRING([--with-howl-libs=DIR], [compile the Bonjour plugin against the Howl libs in DIR])], [ac_howl_libs="$withval"], [ac_howl_libs="no"])
-HOWL_CFLAGS=""
-HOWL_LIBS=""
-
-dnl Attempt to autodetect avahi-compat-howl
-dnl TODO: (This should be removed when the native avahi stuff is stable)
-PKG_CHECK_MODULES(HOWL, avahi-compat-howl, [
-	howlincludes="yes"
-	howllibs="yes"
-], [
-	AC_MSG_RESULT(no)
-	howlincludes="no"
-	howllibs="no"
-])
-
-dnl Attempt to autodetect Howl
-if test "x$howlincludes" = "xno"; then
-	PKG_CHECK_MODULES(HOWL, howl, [
-		howlincludes="yes"
-		howllibs="yes"
-	], [
-		AC_MSG_RESULT(no)
-		howlincludes="no"
-		howllibs="no"
-	])
-fi
-
-dnl Override HOWL_CFLAGS if the user specified an include dir
-if test "$ac_howl_includes" != "no"; then
-	HOWL_CFLAGS="-I$ac_howl_includes"
-fi
-CPPFLAGS_save="$CPPFLAGS"
-CPPFLAGS="$CPPFLAGS $HOWL_CFLAGS"
-AC_CHECK_HEADER(howl.h, [howlincludes=yes], [howlincludes=no])
-CPPFLAGS="$CPPFLAGS_save"
-
-dnl Override HOWL_LIBS if the user specified a libs dir
-if test "$ac_howl_libs" != "no"; then
-	HOWL_LIBS="-L$ac_howl_libs -lhowl"
-fi
-AC_CHECK_LIB(howl, sw_discovery_init, [howllibs=yes], [howllibs=no], $HOWL_LIBS)
-
-AC_SUBST(HOWL_CFLAGS)
-AC_SUBST(HOWL_LIBS)
-
-AM_CONDITIONAL(MDNS_HOWL, test "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes")
-
 
 dnl #######################################################################
 dnl # Check for SILC client includes and libraries
@@ -960,9 +907,7 @@
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'`
 fi
 if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then
-	if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then
-		STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'`
-	fi
+	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'`
 fi
 if test "x$enable_msnp14" != "xyes" ; then
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/msn/msnp9/'`
@@ -1049,9 +994,7 @@
 	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'`
 fi
 if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then
-	if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then
-		DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'`
-	fi
+	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'`
 fi
 if test "x$enable_msnp14" != "xyes" ; then
 	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/msn/msnp9/'`
@@ -1087,7 +1030,7 @@
 		*)			echo "Invalid dynamic protocol $i!!" ; exit ;;
 	esac
 done
-AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes"  -a [ [ "x$avahiincludes" = "xyes" -a "x$avahilibs " = "xyes" ] -o [ "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes" ] ] )
+AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes"  -a [ "x$avahiincludes" = "xyes" -a "x$avahilibs " = "xyes" ] )
 AM_CONDITIONAL(DYNAMIC_GG, test "x$dynamic_gg" = "xyes")
 AM_CONDITIONAL(DYNAMIC_IRC, test "x$dynamic_irc" = "xyes")
 AM_CONDITIONAL(DYNAMIC_JABBER, test "x$dynamic_jabber" = "xyes")
@@ -1336,6 +1279,34 @@
 
 AM_CONDITIONAL(ENABLE_DBUS, test "x$enable_dbus" = "xyes")
 
+dnl Check for Python headers (currently useful only for libgnt)
+dnl (Thanks to XChat)
+AC_PATH_PROG(pythonpath, python)
+if test "_$pythonpath" != _ ; then
+	AC_MSG_CHECKING(for Python compile flags)
+	PY_PREFIX=`$pythonpath -c 'import sys ; print sys.prefix'`
+	PY_EXEC_PREFIX=`$pythonpath -c 'import sys ; print sys.exec_prefix'`
+	changequote(<<, >>)dnl
+	PY_VERSION=`$pythonpath -c 'import sys ; print sys.version[0:3]'`
+	PY_MAJOR=`$pythonpath -c 'import sys ; print sys.version[0:2]'`
+	changequote([, ])dnl
+	if test -f $PY_PREFIX/include/python$PY_VERSION/Python.h -a "$PY_MAJOR" = "2."; then
+		AC_CHECK_LIB(pthread, pthread_create, )
+		AC_CHECK_LIB(util, openpty, )
+		AC_CHECK_LIB(db, dbopen, )
+		PY_LIBS="-lpython$PY_VERSION -L$PY_EXEC_PREFIX/lib/python$PY_VERSION/config"
+		PY_CFLAGS="-I$PY_PREFIX/include/python$PY_VERSION"
+		AC_DEFINE(USE_PYTHON, [1], [Define if python headers are available.])
+		AC_MSG_RESULT(ok)
+	else
+		AC_MSG_RESULT([Can't find Python.h])
+		PY_LIBS=""
+		PY_CFLAGS=""
+	fi
+fi
+AC_SUBST(PY_CFLAGS)
+AC_SUBST(PY_LIBS)
+
 dnl #######################################################################
 dnl # Check for Mono support
 dnl #######################################################################
--- a/doc/pidgin.1.in	Sat Dec 22 17:54:30 2007 +0000
+++ b/doc/pidgin.1.in	Sat Dec 22 23:18:08 2007 +0000
@@ -88,8 +88,8 @@
 .TP
 .B Add Buddy Pounce
 A Buddy Pounce is a configurable automated action to be performed when the
-buddy's state changes.  This will open the \fBBuddy Pounce\fR dialog to be
-discussed later.
+buddy's state changes.  This will open the \fBBuddy Pounce\fR dialog, which
+will be discussed later.
 .TP
 .B View Log
 Pidgin is capable of automatically logging messages.  These logs are
@@ -118,7 +118,7 @@
 
 .SH ACCOUNT EDITOR
 The account editor consists of a list of accounts and information about
-them.  It can be accessed by selecting \fBManage\fR from the Tools menu.
+them.  It can be accessed by selecting \fBManage\fR from the Accounts menu.
 Clicking \fIDelete\fR will delete the currently selected account.
 Clicking \fIAdd\fR or \fIModify\fR will invoke a \fBModify Account\fR
 window.  Here, the user  can add or alter account information.  When creating
@@ -590,6 +590,8 @@
 .br
   Kevin 'SimGuy' Stange (developer and webmaster)
 .br
+  Will 'resiak' Thompson (developer)
+.br
   Stu 'nosnilmot' Tomlinson (developer)
 .br
   Nathan 'faceprint' Walp (developer)
@@ -602,9 +604,9 @@
 .br
   Peter 'fmoo' Ruibal
 .br
-  Gabriel 'Nix' Schulhof
+  Elliott 'QuLogic' Sales de Andrade
 .br
-  Will 'resiak' Thompson
+  Gabriel 'Nix' Schulhof
 .br
 
 
--- a/finch/gntdebug.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/finch/gntdebug.c	Sat Dec 22 23:18:08 2007 +0000
@@ -344,6 +344,9 @@
 	REGISTER_G_LOG_HANDLER("GModule");
 	REGISTER_G_LOG_HANDLER("GLib-GObject");
 	REGISTER_G_LOG_HANDLER("GThread");
+#ifdef USE_GSTREAMER
+	REGISTER_G_LOG_HANDLER("GStreamer");
+#endif
 
 	g_set_print_handler(print_stderr);   /* Redirect the debug messages to stderr */
 	if (!purple_debug_is_enabled())
--- a/finch/gntplugin.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/finch/gntplugin.c	Sat Dec 22 23:18:08 2007 +0000
@@ -53,6 +53,13 @@
 static GntWidget *process_pref_frame(PurplePluginPrefFrame *frame);
 
 static void
+free_stringlist(GList *list)
+{
+	g_list_foreach(list, (GFunc)g_free, NULL);
+	g_list_free(list);
+}
+
+static void
 decide_conf_button(PurplePlugin *plugin)
 {
 	if (purple_plugin_is_loaded(plugin) && 
@@ -426,11 +433,14 @@
 	PurpleRequestFields *fields;
 	PurpleRequestFieldGroup *group = NULL;
 	GList *prefs;
-	
+	GList *stringlist = NULL;
+	GntWidget *ret = NULL;
+
 	fields = purple_request_fields_new();
 
 	for (prefs = purple_plugin_pref_frame_get_prefs(frame); prefs; prefs = prefs->next) {
 		PurplePluginPref *pref = prefs->data;
+		PurplePrefType type;
 		const char *name = purple_plugin_pref_get_name(pref);
 		const char *label = purple_plugin_pref_get_label(pref);
 		if(name == NULL) {
@@ -448,20 +458,67 @@
 		}
 
 		field = NULL;
-		switch(purple_prefs_get_type(name)) {
-			case PURPLE_PREF_BOOLEAN:
-				field = purple_request_field_bool_new(name, label, purple_prefs_get_bool(name));
-				break;
-			case PURPLE_PREF_INT:
-				field = purple_request_field_int_new(name, label, purple_prefs_get_int(name));
-				break;
-			case PURPLE_PREF_STRING:
-				field = purple_request_field_string_new(name, label, purple_prefs_get_string(name),
-						purple_plugin_pref_get_format_type(pref) & PURPLE_STRING_FORMAT_TYPE_MULTILINE);
-				break;
-			default:
-				break;
+		type = purple_prefs_get_type(name);
+		if(purple_plugin_pref_get_type(pref) == PURPLE_PLUGIN_PREF_CHOICE) {
+			GList *list = purple_plugin_pref_get_choices(pref);
+			gpointer current_value = NULL;
+
+			switch(type) {
+				case PURPLE_PREF_BOOLEAN:
+					current_value = g_strdup_printf("%d", (int)purple_prefs_get_bool(name));
+					break;
+				case PURPLE_PREF_INT:
+					current_value = g_strdup_printf("%d", (int)purple_prefs_get_int(name));
+					break;
+				case PURPLE_PREF_STRING:
+					current_value = g_strdup(purple_prefs_get_string(name));
+					break;
+				default:
+					continue;
+			}
+
+			field = purple_request_field_list_new(name, label);
+			purple_request_field_list_set_multi_select(field, FALSE);
+			while (list && list->next) {
+				const char *label = list->data;
+				char *value = NULL;
+				switch(type) {
+					case PURPLE_PREF_BOOLEAN:
+						value = g_strdup_printf("%d", (int)list->next->data);
+						break;
+					case PURPLE_PREF_INT:
+						value = g_strdup_printf("%d", (int)list->next->data);
+						break;
+					case PURPLE_PREF_STRING:
+						value = g_strdup(list->next->data);
+						break;
+					default:
+						break;
+				}
+				stringlist = g_list_prepend(stringlist, value);
+				purple_request_field_list_add(field, label, value);
+				if (strcmp(value, current_value) == 0)
+					purple_request_field_list_add_selected(field, label);
+				list = list->next->next;
+			}
+			g_free(current_value);
+		} else {
+			switch(type) {
+				case PURPLE_PREF_BOOLEAN:
+					field = purple_request_field_bool_new(name, label, purple_prefs_get_bool(name));
+					break;
+				case PURPLE_PREF_INT:
+					field = purple_request_field_int_new(name, label, purple_prefs_get_int(name));
+					break;
+				case PURPLE_PREF_STRING:
+					field = purple_request_field_string_new(name, label, purple_prefs_get_string(name),
+							purple_plugin_pref_get_format_type(pref) & PURPLE_STRING_FORMAT_TYPE_MULTILINE);
+					break;
+				default:
+					break;
+			}
 		}
+
 		if (field) {
 			if (group == NULL) {
 				group = purple_request_field_group_new(_("Preferences"));
@@ -471,9 +528,11 @@
 		}
 	}
 
-	return purple_request_fields(NULL, _("Preferences"), NULL, NULL, fields,
+	ret = purple_request_fields(NULL, _("Preferences"), NULL, NULL, fields,
 			_("Save"), G_CALLBACK(finch_request_save_in_prefs), _("Cancel"), NULL,
 			NULL, NULL, NULL,
 			NULL);
+	g_signal_connect_swapped(G_OBJECT(ret), "destroy", G_CALLBACK(free_stringlist), stringlist);
+	return ret;
 }
 
--- a/finch/gntrequest.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/finch/gntrequest.c	Sat Dec 22 23:18:08 2007 +0000
@@ -44,7 +44,7 @@
 	GntWidget *dialog;
 	GCallback *cbs;
 	gboolean save;
-} PurpleGntFileRequest;
+} FinchFileRequest;
 
 static GntWidget *
 setup_request_window(const char *title, const char *primary,
@@ -387,6 +387,156 @@
 	}
 }
 
+static GntWidget*
+create_boolean_field(PurpleRequestField *field)
+{
+	const char *label = purple_request_field_get_label(field);
+	GntWidget *check = gnt_check_box_new(label);
+	gnt_check_box_set_checked(GNT_CHECK_BOX(check),
+			purple_request_field_bool_get_default_value(field));
+	return check;
+}
+
+static GntWidget*
+create_string_field(PurpleRequestField *field, GntWidget **screenname)
+{
+	const char *hint = purple_request_field_get_type_hint(field);
+	GntWidget *entry = gnt_entry_new(
+			purple_request_field_string_get_default_value(field));
+	gnt_entry_set_masked(GNT_ENTRY(entry),
+			purple_request_field_string_is_masked(field));
+	if (hint && purple_str_has_prefix(hint, "screenname")) {
+		PurpleBlistNode *node = purple_blist_get_root();
+		gboolean offline = purple_str_has_suffix(hint, "all");
+		for (; node; node = purple_blist_node_next(node, offline)) {
+			if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
+				continue;
+			gnt_entry_add_suggest(GNT_ENTRY(entry), purple_buddy_get_name((PurpleBuddy*)node));
+		}
+		gnt_entry_set_always_suggest(GNT_ENTRY(entry), TRUE);
+		if (screenname)
+			*screenname = entry;
+	} else if (hint && !strcmp(hint, "group")) {
+		PurpleBlistNode *node;
+		for (node = purple_blist_get_root(); node; node = node->next) {
+			if (PURPLE_BLIST_NODE_IS_GROUP(node))
+				gnt_entry_add_suggest(GNT_ENTRY(entry), ((PurpleGroup *)node)->name);
+		}
+	}
+	return entry;
+}
+
+static GntWidget*
+create_integer_field(PurpleRequestField *field)
+{
+	char str[256];
+	int val = purple_request_field_int_get_default_value(field);
+	GntWidget *entry;
+
+	snprintf(str, sizeof(str), "%d", val);
+	entry = gnt_entry_new(str);
+	gnt_entry_set_flag(GNT_ENTRY(entry), GNT_ENTRY_FLAG_INT);
+	return entry;
+}
+
+static GntWidget*
+create_choice_field(PurpleRequestField *field)
+{
+	int id;
+	GList *list;
+	GntWidget *combo = gnt_combo_box_new();
+
+	list = purple_request_field_choice_get_labels(field);
+	for (id = 1; list; list = list->next, id++)
+	{
+		gnt_combo_box_add_data(GNT_COMBO_BOX(combo),
+				GINT_TO_POINTER(id), list->data);
+	}
+	gnt_combo_box_set_selected(GNT_COMBO_BOX(combo),
+			GINT_TO_POINTER(purple_request_field_choice_get_default_value(field)));
+	return combo;
+}
+
+static GntWidget*
+create_list_field(PurpleRequestField *field)
+{
+	GntWidget *ret = NULL;
+	GList *list;
+	gboolean multi = purple_request_field_list_get_multi_select(field);
+	if (multi)
+	{
+		GntWidget *tree = gnt_tree_new();
+
+		list = purple_request_field_list_get_items(field);
+		for (; list; list = list->next)
+		{
+			const char *text = list->data;
+			gpointer key = purple_request_field_list_get_data(field, text);
+			gnt_tree_add_choice(GNT_TREE(tree), key,
+					gnt_tree_create_row(GNT_TREE(tree), text), NULL, NULL);
+			if (purple_request_field_list_is_selected(field, text))
+				gnt_tree_set_choice(GNT_TREE(tree), key, TRUE);
+		}
+		ret = tree;
+	}
+	else
+	{
+		GntWidget *combo = gnt_combo_box_new();
+
+		list = purple_request_field_list_get_items(field);
+		for (; list; list = list->next)
+		{
+			const char *text = list->data;
+			gpointer key = purple_request_field_list_get_data(field, text);
+			gnt_combo_box_add_data(GNT_COMBO_BOX(combo), key, text);
+			if (purple_request_field_list_is_selected(field, text))
+				gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), key);
+		}
+		ret = combo;
+	}
+	return ret;
+}
+
+static GntWidget*
+create_account_field(PurpleRequestField *field)
+{
+	gboolean all;
+	PurpleAccount *def;
+	GList *list;
+	GntWidget *combo = gnt_combo_box_new();
+
+	all = purple_request_field_account_get_show_all(field);
+	def = purple_request_field_account_get_value(field);
+	if (!def)
+		def = purple_request_field_account_get_default_value(field);
+
+	if (all)
+		list = purple_accounts_get_all();
+	else
+		list = purple_connections_get_all();
+
+	for (; list; list = list->next)
+	{
+		PurpleAccount *account;
+		char *text;
+
+		if (all)
+			account = list->data;
+		else
+			account = purple_connection_get_account(list->data);
+
+		text = g_strdup_printf("%s (%s)",
+				purple_account_get_username(account),
+				purple_account_get_protocol_name(account));
+		gnt_combo_box_add_data(GNT_COMBO_BOX(combo), account, text);
+		g_free(text);
+		if (account == def)
+			gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), account);
+	}
+	gnt_widget_set_size(combo, 20, 3); /* ew */
+	return combo;
+}
+
 static void *
 finch_request_fields(const char *title, const char *primary,
 		const char *secondary, PurpleRequestFields *allfields,
@@ -424,10 +574,10 @@
 			PurpleRequestField *field = fields->data;
 			PurpleRequestFieldType type = purple_request_field_get_type(field);
 			const char *label = purple_request_field_get_label(field);
-				
+
 			hbox = gnt_hbox_new(TRUE);   /* hrm */
 			gnt_box_add_widget(GNT_BOX(box), hbox);
-			
+
 			if (type != PURPLE_REQUEST_FIELD_BOOLEAN && label)
 			{
 				GntWidget *l = gnt_label_new(label);
@@ -437,154 +587,35 @@
 
 			if (type == PURPLE_REQUEST_FIELD_BOOLEAN)
 			{
-				GntWidget *check = gnt_check_box_new(label);
-				gnt_check_box_set_checked(GNT_CHECK_BOX(check),
-						purple_request_field_bool_get_default_value(field));
-				gnt_box_add_widget(GNT_BOX(hbox), check);
-				field->ui_data = check;
+				field->ui_data = create_boolean_field(field);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_STRING)
 			{
-				const char *hint = purple_request_field_get_type_hint(field);
-				GntWidget *entry = gnt_entry_new(
-							purple_request_field_string_get_default_value(field));
-				gnt_entry_set_masked(GNT_ENTRY(entry),
-						purple_request_field_string_is_masked(field));
-				if (hint && purple_str_has_prefix(hint, "screenname")) {
-					PurpleBlistNode *node = purple_blist_get_root();
-					gboolean offline = purple_str_has_suffix(hint, "all");
-					for (; node; node = purple_blist_node_next(node, offline)) {
-						if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
-							continue;
-						gnt_entry_add_suggest(GNT_ENTRY(entry), purple_buddy_get_name((PurpleBuddy*)node));
-					}
-					gnt_entry_set_always_suggest(GNT_ENTRY(entry), TRUE);
-					screenname = entry;
-				} else if (hint && !strcmp(hint, "group")) {
-					PurpleBlistNode *node;
-					for (node = purple_blist_get_root(); node; node = node->next) {
-						if (PURPLE_BLIST_NODE_IS_GROUP(node))
-							gnt_entry_add_suggest(GNT_ENTRY(entry), ((PurpleGroup *)node)->name);
-					}
-				}
-				gnt_box_add_widget(GNT_BOX(hbox), entry);
-				field->ui_data = entry;
+				field->ui_data = create_string_field(field, &screenname);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_INTEGER)
 			{
-				char str[256];
-				int val = purple_request_field_int_get_default_value(field);
-				GntWidget *entry;
-				
-				snprintf(str, sizeof(str), "%d", val);
-				entry = gnt_entry_new(str);
-				gnt_entry_set_flag(GNT_ENTRY(entry), GNT_ENTRY_FLAG_INT);
-				gnt_box_add_widget(GNT_BOX(hbox), entry);
-				field->ui_data = entry;
+				field->ui_data = create_integer_field(field);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_CHOICE)
 			{
-				int id;
-				GList *list;
-				GntWidget *combo = gnt_combo_box_new();
-				gnt_box_add_widget(GNT_BOX(hbox), combo);
-				field->ui_data = combo;
-
-				list = purple_request_field_choice_get_labels(field);
-				for (id = 1; list; list = list->next, id++)
-				{
-					gnt_combo_box_add_data(GNT_COMBO_BOX(combo),
-							GINT_TO_POINTER(id), list->data);
-				}
-				gnt_combo_box_set_selected(GNT_COMBO_BOX(combo),
-						GINT_TO_POINTER(purple_request_field_choice_get_default_value(field)));
+				field->ui_data = create_choice_field(field);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_LIST)
 			{
-				GList *list;
-				gboolean multi = purple_request_field_list_get_multi_select(field);
-				if (multi)
-				{
-					GntWidget *tree = gnt_tree_new();
-					gnt_box_add_widget(GNT_BOX(hbox), tree);
-					field->ui_data = tree;
-
-					list = purple_request_field_list_get_items(field);
-					for (; list; list = list->next)
-					{
-						const char *text = list->data;
-						gpointer key = purple_request_field_list_get_data(field, text);
-						gnt_tree_add_choice(GNT_TREE(tree), key,
-								gnt_tree_create_row(GNT_TREE(tree), text), NULL, NULL);
-						if (purple_request_field_list_is_selected(field, text))
-							gnt_tree_set_choice(GNT_TREE(tree), key, TRUE);
-					}
-				}
-				else
-				{
-					GntWidget *combo = gnt_combo_box_new();
-					gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID);
-					gnt_box_add_widget(GNT_BOX(hbox), combo);
-					field->ui_data = combo;
-
-					list = purple_request_field_list_get_items(field);
-					for (; list; list = list->next)
-					{
-						const char *text = list->data;
-						gpointer key = purple_request_field_list_get_data(field, text);
-						gnt_combo_box_add_data(GNT_COMBO_BOX(combo), key, text);
-						if (purple_request_field_list_is_selected(field, text))
-							gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), key);
-					}
-				}
+				field->ui_data = create_list_field(field);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_ACCOUNT)
 			{
-				gboolean all;
-				PurpleAccount *def;
-				GList *list;
-				GntWidget *combo = gnt_combo_box_new();
-				gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID);
-				gnt_box_add_widget(GNT_BOX(hbox), combo);
-				field->ui_data = combo;
-
-				all = purple_request_field_account_get_show_all(field);
-				def = purple_request_field_account_get_value(field);
-				if (!def)
-					def = purple_request_field_account_get_default_value(field);
-
-				if (all)
-					list = purple_accounts_get_all();
-				else
-					list = purple_connections_get_all();
-
-				for (; list; list = list->next)
-				{
-					PurpleAccount *account;
-					char *text;
-
-					if (all)
-						account = list->data;
-					else
-						account = purple_connection_get_account(list->data);
-
-					text = g_strdup_printf("%s (%s)",
-							purple_account_get_username(account),
-							purple_account_get_protocol_name(account));
-					gnt_combo_box_add_data(GNT_COMBO_BOX(combo), account, text);
-					g_free(text);
-					if (account == def)
-						gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), account);
-				}
-				gnt_widget_set_size(combo, 20, 3); /* ew */
-				accountlist = combo;
+				accountlist = field->ui_data = create_account_field(field);
 			}
 			else
 			{
-				gnt_box_add_widget(GNT_BOX(hbox),
-						gnt_label_new_with_format(_("Not implemented yet."),
-							GNT_TEXT_FLAG_BOLD));
+				field->ui_data = gnt_label_new_with_format(_("Not implemented yet."),
+						GNT_TEXT_FLAG_BOLD);
 			}
+			gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID);
+			gnt_box_add_widget(GNT_BOX(hbox), GNT_WIDGET(field->ui_data));
 		}
 		if (grlist->next)
 			gnt_box_add_widget(GNT_BOX(box), gnt_hline_new());
@@ -610,7 +641,7 @@
 static void
 file_cancel_cb(gpointer fq, GntWidget *wid)
 {
-	PurpleGntFileRequest *data = fq;
+	FinchFileRequest *data = fq;
 	if (data->cbs[1] != NULL)
 		((PurpleRequestFileCb)data->cbs[1])(data->user_data, NULL);
 
@@ -620,7 +651,7 @@
 static void
 file_ok_cb(gpointer fq, GntWidget *widget)
 {
-	PurpleGntFileRequest *data = fq;
+	FinchFileRequest *data = fq;
 	char *file = gnt_file_sel_get_selected_file(GNT_FILE_SEL(data->dialog));
 	char *dir = g_path_get_dirname(file);
 	if (data->cbs[0] != NULL)
@@ -634,38 +665,30 @@
 }
 
 static void
-file_request_destroy(PurpleGntFileRequest *data)
+file_request_destroy(FinchFileRequest *data)
 {
 	g_free(data->cbs);
 	g_free(data);
 }
 
-static void *
-finch_request_file(const char *title, const char *filename,
-				gboolean savedialog,
+static FinchFileRequest *
+finch_file_request_window(const char *title, const char *path,
 				GCallback ok_cb, GCallback cancel_cb,
-				PurpleAccount *account, const char *who, PurpleConversation *conv,
 				void *user_data)
 {
 	GntWidget *window = gnt_file_sel_new();
 	GntFileSel *sel = GNT_FILE_SEL(window);
-	PurpleGntFileRequest *data = g_new0(PurpleGntFileRequest, 1);
-	const char *path;
+	FinchFileRequest *data = g_new0(FinchFileRequest, 1);
 
 	data->user_data = user_data;
 	data->cbs = g_new0(GCallback, 2);
 	data->cbs[0] = ok_cb;
 	data->cbs[1] = cancel_cb;
 	data->dialog = window;
-	data->save = savedialog;
-	gnt_box_set_title(GNT_BOX(window), title ? title : (savedialog ? _("Save File...") : _("Open File...")));
+	gnt_box_set_title(GNT_BOX(window), title);
 
-	path = purple_prefs_get_path(savedialog ? "/finch/filelocations/last_save_folder" : "/finch/filelocations/last_open_folder");
 	gnt_file_sel_set_current_location(sel, (path && *path) ? path : purple_home_dir());
 
-	if (savedialog)
-		gnt_file_sel_set_suggested_filename(sel, filename);
-
 	g_signal_connect(G_OBJECT(sel->cancel), "activate",
 			G_CALLBACK(action_performed), window);
 	g_signal_connect(G_OBJECT(sel->select), "activate",
@@ -678,9 +701,45 @@
 	setup_default_callback(window, file_cancel_cb, data);
 	g_object_set_data_full(G_OBJECT(window), "filerequestdata", data,
 			(GDestroyNotify)file_request_destroy);
-	gnt_widget_show(window);
+
+	return data;
+}
+
+static void *
+finch_request_file(const char *title, const char *filename,
+				gboolean savedialog,
+				GCallback ok_cb, GCallback cancel_cb,
+				PurpleAccount *account, const char *who, PurpleConversation *conv,
+				void *user_data)
+{
+	FinchFileRequest *data;
+	const char *path;
 
-	return window;
+	path = purple_prefs_get_path(savedialog ? "/finch/filelocations/last_save_folder" : "/finch/filelocations/last_open_folder");
+	data = finch_file_request_window(title ? title : (savedialog ? _("Save File...") : _("Open File...")), path,
+			ok_cb, cancel_cb, user_data);
+	data->save = savedialog;
+	if (savedialog)
+		gnt_file_sel_set_suggested_filename(GNT_FILE_SEL(data->dialog), filename);
+
+	gnt_widget_show(data->dialog);
+	return data->dialog;
+}
+
+static void *
+finch_request_folder(const char *title, const char *dirname, GCallback ok_cb,
+		GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv,
+		void *user_data)
+{
+	FinchFileRequest *data;
+
+	data = finch_file_request_window(title ? title : _("Choose Location..."), dirname,
+			ok_cb, cancel_cb, user_data);
+	data->save = TRUE;
+	gnt_file_sel_set_dirs_only(GNT_FILE_SEL(data->dialog), TRUE);
+
+	gnt_widget_show(data->dialog);
+	return data->dialog;
 }
 
 static PurpleRequestUiOps uiops =
@@ -691,7 +750,7 @@
 	finch_request_fields,
 	finch_request_file,
 	finch_close_request,
-	NULL,                       /* No plans for request_folder */
+	finch_request_folder,
 	NULL,
 	NULL,
 	NULL,
--- a/finch/libgnt/Makefile.am	Sat Dec 22 17:54:30 2007 +0000
+++ b/finch/libgnt/Makefile.am	Sat Dec 22 23:18:08 2007 +0000
@@ -84,10 +84,12 @@
 libgnt_la_LIBADD = \
 	$(GLIB_LIBS) \
 	$(GNT_LIBS) \
-	$(LIBXML_LIBS)
+	$(LIBXML_LIBS) \
+	$(PY_LIBS)
 
 AM_CPPFLAGS = \
 	$(GLIB_CFLAGS) \
 	$(GNT_CFLAGS) \
 	$(DEBUG_CFLAGS) \
-	$(LIBXML_CFLAGS)
+	$(LIBXML_CFLAGS) \
+	$(PY_CFLAGS)
--- a/finch/libgnt/configure.ac	Sat Dec 22 17:54:30 2007 +0000
+++ b/finch/libgnt/configure.ac	Sat Dec 22 23:18:08 2007 +0000
@@ -24,10 +24,10 @@
 # Make sure to update ../../configure.ac with libgnt version changes.
 #
 
-m4_define([gnt_lt_current], [3])
+m4_define([gnt_lt_current], [4])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [3])
-m4_define([gnt_micro_version], [1])
+m4_define([gnt_minor_version], [4])
+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])
@@ -302,6 +302,34 @@
 *** You need ncursesw or ncurses and its header files.])
 fi
 
+dnl Check for Python headers (currently useful only for libgnt)
+dnl (Thanks to XChat)
+AC_PATH_PROG(pythonpath, python)
+if test "_$pythonpath" != _ ; then
+	AC_MSG_CHECKING(for Python compile flags)
+	PY_PREFIX=`$pythonpath -c 'import sys ; print sys.prefix'`
+	PY_EXEC_PREFIX=`$pythonpath -c 'import sys ; print sys.exec_prefix'`
+	changequote(<<, >>)dnl
+	PY_VERSION=`$pythonpath -c 'import sys ; print sys.version[0:3]'`
+	PY_MAJOR=`$pythonpath -c 'import sys ; print sys.version[0:2]'`
+	changequote([, ])dnl
+	if test -f $PY_PREFIX/include/python$PY_VERSION/Python.h -a "$PY_MAJOR" = "2."; then
+		AC_CHECK_LIB(pthread, pthread_create, )
+		AC_CHECK_LIB(util, openpty, )
+		AC_CHECK_LIB(db, dbopen, )
+		PY_LIBS="-lpython$PY_VERSION -L$PY_EXEC_PREFIX/lib/python$PY_VERSION/config"
+		PY_CFLAGS="-I$PY_PREFIX/include/python$PY_VERSION"
+		AC_DEFINE(USE_PYTHON, [1], [Define if python headers are available.])
+		AC_MSG_RESULT(ok)
+	else
+		AC_MSG_RESULT([Can't find Python.h])
+		PY_LIBS=""
+		PY_CFLAGS=""
+	fi
+fi
+AC_SUBST(PY_CFLAGS)
+AC_SUBST(PY_LIBS)
+
 dnl Check for libxml
 have_libxml=yes
 PKG_CHECK_MODULES(LIBXML, [libxml-2.0], , [
--- a/finch/libgnt/gntcolors.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/finch/libgnt/gntcolors.h	Sat Dec 22 23:18:08 2007 +0000
@@ -93,7 +93,7 @@
  *
  * @return A color
  *
- * @since 2.3.1 (gnt), 2.3.1 (pidgin)
+ * @since 2.4.0
  */
 int gnt_colors_get_color(char *key);
 #endif
@@ -119,7 +119,7 @@
  *
  * @return  A color pair
  *
- * @since 2.3.1
+ * @since 2.4.0
  */
 int gnt_color_add_pair(int fg, int bg);
 #endif
--- a/finch/libgnt/gntstyle.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/finch/libgnt/gntstyle.h	Sat Dec 22 23:18:08 2007 +0000
@@ -74,7 +74,7 @@
  *
  * @return        NULL terminated string array. The array should be freed with g_strfreev().
  *
- * @since 2.3.2
+ * @since 2.4.0
  */
 char **gnt_style_get_string_list(const char *group, const char *key, gsize *length);
 
@@ -87,7 +87,7 @@
  *
  * @return  The value of the color as an int, or 0 on error.
  *
- * @since 2.3.2
+ * @since 2.4.0
  */
 int gnt_style_get_color(char *group, char *key);
 
--- a/finch/libgnt/gnttree.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/finch/libgnt/gnttree.h	Sat Dec 22 23:18:08 2007 +0000
@@ -330,6 +330,7 @@
  * @param tree   The tree
  * @param key    The key for the row
  * @param color  The color
+ * @since 2.4.0
  */
 void gnt_tree_set_row_color(GntTree *, void *, int);
 
--- a/finch/libgnt/gntwm.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/finch/libgnt/gntwm.c	Sat Dec 22 23:18:08 2007 +0000
@@ -20,12 +20,16 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
+#include "config.h"
+
+#ifdef USE_PYTHON
+#include <Python.h>
+#else
 #define _GNU_SOURCE
 #if (defined(__APPLE__) || defined(__unix__)) && !defined(__FreeBSD__)
 #define _XOPEN_SOURCE_EXTENDED
 #endif
-
-#include "config.h"
+#endif
 
 #include <glib.h>
 #include <glib/gstdio.h>
@@ -1198,12 +1202,50 @@
 	return ignore_keys ? !(ignore_keys = FALSE) : FALSE;
 }
 
+#ifdef USE_PYTHON
+static void
+python_script_selected(GntFileSel *fs, const char *path, const char *f, gpointer n)
+{
+	char *dir = g_path_get_dirname(path);
+	FILE *file = fopen(path, "r");
+	PyObject *pp = PySys_GetObject("path"), *dirobj = PyString_FromString(dir);
+
+	PyList_Insert(pp, 0, dirobj);
+	Py_DECREF(dirobj);
+	PyRun_SimpleFile(file, path);
+	fclose(file);
+
+	if (PyErr_Occurred()) {
+		PyErr_Print();
+	}
+	g_free(dir);
+
+	gnt_widget_destroy(GNT_WIDGET(fs));
+}
+
+static gboolean
+run_python(GntBindable *bindable, GList *n)
+{
+	GntWidget *window = gnt_file_sel_new();
+	GntFileSel *sel = GNT_FILE_SEL(window);
+
+	g_object_set(G_OBJECT(window), "vertical", TRUE, NULL);
+	gnt_box_add_widget(GNT_BOX(window), gnt_label_new("Please select the python script you want to run."));
+	gnt_box_set_title(GNT_BOX(window), "Select Python Script...");
+
+	g_signal_connect(G_OBJECT(sel), "file_selected", G_CALLBACK(python_script_selected), NULL);
+	g_signal_connect_swapped(G_OBJECT(sel->cancel), "activate", G_CALLBACK(gnt_widget_destroy), sel);
+	gnt_widget_show(window);
+	return TRUE;
+}
+#endif  /* USE_PYTHON */
+
 static gboolean
 help_for_bindable(GntWM *wm, GntBindable *bindable)
 {
 	gboolean ret = TRUE;
 	GntBindableClass *klass = GNT_BINDABLE_GET_CLASS(bindable);
- 
+
 	if (klass->help_window) {
 		gnt_wm_raise_window(wm, GNT_WIDGET(klass->help_window));
 	} else {
@@ -1271,6 +1313,9 @@
 		g_object_unref(wm->workspaces->data);
 		wm->workspaces = g_list_delete_link(wm->workspaces, wm->workspaces);
 	}
+#ifdef USE_PYTHON
+	Py_Finalize();
+#endif
 }
 
 static void
@@ -1441,6 +1486,12 @@
 				GNT_KEY_CTRL_G, NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "ignore-keys-end", ignore_keys_end, 
 				"\033" GNT_KEY_CTRL_G, NULL);
+#ifdef USE_PYTHON
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "run-python", run_python,
+				GNT_KEY_F3, NULL);
+	Py_SetProgramName("gnt");
+	Py_Initialize();
+#endif
 
 	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
 
--- a/libpurple/Makefile.am	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/Makefile.am	Sat Dec 22 23:18:08 2007 +0000
@@ -135,8 +135,6 @@
 
 purple_builtheaders = purple.h version.h
 
-BUILT_SOURCES = $(purple_builtheaders)
-
 if ENABLE_DBUS
 
 CLEANFILES = \
@@ -211,6 +209,17 @@
 
 bin_SCRIPTS = purple-remote purple-send purple-send-async purple-url-handler
 
+BUILT_SOURCES = $(purple_builtheaders) \
+	dbus-types.c \
+	dbus-types.h \
+	dbus-bindings.c \
+	purple-client-bindings.c \
+	purple-client-bindings.h
+
+else
+
+BUILT_SOURCES = $(purple_builtheaders)
+
 endif
 
 lib_LTLIBRARIES = libpurple.la $(libpurple_client_lib)
--- a/libpurple/account.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/account.c	Sat Dec 22 23:18:08 2007 +0000
@@ -331,13 +331,24 @@
 	if(err == NULL)
 		return node;
 
+	/* It doesn't make sense to have transient errors persist across a
+	 * restart.
+	 */
+	if(!purple_connection_error_is_fatal (err->type))
+		return node;
+
 	child = xmlnode_new_child(node, "type");
 	snprintf(type_str, sizeof(type_str), "%u", err->type);
 	xmlnode_insert_data(child, type_str, -1);
 
 	child = xmlnode_new_child(node, "description");
-	if(err->description)
-		xmlnode_insert_data(child, err->description, -1);
+	if(err->description) {
+		char *utf8ized = purple_utf8_try_convert(err->description);
+		if(utf8ized == NULL)
+			utf8ized = purple_utf8_salvage(err->description);
+		xmlnode_insert_data(child, utf8ized, -1);
+		g_free(utf8ized);
+	}
 
 	return node;
 }
@@ -2529,22 +2540,19 @@
 
 	g_return_val_if_fail(name != NULL, NULL);
 
-	who = g_strdup(purple_normalize(NULL, name));
-
 	for (l = purple_accounts_get_all(); l != NULL; l = l->next) {
 		account = (PurpleAccount *)l->data;
 
-		if (!strcmp(purple_normalize(NULL, purple_account_get_username(account)), who) &&
+		who = g_strdup(purple_normalize(account, name));
+		if (!strcmp(purple_normalize(account, purple_account_get_username(account)), who) &&
 			(!protocol_id || !strcmp(account->protocol_id, protocol_id))) {
-
+			g_free(who);
 			break;
 		}
-
+		g_free(who);
 		account = NULL;
 	}
 
-	g_free(who);
-
 	return account;
 }
 
--- a/libpurple/account.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/account.h	Sat Dec 22 23:18:08 2007 +0000
@@ -256,7 +256,7 @@
  * @param remote_user  The name of the user that added this account.
  * @param id           The optional ID of the local account. Rarely used.
  * @param alias        The optional alias of the remote user.
- * @param message      The optional message sent from the uer requesting you
+ * @param message      The optional message sent by the user wanting to add you.
  * @param on_list      Is the remote user already on the buddy list?
  * @param auth_cb      The callback called when the local user accepts
  * @param deny_cb      The callback called when the local user rejects
--- a/libpurple/buddyicon.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/buddyicon.c	Sat Dec 22 23:18:08 2007 +0000
@@ -98,8 +98,7 @@
 {
 	const char *dirname;
 	char *path;
-	FILE *file = NULL;
-
+	
 	g_return_if_fail(img != NULL);
 
 	if (!purple_buddy_icons_is_caching())
@@ -120,24 +119,12 @@
 		}
 	}
 
-	if ((file = g_fopen(path, "wb")) != NULL)
-	{
-		if (!fwrite(purple_imgstore_get_data(img), purple_imgstore_get_size(img), 1, file))
-		{
-			purple_debug_error("buddyicon", "Error writing %s: %s\n",
-			                   path, g_strerror(errno));
-		}
-		else
-			purple_debug_info("buddyicon", "Wrote cache file: %s\n", path);
-
-		fclose(file);
-	}
-	else
-	{
+	if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
+		purple_util_write_data_to_file_absolute(path, purple_imgstore_get_data(img),
+							purple_imgstore_get_size(img));	
+	} else 	{
 		purple_debug_error("buddyicon", "Unable to create file %s: %s\n",
 		                   path, g_strerror(errno));
-		g_free(path);
-		return;
 	}
 	g_free(path);
 }
--- a/libpurple/certificate.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/certificate.c	Sat Dec 22 23:18:08 2007 +0000
@@ -627,7 +627,7 @@
 
 /** System directory to probe for CA certificates */
 /* This is set in the lazy_init function */
-static const gchar *x509_ca_syspath = NULL;
+static GList *x509_ca_paths = NULL;
 
 /** A list of loaded CAs, populated from the above path whenever the lazy_init
     happens. Contains pointers to x509_ca_elements */
@@ -674,6 +674,7 @@
 	GDir *certdir;
 	const gchar *entry;
 	GPatternSpec *pempat;
+	GList *iter = NULL;
 	
 	if (x509_ca_initialized) return TRUE;
 
@@ -687,54 +688,48 @@
 		return FALSE;
 	}
 
-	/* Attempt to point at the appropriate system path */
-	if (NULL == x509_ca_syspath) {
-#ifdef _WIN32
-		x509_ca_syspath = g_build_filename(DATADIR,
-						   "ca-certs", NULL);
-#else
-		x509_ca_syspath = g_build_filename(DATADIR,
-						   "purple", "ca-certs", NULL);
-#endif
-	}
-
-	/* Populate the certificates pool from the system path */
-	certdir = g_dir_open(x509_ca_syspath, 0, NULL);
-	g_return_val_if_fail(certdir, FALSE);
-
 	/* Use a glob to only read .pem files */
 	pempat = g_pattern_spec_new("*.pem");
-	
-	while ( (entry = g_dir_read_name(certdir)) ) {
-		gchar *fullpath;
-		PurpleCertificate *crt;
 
-		if ( !g_pattern_match_string(pempat, entry) ) {
+	/* Populate the certificates pool from the search path(s) */
+	for (iter = x509_ca_paths; iter; iter = iter->next) {
+		certdir = g_dir_open(iter->data, 0, NULL);
+		if (!certdir) {
+			purple_debug_error("certificate/x509/ca", "Couldn't open location '%s'\n", iter->data);
 			continue;
 		}
 
-		fullpath = g_build_filename(x509_ca_syspath, entry, NULL);
-		
-		/* TODO: Respond to a failure in the following? */
-		crt = purple_certificate_import(x509, fullpath);
+		while ( (entry = g_dir_read_name(certdir)) ) {
+			gchar *fullpath;
+			PurpleCertificate *crt;
+
+			if ( !g_pattern_match_string(pempat, entry) ) {
+				continue;
+			}
+
+			fullpath = g_build_filename(iter->data, entry, NULL);
+
+			/* TODO: Respond to a failure in the following? */
+			crt = purple_certificate_import(x509, fullpath);
 
-		if (x509_ca_quiet_put_cert(crt)) {
-			purple_debug_info("certificate/x509/ca",
-					  "Loaded %s\n",
-					  fullpath);
-		} else {
-			purple_debug_error("certificate/x509/ca",
-					  "Failed to load %s\n",
-					  fullpath);
+			if (x509_ca_quiet_put_cert(crt)) {
+				purple_debug_info("certificate/x509/ca",
+						  "Loaded %s\n",
+						  fullpath);
+			} else {
+				purple_debug_error("certificate/x509/ca",
+						  "Failed to load %s\n",
+						  fullpath);
+			}
+
+			purple_certificate_destroy(crt);
+			g_free(fullpath);
 		}
-
-		purple_certificate_destroy(crt);
-		g_free(fullpath);
+		g_dir_close(certdir);
 	}
 
 	g_pattern_spec_free(pempat);
-	g_dir_close(certdir);
-	
+
 	purple_debug_info("certificate/x509/ca",
 			  "Lazy init completed.\n");
 	x509_ca_initialized = TRUE;
@@ -744,6 +739,17 @@
 static gboolean
 x509_ca_init(void)
 {
+	/* Attempt to point at the appropriate system path */
+	if (NULL == x509_ca_paths) {
+#ifdef _WIN32
+		x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR,
+						   "ca-certs", NULL));
+#else
+		x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR,
+						   "purple", "ca-certs", NULL));
+#endif
+	}
+
 	/* Attempt to initialize now, but if it doesn't work, that's OK;
 	   it will get done later */
 	if ( ! x509_ca_lazy_init()) {
@@ -752,7 +758,7 @@
 				  "dependency is not yet registered. "
 				  "It has been deferred to later.\n");
 	}
-	
+
 	return TRUE;
 }
 
@@ -768,6 +774,9 @@
 	g_list_free(x509_ca_certs);
 	x509_ca_certs = NULL;
 	x509_ca_initialized = FALSE;
+	g_list_foreach(x509_ca_paths, (GFunc)g_free, NULL);
+	g_list_free(x509_ca_paths);
+	x509_ca_paths = NULL;
 }
 
 /** Look up a ca_element by dn */
@@ -1901,3 +1910,10 @@
 	g_byte_array_free(sha_bin, TRUE);
 }
 
+void purple_certificate_add_ca_search_path(const char *path)
+{
+	if (g_list_find_custom(x509_ca_paths, path, (GCompareFunc)strcmp))
+		return;
+	x509_ca_paths = g_list_append(x509_ca_paths, g_strdup(path));
+}
+
--- a/libpurple/certificate.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/certificate.h	Sat Dec 22 23:18:08 2007 +0000
@@ -786,6 +786,12 @@
 void
 purple_certificate_display_x509(PurpleCertificate *crt);
 
+/**
+ * Add a search path for certificates.
+ *
+ * @param path   Path to search for certificates.
+ */
+void purple_certificate_add_ca_search_path(const char *path);
 
 #ifdef __cplusplus
 }
--- a/libpurple/cipher.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/cipher.c	Sat Dec 22 23:18:08 2007 +0000
@@ -64,6 +64,8 @@
 /*******************************************************************************
  * MD5
  ******************************************************************************/
+#define MD5_HMAC_BLOCK_SIZE	64
+
 struct MD5Context {
 	guint32 total[2];
 	guint32 state[4];
@@ -325,6 +327,13 @@
 	return TRUE;
 }
 
+static size_t
+md5_get_block_size(PurpleCipherContext *context)
+{
+	/* This does not change (in this case) */
+	return MD5_HMAC_BLOCK_SIZE;
+}
+
 static PurpleCipherOps MD5Ops = {
 	NULL,			/* Set option */
 	NULL,			/* Get option */
@@ -340,12 +349,10 @@
 	NULL,			/* get salt size */
 	NULL,			/* set key */
 	NULL,			/* get key size */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	NULL,			/* set batch mode */
+	NULL,			/* get batch mode */
+	md5_get_block_size,	/* get block size */
+	NULL			/* set key with len */
 };
 
 /*******************************************************************************
@@ -580,6 +587,13 @@
 	md4_context = NULL;
 }
 
+static size_t
+md4_get_block_size(PurpleCipherContext *context)
+{
+	/* This does not change (in this case) */
+	return MD4_HMAC_BLOCK_SIZE;
+}
+
 static PurpleCipherOps MD4Ops = {
 	NULL,                   /* Set option */
 	NULL,                   /* Get option */
@@ -595,12 +609,202 @@
 	NULL,                   /* get salt size */
 	NULL,                   /* set key */
 	NULL,                   /* get key size */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	NULL,                   /* set batch mode */
+	NULL,                   /* get batch mode */
+	md4_get_block_size,     /* get block size */
+	NULL                    /* set key with len */
+};
+
+/*******************************************************************************
+ * HMAC
+ ******************************************************************************/
+
+struct HMAC_Context {
+	PurpleCipherContext *hash;
+	char *name;
+	int blocksize;
+	guchar *opad;
+};
+
+static void
+hmac_init(PurpleCipherContext *context, gpointer extra)
+{
+	struct HMAC_Context *hctx;
+	hctx = g_new0(struct HMAC_Context, 1);
+	purple_cipher_context_set_data(context, hctx);
+	purple_cipher_context_reset(context, extra);
+}
+
+static void
+hmac_reset(PurpleCipherContext *context, gpointer extra)
+{
+	struct HMAC_Context *hctx;
+
+	hctx = purple_cipher_context_get_data(context);
+
+	g_free(hctx->name);
+	hctx->name = NULL;
+	if (hctx->hash)
+		purple_cipher_context_destroy(hctx->hash);
+	hctx->hash = NULL;
+	hctx->blocksize = 0;
+	g_free(hctx->opad);
+	hctx->opad = NULL;
+}
+
+static void
+hmac_set_opt(PurpleCipherContext *context, const gchar *name, void *value)
+{
+	struct HMAC_Context *hctx;
+
+	hctx = purple_cipher_context_get_data(context);
+
+	if (!strcmp(name, "hash")) {
+		g_free(hctx->name);
+		if (hctx->hash)
+			purple_cipher_context_destroy(hctx->hash);
+		hctx->name = g_strdup((char*)value);
+		hctx->hash = purple_cipher_context_new_by_name((char *)value, NULL);
+		hctx->blocksize = purple_cipher_context_get_block_size(hctx->hash);
+	}
+}
+
+static void *
+hmac_get_opt(PurpleCipherContext *context, const gchar *name)
+{
+	struct HMAC_Context *hctx;
+
+	hctx = purple_cipher_context_get_data(context);
+
+	if (!strcmp(name, "hash")) {
+		return hctx->name;
+	}
+
+	return NULL;
+}
+
+static void
+hmac_append(PurpleCipherContext *context, const guchar *data, size_t len) 
+{
+	struct HMAC_Context *hctx = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(hctx->hash != NULL);
+
+	purple_cipher_context_append(hctx->hash, data, len);
+}
+
+static gboolean
+hmac_digest(PurpleCipherContext *context, size_t in_len, guchar *out, size_t *out_len)
+{
+	struct HMAC_Context *hctx = purple_cipher_context_get_data(context);
+	PurpleCipherContext *hash = hctx->hash;
+	guchar *inner_hash;
+	size_t hash_len;
+	gboolean result;
+
+	g_return_val_if_fail(hash != NULL, FALSE);
+
+	inner_hash = g_malloc(100); /* TODO: Should be enough for now... */
+	result = purple_cipher_context_digest(hash, 100, inner_hash, &hash_len);
+
+	purple_cipher_context_reset(hash, NULL);
+
+	purple_cipher_context_append(hash, hctx->opad, hctx->blocksize);
+	purple_cipher_context_append(hash, inner_hash, hash_len);
+
+	g_free(inner_hash);
+
+	result = result && purple_cipher_context_digest(hash, in_len, out, out_len);
+
+	return result;
+}
+
+static void
+hmac_uninit(PurpleCipherContext *context)
+{
+	struct HMAC_Context *hctx;
+
+	purple_cipher_context_reset(context, NULL);
+
+	hctx = purple_cipher_context_get_data(context);
+
+	g_free(hctx);
+}
+
+static void
+hmac_set_key_with_len(PurpleCipherContext *context, const guchar * key, size_t key_len)
+{
+	struct HMAC_Context *hctx = purple_cipher_context_get_data(context);
+	int blocksize, i;
+	guchar *ipad;
+	guchar *full_key;
+
+	g_return_if_fail(hctx->hash != NULL);
+
+	g_free(hctx->opad);
+
+	blocksize = hctx->blocksize;
+	ipad = g_malloc(blocksize);
+	hctx->opad = g_malloc(blocksize);
+
+	if (key_len > blocksize) {
+		purple_cipher_context_reset(hctx->hash, NULL);
+		purple_cipher_context_append(hctx->hash, key, key_len);
+		full_key = g_malloc(100); /* TODO: Should be enough for now... */
+		purple_cipher_context_digest(hctx->hash, 100, full_key, &key_len);
+	} else
+		full_key = g_memdup(key, key_len);
+
+    if (key_len < blocksize) {
+    	full_key = g_realloc(full_key, blocksize);
+    	memset(full_key + key_len, 0, blocksize - key_len);
+    }
+
+	for(i = 0; i < blocksize; i++) {
+		ipad[i] = 0x36 ^ full_key[i];
+		hctx->opad[i] = 0x5c ^ full_key[i];
+	}
+
+	g_free(full_key);
+
+	purple_cipher_context_reset(hctx->hash, NULL);
+	purple_cipher_context_append(hctx->hash, ipad, blocksize);
+	g_free(ipad);
+}
+
+static void
+hmac_set_key(PurpleCipherContext *context, const guchar * key)
+{
+	hmac_set_key_with_len(context, key, strlen(key));
+}
+
+static size_t 
+hmac_get_block_size(PurpleCipherContext *context)
+{
+	struct HMAC_Context *hctx = purple_cipher_context_get_data(context);
+
+	return hctx->blocksize;
+}
+
+static PurpleCipherOps HMACOps = {
+	hmac_set_opt,           /* Set option */
+	hmac_get_opt,           /* Get option */
+	hmac_init,               /* init */
+	hmac_reset,              /* reset */
+	hmac_uninit,             /* uninit */
+	NULL,                   /* set iv */
+	hmac_append,             /* append */
+	hmac_digest,             /* digest */
+	NULL,                   /* encrypt */
+	NULL,                   /* decrypt */
+	NULL,                   /* set salt */
+	NULL,                   /* get salt size */
+	hmac_set_key,           /* set key */
+	NULL,                   /* get key size */
+	NULL,                   /* set batch mode */
+	NULL,                   /* get batch mode */
+	hmac_get_block_size,    /* get block size */
+	hmac_set_key_with_len   /* set key with len */
 };
 
 /******************************************************************************
@@ -986,6 +1190,36 @@
 	return 0;
 }
 
+static gint
+des_decrypt(PurpleCipherContext *context, const guchar data[],
+	    size_t len, guchar output[], size_t *outlen) {
+	int offset = 0;
+	int i = 0;
+	int tmp;
+	guint8 buf[8] = {0,0,0,0,0,0,0,0};
+	while(offset+8<=len) {
+		des_ecb_crypt(purple_cipher_context_get_data(context),
+				data+offset,
+				output+offset,
+				1);
+		offset+=8;
+	}
+	*outlen = len;
+	if(offset<len) {
+		*outlen += len - offset;
+		tmp = offset;
+		while(tmp<len) {
+			buf[i++] = data[tmp];
+			tmp++;
+		}
+		des_ecb_crypt(purple_cipher_context_get_data(context),
+				buf,
+				output+offset,
+				1);
+	}	
+	return 0;
+}
+
 static void
 des_init(PurpleCipherContext *context, gpointer extra) {
 	struct _des_ctx *mctx;
@@ -1005,32 +1239,380 @@
 }
 
 static PurpleCipherOps DESOps = {
-	NULL,                   /* Set option */
-	NULL,                   /* Get option */
-	des_init,               /* init */
+	NULL,              /* Set option */
+	NULL,              /* Get option */
+	des_init,          /* init */
+ 	NULL,              /* reset */
+	des_uninit,        /* uninit */
+	NULL,              /* set iv */
+	NULL,              /* append */
+	NULL,              /* digest */
+	des_encrypt,       /* encrypt */
+	des_decrypt,       /* decrypt */
+	NULL,              /* set salt */
+	NULL,              /* get salt size */
+	des_set_key,       /* set key */
+	NULL,              /* get key size */
+	NULL,              /* set batch mode */
+	NULL,              /* get batch mode */
+	NULL,              /* get block size */
+	NULL               /* set key with len */
+};
+
+/******************************************************************************
+ * Triple-DES
+ *****************************************************************************/
+
+typedef struct _des3_ctx
+{
+	PurpleCipherBatchMode mode;
+	guchar iv[8];
+	/* First key for encryption */
+	struct _des_ctx key1;
+	/* Second key for decryption */
+	struct _des_ctx key2;
+	/* Third key for encryption */
+	struct _des_ctx key3;
+} des3_ctx[1];
+
+/*
+ *  Fill a DES3 context with subkeys calculated from 3 64bit key.
+ *  Does not check parity bits, but simply ignore them.
+ *  Does not check for weak keys.
+ **/
+static void
+des3_set_key(PurpleCipherContext *context, const guchar * key)
+{
+	struct _des3_ctx *ctx = purple_cipher_context_get_data(context);
+	int i;
+
+	des_key_schedule (key +  0, ctx->key1.encrypt_subkeys);
+	des_key_schedule (key +  8, ctx->key2.encrypt_subkeys);
+	des_key_schedule (key + 16, ctx->key3.encrypt_subkeys);
+
+	for (i = 0; i < 32; i += 2)
+	{
+		ctx->key1.decrypt_subkeys[i]	= ctx->key1.encrypt_subkeys[30-i];
+		ctx->key1.decrypt_subkeys[i+1]	= ctx->key1.encrypt_subkeys[31-i];
+		ctx->key2.decrypt_subkeys[i]	= ctx->key2.encrypt_subkeys[30-i];
+		ctx->key2.decrypt_subkeys[i+1]	= ctx->key2.encrypt_subkeys[31-i];
+		ctx->key3.decrypt_subkeys[i]	= ctx->key3.encrypt_subkeys[30-i];
+		ctx->key3.decrypt_subkeys[i+1]	= ctx->key3.encrypt_subkeys[31-i];
+	}
+}
+
+static gint
+des3_ecb_encrypt(struct _des3_ctx *ctx, const guchar data[],
+                 size_t len, guchar output[], size_t *outlen)
+{
+	int offset = 0;
+	int i = 0;
+	int tmp;
+	guint8 buf[8] = {0,0,0,0,0,0,0,0};
+	while (offset + 8 <= len) {
+		des_ecb_crypt(&ctx->key1,
+		              data+offset,
+		              output+offset,
+		              0);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              1);
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              0);
+		offset += 8;
+	}
+	*outlen = len;
+	if (offset < len) {
+		*outlen += len - offset;
+		tmp = offset;
+		memset(buf, 0, 8);
+		while (tmp < len) {
+			buf[i++] = data[tmp];
+			tmp++;
+		}
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              0);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              1);
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              0);
+	}
+	return 0;
+}
+
+static gint
+des3_cbc_encrypt(struct _des3_ctx *ctx, const guchar data[],
+                 size_t len, guchar output[], size_t *outlen)
+{
+	int offset = 0;
+	int i = 0;
+	int tmp;
+	guint8 buf[8];
+	memcpy(buf, ctx->iv, 8);
+	while (offset + 8 <= len) {
+		for (i = 0; i < 8; i++)
+			buf[i] ^= data[offset + i];
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              0);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              1);
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              0);
+		memcpy(buf, output+offset, 8);
+		offset += 8;
+	}
+	*outlen = len;
+	if (offset < len) {
+		*outlen += len - offset;
+		tmp = offset;
+		i = 0;
+		while (tmp < len) {
+			buf[i++] ^= data[tmp];
+			tmp++;
+		}
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              0);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              1);
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              0);
+	}
+	return 0;
+}
+
+static gint
+des3_encrypt(PurpleCipherContext *context, const guchar data[],
+             size_t len, guchar output[], size_t *outlen)
+{
+	struct _des3_ctx *ctx = purple_cipher_context_get_data(context);
+
+	if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_ECB) {
+		return des3_ecb_encrypt(ctx, data, len, output, outlen);
+	} else if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_CBC) {
+		return des3_cbc_encrypt(ctx, data, len, output, outlen);
+	} else {
+		g_return_val_if_reached(0);
+	}
+
+	return 0;
+}
+
+static gint
+des3_ecb_decrypt(struct _des3_ctx *ctx, const guchar data[],
+                 size_t len, guchar output[], size_t *outlen)
+{
+	int offset = 0;
+	int i = 0;
+	int tmp;
+	guint8 buf[8] = {0,0,0,0,0,0,0,0};
+	while (offset + 8 <= len) {
+		/* NOTE: Apply key in reverse */
+		des_ecb_crypt(&ctx->key3,
+		              data+offset,
+		              output+offset,
+		              1);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              0);
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              1);
+		offset+=8;
+	}
+	*outlen = len;
+	if (offset < len) {
+		*outlen += len - offset;
+		tmp = offset;
+		memset(buf, 0, 8);
+		while (tmp < len) {
+			buf[i++] = data[tmp];
+			tmp++;
+		}
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              1);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              0);
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              1);
+	}
+	return 0;
+}
+
+static gint
+des3_cbc_decrypt(struct _des3_ctx *ctx, const guchar data[],
+                 size_t len, guchar output[], size_t *outlen)
+{
+	int offset = 0;
+	int i = 0;
+	int tmp;
+	guint8 buf[8] = {0,0,0,0,0,0,0,0};
+	guint8 link[8];
+	memcpy(link, ctx->iv, 8);
+	while (offset + 8 <= len) {
+		des_ecb_crypt(&ctx->key3,
+		              data+offset,
+		              output+offset,
+		              1);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              0);
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              1);
+		for (i = 0; i < 8; i++)
+			output[offset + i] ^= link[i];
+		memcpy(link, data + offset, 8);
+		offset+=8;
+	}
+	*outlen = len;
+	if(offset<len) {
+		*outlen += len - offset;
+		tmp = offset;
+		memset(buf, 0, 8);
+		i = 0;
+		while(tmp<len) {
+			buf[i++] = data[tmp];
+			tmp++;
+		}
+		des_ecb_crypt(&ctx->key3,
+		              buf,
+		              output+offset,
+		              1);
+		des_ecb_crypt(&ctx->key2,
+		              output+offset,
+		              buf,
+		              0);
+		des_ecb_crypt(&ctx->key1,
+		              buf,
+		              output+offset,
+		              1);
+		for (i = 0; i < 8; i++)
+			output[offset + i] ^= link[i];
+	}
+	return 0;
+}
+
+static gint
+des3_decrypt(PurpleCipherContext *context, const guchar data[],
+             size_t len, guchar output[], size_t *outlen)
+{
+	struct _des3_ctx *ctx = purple_cipher_context_get_data(context);
+
+	if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_ECB) {
+		return des3_ecb_decrypt(ctx, data, len, output, outlen);
+	} else if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_CBC) {
+		return des3_cbc_decrypt(ctx, data, len, output, outlen);
+	} else {
+		g_return_val_if_reached(0);
+	}
+
+	return 0;
+}
+
+static void
+des3_set_batch(PurpleCipherContext *context, PurpleCipherBatchMode mode)
+{
+	struct _des3_ctx *ctx = purple_cipher_context_get_data(context);
+
+	ctx->mode = mode;
+}
+
+static PurpleCipherBatchMode
+des3_get_batch(PurpleCipherContext *context)
+{
+	struct _des3_ctx *ctx = purple_cipher_context_get_data(context);
+
+	return ctx->mode;
+}
+
+static void
+des3_set_iv(PurpleCipherContext *context, guchar *iv, size_t len)
+{
+	struct _des3_ctx *ctx;
+
+	g_return_if_fail(len == 8);
+
+	ctx = purple_cipher_context_get_data(context);
+
+	memcpy(ctx->iv, iv, len);
+}
+
+static void
+des3_init(PurpleCipherContext *context, gpointer extra)
+{
+	struct _des3_ctx *mctx;
+	mctx = g_new0(struct _des3_ctx, 1);
+	purple_cipher_context_set_data(context, mctx);
+}
+
+static void
+des3_uninit(PurpleCipherContext *context)
+{
+	struct _des3_ctx *des3_context;
+
+	des3_context = purple_cipher_context_get_data(context);
+	memset(des3_context, 0, sizeof(des3_context));
+
+	g_free(des3_context);
+	des3_context = NULL;
+}
+
+static PurpleCipherOps DES3Ops = {
+	NULL,              /* Set option */
+	NULL,              /* Get option */
+	des3_init,         /* init */
 	NULL,              /* reset */
-	des_uninit,             /* uninit */
-	NULL,                   /* set iv */
-	NULL,             /* append */
-	NULL,             /* digest */
-	des_encrypt,                   /* encrypt */
-	NULL,                   /* decrypt */
-	NULL,                   /* set salt */
-	NULL,                   /* get salt size */
-	des_set_key,		/* set key */
-	NULL,                   /* get key size */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	des3_uninit,       /* uninit */
+	des3_set_iv,       /* set iv */
+	NULL,              /* append */
+	NULL,              /* digest */
+	des3_encrypt,      /* encrypt */
+	des3_decrypt,      /* decrypt */
+	NULL,              /* set salt */
+	NULL,              /* get salt size */
+	des3_set_key,      /* set key */
+	NULL,              /* get key size */
+	des3_set_batch,    /* set batch mode */
+	des3_get_batch,    /* get batch mode */
+	NULL,              /* get block size */
+	NULL               /* set key with len */
 };
 
-
 /*******************************************************************************
  * SHA-1
  ******************************************************************************/
+#define SHA1_HMAC_BLOCK_SIZE	64
 #define SHA1_ROTL(X,n) ((((X) << (n)) | ((X) >> (32-(n)))) & 0xFFFFFFFF)
 
 struct SHA1Context {
@@ -1251,6 +1833,13 @@
 	return TRUE;
 }
 
+static size_t
+sha1_get_block_size(PurpleCipherContext *context)
+{
+	/* This does not change (in this case) */
+	return SHA1_HMAC_BLOCK_SIZE;
+}
+
 static PurpleCipherOps SHA1Ops = {
 	sha1_set_opt,	/* Set Option		*/
 	sha1_get_opt,	/* Get Option		*/
@@ -1266,12 +1855,10 @@
 	NULL,			/* get salt size	*/
 	NULL,			/* set key			*/
 	NULL,			/* get key size		*/
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	NULL,			/* set batch mode */
+	NULL,			/* get batch mode */
+	sha1_get_block_size,	/* get block size */
+	NULL			/* set key with len */
 };
 
 /*******************************************************************************
@@ -1435,12 +2022,10 @@
 	NULL,          /* get salt size */
 	rc4_set_key,   /* set key       */
 	rc4_get_key_size, /* get key size  */
-
-	/* padding */
-	NULL,
-	NULL,
-	NULL,
-	NULL
+	NULL,          /* set batch mode */
+	NULL,          /* get batch mode */
+	NULL,          /* get block size */
+	NULL           /* set key with len */
 };
 
 /*******************************************************************************
@@ -1510,6 +2095,14 @@
 		caps |= PURPLE_CIPHER_CAPS_SET_KEY;
 	if(ops->get_key_size)
 		caps |= PURPLE_CIPHER_CAPS_GET_KEY_SIZE;
+	if(ops->set_batch_mode)
+		caps |= PURPLE_CIPHER_CAPS_SET_BATCH_MODE;
+	if(ops->get_batch_mode)
+		caps |= PURPLE_CIPHER_CAPS_GET_BATCH_MODE;
+	if(ops->get_block_size)
+		caps |= PURPLE_CIPHER_CAPS_GET_BLOCK_SIZE;
+	if(ops->set_key_with_len)
+		caps |= PURPLE_CIPHER_CAPS_SET_KEY_WITH_LEN;
 
 	return caps;
 }
@@ -1636,7 +2229,9 @@
 	purple_ciphers_register_cipher("md5", &MD5Ops);
 	purple_ciphers_register_cipher("sha1", &SHA1Ops);
 	purple_ciphers_register_cipher("md4", &MD4Ops);
+	purple_ciphers_register_cipher("hmac", &HMACOps);
 	purple_ciphers_register_cipher("des", &DESOps);
+	purple_ciphers_register_cipher("des3", &DES3Ops);
 	purple_ciphers_register_cipher("rc4", &RC4Ops);
 }
 
@@ -1967,6 +2562,80 @@
 }
 
 void
+purple_cipher_context_set_batch_mode(PurpleCipherContext *context,
+                                     PurpleCipherBatchMode mode)
+{
+	PurpleCipher *cipher = NULL;
+
+	g_return_if_fail(context);
+
+	cipher = context->cipher;
+	g_return_if_fail(cipher);
+
+	if(cipher->ops && cipher->ops->set_batch_mode)
+		cipher->ops->set_batch_mode(context, mode);
+	else
+		purple_debug_info("cipher", "The %s cipher does not support the "
+		                            "set_batch_mode operation\n", cipher->name);
+}
+
+PurpleCipherBatchMode
+purple_cipher_context_get_batch_mode(PurpleCipherContext *context)
+{
+	PurpleCipher *cipher = NULL;
+
+	g_return_val_if_fail(context, -1);
+
+	cipher = context->cipher;
+	g_return_val_if_fail(cipher, -1);
+
+	if(cipher->ops && cipher->ops->get_batch_mode)
+		return cipher->ops->get_batch_mode(context);
+	else {
+		purple_debug_info("cipher", "The %s cipher does not support the "
+		                            "get_batch_mode operation\n", cipher->name);
+		return -1;
+	}
+}
+
+size_t
+purple_cipher_context_get_block_size(PurpleCipherContext *context)
+{
+	PurpleCipher *cipher = NULL;
+
+	g_return_val_if_fail(context, -1);
+
+	cipher = context->cipher;
+	g_return_val_if_fail(cipher, -1);
+
+	if(cipher->ops && cipher->ops->get_block_size)
+		return cipher->ops->get_block_size(context);
+	else {
+		purple_debug_info("cipher", "The %s cipher does not support the "
+		                            "get_block_size operation\n", cipher->name);
+		return -1;
+	}
+}
+
+void
+purple_cipher_context_set_key_with_len(PurpleCipherContext *context,
+                                       const guchar *key, size_t len)
+{
+	PurpleCipher *cipher = NULL;
+
+	g_return_if_fail(context);
+
+	cipher = context->cipher;
+	g_return_if_fail(cipher);
+
+	if(cipher->ops && cipher->ops->set_key_with_len)
+		cipher->ops->set_key_with_len(context, key, len);
+	else
+		purple_debug_info("cipher", "The %s cipher does not support the "
+		                            "set_key_with_len operation\n", cipher->name);
+}
+
+void
 purple_cipher_context_set_data(PurpleCipherContext *context, gpointer data) {
 	g_return_if_fail(context);
 
--- a/libpurple/cipher.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/cipher.h	Sat Dec 22 23:18:08 2007 +0000
@@ -37,26 +37,37 @@
 typedef struct _PurpleCipherOps		PurpleCipherOps;		/**< Ops for a PurpleCipher		*/
 typedef struct _PurpleCipherContext	PurpleCipherContext;	/**< A context for a PurpleCipher	*/
 
+/**
+ * Modes for batch encrypters
+ */
+typedef enum _PurpleCipherBatchMode {
+	PURPLE_CIPHER_BATCH_MODE_ECB,
+	PURPLE_CIPHER_BATCH_MODE_CBC
+} PurpleCipherBatchMode;
 
 /**
  * The operation flags for a cipher
  */
 typedef enum _PurpleCipherCaps {
-	PURPLE_CIPHER_CAPS_SET_OPT			= 1 << 1,		/**< Set option flag	*/
-	PURPLE_CIPHER_CAPS_GET_OPT			= 1 << 2,		/**< Get option flag	*/
-	PURPLE_CIPHER_CAPS_INIT				= 1 << 3,		/**< Init flag			*/
-	PURPLE_CIPHER_CAPS_RESET				= 1 << 4,		/**< Reset flag			*/
-	PURPLE_CIPHER_CAPS_UNINIT				= 1 << 5,		/**< Uninit flag		*/
-	PURPLE_CIPHER_CAPS_SET_IV				= 1 << 6,		/**< Set IV flag		*/
-	PURPLE_CIPHER_CAPS_APPEND				= 1 << 7,		/**< Append flag		*/
-	PURPLE_CIPHER_CAPS_DIGEST				= 1 << 8,		/**< Digest flag		*/
-	PURPLE_CIPHER_CAPS_ENCRYPT			= 1 << 9,		/**< Encrypt flag		*/
-	PURPLE_CIPHER_CAPS_DECRYPT			= 1 << 10,		/**< Decrypt flag		*/
-	PURPLE_CIPHER_CAPS_SET_SALT			= 1 << 11,		/**< Set salt flag		*/
-	PURPLE_CIPHER_CAPS_GET_SALT_SIZE		= 1 << 12,		/**< Get salt size flag	*/
-	PURPLE_CIPHER_CAPS_SET_KEY			= 1 << 13,		/**< Set key flag		*/
-	PURPLE_CIPHER_CAPS_GET_KEY_SIZE		= 1 << 14,		/**< Get key size flag	*/
-	PURPLE_CIPHER_CAPS_UNKNOWN			= 1 << 16		/**< Unknown			*/
+	PURPLE_CIPHER_CAPS_SET_OPT          = 1 << 1,   /**< Set option flag	*/
+	PURPLE_CIPHER_CAPS_GET_OPT          = 1 << 2,   /**< Get option flag	*/
+	PURPLE_CIPHER_CAPS_INIT             = 1 << 3,   /**< Init flag			*/
+	PURPLE_CIPHER_CAPS_RESET            = 1 << 4,   /**< Reset flag			*/
+	PURPLE_CIPHER_CAPS_UNINIT           = 1 << 5,   /**< Uninit flag		*/
+	PURPLE_CIPHER_CAPS_SET_IV           = 1 << 6,   /**< Set IV flag		*/
+	PURPLE_CIPHER_CAPS_APPEND           = 1 << 7,   /**< Append flag		*/
+	PURPLE_CIPHER_CAPS_DIGEST           = 1 << 8,   /**< Digest flag		*/
+	PURPLE_CIPHER_CAPS_ENCRYPT          = 1 << 9,   /**< Encrypt flag		*/
+	PURPLE_CIPHER_CAPS_DECRYPT          = 1 << 10,  /**< Decrypt flag		*/
+	PURPLE_CIPHER_CAPS_SET_SALT         = 1 << 11,  /**< Set salt flag		*/
+	PURPLE_CIPHER_CAPS_GET_SALT_SIZE    = 1 << 12,  /**< Get salt size flag	*/
+	PURPLE_CIPHER_CAPS_SET_KEY          = 1 << 13,  /**< Set key flag		*/
+	PURPLE_CIPHER_CAPS_GET_KEY_SIZE     = 1 << 14,  /**< Get key size flag	*/
+	PURPLE_CIPHER_CAPS_SET_BATCH_MODE   = 1 << 15,  /**< Set batch mode flag */
+	PURPLE_CIPHER_CAPS_GET_BATCH_MODE   = 1 << 16,  /**< Get batch mode flag */
+	PURPLE_CIPHER_CAPS_GET_BLOCK_SIZE   = 1 << 17,  /**< The get block size flag */
+	PURPLE_CIPHER_CAPS_SET_KEY_WITH_LEN = 1 << 18,  /**< The set key with length flag */
+	PURPLE_CIPHER_CAPS_UNKNOWN          = 1 << 19   /**< Unknown			*/
 } PurpleCipherCaps;
 
 /**
@@ -105,10 +116,17 @@
 	/** The get key size function */
 	size_t (*get_key_size)(PurpleCipherContext *context);
 
-	void (*_purple_reserved1)(void);
-	void (*_purple_reserved2)(void);
-	void (*_purple_reserved3)(void);
-	void (*_purple_reserved4)(void);
+	/** The set batch mode function */
+	void (*set_batch_mode)(PurpleCipherContext *context, PurpleCipherBatchMode mode);
+
+	/** The get batch mode function */
+	PurpleCipherBatchMode (*get_batch_mode)(PurpleCipherContext *context);
+
+	/** The get block size function */
+	size_t (*get_block_size)(PurpleCipherContext *context);
+
+	/** The set key with length function */
+	void (*set_key_with_len)(PurpleCipherContext *context, const guchar *key, size_t len);
 };
 
 #ifdef __cplusplus
@@ -345,7 +363,7 @@
 /**
  * Sets the salt on a context
  *
- * @param context The context who's salt to set
+ * @param context The context whose salt to set
  * @param salt    The salt
  */
 void purple_cipher_context_set_salt(PurpleCipherContext *context, guchar *salt);
@@ -353,7 +371,7 @@
 /**
  * Gets the size of the salt if the cipher supports it
  *
- * @param context The context who's salt size to get
+ * @param context The context whose salt size to get
  *
  * @return The size of the salt
  */
@@ -362,7 +380,7 @@
 /**
  * Sets the key on a context
  *
- * @param context The context who's key to set
+ * @param context The context whose key to set
  * @param key     The key
  */
 void purple_cipher_context_set_key(PurpleCipherContext *context, const guchar *key);
@@ -370,16 +388,53 @@
 /**
  * Gets the key size for a context
  *
- * @param context The context who's key size to get
+ * @param context The context whose key size to get
  *
  * @return The size of the key
  */
 size_t purple_cipher_context_get_key_size(PurpleCipherContext *context);
 
 /**
+ * Sets the batch mode of a context
+ *
+ * @param context The context whose batch mode to set
+ * @param mode    The batch mode under which the cipher should operate
+ *
+ */
+void purple_cipher_context_set_batch_mode(PurpleCipherContext *context, PurpleCipherBatchMode mode);
+
+/**
+ * Gets the batch mode of a context
+ *
+ * @param context The context whose batch mode to get
+ *
+ * @return The batch mode under which the cipher is operating
+ */
+PurpleCipherBatchMode purple_cipher_context_get_batch_mode(PurpleCipherContext *context);
+
+/**
+ * Gets the block size of a context
+ *
+ * @param context The context whose block size to get
+ *
+ * @return The block size of the context
+ */
+size_t purple_cipher_context_get_block_size(PurpleCipherContext *context);
+
+/**
+ * Sets the key with a given length on a context 
+ *
+ * @param context The context whose key to set
+ * @param key     The key
+ * @param len     The length of the key
+ *
+ */
+void purple_cipher_context_set_key_with_len(PurpleCipherContext *context, const guchar *key, size_t len);
+
+/**
  * Sets the cipher data for a context
  *
- * @param context The context who's cipher data to set
+ * @param context The context whose cipher data to set
  * @param data    The cipher data to set
  */
 void purple_cipher_context_set_data(PurpleCipherContext *context, gpointer data);
@@ -387,7 +442,7 @@
 /**
  * Gets the cipher data for a context
  *
- * @param context The context who's cipher data to get
+ * @param context The context whose cipher data to get
  *
  * @return The cipher data
  */
--- a/libpurple/dnsquery.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/dnsquery.c	Sat Dec 22 23:18:08 2007 +0000
@@ -547,7 +547,7 @@
 	{
 #ifdef HAVE_GETADDRINFO
 		g_snprintf(message, sizeof(message), _("Error resolving %s:\n%s"),
-				query_data->hostname, gai_strerror(err));
+				query_data->hostname, purple_gai_strerror(err));
 #else
 		g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
 				query_data->hostname, err);
@@ -695,7 +695,7 @@
 		}
 		freeaddrinfo(tmp);
 	} else {
-		query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, gai_strerror(rc));
+		query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, purple_gai_strerror(rc));
 	}
 #else
 	if ((hp = gethostbyname(query_data->hostname))) {
--- a/libpurple/ft.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/ft.c	Sat Dec 22 23:18:08 2007 +0000
@@ -479,10 +479,11 @@
 		/* Sending a file */
 		/* Check the filename. */
 #ifdef _WIN32
-		if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\")) {
+		if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\"))
 #else
-		if (g_strrstr(filename, "../")) {
+		if (g_strrstr(filename, "../"))
 #endif
+		{
 			char *utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
 
 			msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8);
--- a/libpurple/log.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/log.c	Sat Dec 22 23:18:08 2007 +0000
@@ -348,7 +348,6 @@
 				void(*get_log_sets)(PurpleLogSetCallback cb, GHashTable *sets),
 				gboolean(*remove)(PurpleLog *log),
 				gboolean(*is_deletable)(PurpleLog *log))
-{
 #endif
 	PurpleLogLogger *logger;
 	va_list args;
--- a/libpurple/network.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/network.c	Sat Dec 22 23:18:08 2007 +0000
@@ -289,7 +289,7 @@
 	errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res);
 	if (errnum != 0) {
 #ifndef _WIN32
-		purple_debug_warning("network", "getaddrinfo: %s\n", gai_strerror(errnum));
+		purple_debug_warning("network", "getaddrinfo: %s\n", purple_gai_strerror(errnum));
 		if (errnum == EAI_SYSTEM)
 			purple_debug_warning("network", "getaddrinfo: system error: %s\n", g_strerror(errno));
 #else
--- a/libpurple/plugins/perl/common/Prpl.xs	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/plugins/perl/common/Prpl.xs	Sat Dec 22 23:18:08 2007 +0000
@@ -54,3 +54,20 @@
 	Purple::Account account
 	const char *name
 	time_t login_time
+
+int
+purple_prpl_send_raw(gc, str)
+	Purple::Connection gc
+	const char *str
+PREINIT:
+	PurplePluginProtocolInfo *prpl_info;
+CODE:
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+	if (prpl_info && prpl_info->send_raw != NULL) {
+		RETVAL = prpl_info->send_raw(gc, str, strlen(str));
+	} else {
+		RETVAL = 0;
+	}
+OUTPUT:
+	RETVAL
+
--- a/libpurple/pounce.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/pounce.c	Sat Dec 22 23:18:08 2007 +0000
@@ -181,7 +181,8 @@
 
 	child = xmlnode_new_child(node, "account");
 	xmlnode_set_attrib(child, "protocol", pouncer->protocol_id);
-	xmlnode_insert_data(child, purple_account_get_username(pouncer), -1);
+	xmlnode_insert_data(child,
+			purple_normalize(pouncer, purple_account_get_username(pouncer)), -1);
 
 	child = xmlnode_new_child(node, "pouncee");
 	xmlnode_insert_data(child, purple_pounce_get_pouncee(pounce), -1);
--- a/libpurple/prefs.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/prefs.c	Sat Dec 22 23:18:08 2007 +0000
@@ -914,7 +914,7 @@
 	if(pref) {
 		if(pref->type != PURPLE_PREF_PATH) {
 			purple_debug_error("prefs",
-					"purple_prefs_set_path: %s not a string pref\n", name);
+					"purple_prefs_set_path: %s not a path pref\n", name);
 			return;
 		}
 
--- a/libpurple/protocols/bonjour/Makefile.am	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/Makefile.am	Sat Dec 22 23:18:08 2007 +0000
@@ -13,6 +13,7 @@
 	buddy.h \
 	jabber.c \
 	jabber.h \
+	mdns_avahi.c \
 	mdns_common.c \
 	mdns_common.h \
 	mdns_interface.h \
@@ -22,14 +23,6 @@
 	bonjour_ft.c \
 	bonjour_ft.h 
 
-if MDNS_AVAHI
-  BONJOURSOURCES += mdns_avahi.c
-else
-if MDNS_HOWL
-    BONJOURSOURCES += mdns_howl.c
-endif
-endif
-
 AM_CFLAGS = $(st)
 
 libbonjour_la_LDFLAGS = -module -avoid-version
@@ -40,29 +33,14 @@
 noinst_LIBRARIES     = libbonjour.a
 libbonjour_a_SOURCES = $(BONJOURSOURCES)
 libbonjour_a_CFLAGS  = $(AM_CFLAGS)
-libbonjour_a_LIBADD  =
-
-if MDNS_AVAHI
-  libbonjour_a_LIBADD  += $(AVAHI_LIBS)
-else
-if MDNS_HOWL
-    libbonjour_a_LIBADD  += $(HOWL_LIBS)
-endif
-endif
+libbonjour_a_LIBADD  = $(AVAHI_LIBS)
 
 else
 
 st =
 pkg_LTLIBRARIES       = libbonjour.la
 libbonjour_la_SOURCES = $(BONJOURSOURCES)
-libbonjour_la_LIBADD  = $(GLIB_LIBS) $(LIBXML_LIBS)
-if MDNS_AVAHI
-  libbonjour_la_LIBADD  += $(AVAHI_LIBS)
-else
-if MDNS_HOWL
-    libbonjour_la_LIBADD  += $(HOWL_LIBS)
-endif
-endif
+libbonjour_la_LIBADD  = $(GLIB_LIBS) $(LIBXML_LIBS) $(AVAHI_LIBS)
 
 endif
 
@@ -72,13 +50,6 @@
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
 	$(DEBUG_CFLAGS) \
-	$(LIBXML_CFLAGS)
+	$(LIBXML_CFLAGS) \
+	$(AVAHI_CFLAGS)
 
-if MDNS_AVAHI
-  AM_CPPFLAGS += $(AVAHI_CFLAGS)
-else
-if MDNS_HOWL
-    AM_CPPFLAGS += $(HOWL_CFLAGS)
-endif
-endif
-
--- a/libpurple/protocols/bonjour/bonjour.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Sat Dec 22 23:18:08 2007 +0000
@@ -109,7 +109,7 @@
 	gc->proto_data = bd = g_new0(BonjourData, 1);
 
 	/* Start waiting for jabber connections (iChat style) */
-	bd->jabber_data = g_new(BonjourJabber, 1);
+	bd->jabber_data = g_new0(BonjourJabber, 1);
 	bd->jabber_data->port = BONJOUR_DEFAULT_PORT_INT;
 	bd->jabber_data->account = account;
 
--- a/libpurple/protocols/bonjour/bonjour.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.h	Sat Dec 22 23:18:08 2007 +0000
@@ -44,7 +44,7 @@
 {
 	BonjourDnsSd *dns_sd_data;
 	BonjourJabber *jabber_data;
-	GList *xfer_lists;
+	GSList *xfer_lists;
 } BonjourData;
 
 #endif /* _BONJOUR_H_ */
--- a/libpurple/protocols/bonjour/bonjour_ft.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour_ft.c	Sat Dec 22 23:18:08 2007 +0000
@@ -149,7 +149,7 @@
 static PurpleXfer*
 bonjour_si_xfer_find(BonjourData *bd, const char *sid, const char *from)
 {
-	GList *xfers = NULL;
+	GSList *xfers = NULL;
 	PurpleXfer *xfer = NULL;
 	XepXfer *xf = NULL;
 
@@ -309,7 +309,7 @@
 	if(xf != NULL) {
 		bd = (BonjourData*)xf->data;
 		if(bd != NULL) {
-			bd->xfer_lists = g_list_remove(bd->xfer_lists, xfer);
+			bd->xfer_lists = g_slist_remove(bd->xfer_lists, xfer);
 			purple_debug_info("bonjour", "B free xfer from lists(%p).\n", bd->xfer_lists);
 		}
 		if (xf->proxy_connection != NULL)
@@ -359,7 +359,7 @@
 	purple_xfer_set_cancel_send_fnc(xfer, bonjour_xfer_cancel_send);
 	purple_xfer_set_end_fnc(xfer, bonjour_xfer_end);
 
-	bd->xfer_lists = g_list_append(bd->xfer_lists, xfer);
+	bd->xfer_lists = g_slist_append(bd->xfer_lists, xfer);
 
 	return xfer;
 }
@@ -387,7 +387,7 @@
 bonjour_xfer_init(PurpleXfer *xfer)
 {
 	PurpleBuddy *buddy = NULL;
-	BonjourBuddy *bd = NULL;
+	BonjourBuddy *bb = NULL;
 	XepXfer *xf = NULL;
 
 	xf = (XepXfer*)xfer->data;
@@ -401,8 +401,10 @@
 	if (buddy == NULL)
 		return;
 
-	bd = (BonjourBuddy *)buddy->proto_data;
-	xf->buddy_ip = g_strdup(bd->ip);
+	bb = (BonjourBuddy *)buddy->proto_data;
+	/* Assume it is the first IP. We could do something like keep track of which one is in use or something. */
+	if (bb->ips)
+		xf->buddy_ip = g_strdup(bb->ips->data);
 	if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
 		/* initiate file transfer, send SI offer. */
 		purple_debug_info("bonjour", "Bonjour xfer type is PURPLE_XFER_SEND.\n");
@@ -603,7 +605,7 @@
 	purple_xfer_set_cancel_recv_fnc(xfer, bonjour_xfer_cancel_recv);
 	purple_xfer_set_end_fnc(xfer, bonjour_xfer_end);
 
-	bd->xfer_lists = g_list_append(bd->xfer_lists, xfer);
+	bd->xfer_lists = g_slist_append(bd->xfer_lists, xfer);
 
 	purple_xfer_request(xfer);
 }
--- a/libpurple/protocols/bonjour/buddy.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Sat Dec 22 23:18:08 2007 +0000
@@ -237,7 +237,10 @@
 bonjour_buddy_delete(BonjourBuddy *buddy)
 {
 	g_free(buddy->name);
-	g_free(buddy->ip);
+	while (buddy->ips != NULL) {
+		g_free(buddy->ips->data);
+		buddy->ips = g_slist_delete_link(buddy->ips, buddy->ips);
+	}
 	g_free(buddy->first);
 	g_free(buddy->phsh);
 	g_free(buddy->status);
--- a/libpurple/protocols/bonjour/buddy.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Sat Dec 22 23:18:08 2007 +0000
@@ -27,8 +27,7 @@
 	PurpleAccount *account;
 
 	gchar *name;
-	/* TODO: Remove and just use the hostname */
-	gchar *ip;
+	GSList *ips;
 	gint port_p2pj;
 
 	gchar *first;
--- a/libpurple/protocols/bonjour/jabber.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Sat Dec 22 23:18:08 2007 +0000
@@ -32,6 +32,12 @@
 #include "libc_interface.h"
 #endif
 #include <sys/types.h>
+
+/* Solaris */
+#if defined (__SVR4) && defined (__sun)
+#include <sys/sockio.h>
+#endif
+
 #include <glib.h>
 #include <unistd.h>
 #include <fcntl.h>
@@ -71,33 +77,8 @@
 static void
 xep_iq_parse(xmlnode *packet, PurpleConnection *connection, PurpleBuddy *pb);
 
-#if 0 /* this isn't used anywhere... */
-static const char *
-_font_size_purple_to_ichat(int size)
-{
-	switch (size) {
-		case 1:
-			return "8";
-		case 2:
-			return "10";
-		case 3:
-			return "12";
-		case 4:
-			return "14";
-		case 5:
-			return "17";
-		case 6:
-			return "21";
-		case 7:
-			return "24";
-	}
-
-	return "12";
-}
-#endif
-
 static BonjourJabberConversation *
-bonjour_jabber_conv_new(PurpleBuddy *pb) {
+bonjour_jabber_conv_new(PurpleBuddy *pb, PurpleAccount *account, const char *ip) {
 
 	BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1);
 	bconv->socket = -1;
@@ -105,13 +86,14 @@
 	bconv->tx_handler = 0;
 	bconv->rx_handler = 0;
 	bconv->pb = pb;
+	bconv->account = account;
+	bconv->ip = g_strdup(ip);
 
 	bonjour_parser_setup(bconv);
 
 	return bconv;
 }
 
-
 static const char *
 _font_size_ichat_to_purple(int size)
 {
@@ -137,34 +119,48 @@
 {
 	xmlnode *body_node, *html_node, *events_node;
 	PurpleConnection *gc = pb->account->gc;
-	char *body, *html_body = NULL;
-	const char *ichat_balloon_color = NULL;
-	const char *ichat_text_color = NULL;
-	const char *font_face = NULL;
-	const char *font_size = NULL;
-	const char *font_color = NULL;
+	gchar *body = NULL;
 	gboolean composing_event = FALSE;
 
 	body_node = xmlnode_get_child(message_node, "body");
-	if (body_node == NULL)
+	html_node = xmlnode_get_child(message_node, "html");
+
+	if (body_node == NULL && html_node == NULL) {
+		purple_debug_error("bonjour", "No body or html node found, discarding message.\n");
 		return;
-	body = xmlnode_get_data(body_node);
+	}
 
-	html_node = xmlnode_get_child(message_node, "html");
-	if (html_node != NULL)
-	{
+	events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event");
+	if (events_node != NULL) {
+		if (xmlnode_get_child(events_node, "composing") != NULL)
+			composing_event = TRUE;
+		if (xmlnode_get_child(events_node, "id") != NULL) {
+			/* The user is just typing */
+			/* TODO: Deal with typing notification */
+			return;
+		}
+	}
+
+	if (html_node != NULL) {
 		xmlnode *html_body_node;
 
 		html_body_node = xmlnode_get_child(html_node, "body");
-		if (html_body_node != NULL)
-		{
+		if (html_body_node != NULL) {
 			xmlnode *html_body_font_node;
+			const char *ichat_balloon_color = NULL;
+			const char *ichat_text_color = NULL;
+			const char *font_face = NULL;
+			const char *font_size = NULL;
+			const char *font_color = NULL;
 
 			ichat_balloon_color = xmlnode_get_attrib(html_body_node, "ichatballooncolor");
 			ichat_text_color = xmlnode_get_attrib(html_body_node, "ichattextcolor");
 			html_body_font_node = xmlnode_get_child(html_body_node, "font");
-			if (html_body_font_node != NULL)
-			{ /* Types of messages sent by iChat */
+
+			/* Types of messages sent by iChat */
+			if (html_body_font_node != NULL) {
+				gchar *html_body;
+
 				font_face = xmlnode_get_attrib(html_body_font_node, "face");
 				/* The absolute iChat font sizes should be converted to 1..7 range */
 				font_size = xmlnode_get_attrib(html_body_font_node, "ABSZ");
@@ -172,74 +168,74 @@
 					font_size = _font_size_ichat_to_purple(atoi(font_size));
 				font_color = xmlnode_get_attrib(html_body_font_node, "color");
 				html_body = xmlnode_get_data(html_body_font_node);
+
 				if (html_body == NULL)
 					/* This is the kind of formated messages that Purple creates */
 					html_body = xmlnode_to_str(html_body_font_node, NULL);
+
+				if (html_body != NULL) {
+					/* Use some sane defaults */
+					if (font_face == NULL) font_face = "Helvetica";
+					if (font_size == NULL) font_size = "3";
+					if (ichat_text_color == NULL) ichat_text_color = "#000000";
+					if (ichat_balloon_color == NULL) ichat_balloon_color = "#FFFFFF";
+
+					body = g_strdup_printf("<font face='%s' size='%s' color='%s' back='%s'>%s</font>",
+								   font_face, font_size, ichat_text_color, ichat_balloon_color,
+								   html_body);
+
+					g_free(html_body);
+				}
 			}
 		}
 	}
 
-	events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event");
-	if (events_node != NULL)
-	{
-		if (xmlnode_get_child(events_node, "composing") != NULL)
-			composing_event = TRUE;
-		if (xmlnode_get_child(events_node, "id") != NULL)
-		{
-			/* The user is just typing */
-			/* TODO: Deal with typing notification */
-			g_free(body);
-			g_free(html_body);
-			return;
-		}
-	}
+	/* Compose the message */
+	if (body == NULL && body_node != NULL)
+		body = xmlnode_get_data(body_node);
 
-	/* Compose the message */
-	if (html_body != NULL)
-	{
-		g_free(body);
-
-		if (font_face == NULL) font_face = "Helvetica";
-		if (font_size == NULL) font_size = "3";
-		if (ichat_text_color == NULL) ichat_text_color = "#000000";
-		if (ichat_balloon_color == NULL) ichat_balloon_color = "#FFFFFF";
-		body = g_strdup_printf("<font face='%s' size='%s' color='%s' back='%s'>%s</font>",
-				       font_face, font_size, ichat_text_color, ichat_balloon_color,
-				       html_body);
+	if (body == NULL) {
+		purple_debug_error("bonjour", "No html body or regular body found.\n");
+		return;
 	}
 
-	/* TODO: Should we do something with "composing_event" here? */
-
 	/* Send the message to the UI */
 	serv_got_im(gc, pb->name, body, 0, time(NULL));
 
 	g_free(body);
-	g_free(html_body);
 }
 
-struct _check_buddy_by_address_t {
+struct _match_buddies_by_address_t {
 	const char *address;
-	PurpleBuddy **pb;
-	BonjourJabber *bj;
+	GSList *matched_buddies;
+	BonjourJabber *jdata;
 };
 
 static void
-_check_buddy_by_address(gpointer key, gpointer value, gpointer data)
+_match_buddies_by_address(gpointer key, gpointer value, gpointer data)
 {
 	PurpleBuddy *pb = value;
-	BonjourBuddy *bb;
-	struct _check_buddy_by_address_t *cbba = data;
+	struct _match_buddies_by_address_t *mbba = data;
 
 	/*
 	 * If the current PurpleBuddy's data is not null and the PurpleBuddy's account
 	 * is the same as the account requesting the check then continue to determine
-	 * whether the buddies IP matches the target IP.
+	 * whether one of the buddies IPs matches the target IP.
 	 */
-	if (cbba->bj->account == pb->account)
+	if (mbba->jdata->account == pb->account && pb->proto_data != NULL)
 	{
-		bb = pb->proto_data;
-		if ((bb != NULL) && (g_ascii_strcasecmp(bb->ip, cbba->address) == 0))
-			*(cbba->pb) = pb;
+		const char *ip;
+		BonjourBuddy *bb = pb->proto_data;
+		GSList *tmp = bb->ips;
+
+		while(tmp) {
+			ip = tmp->data;
+			if (ip != NULL && g_ascii_strcasecmp(ip, mbba->address) == 0) {
+				mbba->matched_buddies = g_slist_prepend(mbba->matched_buddies, pb);
+				break;
+			}
+			tmp = tmp->next;
+		}
 	}
 }
 
@@ -251,8 +247,6 @@
 	BonjourJabberConversation *bconv = bb->conversation;
 	int ret, writelen;
 
-	/* TODO: Make sure that the stream has been established before sending */
-
 	writelen = purple_circ_buffer_get_max_read(bconv->tx_buf);
 
 	if (writelen == 0) {
@@ -328,7 +322,7 @@
 
 	if (ret < len) {
 		/* Don't interfere with the stream starting */
-		if (bconv->tx_handler == 0)
+		if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start && bconv->tx_handler == 0)
 			bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
 				_send_data_write_cb, pb);
 		purple_circ_buffer_append(bconv->tx_buf, message + ret, len - ret);
@@ -340,6 +334,7 @@
 void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet) {
 
 	g_return_if_fail(packet != NULL);
+	g_return_if_fail(pb != NULL);
 
 	if (!strcmp(packet->name, "message"))
 		_jabber_parse_and_write_message_to_ui(packet, pb);
@@ -353,31 +348,31 @@
 static void
 _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition)
 {
-	PurpleBuddy *pb = data;
+	BonjourJabberConversation *bconv = data;
 	gint len, message_length;
 	static char message[4096];
 
-	/*TODO: use a static buffer */
-
 	/* Read the data from the socket */
 	if ((len = recv(socket, message, sizeof(message) - 1, 0)) == -1) {
 		/* There have been an error reading from the socket */
 		if (errno != EAGAIN) {
-			BonjourBuddy *bb = pb->proto_data;
 			const char *err = g_strerror(errno);
 
 			purple_debug_warning("bonjour", "receive error: %s\n", err ? err : "(null)");
 
-			bonjour_jabber_close_conversation(bb->conversation);
-			bb->conversation = NULL;
+			bonjour_jabber_close_conversation(bconv);
+			if (bconv->pb != NULL) {
+				BonjourBuddy *bb = bconv->pb->proto_data;
+				bb->conversation = NULL;
+			}
 
 			/* I guess we really don't need to notify the user.
 			 * If they try to send another message it'll reconnect */
 		}
 		return;
 	} else if (len == 0) { /* The other end has closed the socket */
-		purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name ? pb->name : "(null)");
-		bonjour_jabber_stream_ended(pb);
+		purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", (bconv->pb && bconv->pb->name) ? bconv->pb->name : "(unknown)");
+		bonjour_jabber_stream_ended(bconv);
 		return;
 	} else {
 		message_length = len;
@@ -391,30 +386,34 @@
 
 	purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", message, len);
 
-	bonjour_parser_process(pb, message, message_length);
+	bonjour_parser_process(bconv, message, message_length);
 }
 
-void bonjour_jabber_stream_ended(PurpleBuddy *pb) {
-	BonjourBuddy *bb = pb->proto_data;
+void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv) {
 
-	purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", pb->name);
-
-	g_return_if_fail(bb != NULL);
+	purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", bconv->pb ? bconv->pb->name : "(unknown)");
 
 	/* Inform the user that the conversation has been closed */
-	if (bb->conversation != NULL) {
+	if (bconv != NULL) {
+		BonjourBuddy *bb = NULL;
+
+		if(bconv->pb != NULL)
+			bb = bconv->pb->proto_data;
 #if 0
-		PurpleConversation *conv;
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account);
-		if (conv != NULL) {
-			char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
-			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
-			g_free(tmp);
+		if(bconv->pb != NULL) {
+			PurpleConversation *conv;
+			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bconv->pb->name, bconv->pb->account);
+			if (conv != NULL) {
+				char *tmp = g_strdup_printf(_("%s has closed the conversation."), bconv->pb->name);
+				purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+				g_free(tmp);
+			}
 		}
 #endif
 		/* Close the socket, clear the watcher and free memory */
-		bonjour_jabber_close_conversation(bb->conversation);
-		bb->conversation = NULL;
+		bonjour_jabber_close_conversation(bconv);
+		if(bb)
+			bb->conversation = NULL;
 	}
 }
 
@@ -427,9 +426,7 @@
 static void
 _start_stream(gpointer data, gint source, PurpleInputCondition condition)
 {
-	PurpleBuddy *pb = data;
-	BonjourBuddy *bb = pb->proto_data;
-	BonjourJabberConversation *bconv = bb->conversation;
+	BonjourJabberConversation *bconv = data;
 	struct _stream_start_data *ss = bconv->stream_data;
 	int len, ret;
 
@@ -443,18 +440,26 @@
 	else if (ret <= 0) {
 		const char *err = g_strerror(errno);
 		PurpleConversation *conv;
+		const char *bname = bconv->buddy_name;
+		BonjourBuddy *bb = NULL;
 
-		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
-				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+		if(bconv->pb) {
+			bb = bconv->pb->proto_data;
+			bname = purple_buddy_get_name(bconv->pb);
+		}
 
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n",
+				   bname ? bname : "(unknown)", bconv->ip, err ? err : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->account);
 		if (conv != NULL)
 			purple_conversation_write(conv, NULL,
 				  _("Unable to send the message, the conversation couldn't be started."),
 				  PURPLE_MESSAGE_SYSTEM, time(NULL));
 
 		bonjour_jabber_close_conversation(bconv);
-		bb->conversation = NULL;
+		if(bb != NULL)
+			bb->conversation = NULL;
 
 		return;
 	}
@@ -476,20 +481,27 @@
 	bconv->tx_handler = 0;
 	bconv->sent_stream_start = FULLY_SENT;
 
-	bonjour_jabber_stream_started(pb);
+	bonjour_jabber_stream_started(bconv);
 }
 
-static gboolean bonjour_jabber_send_stream_init(PurpleBuddy *pb, int client_socket)
+static gboolean bonjour_jabber_send_stream_init(BonjourJabberConversation *bconv, int client_socket)
 {
 	int ret, len;
 	char *stream_start;
-	BonjourBuddy *bb = pb->proto_data;
+	const char *bname = bconv->buddy_name;
+
+	if (bconv->pb != NULL)
+		bname = purple_buddy_get_name(bconv->pb);
 
-	stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
-								   purple_buddy_get_name(pb));
+	/* If we have no idea who "to" is, use an empty string.
+	 * If we don't know now, it is because the other side isn't playing nice, so they can't complain. */
+	if (bname == NULL)
+		bname = "";
+
+	stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(bconv->account), bname);
 	len = strlen(stream_start);
 
-	bb->conversation->sent_stream_start = PARTIALLY_SENT;
+	bconv->sent_stream_start = PARTIALLY_SENT;
 
 	/* Start the stream */
 	ret = send(client_socket, stream_start, len, 0);
@@ -499,8 +511,17 @@
 	else if (ret <= 0) {
 		const char *err = g_strerror(errno);
 
-		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
-				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n",
+				   (*bname) ? bname : "(unknown)", bconv->ip, err ? err : "(null)");
+
+		if (bconv->pb) {
+			PurpleConversation *conv;
+			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->account);
+			if (conv != NULL)
+				purple_conversation_write(conv, NULL,
+					  _("Unable to send the message, the conversation couldn't be started."),
+					  PURPLE_MESSAGE_SYSTEM, time(NULL));
+		}
 
 		close(client_socket);
 		g_free(stream_start);
@@ -512,58 +533,61 @@
 	if (ret < len) {
 		struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
 		ss->msg = g_strdup(stream_start + ret);
-		bb->conversation->stream_data = ss;
+		bconv->stream_data = ss;
 		/* Finish sending the stream start */
-		bb->conversation->tx_handler = purple_input_add(client_socket,
-			PURPLE_INPUT_WRITE, _start_stream, pb);
+		bconv->tx_handler = purple_input_add(client_socket,
+			PURPLE_INPUT_WRITE, _start_stream, bconv);
 	} else
-		bb->conversation->sent_stream_start = FULLY_SENT;
+		bconv->sent_stream_start = FULLY_SENT;
 
 	g_free(stream_start);
 
 	return TRUE;
 }
 
-static gboolean
-_async_bonjour_jabber_close_conversation(gpointer data) {
-	BonjourJabberConversation *bconv = data;
-	bonjour_jabber_close_conversation(bconv);
-	return FALSE;
-}
+/* This gets called when we've successfully sent our <stream:stream />
+ * AND when we've recieved a <stream:stream /> */
+void bonjour_jabber_stream_started(BonjourJabberConversation *bconv) {
 
-void bonjour_jabber_stream_started(PurpleBuddy *pb) {
-	BonjourBuddy *bb = pb->proto_data;
-	BonjourJabberConversation *bconv = bb->conversation;
-
-	if (bconv->sent_stream_start == NOT_SENT && !bonjour_jabber_send_stream_init(pb, bconv->socket)) {
+	if (bconv->sent_stream_start == NOT_SENT && !bonjour_jabber_send_stream_init(bconv, bconv->socket)) {
 		const char *err = g_strerror(errno);
-		PurpleConversation *conv;
+		const char *bname = bconv->buddy_name;
 
-		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
-				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+		if (bconv->pb)
+			bname = purple_buddy_get_name(bconv->pb);
+
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n",
+				   bname ? bname : "(unknown)", bconv->ip, err ? err : "(null)");
 
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
-		if (conv != NULL)
-			purple_conversation_write(conv, NULL,
-				  _("Unable to send the message, the conversation couldn't be started."),
-				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+		if (bconv->pb) {
+			PurpleConversation *conv;
+			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->account);
+			if (conv != NULL)
+				purple_conversation_write(conv, NULL,
+					  _("Unable to send the message, the conversation couldn't be started."),
+					  PURPLE_MESSAGE_SYSTEM, time(NULL));
+		}
 
+		/* We don't want to recieve anything else */
 		close(bconv->socket);
+		bconv->socket = -1;
+
 		/* This must be asynchronous because it destroys the parser and we
 		 * may be in the middle of parsing.
 		 */
-		purple_timeout_add(0, _async_bonjour_jabber_close_conversation, bb->conversation);
-		bb->conversation = NULL;
+		async_bonjour_jabber_close_conversation(bconv);
 		return;
 	}
 
-	/* If the stream has been completely started, we can start doing stuff */
-	if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
+	/* If the stream has been completely started and we know who we're talking to, we can start doing stuff. */
+	/* I don't think the circ_buffer can actually contain anything without a buddy being associated, but lets be explicit. */
+	if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start
+			&& bconv->pb && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
 		/* Watch for when we can write the buffered messages */
 		bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
-			_send_data_write_cb, pb);
+			_send_data_write_cb, bconv->pb);
 		/* We can probably write the data right now. */
-		_send_data_write_cb(pb, bconv->socket, PURPLE_INPUT_WRITE);
+		_send_data_write_cb(bconv->pb, bconv->socket, PURPLE_INPUT_WRITE);
 	}
 
 }
@@ -571,15 +595,14 @@
 static void
 _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition)
 {
-	PurpleBuddy *pb = NULL;
+	BonjourJabber *jdata = data;
 	struct sockaddr_in their_addr; /* connector's address information */
 	socklen_t sin_size = sizeof(struct sockaddr);
 	int client_socket;
 	int flags;
-	BonjourBuddy *bb;
 	char *address_text = NULL;
-	PurpleBuddyList *bl = purple_get_blist();
-	struct _check_buddy_by_address_t *cbba;
+	struct _match_buddies_by_address_t *mbba;
+	BonjourJabberConversation *bconv;
 
 	/* Check that it is a read condition */
 	if (condition != PURPLE_INPUT_READ)
@@ -594,39 +617,35 @@
 	/* Look for the buddy that has opened the conversation and fill information */
 	address_text = inet_ntoa(their_addr.sin_addr);
 	purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text);
-	cbba = g_new0(struct _check_buddy_by_address_t, 1);
-	cbba->address = address_text;
-	cbba->pb = &pb;
-	cbba->bj = data;
-	g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba);
-	g_free(cbba);
-	if (pb == NULL)
-	{
+	mbba = g_new0(struct _match_buddies_by_address_t, 1);
+	mbba->address = address_text;
+	mbba->jdata = jdata;
+	g_hash_table_foreach(purple_get_blist()->buddies, _match_buddies_by_address, mbba);
+
+	if (mbba->matched_buddies == NULL) {
 		purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheros comic\n");
+		g_slist_free(mbba->matched_buddies);
+		g_free(mbba);
 		close(client_socket);
 		return;
 	}
-	bb = pb->proto_data;
 
-	/* Check if the conversation has been previously started */
-	/* This really shouldn't ever happen unless something weird is going on */
-	if (bb->conversation == NULL)
-	{
-		bb->conversation = bonjour_jabber_conv_new(pb);
+	g_slist_free(mbba->matched_buddies);
+	g_free(mbba);
 
-		/* We wait for the stream start before doing anything else */
-		bb->conversation->socket = client_socket;
-		bb->conversation->rx_handler = purple_input_add(client_socket,
-			PURPLE_INPUT_READ, _client_socket_handler, pb);
+	/* We've established that this *could* be from one of our buddies.
+	 * Wait for the stream open to see if that matches too before assigning it.
+	 */
+	bconv = bonjour_jabber_conv_new(NULL, jdata->account, address_text);
 
-	} else {
-		purple_debug_warning("bonjour", "Ignoring incoming connection because an existing connection exists.\n");
-		close(client_socket);
-	}
+	/* We wait for the stream start before doing anything else */
+	bconv->socket = client_socket;
+	bconv->rx_handler = purple_input_add(client_socket, PURPLE_INPUT_READ, _client_socket_handler, bconv);
+
 }
 
 gint
-bonjour_jabber_start(BonjourJabber *data)
+bonjour_jabber_start(BonjourJabber *jdata)
 {
 	struct sockaddr_in my_addr;
 	int yes = 1;
@@ -634,20 +653,20 @@
 	gboolean bind_successful;
 
 	/* Open a listening socket for incoming conversations */
-	if ((data->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0)
+	if ((jdata->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0)
 	{
 		purple_debug_error("bonjour", "Cannot open socket: %s\n", g_strerror(errno));
-		purple_connection_error_reason (data->account->gc,
+		purple_connection_error_reason (jdata->account->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Cannot open socket"));
 		return -1;
 	}
 
 	/* Make the socket reusable */
-	if (setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0)
+	if (setsockopt(jdata->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0)
 	{
 		purple_debug_error("bonjour", "Error setting socket options: %s\n", g_strerror(errno));
-		purple_connection_error_reason (data->account->gc,
+		purple_connection_error_reason (jdata->account->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Error setting socket options"));
 		return -1;
@@ -660,30 +679,30 @@
 	bind_successful = FALSE;
 	for (i = 0; i < 10; i++)
 	{
-		my_addr.sin_port = htons(data->port);
-		if (bind(data->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == 0)
+		my_addr.sin_port = htons(jdata->port);
+		if (bind(jdata->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == 0)
 		{
 			bind_successful = TRUE;
 			break;
 		}
-		data->port++;
+		jdata->port++;
 	}
 
 	/* On no!  We tried 10 ports and could not bind to ANY of them */
 	if (!bind_successful)
 	{
 		purple_debug_error("bonjour", "Cannot bind socket: %s\n", g_strerror(errno));
-		purple_connection_error_reason (data->account->gc,
+		purple_connection_error_reason (jdata->account->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Could not bind socket to port"));
 		return -1;
 	}
 
 	/* Attempt to listen on the bound socket */
-	if (listen(data->socket, 10) != 0)
+	if (listen(jdata->socket, 10) != 0)
 	{
 		purple_debug_error("bonjour", "Cannot listen on socket: %s\n", g_strerror(errno));
-		purple_connection_error_reason (data->account->gc,
+		purple_connection_error_reason (jdata->account->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Could not listen on socket"));
 		return -1;
@@ -691,18 +710,18 @@
 
 #if 0
 	/* TODO: Why isn't this being used? */
-	data->socket = purple_network_listen(data->port, SOCK_STREAM);
+	data->socket = purple_network_listen(jdata->port, SOCK_STREAM);
 
-	if (data->socket == -1)
+	if (jdata->socket == -1)
 	{
 		purple_debug_error("bonjour", "No se ha podido crear el socket\n");
 	}
 #endif
 
 	/* Open a watcher in the socket we have just opened */
-	data->watcher_id = purple_input_add(data->socket, PURPLE_INPUT_READ, _server_socket_handler, data);
+	jdata->watcher_id = purple_input_add(jdata->socket, PURPLE_INPUT_READ, _server_socket_handler, jdata);
 
-	return data->port;
+	return jdata->port;
 }
 
 static void
@@ -717,7 +736,7 @@
 		PurpleConversation *conv;
 
 		purple_debug_error("bonjour", "Error connecting to buddy %s at %s:%d error: %s\n",
-				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, error ? error : "(null)");
+				   purple_buddy_get_name(pb), bb->conversation->ip, bb->port_p2pj, error ? error : "(null)");
 
 		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
 		if (conv != NULL)
@@ -730,12 +749,12 @@
 		return;
 	}
 
-	if (!bonjour_jabber_send_stream_init(pb, source)) {
+	if (!bonjour_jabber_send_stream_init(bb->conversation, source)) {
 		const char *err = g_strerror(errno);
 		PurpleConversation *conv;
 
 		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
-				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+				   purple_buddy_get_name(pb), bb->conversation->ip, bb->port_p2pj, err ? err : "(null)");
 
 		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
 		if (conv != NULL)
@@ -752,19 +771,116 @@
 	/* Start listening for the stream acknowledgement */
 	bb->conversation->socket = source;
 	bb->conversation->rx_handler = purple_input_add(source,
-		PURPLE_INPUT_READ, _client_socket_handler, pb);
+		PURPLE_INPUT_READ, _client_socket_handler, bb->conversation);
+}
+
+void
+bonjour_jabber_conv_match_by_name(BonjourJabberConversation *bconv) {
+	PurpleBuddy *pb;
+
+	g_return_if_fail(bconv->ip != NULL);
+	g_return_if_fail(bconv->pb == NULL);
+
+	pb = purple_find_buddy(bconv->account, bconv->buddy_name);
+	if (pb && pb->proto_data) {
+		BonjourBuddy *bb = pb->proto_data;
+		const char *ip;
+		GSList *tmp = bb->ips;
+
+		purple_debug_info("bonjour", "Found buddy %s for incoming conversation \"from\" attrib.\n",
+			purple_buddy_get_name(pb));
+
+		/* Check that one of the buddy's IPs matches */
+		while(tmp) {
+			ip = tmp->data;
+			if (ip != NULL && g_ascii_strcasecmp(ip, bconv->ip) == 0) {
+				BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data;
+
+				purple_debug_info("bonjour", "Matched buddy %s to incoming conversation \"from\" attrib and IP (%s)\n",
+					purple_buddy_get_name(pb), bconv->ip);
+
+				/* Attach conv. to buddy and remove from pending list */
+				jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv);
+
+				/* Check if the buddy already has a conversation and, if so, replace it */
+				if(bb->conversation != NULL && bb->conversation != bconv)
+					bonjour_jabber_close_conversation(bb->conversation);
+
+				bconv->pb = pb;
+				bb->conversation = bconv;
+
+				break;
+			}
+			tmp = tmp->next;
+		}
+	}
+
+	/* We've failed to match a buddy - give up */
+	if (bconv->pb == NULL) {
+		/* This must be asynchronous because it destroys the parser and we
+		 * may be in the middle of parsing.
+		 */
+		async_bonjour_jabber_close_conversation(bconv);
+	}
+}
+
+
+void
+bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv) {
+	BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data;
+	struct _match_buddies_by_address_t *mbba;
+
+	mbba = g_new0(struct _match_buddies_by_address_t, 1);
+	mbba->address = bconv->ip;
+	mbba->jdata = jdata;
+	g_hash_table_foreach(purple_get_blist()->buddies, _match_buddies_by_address, mbba);
+
+	/* If there is exactly one match, use it */
+	if(mbba->matched_buddies != NULL) {
+		if(mbba->matched_buddies->next != NULL)
+			purple_debug_error("bonjour", "More than one buddy matched for ip %s.\n", bconv->ip);
+		else {
+			PurpleBuddy *pb = mbba->matched_buddies->data;
+			BonjourBuddy *bb = pb->proto_data;
+
+			purple_debug_info("bonjour", "Matched buddy %s to incoming conversation using IP (%s)\n",
+				purple_buddy_get_name(pb), bconv->ip);
+
+			/* Attach conv. to buddy and remove from pending list */
+			jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv);
+
+			/* Check if the buddy already has a conversation and, if so, replace it */
+			if (bb->conversation != NULL && bb->conversation != bconv)
+				bonjour_jabber_close_conversation(bb->conversation);
+
+			bconv->pb = pb;
+			bb->conversation = bconv;
+		}
+	} else
+		purple_debug_error("bonjour", "No buddies matched for ip %s.\n", bconv->ip);
+
+	/* We've failed to match a buddy - give up */
+	if (bconv->pb == NULL) {
+		/* This must be asynchronous because it destroys the parser and we
+		 * may be in the middle of parsing.
+		 */
+		async_bonjour_jabber_close_conversation(bconv);
+	}
+
+	g_slist_free(mbba->matched_buddies);
+	g_free(mbba);
 }
 
 static PurpleBuddy *
-_find_or_start_conversation(BonjourJabber *data, const gchar *to)
+_find_or_start_conversation(BonjourJabber *jdata, const gchar *to)
 {
 	PurpleBuddy *pb = NULL;
 	BonjourBuddy *bb = NULL;
 
-	g_return_val_if_fail(data != NULL, NULL);
+	g_return_val_if_fail(jdata != NULL, NULL);
 	g_return_val_if_fail(to != NULL, NULL);
 
-	pb = purple_find_buddy(data->account, to);
+	pb = purple_find_buddy(jdata->account, to);
 	if (pb == NULL || pb->proto_data == NULL)
 		/* You can not send a message to an offline buddy */
 		return NULL;
@@ -776,27 +892,29 @@
 	{
 		PurpleProxyConnectData *connect_data;
 		PurpleProxyInfo *proxy_info;
+		/* For better or worse, use the first IP*/
+		const char *ip = bb->ips->data;
 
 		purple_debug_info("bonjour", "Starting conversation with %s\n", to);
 
 		/* Make sure that the account always has a proxy of "none".
 		 * This is kind of dirty, but proxy_connect_none() isn't exposed. */
-		proxy_info = purple_account_get_proxy_info(data->account);
+		proxy_info = purple_account_get_proxy_info(jdata->account);
 		if (proxy_info == NULL) {
 			proxy_info = purple_proxy_info_new();
-			purple_account_set_proxy_info(data->account, proxy_info);
+			purple_account_set_proxy_info(jdata->account, proxy_info);
 		}
 		purple_proxy_info_set_type(proxy_info, PURPLE_PROXY_NONE);
 
-		connect_data = purple_proxy_connect(data->account->gc, data->account,
-						    bb->ip, bb->port_p2pj, _connected_to_buddy, pb);
+		connect_data = purple_proxy_connect(NULL, jdata->account,
+						    ip, bb->port_p2pj, _connected_to_buddy, pb);
 
 		if (connect_data == NULL) {
 			purple_debug_error("bonjour", "Unable to connect to buddy (%s).\n", to);
 			return NULL;
 		}
 
-		bb->conversation = bonjour_jabber_conv_new(pb);
+		bb->conversation = bonjour_jabber_conv_new(pb, jdata->account, ip);
 		bb->conversation->connect_data = connect_data;
 		/* We don't want _send_data() to register the tx_handler;
 		 * that neeeds to wait until we're actually connected. */
@@ -806,7 +924,7 @@
 }
 
 int
-bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body)
+bonjour_jabber_send_message(BonjourJabber *jdata, const gchar *to, const gchar *body)
 {
 	xmlnode *message_node, *node, *node2;
 	gchar *message;
@@ -814,7 +932,7 @@
 	BonjourBuddy *bb;
 	int ret;
 
-	pb = _find_or_start_conversation(data, to);
+	pb = _find_or_start_conversation(jdata, to);
 	if (pb == NULL) {
 		purple_debug_info("bonjour", "Can't send a message to an offline buddy (%s).\n", to);
 		/* You can not send a message to an offline buddy */
@@ -825,7 +943,7 @@
 
 	message_node = xmlnode_new("message");
 	xmlnode_set_attrib(message_node, "to", bb->name);
-	xmlnode_set_attrib(message_node, "from", purple_account_get_username(data->account));
+	xmlnode_set_attrib(message_node, "from", purple_account_get_username(jdata->account));
 	xmlnode_set_attrib(message_node, "type", "chat");
 
 	/* Enclose the message from the UI within a "font" node */
@@ -857,26 +975,57 @@
 	return ret;
 }
 
+static gboolean
+_async_bonjour_jabber_close_conversation_cb(gpointer data) {
+	BonjourJabberConversation *bconv = data;
+	bonjour_jabber_close_conversation(bconv);
+	return FALSE;
+}
+
+void
+async_bonjour_jabber_close_conversation(BonjourJabberConversation *bconv) {
+	BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data;
+
+	jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv);
+
+	/* Disconnect this conv. from the buddy here so it can't be disposed of twice.*/
+	if(bconv->pb != NULL) {
+		BonjourBuddy *bb = bconv->pb->proto_data;
+		if (bb->conversation == bconv)
+			bb->conversation = NULL;
+	}
+
+	purple_timeout_add(0, _async_bonjour_jabber_close_conversation_cb, bconv);
+}
+
 void
 bonjour_jabber_close_conversation(BonjourJabberConversation *bconv)
 {
 	if (bconv != NULL) {
-		GList *xfers, *tmp_next;
-		BonjourData *bd = bconv->pb->account->gc->proto_data;
+		BonjourData *bd = NULL;
+
+		if(PURPLE_CONNECTION_IS_VALID(bconv->account->gc)) {
+			bd = bconv->account->gc->proto_data;
+			bd->jabber_data->pending_conversations = g_slist_remove(bd->jabber_data->pending_conversations, bconv);
+		}
 
 		/* Cancel any file transfers that are waiting to begin */
-		xfers = bd->xfer_lists;
-		while(xfers != NULL) {
-			PurpleXfer *xfer = xfers->data;
-			tmp_next = xfers->next;
-			/* We only need to cancel this if it hasn't actually started transferring. */
-			/* This will change if we ever support IBB transfers. */
-			if (strcmp(xfer->who, bconv->pb->name) == 0
-					&& (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED
-					    || purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN)) {
-				purple_xfer_cancel_remote(xfer);
+		/* There wont be any transfers if it hasn't been attached to a buddy */
+		if (bconv->pb != NULL && bd != NULL) {
+			GSList *xfers, *tmp_next;
+			xfers = bd->xfer_lists;
+			while(xfers != NULL) {
+				PurpleXfer *xfer = xfers->data;
+				tmp_next = xfers->next;
+				/* We only need to cancel this if it hasn't actually started transferring. */
+				/* This will change if we ever support IBB transfers. */
+				if (strcmp(xfer->who, bconv->pb->name) == 0
+						&& (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED
+							|| purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN)) {
+					purple_xfer_cancel_remote(xfer);
+				}
+				xfers = tmp_next;
 			}
-			xfers = tmp_next;
 		}
 
 		/* Close the socket and remove the watcher */
@@ -905,25 +1054,26 @@
 		if (bconv->context != NULL)
 			bonjour_parser_setup(bconv);
 
+		g_free(bconv->buddy_name);
+		g_free(bconv->ip);
 		g_free(bconv);
 	}
 }
 
 void
-bonjour_jabber_stop(BonjourJabber *data)
+bonjour_jabber_stop(BonjourJabber *jdata)
 {
 	/* Close the server socket and remove the watcher */
-	if (data->socket >= 0)
-		close(data->socket);
-	if (data->watcher_id > 0)
-		purple_input_remove(data->watcher_id);
+	if (jdata->socket >= 0)
+		close(jdata->socket);
+	if (jdata->watcher_id > 0)
+		purple_input_remove(jdata->watcher_id);
 
 	/* Close all the conversation sockets and remove all the watchers after sending end streams */
-	if (data->account->gc != NULL)
-	{
+	if (jdata->account->gc != NULL) {
 		GSList *buddies, *l;
 
-		buddies = purple_find_buddies(data->account, purple_account_get_username(data->account));
+		buddies = purple_find_buddies(jdata->account, NULL);
 		for (l = buddies; l; l = l->next) {
 			BonjourBuddy *bb = ((PurpleBuddy*) l->data)->proto_data;
 			bonjour_jabber_close_conversation(bb->conversation);
@@ -932,6 +1082,11 @@
 
 		g_slist_free(buddies);
 	}
+
+	while (jdata->pending_conversations != NULL) {
+		bonjour_jabber_close_conversation(jdata->pending_conversations->data);
+		jdata->pending_conversations = g_slist_delete_link(jdata->pending_conversations, jdata->pending_conversations);
+	}
 }
 
 XepIq *
@@ -1028,7 +1183,7 @@
 	PurpleBuddy *pb = NULL;
 
 	/* start the talk, reuse the message socket  */
-	pb = _find_or_start_conversation ((BonjourJabber*)iq->data, iq->to);
+	pb = _find_or_start_conversation((BonjourJabber*) iq->data, iq->to);
 	/* Send the message */
 	if (pb != NULL) {
 		/* Convert xml node into stream */
--- a/libpurple/protocols/bonjour/jabber.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.h	Sat Dec 22 23:18:08 2007 +0000
@@ -38,7 +38,8 @@
 	gint port;
 	gint socket;
 	gint watcher_id;
-	PurpleAccount* account;
+	PurpleAccount *account;
+	GSList *pending_conversations;
 } BonjourJabber;
 
 typedef struct _BonjourJabberConversation
@@ -54,6 +55,11 @@
 	xmlParserCtxt *context;
 	xmlnode *current;
 	PurpleBuddy *pb;
+	PurpleAccount *account;
+
+	/* The following are only needed before attaching to a PurpleBuddy */
+	gchar *buddy_name;
+	gchar *ip;
 } BonjourJabberConversation;
 
 /**
@@ -68,14 +74,20 @@
 
 void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv);
 
-void bonjour_jabber_stream_started(PurpleBuddy *pb);
+void async_bonjour_jabber_close_conversation(BonjourJabberConversation *bconv);
 
-void bonjour_jabber_stream_ended(PurpleBuddy *pb);
+void bonjour_jabber_stream_started(BonjourJabberConversation *bconv);
+
+void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv);
 
 void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet);
 
 void bonjour_jabber_stop(BonjourJabber *data);
 
+void bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv);
+
+void bonjour_jabber_conv_match_by_name(BonjourJabberConversation *bconv);
+
 typedef enum {
 	XEP_IQ_SET,
 	XEP_IQ_GET,
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Sat Dec 22 23:18:08 2007 +0000
@@ -47,11 +47,62 @@
 	AvahiEntryGroup *buddy_icon_group;
 } AvahiSessionImplData;
 
+typedef struct _avahi_buddy_service_resolver_data {
+	AvahiServiceResolver *resolver;
+	AvahiIfIndex interface;
+	AvahiProtocol protocol;
+	gchar *name;
+	gchar *type;
+	gchar *domain;
+	/* This is a reference to the entry in BonjourBuddy->ips */
+	const char *ip;
+} AvahiSvcResolverData;
+
 typedef struct _avahi_buddy_impl_data {
-	AvahiServiceResolver *resolver;
+	GSList *resolvers;
 	AvahiRecordBrowser *buddy_icon_rec_browser;
 } AvahiBuddyImplData;
 
+static gint
+_find_resolver_data(gconstpointer a, gconstpointer b) {
+	const AvahiSvcResolverData *rd_a = a;
+	const AvahiSvcResolverData *rd_b = b;
+	gint ret = 1;
+
+	if(rd_a->interface == rd_b->interface
+			&& rd_a->protocol == rd_b->protocol
+			&& !strcmp(rd_a->name, rd_b->name)
+			&& !strcmp(rd_a->type, rd_b->type)
+			&& !strcmp(rd_a->domain, rd_b->domain)) {
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static gint
+_find_resolver_data_by_resolver(gconstpointer a, gconstpointer b) {
+	const AvahiSvcResolverData *rd_a = a;
+	const AvahiServiceResolver *resolver = b;
+	gint ret = 1;
+
+	if(rd_a->resolver == resolver)
+		ret = 0;
+
+	return ret;
+}
+
+static void
+_cleanup_resolver_data(AvahiSvcResolverData *rd) {
+	if (rd->resolver)
+		avahi_service_resolver_free(rd->resolver);
+	g_free(rd->name);
+	g_free(rd->type);
+	g_free(rd->domain);
+	g_free(rd);
+}
+
+
 static void
 _resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol,
 		  AvahiResolverEvent event, const char *name, const char *type, const char *domain,
@@ -65,6 +116,10 @@
 	size_t size;
 	char *key, *value;
 	int ret;
+	char ip[AVAHI_ADDRESS_STR_MAX];
+	AvahiBuddyImplData *b_impl;
+	AvahiSvcResolverData *rd;
+	GSList *res;
 
 	g_return_if_fail(r != NULL);
 
@@ -78,30 +133,59 @@
 
 			avahi_service_resolver_free(r);
 			if (bb != NULL) {
-				/* We've already freed the resolver */
-				if (r == ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver)
-					((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = NULL;
-				purple_account_remove_buddy(account, pb, NULL);
-				purple_blist_remove_buddy(pb);
+				b_impl = bb->mdns_impl_data;
+				res = g_slist_find_custom(b_impl->resolvers, r, _find_resolver_data_by_resolver);
+				if (res != NULL) {
+					rd = res->data;
+					b_impl->resolvers = g_slist_remove_link(b_impl->resolvers, res);
+
+					/* We've already freed the resolver */
+					rd->resolver = NULL;
+					_cleanup_resolver_data(rd);
+
+					/* If this was the last resolver, remove the buddy */
+					if (b_impl->resolvers == NULL) {
+						purple_account_remove_buddy(account, pb, NULL);
+						purple_blist_remove_buddy(pb);
+					}
+				}
 			}
 			break;
 		case AVAHI_RESOLVER_FOUND:
 			/* create a buddy record */
 			if (bb == NULL)
 				bb = bonjour_buddy_new(name, account);
+			b_impl = bb->mdns_impl_data;
 
-			/* If we're reusing an existing buddy, make sure if it is a different resolver to clean up the old one.
-			 * I don't think this should ever happen, but I'm afraid we might get events out of sequence. */
-			if (((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver != NULL
-					&& ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver != r) {
-				avahi_service_resolver_free(((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver);
+			/* If we're reusing an existing buddy, it may be a new resolver or an existing one. */
+			res = g_slist_find_custom(b_impl->resolvers, r, _find_resolver_data_by_resolver);
+			if (res != NULL)
+				rd = res->data;
+			else {
+				rd = g_new0(AvahiSvcResolverData, 1);
+				rd->resolver = r;
+				rd->interface = interface;
+				rd->protocol = protocol;
+				rd->name = g_strdup(name);
+				rd->type = g_strdup(type);
+				rd->domain = g_strdup(domain);
+
+				b_impl->resolvers = g_slist_prepend(b_impl->resolvers, rd);
 			}
-			((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = r;
+
+
+			/* Get the ip as a string */
+			avahi_address_snprint(ip, AVAHI_ADDRESS_STR_MAX, a);
 
-			g_free(bb->ip);
-			/* Get the ip as a string */
-			bb->ip = g_malloc(AVAHI_ADDRESS_STR_MAX);
-			avahi_address_snprint(bb->ip, AVAHI_ADDRESS_STR_MAX, a);
+			if (rd->ip == NULL || strcmp(rd->ip, ip) != 0) {
+				/* We store duplicates in bb->ips, so we always remove the one */
+				if (rd->ip != NULL) {
+					bb->ips = g_slist_remove(bb->ips, rd->ip);
+					g_free((gchar *) rd->ip);
+				}
+				bb->ips = g_slist_prepend(bb->ips, g_strdup(ip));
+				rd->ip = bb->ips->data;
+			}
 
 			bb->port_p2pj = port;
 
@@ -118,11 +202,16 @@
 			}
 
 			if (!bonjour_buddy_check(bb)) {
-				if (pb != NULL) {
-					purple_account_remove_buddy(account, pb, NULL);
-					purple_blist_remove_buddy(pb);
-				} else
-					bonjour_buddy_delete(bb);
+				_cleanup_resolver_data(rd);
+				b_impl->resolvers = g_slist_remove(b_impl->resolvers, rd);
+				/* If this was the last resolver, remove the buddy */
+				if (b_impl->resolvers == NULL) {
+					if (pb != NULL) {
+						purple_account_remove_buddy(account, pb, NULL);
+						purple_blist_remove_buddy(pb);
+					} else
+						bonjour_buddy_delete(bb);
+				}
 			} else
 				/* Add or update the buddy in our buddy list */
 				bonjour_buddy_add_to_purple(bb, pb);
@@ -166,8 +255,44 @@
 			purple_debug_info("bonjour", "_browser_callback - Remove service\n");
 			pb = purple_find_buddy(account, name);
 			if (pb != NULL) {
-				purple_account_remove_buddy(account, pb, NULL);
-				purple_blist_remove_buddy(pb);
+				BonjourBuddy *bb = pb->proto_data;
+				AvahiBuddyImplData *b_impl;
+				GSList *l;
+				AvahiSvcResolverData *rd_search;
+
+				g_return_if_fail(bb != NULL);
+
+				b_impl = bb->mdns_impl_data;
+
+				/* There may be multiple presences, we should only get rid of this one */
+
+				rd_search = g_new0(AvahiSvcResolverData, 1);
+				rd_search->interface = interface;
+				rd_search->protocol = protocol;
+				rd_search->name = (gchar *) name;
+				rd_search->type = (gchar *) type;
+				rd_search->domain = (gchar *) domain;
+
+				l = g_slist_find_custom(b_impl->resolvers, rd_search, _find_resolver_data);
+
+				g_free(rd_search);
+
+				if (l != NULL) {
+					AvahiSvcResolverData *rd = l->data;
+					b_impl->resolvers = g_slist_remove(b_impl->resolvers, rd);
+					/* This IP is no longer available */
+					if (rd->ip != NULL) {
+						bb->ips = g_slist_remove(bb->ips, rd->ip);
+						g_free((gchar *) rd->ip);
+					}
+					_cleanup_resolver_data(rd);
+
+					/* If this was the last resolver, remove the buddy */
+					if (b_impl->resolvers == NULL) {
+						purple_account_remove_buddy(account, pb, NULL);
+						purple_blist_remove_buddy(pb);
+					}
+				}
 			}
 			break;
 		case AVAHI_BROWSER_ALL_FOR_NOW:
@@ -188,6 +313,7 @@
 	switch(state) {
 		case AVAHI_ENTRY_GROUP_ESTABLISHED:
 			purple_debug_info("bonjour", "Successfully registered buddy icon data.\n");
+			break;
 		case AVAHI_ENTRY_GROUP_COLLISION:
 			purple_debug_error("bonjour", "Collision registering buddy icon data.\n");
 			break;
@@ -249,8 +375,10 @@
 	}
 
 	/* Stop listening */
-	avahi_record_browser_free(idata->buddy_icon_rec_browser);
-	idata->buddy_icon_rec_browser = NULL;
+	avahi_record_browser_free(b);
+	if (idata->buddy_icon_rec_browser == b) {
+		idata->buddy_icon_rec_browser = NULL;
+	}
 }
 
 /****************************
@@ -462,8 +590,11 @@
 	if (idata->buddy_icon_rec_browser != NULL)
 		avahi_record_browser_free(idata->buddy_icon_rec_browser);
 
-	if (idata->resolver != NULL)
-		avahi_service_resolver_free(idata->resolver);
+	while(idata->resolvers != NULL) {
+		AvahiSvcResolverData *rd = idata->resolvers->data;
+		_cleanup_resolver_data(rd);
+		idata->resolvers = g_slist_delete_link(idata->resolvers, idata->resolvers);
+	}
 
 	g_free(idata);
 
--- a/libpurple/protocols/bonjour/mdns_howl.c	Sat Dec 22 17:54:30 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,288 +0,0 @@
-/*
- *  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 Library 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 "mdns_interface.h"
-#include "debug.h"
-#include "buddy.h"
-
-#include <howl.h>
-
-/* data used by howl bonjour implementation */
-typedef struct _howl_impl_data {
-	sw_discovery session;
-	sw_discovery_oid session_id;
-	guint session_handler;
-} HowlSessionImplData;
-
-static sw_result HOWL_API
-_publish_reply(sw_discovery discovery, sw_discovery_oid oid,
-			   sw_discovery_publish_status status, sw_opaque extra)
-{
-	purple_debug_warning("bonjour", "_publish_reply --> Start\n");
-
-	/* Check the answer from the mDNS daemon */
-	switch (status)
-	{
-		case SW_DISCOVERY_PUBLISH_STARTED :
-			purple_debug_info("bonjour", "_publish_reply --> Service started\n");
-			break;
-		case SW_DISCOVERY_PUBLISH_STOPPED :
-			purple_debug_info("bonjour", "_publish_reply --> Service stopped\n");
-			break;
-		case SW_DISCOVERY_PUBLISH_NAME_COLLISION :
-			purple_debug_info("bonjour", "_publish_reply --> Name collision\n");
-			break;
-		case SW_DISCOVERY_PUBLISH_INVALID :
-			purple_debug_info("bonjour", "_publish_reply --> Service invalid\n");
-			break;
-	}
-
-	return SW_OKAY;
-}
-
-static sw_result HOWL_API
-_resolve_reply(sw_discovery discovery, sw_discovery_oid oid,
-			   sw_uint32 interface_index, sw_const_string name,
-			   sw_const_string type, sw_const_string domain,
-			   sw_ipv4_address address, sw_port port,
-			   sw_octets text_record, sw_ulong text_record_len,
-			   sw_opaque extra)
-{
-	BonjourBuddy *buddy;
-	PurpleAccount *account = (PurpleAccount*)extra;
-	gint address_length = 16;
-	sw_text_record_iterator iterator;
-	char key[SW_TEXT_RECORD_MAX_LEN];
-	char value[SW_TEXT_RECORD_MAX_LEN];
-	sw_uint32 value_length;
-
-	/* TODO: We want to keep listening for updates*/
-	sw_discovery_cancel(discovery, oid);
-
-	/* create a buddy record */
-	buddy = bonjour_buddy_new(name, account);
-
-	/* Get the ip as a string */
-	buddy->ip = g_malloc(address_length);
-	sw_ipv4_address_name(address, buddy->ip, address_length);
-
-	buddy->port_p2pj = port;
-
-	/* Obtain the parameters from the text_record */
-	if ((text_record_len > 0) && (text_record) && (*text_record != '\0'))
-	{
-		clear_bonjour_buddy_values(buddy);
-		sw_text_record_iterator_init(&iterator, text_record, text_record_len);
-		while (sw_text_record_iterator_next(iterator, key, (sw_octet *)value, &value_length) == SW_OKAY)
-			set_bonjour_buddy_value(buddy, key, value, value_length);
-
-		sw_text_record_iterator_fina(iterator);
-	}
-
-	if (!bonjour_buddy_check(buddy))
-	{
-		bonjour_buddy_delete(buddy);
-		return SW_DISCOVERY_E_UNKNOWN;
-	}
-
-	/* Add or update the buddy in our buddy list */
-	bonjour_buddy_add_to_purple(buddy, NULL);
-
-	return SW_OKAY;
-}
-
-static sw_result HOWL_API
-_browser_reply(sw_discovery discovery, sw_discovery_oid oid,
-			   sw_discovery_browse_status status,
-			   sw_uint32 interface_index, sw_const_string name,
-			   sw_const_string type, sw_const_string domain,
-			   sw_opaque_t extra)
-{
-	sw_discovery_resolve_id rid;
-	PurpleAccount *account = (PurpleAccount*)extra;
-	PurpleBuddy *pb = NULL;
-
-	switch (status)
-	{
-		case SW_DISCOVERY_BROWSE_INVALID:
-			purple_debug_warning("bonjour", "_browser_reply --> Invalid\n");
-			break;
-		case SW_DISCOVERY_BROWSE_RELEASE:
-			purple_debug_warning("bonjour", "_browser_reply --> Release\n");
-			break;
-		case SW_DISCOVERY_BROWSE_ADD_DOMAIN:
-			purple_debug_warning("bonjour", "_browser_reply --> Add domain\n");
-			break;
-		case SW_DISCOVERY_BROWSE_ADD_DEFAULT_DOMAIN:
-			purple_debug_warning("bonjour", "_browser_reply --> Add default domain\n");
-			break;
-		case SW_DISCOVERY_BROWSE_REMOVE_DOMAIN:
-			purple_debug_warning("bonjour", "_browser_reply --> Remove domain\n");
-			break;
-		case SW_DISCOVERY_BROWSE_ADD_SERVICE:
-			/* A new peer has joined the network and uses iChat bonjour */
-			purple_debug_info("bonjour", "_browser_reply --> Add service\n");
-			if (g_ascii_strcasecmp(name, account->username) != 0)
-			{
-				if (sw_discovery_resolve(discovery, interface_index, name, type,
-						domain, _resolve_reply, extra, &rid) != SW_OKAY)
-				{
-					purple_debug_warning("bonjour", "_browser_reply --> Cannot send resolve\n");
-				}
-			}
-			break;
-		case SW_DISCOVERY_BROWSE_REMOVE_SERVICE:
-			purple_debug_info("bonjour", "_browser_reply --> Remove service\n");
-			pb = purple_find_buddy(account, name);
-			if (pb != NULL) {
-				purple_account_remove_buddy(account, pb, NULL);
-				purple_blist_remove_buddy(pb);
-			}
-			break;
-		case SW_DISCOVERY_BROWSE_RESOLVED:
-			purple_debug_info("bonjour", "_browse_reply --> Resolved\n");
-			break;
-		default:
-			break;
-	}
-
-	return SW_OKAY;
-}
-
-static void
-_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition)
-{
-	sw_discovery_read_socket((sw_discovery)data);
-}
-
-/****************************
- * mdns_interface functions *
- ****************************/
-
-gboolean _mdns_init_session(BonjourDnsSd *data) {
-	HowlSessionImplData *idata = g_new0(HowlSessionImplData, 1);
-
-	if (sw_discovery_init(&idata->session) != SW_OKAY) {
-		purple_debug_error("bonjour", "Unable to initialize an mDNS session.\n");
-
-		/* In Avahi, sw_discovery_init frees data->session but doesn't clear it */
-		idata->session = NULL;
-
-		g_free(idata);
-
-		return FALSE;
-	}
-
-	data->mdns_impl_data = idata;
-
-	return TRUE;
-}
-
-
-gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) {
-	sw_text_record dns_data;
-	sw_result publish_result = SW_OKAY;
-	HowlSessionImplData *idata = data->mdns_impl_data;
-
-	g_return_val_if_fail(idata != NULL, FALSE);
-
-	/* Fill the data for the service */
-	if (sw_text_record_init(&dns_data) != SW_OKAY) {
-		purple_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n");
-		return FALSE;
-	}
-
-	while (records) {
-		PurpleKeyValuePair *kvp = records->data;
-		sw_text_record_add_key_and_string_value(dns_data, kvp->key, kvp->value);
-		records = records->next;
-	}
-
-	/* Publish the service */
-	switch (type) {
-		case PUBLISH_START:
-			publish_result = sw_discovery_publish(idata->session, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL,
-								NULL, data->port_p2pj, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data),
-								_publish_reply, NULL, &idata->session_id);
-			break;
-		case PUBLISH_UPDATE:
-			publish_result = sw_discovery_publish_update(idata->session, idata->session_id,
-								sw_text_record_bytes(dns_data), sw_text_record_len(dns_data));
-			break;
-	}
-
-	/* Free the memory used by temp data */
-	sw_text_record_fina(dns_data);
-
-	if (publish_result != SW_OKAY) {
-		purple_debug_error("bonjour", "Unable to publish or change the status of the " ICHAT_SERVICE " service.\n");
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
-gboolean _mdns_browse(BonjourDnsSd *data) {
-	HowlSessionImplData *idata = data->mdns_impl_data;
-	/* TODO: don't we need to hang onto this to cancel later? */
-	sw_discovery_oid session_id;
-
-	g_return_val_if_fail(idata != NULL, FALSE);
-
-	if (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
-				    data->account, &session_id) == SW_OKAY) {
-		idata->session_handler = purple_input_add(sw_discovery_socket(idata->session),
-				PURPLE_INPUT_READ, _mdns_handle_event, idata->session);
-		return TRUE;
-	}
-
-	return FALSE;
-}
-
-gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
-	return FALSE;
-}
-
-void _mdns_stop(BonjourDnsSd *data) {
-	HowlSessionImplData *idata = data->mdns_impl_data;
-
-	if (idata == NULL || idata->session == NULL)
-		return;
-
-	sw_discovery_cancel(idata->session, idata->session_id);
-
-	purple_input_remove(idata->session_handler);
-
-	/* TODO: should this really be g_free()'d ??? */
-	g_free(idata->session);
-
-	g_free(idata);
-
-	data->mdns_impl_data = NULL;
-}
-
-void _mdns_init_buddy(BonjourBuddy *buddy) {
-}
-
-void _mdns_delete_buddy(BonjourBuddy *buddy) {
-}
-
-void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
-}
-
-
--- a/libpurple/protocols/bonjour/mdns_win32.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Sat Dec 22 23:18:08 2007 +0000
@@ -28,17 +28,7 @@
 #include "dnsquery.h"
 #include "mdns_common.h"
 
-
-/* data structure for the resolve callback */
-typedef struct _ResolveCallbackArgs {
-	DNSServiceRef resolver;
-	guint resolver_handler;
-	gchar *full_service_name;
-
-	PurpleDnsQueryData *query;
-
-	BonjourBuddy* buddy;
-} ResolveCallbackArgs;
+static GSList *pending_buddies = NULL;
 
 /* data used by win32 bonjour implementation */
 typedef struct _win32_session_impl_data {
@@ -50,20 +40,69 @@
 	guint browser_handler;
 } Win32SessionImplData;
 
-typedef struct _win32_buddy_impl_data {
+typedef struct _win32_buddy_service_resolver_data {
 	DNSServiceRef txt_query;
 	guint txt_query_handler;
+	uint32_t if_idx;
+	gchar *name;
+	gchar *type;
+	gchar *domain;
+	/* This is a reference to the entry in BonjourBuddy->ips */
+	const char *ip;
+} Win32SvcResolverData;
+
+typedef struct _win32_buddy_impl_data {
+	GSList *resolvers;
 	DNSServiceRef null_query;
 	guint null_query_handler;
 } Win32BuddyImplData;
 
+/* data structure for the resolve callback */
+typedef struct _ResolveCallbackArgs {
+	DNSServiceRef resolver;
+	guint resolver_handler;
+	PurpleAccount *account;
+	BonjourBuddy *bb;
+	Win32SvcResolverData *res_data;
+	gchar *full_service_name;
+	PurpleDnsQueryData *query;
+} ResolveCallbackArgs;
+
+static gint
+_find_resolver_data(gconstpointer a, gconstpointer b) {
+	const Win32SvcResolverData *rd_a = a;
+	const Win32SvcResolverData *rd_b = b;
+	gint ret = 1;
+
+	if(rd_a->if_idx == rd_b->if_idx
+			&& !strcmp(rd_a->name, rd_b->name)
+			&& !strcmp(rd_a->type, rd_b->type)
+			&& !strcmp(rd_a->domain, rd_b->domain)) {
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static void
+_cleanup_resolver_data(Win32SvcResolverData *rd) {
+	if (rd->txt_query != NULL) {
+		purple_input_remove(rd->txt_query_handler);
+		DNSServiceRefDeallocate(rd->txt_query);
+	}
+	g_free(rd->name);
+	g_free(rd->type);
+	g_free(rd->domain);
+	g_free(rd);
+}
+
 static void
 _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) {
 	DNSServiceProcessResult((DNSServiceRef) data);
 }
 
 static void
-_mdns_parse_text_record(BonjourBuddy* buddy, const char* record, uint16_t record_len)
+_mdns_parse_text_record(BonjourBuddy *buddy, const char *record, uint16_t record_len)
 {
 	const char *txt_entry;
 	uint8_t txt_len;
@@ -114,35 +153,44 @@
 static void
 _mdns_resolve_host_callback(GSList *hosts, gpointer data, const char *error_message)
 {
-	ResolveCallbackArgs* args = (ResolveCallbackArgs*)data;
-	BonjourBuddy* bb = args->buddy;
+	ResolveCallbackArgs* args = (ResolveCallbackArgs*) data;
+	Win32BuddyImplData *idata = args->bb->mdns_impl_data;
+	gboolean delete_buddy = FALSE;
+	PurpleBuddy *pb;
+
+	if ((pb = purple_find_buddy(args->account, args->bb->name)))
+		if (pb->proto_data != args->bb)
+			purple_debug_error("bonjour", "Found purple buddy for %s not matching bonjour buddy record. "
+				"This is going to be ugly!.\n", args->bb->name);
 
 	if (!hosts || !hosts->data) {
 		purple_debug_error("bonjour", "host resolution - callback error.\n");
-		bonjour_buddy_delete(bb);
+		delete_buddy = TRUE;
 	} else {
-		struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1);
-		Win32BuddyImplData *idata = bb->mdns_impl_data;
-
-		g_return_if_fail(idata != NULL);
-
-		g_free(bb->ip);
-		bb->ip = g_strdup(inet_ntoa(addr->sin_addr));
+		struct sockaddr_in *addr = g_slist_nth_data(hosts, 1);
 
 		/* finally, set up the continuous txt record watcher, and add the buddy to purple */
 
-		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, kDNSServiceFlagsLongLivedQuery,
+		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&args->res_data->txt_query, kDNSServiceFlagsLongLivedQuery,
 				kDNSServiceInterfaceIndexAny, args->full_service_name, kDNSServiceType_TXT,
-				kDNSServiceClass_IN, _mdns_record_query_callback, bb)) {
+				kDNSServiceClass_IN, _mdns_record_query_callback, args->bb)) {
 
-			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", bb->name, bb->ip, bb->port_p2pj);
+			const char *ip = inet_ntoa(addr->sin_addr);
+
+			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", args->bb->name, ip, args->bb->port_p2pj);
+
 
-			idata->txt_query_handler = purple_input_add(DNSServiceRefSockFD(idata->txt_query),
-				PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query);
+			args->bb->ips = g_slist_prepend(args->bb->ips, g_strdup(ip));
+			args->res_data->ip = args->bb->ips->data;
+
+			args->res_data->txt_query_handler = purple_input_add(DNSServiceRefSockFD(args->res_data->txt_query),
+				PURPLE_INPUT_READ, _mdns_handle_event, args->res_data->txt_query);
 
-			bonjour_buddy_add_to_purple(bb, NULL);
-		} else
-			bonjour_buddy_delete(bb);
+			bonjour_buddy_add_to_purple(args->bb, NULL);
+		} else {
+			purple_debug_error("bonjour", "Unable to set up record watcher for buddy %s\n", args->bb->name);
+			delete_buddy = TRUE;
+		}
 
 	}
 
@@ -153,6 +201,26 @@
 		hosts = g_slist_remove(hosts, hosts->data);
 	}
 
+	if (delete_buddy) {
+		idata->resolvers = g_slist_remove(idata->resolvers, args->res_data);
+		_cleanup_resolver_data(args->res_data);
+
+		/* If this was the last resolver, remove the buddy */
+		if (idata->resolvers == NULL) {
+			if (pb) {
+				purple_account_remove_buddy(args->account, pb, NULL);
+				purple_blist_remove_buddy(pb);
+			} else
+				bonjour_buddy_delete(args->bb);
+
+			/* Remove from the pending list */
+			pending_buddies = g_slist_remove(pending_buddies, args->bb);
+		}
+	} else {
+		/* Remove from the pending list */
+		pending_buddies = g_slist_remove(pending_buddies, args->bb);
+	}
+
 	/* free the remaining args memory */
 	g_free(args->full_service_name);
 	g_free(args);
@@ -163,37 +231,54 @@
     const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context)
 {
 	ResolveCallbackArgs *args = (ResolveCallbackArgs*)context;
+	Win32BuddyImplData *idata = args->bb->mdns_impl_data;
 
 	/* remove the input fd and destroy the service ref */
 	purple_input_remove(args->resolver_handler);
+	args->resolver_handler = 0;
 	DNSServiceRefDeallocate(args->resolver);
+	args->resolver = NULL;
 
 	if (kDNSServiceErr_NoError != errorCode)
-	{
 		purple_debug_error("bonjour", "service resolver - callback error.\n");
-		bonjour_buddy_delete(args->buddy);
-		g_free(args);
-	}
-	else
-	{
-		args->buddy->port_p2pj = ntohs(port);
+	else {
+		/* set more arguments, and start the host resolver */
+
+		if ((args->query =
+				purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args)) != NULL) {
+
+			args->full_service_name = g_strdup(fullname);
+
+			/* TODO: Should this be per resolver? */
+			args->bb->port_p2pj = ntohs(port);
 
-		/* parse the text record */
-		_mdns_parse_text_record(args->buddy, txtRecord, txtLen);
+			/* We don't want to hit the cleanup code */
+			return;
+		} else
+			purple_debug_error("bonjour", "service resolver - host resolution failed.\n");
+	}
 
-		/* set more arguments, and start the host resolver */
-		args->full_service_name = g_strdup(fullname);
+	/* If we get this far, clean up */
+
+	idata->resolvers = g_slist_remove(idata->resolvers, args->res_data);
+	_cleanup_resolver_data(args->res_data);
 
-		if (!(args->query =
-			purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args)))
-		{
-			purple_debug_error("bonjour", "service resolver - host resolution failed.\n");
-			bonjour_buddy_delete(args->buddy);
-			g_free(args->full_service_name);
-			g_free(args);
+	/* If this was the last resolver, remove the buddy */
+	if (idata->resolvers == NULL) {
+		PurpleBuddy *pb;
+		/* See if this is now attached to a PurpleBuddy */
+		if ((pb = purple_find_buddy(args->account, args->bb->name))) {
+			purple_account_remove_buddy(args->account, pb, NULL);
+			purple_blist_remove_buddy(pb);
+		} else {
+			/* Remove from the pending list */
+			pending_buddies = g_slist_remove(pending_buddies, args->bb);
+			bonjour_buddy_delete(args->bb);
 		}
 	}
 
+	g_free(args);
+
 }
 
 static void DNSSD_API
@@ -212,7 +297,6 @@
     DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context)
 {
 	PurpleAccount *account = (PurpleAccount*)context;
-	PurpleBuddy *pb = NULL;
 
 	if (kDNSServiceErr_NoError != errorCode)
 		purple_debug_error("bonjour", "service browser - callback error\n");
@@ -221,28 +305,118 @@
 		if (purple_utf8_strcasecmp(serviceName, account->username) != 0) {
 			/* OK, lets go ahead and resolve it to add to the buddy list */
 			ResolveCallbackArgs *args = g_new0(ResolveCallbackArgs, 1);
-			args->buddy = bonjour_buddy_new(serviceName, account);
+
+			purple_debug_info("bonjour", "Received new record for '%s' on iface %u (%s, %s)\n",
+							  serviceName, interfaceIndex, regtype ? regtype : "",
+							  replyDomain ? replyDomain : "");
+
+			if (kDNSServiceErr_NoError == DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype,
+					replyDomain, _mdns_service_resolve_callback, args)) {
+				GSList *tmp = pending_buddies;
+				PurpleBuddy *pb;
+				BonjourBuddy* bb = NULL;
+				Win32SvcResolverData *rd;
+				Win32BuddyImplData *idata;
+				gint fd;
+
+				/* Is there an existing buddy? */
+				if ((pb = purple_find_buddy(account, serviceName)))
+					bb = pb->proto_data;
+				/* Is there a pending buddy? */
+				else {
+					while (tmp) {
+						BonjourBuddy *bb_tmp = tmp->data;
+						if (!strcmp(bb_tmp->name, serviceName)) {
+							bb = bb_tmp;
+							break;
+						}
+						tmp = tmp->next;
+					}
+				}
 
-			if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype,
-					replyDomain, _mdns_service_resolve_callback, args)) {
-				bonjour_buddy_delete(args->buddy);
+				if (bb == NULL) {
+					bb = bonjour_buddy_new(serviceName, account);
+
+					/* This is only necessary for the wacky case where someone previously manually added a buddy. */
+					if (pb == NULL)
+						pending_buddies = g_slist_prepend(pending_buddies, bb);
+					else
+						pb->proto_data = bb;
+				}
+
+
+				rd = g_new0(Win32SvcResolverData, 1);
+				rd->if_idx = interfaceIndex;
+				rd->name = g_strdup(serviceName);
+				rd->type = g_strdup(regtype);
+				rd->domain = g_strdup(replyDomain);
+
+				idata = bb->mdns_impl_data;
+				idata->resolvers = g_slist_prepend(idata->resolvers, rd);
+
+				args->bb = bb;
+				args->res_data = rd;
+				args->account = account;
+
+				/* get a file descriptor for this service ref, and add it to the input list */
+				fd = DNSServiceRefSockFD(args->resolver);
+				args->resolver_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver);
+			} else {
+				purple_debug_error("bonjour", "service browser - failed to resolve service.\n");
 				g_free(args);
-				purple_debug_error("bonjour", "service browser - failed to resolve service.\n");
-			} else {
-				/* get a file descriptor for this service ref, and add it to the input list */
-				gint fd = DNSServiceRefSockFD(args->resolver);
-				args->resolver_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver);
 			}
 		}
 	} else {
+		PurpleBuddy *pb = NULL;
+
 		/* A peer has sent a goodbye packet, remove them from the buddy list */
-		purple_debug_info("bonjour", "service browser - remove notification\n");
+		purple_debug_info("bonjour", "Received remove notification for '%s' on iface %u (%s, %s)\n",
+						  serviceName, interfaceIndex, regtype ? regtype : "",
+						  replyDomain ? replyDomain : "");
+
 		pb = purple_find_buddy(account, serviceName);
 		if (pb != NULL) {
-			purple_account_remove_buddy(account, pb, NULL);
-			purple_blist_remove_buddy(pb);
-		} else
+			GSList *l;
+			/* There may be multiple presences, we should only get rid of this one */
+			Win32SvcResolverData *rd_search;
+			BonjourBuddy *bb = pb->proto_data;
+			Win32BuddyImplData *idata;
+
+			g_return_if_fail(bb != NULL);
+
+			idata = bb->mdns_impl_data;
+
+			rd_search = g_new0(Win32SvcResolverData, 1);
+			rd_search->if_idx = interfaceIndex;
+			rd_search->name = (gchar *) serviceName;
+			rd_search->type = (gchar *) regtype;
+			rd_search->domain = (gchar *) replyDomain;
+
+			l = g_slist_find_custom(idata->resolvers, rd_search, _find_resolver_data);
+
+			g_free(rd_search);
+
+			if (l != NULL) {
+				Win32SvcResolverData *rd = l->data;
+				idata->resolvers = g_slist_delete_link(idata->resolvers, l);
+				/* This IP is no longer available */
+				if (rd->ip != NULL) {
+					bb->ips = g_slist_remove(bb->ips, rd->ip);
+					g_free((gchar *) rd->ip);
+				}
+				_cleanup_resolver_data(rd);
+
+				/* If this was the last resolver, remove the buddy */
+				if (idata->resolvers == NULL) {
+					purple_debug_info("bonjour", "Removed last presence for buddy '%s'; removing buddy.\n", serviceName);
+					purple_account_remove_buddy(account, pb, NULL);
+					purple_blist_remove_buddy(pb);
+				}
+			}
+		} else {
 			purple_debug_warning("bonjour", "Unable to find buddy (%s) to remove\n", serviceName ? serviceName : "(null)");
+			/* TODO: Should we look in the pending buddies list? */
+		}
 	}
 }
 
@@ -385,9 +559,10 @@
 
 	g_return_if_fail(idata != NULL);
 
-	if (idata->txt_query != NULL) {
-		purple_input_remove(idata->txt_query_handler);
-		DNSServiceRefDeallocate(idata->txt_query);
+	while (idata->resolvers) {
+		Win32SvcResolverData *rd = idata->resolvers->data;
+		_cleanup_resolver_data(rd);
+		idata->resolvers = g_slist_delete_link(idata->resolvers, idata->resolvers);
 	}
 
 	if (idata->null_query != NULL) {
--- a/libpurple/protocols/bonjour/parser.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/parser.c	Sat Dec 22 23:18:08 2007 +0000
@@ -31,26 +31,59 @@
 #include "util.h"
 #include "xmlnode.h"
 
+static gboolean
+parse_from_attrib_and_find_buddy(BonjourJabberConversation *bconv, int nb_attributes, const xmlChar **attributes) {
+	int i;
+
+	/* If the "from" attribute is specified, attach it to the conversation. */
+	for(i=0; i < nb_attributes * 5; i+=5) {
+		if(!xmlStrcmp(attributes[i], (xmlChar*) "from")) {
+			int len = attributes[i+4] - attributes[i+3];
+			bconv->buddy_name = g_strndup(attributes[i+3], len);
+			bonjour_jabber_conv_match_by_name(bconv);
+
+			return (bconv->pb != NULL);
+		}
+	}
+
+	return FALSE;
+}
+
 static void
 bonjour_parser_element_start_libxml(void *user_data,
 				   const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace,
 				   int nb_namespaces, const xmlChar **namespaces,
 				   int nb_attributes, int nb_defaulted, const xmlChar **attributes)
 {
-	PurpleBuddy *pb = user_data;
-	BonjourBuddy *bb = pb->proto_data;
-	BonjourJabberConversation *bconv = bb->conversation;
+	BonjourJabberConversation *bconv = user_data;
 
 	xmlnode *node;
 	int i;
 
-	if(!element_name) {
-		return;
-	} else if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
-		bconv->recv_stream_start = TRUE;
-		bonjour_jabber_stream_started(pb);
+	g_return_if_fail(element_name != NULL);
+
+	if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
+		if(!bconv->recv_stream_start) {
+			bconv->recv_stream_start = TRUE;
+
+			if (bconv->pb == NULL)
+				parse_from_attrib_and_find_buddy(bconv, nb_attributes, attributes);
+
+			bonjour_jabber_stream_started(bconv);
+		}
 	} else {
 
+		/* If we haven't yet attached a buddy and this isn't "<stream:features />",
+		 * try to get a "from" attribute as a last resort to match our buddy. */
+		if(bconv->pb == NULL
+				&& !(prefix && !xmlStrcmp(prefix, (xmlChar*) "stream")
+					&& !xmlStrcmp(element_name, (xmlChar*) "features"))
+				&& !parse_from_attrib_and_find_buddy(bconv, nb_attributes, attributes))
+			/* We've run out of options for finding who the conversation is from
+			   using explicitly specified stuff; see if we can make a good match
+			   by using the IP */
+			bonjour_jabber_conv_match_by_ip(bconv);
+
 		if(bconv->current)
 			node = xmlnode_new_child(bconv->current, (const char*) element_name);
 		else
@@ -82,27 +115,19 @@
 	}
 }
 
-static gboolean _async_bonjour_jabber_stream_ended_cb(gpointer data) {
-	bonjour_jabber_stream_ended((PurpleBuddy *) data);
-	return FALSE;
-}
-
 static void
 bonjour_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
 				 const xmlChar *prefix, const xmlChar *namespace)
 {
-	PurpleBuddy *pb = user_data;
-	BonjourBuddy *bb = pb->proto_data;
-	BonjourJabberConversation *bconv = bb->conversation;
+	BonjourJabberConversation *bconv = user_data;
 
 	if(!bconv->current) {
 		/* We don't keep a reference to the start stream xmlnode,
 		 * so we have to check for it here to close the conversation */
-		if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
+		if(!xmlStrcmp(element_name, (xmlChar*) "stream"))
 			/* Asynchronously close the conversation to prevent bonjour_parser_setup()
 			 * being called from within this context */
-			purple_timeout_add(0, _async_bonjour_jabber_stream_ended_cb, pb);
-		}
+			async_bonjour_jabber_close_conversation(bconv);
 		return;
 	}
 
@@ -112,7 +137,7 @@
 	} else {
 		xmlnode *packet = bconv->current;
 		bconv->current = NULL;
-		bonjour_jabber_process_packet(pb, packet);
+		bonjour_jabber_process_packet(bconv->pb, packet);
 		xmlnode_free(packet);
 	}
 }
@@ -120,9 +145,7 @@
 static void
 bonjour_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len)
 {
-	PurpleBuddy *pb = user_data;
-	BonjourBuddy *bb = pb->proto_data;
-	BonjourJabberConversation *bconv = bb->conversation;
+	BonjourJabberConversation *bconv = user_data;
 
 	if(!bconv->current)
 		return;
@@ -184,21 +207,17 @@
 }
 
 
-void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len)
+void bonjour_parser_process(BonjourJabberConversation *bconv, const char *buf, int len)
 {
-	BonjourBuddy *bb = pb->proto_data;
 
-	g_return_if_fail(bb != NULL);
-	g_return_if_fail(bb->conversation != NULL);
-
-	if (bb->conversation->context ==  NULL) {
+	if (bconv->context == NULL) {
 		/* libxml inconsistently starts parsing on creating the
 		 * parser, so do a ParseChunk right afterwards to force it. */
-		bb->conversation->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, pb, buf, len, NULL);
-		xmlParseChunk(bb->conversation->context, "", 0, 0);
-	} else if (xmlParseChunk(bb->conversation->context, buf, len, 0) < 0) {
+		bconv->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, bconv, buf, len, NULL);
+		xmlParseChunk(bconv->context, "", 0, 0);
+	} else if (xmlParseChunk(bconv->context, buf, len, 0) < 0)
 		/* TODO: What should we do here - I assume we should display an error or something (maybe just print something to the conv?) */
 		purple_debug_error("bonjour", "Error parsing xml.\n");
-	}
+
 }
 
--- a/libpurple/protocols/bonjour/parser.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/bonjour/parser.h	Sat Dec 22 23:18:08 2007 +0000
@@ -28,6 +28,6 @@
 #include "jabber.h"
 
 void bonjour_parser_setup(BonjourJabberConversation *bconv);
-void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len);
+void bonjour_parser_process(BonjourJabberConversation *bconv, const char *buf, int len);
 
 #endif /* _PURPLE_BONJOUR_PARSER_H_ */
--- a/libpurple/protocols/jabber/auth.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/jabber/auth.c	Sat Dec 22 23:18:08 2007 +0000
@@ -330,14 +330,21 @@
 							disallow_plaintext_auth);
 					g_free(msg);
 					return;
-				/* Everything else has failed, so fail the
-				 * connection. Should probably have a better
-				 * error here.
-				 */
+
 				} else {
-					purple_connection_error_reason (js->gc,
-						PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
-						_("Server does not use any supported authentication method"));
+					/* We have no mechs which can work.
+					 * Try falling back on the old jabber:iq:auth method. We get here if the server supports
+					 * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of
+					 * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect
+					 * jabber:iq:auth in this situation.  iChat Server in particular offers SASL GSSAPI by default, which is often
+					 * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails.
+					 *
+					 * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However,
+					 * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms.
+					 * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
+					 * which would connect without issue otherwise. -evands
+					 */
+					jabber_auth_start_old(js);
 					return;
 				}
 				/* not reached */
@@ -389,6 +396,24 @@
 				g_free(enc_out);
 			}
 		}
+		
+		if (mech && (strcmp(mech, "DIGEST-MD5") == 0)) {
+			/* CYRUS-SASL's DIGEST-MD5 and Java's DIGEST-MD5 are mutually incompatible because of different interpretations of RFC2831.
+			 * This means that if we are using SASL and connecting to a Java-based server such as OpenFire, we will receive an authentication
+			 * failure if that server offers DIGEST-MD5 in such a way that SASL chooses it as the best mechanism for us.
+			 *
+			 * However, we implement our own DIGEST-MD5 for use when we're compiled without SASL support, and that implementation
+			 * works correctly. Therefore, if SASL chooses DIGEST-MD5, we switch over to our own implementation.
+			 * jabber_auth_handle_challenge() will note the auth_type and take it from there.
+			 *
+			 * SASL would change state to SASL_OK after when handling the challenge; we do so immediately to avoid an error later.
+			 */
+			js->auth_type = JABBER_AUTH_DIGEST_MD5;
+			js->sasl_state = SASL_OK;
+			sasl_dispose(&js->sasl);
+			js->sasl = NULL;
+		}
+
 		jabber_send(js, auth);
 		xmlnode_free(auth);
 	} else {
@@ -563,6 +588,75 @@
 	}
 }
 
+/*!
+ * @brief Given the server challenge (message) and the key (password), calculate the HMAC-MD5 digest
+ *
+ * This is the crammd5 response.  Inspired by cyrus-sasl's _sasl_hmac_md5()
+ */
+static void
+auth_hmac_md5(const char *challenge, size_t challenge_len, const char *key, size_t key_len, guchar *digest)
+{
+	PurpleCipher *cipher;
+	PurpleCipherContext *context;
+	int i;
+	/* inner padding - key XORd with ipad */
+	unsigned char k_ipad[65];    
+	/* outer padding - key XORd with opad */
+	unsigned char k_opad[65];    
+
+	cipher = purple_ciphers_find_cipher("md5");
+
+	/* if key is longer than 64 bytes reset it to key=MD5(key) */
+	if (strlen(key) > 64) {
+		guchar keydigest[16];
+
+		context = purple_cipher_context_new(cipher, NULL);
+		purple_cipher_context_append(context, (const guchar *)key, strlen(key));
+		purple_cipher_context_digest(context, 16, keydigest, NULL);
+		purple_cipher_context_destroy(context);
+
+		key = (char *)keydigest;
+		key_len = 16;
+	} 
+
+	/*
+	 * the HMAC_MD5 transform looks like:
+	 *
+	 * MD5(K XOR opad, MD5(K XOR ipad, text))
+	 *
+	 * where K is an n byte key
+	 * ipad is the byte 0x36 repeated 64 times
+	 * opad is the byte 0x5c repeated 64 times
+	 * and text is the data being protected
+	 */
+
+	/* start out by storing key in pads */
+	memset(k_ipad, '\0', sizeof k_ipad);
+	memset(k_opad, '\0', sizeof k_opad);
+	memcpy(k_ipad, (void *)key, key_len);
+	memcpy(k_opad, (void *)key, key_len);
+
+	/* XOR key with ipad and opad values */
+	for (i=0; i<64; i++) {
+		k_ipad[i] ^= 0x36;
+		k_opad[i] ^= 0x5c;
+	}
+
+	/* perform inner MD5 */
+	context = purple_cipher_context_new(cipher, NULL);
+	purple_cipher_context_append(context, k_ipad, 64); /* start with inner pad */
+	purple_cipher_context_append(context, (const guchar *)challenge, challenge_len); /* then text of datagram */
+	purple_cipher_context_digest(context, 16, digest, NULL); /* finish up 1st pass */
+	purple_cipher_context_destroy(context);
+
+	/* perform outer MD5 */	
+	context = purple_cipher_context_new(cipher, NULL);
+	purple_cipher_context_append(context, k_opad, 64); /* start with outer pad */
+	purple_cipher_context_append(context, digest, 16); /* then results of 1st hash */
+	purple_cipher_context_digest(context, 16, digest, NULL); /* finish up 2nd pass */
+	purple_cipher_context_destroy(context);
+}
+
 static void auth_old_cb(JabberStream *js, xmlnode *packet, gpointer data)
 {
 	JabberIq *iq;
@@ -608,6 +702,35 @@
 			jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
 			jabber_iq_send(iq);
 
+		} else if(js->stream_id && xmlnode_get_child(query, "crammd5")) {
+			const char *challenge;
+			guchar digest[16];
+			char h[17], *p;
+			int i;
+
+			challenge = xmlnode_get_attrib(xmlnode_get_child(query, "crammd5"), "challenge");
+			auth_hmac_md5(challenge, strlen(challenge), pw, strlen(pw), digest);
+
+			/* Create the response query */
+			iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
+			query = xmlnode_get_child(iq->node, "query");
+
+			x = xmlnode_new_child(query, "username");
+			xmlnode_insert_data(x, js->user->node, -1);
+			x = xmlnode_new_child(query, "resource");
+			xmlnode_insert_data(x, js->user->resource, -1);
+
+			x = xmlnode_new_child(query, "crammd5");
+
+			/* Translate the digest to a hexadecimal notation */
+			p = h;
+			for(i=0; i<16; i++, p+=2)
+				snprintf(p, 3, "%02x", digest[i]);
+			xmlnode_insert_data(x, h, -1);
+
+			jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
+			jabber_iq_send(iq);
+
 		} else if(xmlnode_get_child(query, "password")) {
 			if(js->gsc == NULL && !purple_account_get_bool(js->gc->account,
 						"auth_plain_in_clear", FALSE)) {
--- a/libpurple/protocols/jabber/buddy.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Sat Dec 22 23:18:08 2007 +0000
@@ -1153,8 +1153,10 @@
 
 void jabber_vcard_fetch_mine(JabberStream *js)
 {
-	JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, "vcard-temp");
-
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
+	
+	xmlnode *vcard = xmlnode_new_child(iq->node, "vCard");
+	xmlnode_set_namespace(vcard, "vcard-temp");
 	jabber_iq_set_callback(iq, jabber_vcard_save_mine, NULL);
 
 	jabber_iq_send(iq);
--- a/libpurple/protocols/jabber/jabber.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sat Dec 22 23:18:08 2007 +0000
@@ -388,9 +388,29 @@
 	g_free(txt);
 }
 
+static void jabber_pong_cb(JabberStream *js, xmlnode *packet, gpointer timeout) 
+{
+	g_source_remove(GPOINTER_TO_INT(timeout));
+}
+
+static gboolean jabber_pong_timeout(PurpleConnection *gc)
+{
+	purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+					_("Ping timeout"));
+	return FALSE;
+}
+
 void jabber_keepalive(PurpleConnection *gc)
 {
-	jabber_send_raw(gc->proto_data, "\t", -1);
+	JabberIq *iq = jabber_iq_new(gc->proto_data, JABBER_IQ_GET);
+	guint timeout;
+
+        xmlnode *ping = xmlnode_new_child(iq->node, "ping");
+        xmlnode_set_namespace(ping, "urn:xmpp:ping");
+
+	timeout = purple_timeout_add_seconds(20, (GSourceFunc)(jabber_pong_timeout), gc);
+        jabber_iq_set_callback(iq, jabber_pong_cb, GINT_TO_POINTER(timeout));
+	jabber_iq_send(iq);
 }
 
 static void
@@ -536,12 +556,13 @@
 	purple_input_remove(js->gc->inpa);
 	js->gc->inpa = 0;
 	js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd,
-			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->serverFQDN, js->gc);
+			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->host, js->gc);
 }
 
 static void jabber_login_connect(JabberStream *js, const char *fqdn, const char *host, int port)
 {
 	js->serverFQDN = g_strdup(fqdn);
+	js->host = g_strdup(host);
 
 	if (purple_proxy_connect(js->gc, js->gc->account, host,
 			port, jabber_login_callback, js->gc) == NULL)
@@ -1279,6 +1300,7 @@
 		js->commands = g_list_delete_link(js->commands, js->commands);
 	}
 	g_free(js->server_name);
+	g_free(js->host);
 	g_free(js->gmail_last_time);
 	g_free(js->gmail_last_tid);
 	g_free(js->old_msg);
--- a/libpurple/protocols/jabber/jabber.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sat Dec 22 23:18:08 2007 +0000
@@ -191,6 +191,8 @@
 	char *old_uri;
 	int old_length;
 	char *old_track;
+	
+	char *host;
 };
 
 typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
--- a/libpurple/protocols/jabber/libxmpp.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Sat Dec 22 23:18:08 2007 +0000
@@ -53,7 +53,7 @@
 	OPT_PROTO_SLASH_COMMANDS_NATIVE,
 	NULL,							/* user_splits */
 	NULL,							/* protocol_options */
-	{"png", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
+	{"png", 32, 32, 96, 96, 0, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
 	jabber_list_icon,				/* list_icon */
 	jabber_list_emblem,			/* list_emblems */
 	jabber_status_text,				/* status_text */
--- a/libpurple/protocols/msn/contact.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/msn/contact.c	Sat Dec 22 23:18:08 2007 +0000
@@ -1289,16 +1289,16 @@
 				purple_debug_info("MSNCL", "Adding group %s with guid = %s to the userlist\n", state->new_group_name, guid);
 				msn_group_new(session->userlist, guid, state->new_group_name);
 
-				g_free(guid);
-
 				if (state->action & MSN_ADD_BUDDY) {
 					msn_userlist_add_buddy(session->userlist,
 						state->who,
 						state->new_group_name);
 				} else if (state->action & MSN_MOVE_BUDDY) {
 					msn_add_contact_to_group(session->contact, state, state->who, guid); 
+					g_free(guid);
 					return;
 				}
+				g_free(guid);
 			} else {
 				purple_debug_info("MSNCL", "Adding group %s failed\n",
 					state->new_group_name);
--- a/libpurple/protocols/msn/notification.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/msn/notification.c	Sat Dec 22 23:18:08 2007 +0000
@@ -1579,6 +1579,7 @@
 {
 	xmlnode * root;
 	gchar * buf;
+	int xmllen;
 
 	g_return_if_fail(cmd->payload != NULL);
 
@@ -1588,10 +1589,10 @@
 		return;
 	}
 	
-	buf = xmlnode_to_formatted_str(root, NULL);
+	buf = xmlnode_to_formatted_str(root, &xmllen);
 
 	/* get the payload content */
-	purple_debug_info("MSNP14","GCF command payload:\n%s\n",buf);
+	purple_debug_info("MSNP14","GCF command payload:\n%.*s\n", xmllen, buf);
 	
 	g_free(buf);
 	xmlnode_free(root);
--- a/libpurple/protocols/msn/session.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/msn/session.c	Sat Dec 22 23:18:08 2007 +0000
@@ -74,6 +74,7 @@
 
 	msn_userlist_destroy(session->userlist);
 
+	g_free(session->psm);
 	g_free(session->passport_info.t);
 	g_free(session->passport_info.p);
 	g_free(session->passport_info.kv);
--- a/libpurple/protocols/msn/soap.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/msn/soap.c	Sat Dec 22 23:18:08 2007 +0000
@@ -813,10 +813,8 @@
 		purple_debug_info("MSN SOAP", "Currently processing another SOAP request\n");
 	} else {
 		purple_debug_info("MSN SOAP", "No requests left to dispatch\n");
+#endif
 	}
-#else
-      }
-#endif
 
 }
 
--- a/libpurple/protocols/msn/state.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/msn/state.c	Sat Dec 22 23:18:08 2007 +0000
@@ -238,13 +238,15 @@
 	media = create_media_string(presence);
 	g_free(session->psm);
 	session->psm = msn_build_psm(statusline_stripped, media, NULL);
-	g_free(statusline_stripped);
 
 	payload = session->psm;
 	purple_debug_misc("MSNP14","Sending UUX command with payload: %s\n",payload);
 	trans = msn_transaction_new(cmdproc, "UUX", "%d", strlen(payload));
 	msn_transaction_set_payload(trans, payload, strlen(payload));
 	msn_cmdproc_send_trans(cmdproc, trans);
+
+	g_free(statusline_stripped);
+	g_free(media);
 }
 
 void
--- a/libpurple/protocols/msn/user.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/msn/user.c	Sat Dec 22 23:18:08 2007 +0000
@@ -83,6 +83,7 @@
 	g_free(user->media.artist);
 	g_free(user->media.title);
 	g_free(user->media.album);
+	g_free(user->statusline);
 
 	g_free(user);
 }
--- a/libpurple/protocols/sametime/sametime.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Sat Dec 22 23:18:08 2007 +0000
@@ -3749,7 +3749,7 @@
 
     client = purple_account_get_int(account, MW_KEY_CLIENT, mwLogin_BINARY);
     major = purple_account_get_int(account, MW_KEY_MAJOR, 0x001e);
-    minor = purple_account_get_int(account, MW_KEY_MINOR, 0x001d);
+    minor = purple_account_get_int(account, MW_KEY_MINOR, 0x196f);
 
     DEBUG_INFO("client id: 0x%04x\n", client);
     DEBUG_INFO("client major: 0x%04x\n", major);
--- a/libpurple/protocols/simple/sipmsg.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/simple/sipmsg.c	Sat Dec 22 23:18:08 2007 +0000
@@ -45,7 +45,10 @@
 	line = g_strndup(msg, tmp - msg);
 
 	smsg = sipmsg_parse_header(line);
-	smsg->body = g_strdup(tmp + 4);
+	if(smsg != NULL)
+		smsg->body = g_strdup(tmp + 4);
+	else
+		purple_debug_error("SIMPLE", "No header parsed from line: %s\n", line);
 
 	g_free(line);
 	return smsg;
--- a/libpurple/protocols/yahoo/util.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/yahoo/util.c	Sat Dec 22 23:18:08 2007 +0000
@@ -715,12 +715,15 @@
 			} else if (((len - i) >= 4) && !strncmp(&src[i], "&gt;", 4)) {
 				g_string_append_c(dest, '>');
 				i += 3;
-			} else if (((len - i) >= 5) && !strncmp(&src[i], "&amp;", 4)) {
+			} else if (((len - i) >= 5) && !strncmp(&src[i], "&amp;", 5)) {
 				g_string_append_c(dest, '&');
 				i += 4;
-			} else if (((len - i) >= 6) && !strncmp(&src[i], "&quot;", 4)) {
+			} else if (((len - i) >= 6) && !strncmp(&src[i], "&quot;", 6)) {
 				g_string_append_c(dest, '"');
 				i += 5;
+			} else if (((len - i) >= 6) && !strncmp(&src[i], "&apos;", 6)) {
+				g_string_append_c(dest, '\'');
+				i += 5;
 			} else {
 				g_string_append_c(dest, src[i]);
 			}
--- a/libpurple/protocols/yahoo/yahoo.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Sat Dec 22 23:18:08 2007 +0000
@@ -493,13 +493,14 @@
 static void yahoo_process_cookie(struct yahoo_data *yd, char *c)
 {
 	if (c[0] == 'Y') {
-		if (yd->cookie_y)
-			g_free(yd->cookie_y);
+		g_free(yd->cookie_y);
 		yd->cookie_y = _getcookie(c);
 	} else if (c[0] == 'T') {
-		if (yd->cookie_t)
-			g_free(yd->cookie_t);
+		g_free(yd->cookie_t);
 		yd->cookie_t = _getcookie(c);
+	} else if (c[0] == 'C') {
+		g_free(yd->cookie_c);
+		yd->cookie_c = _getcookie(c);
 	} else
 		purple_debug_info("yahoo", "Ignoring unrecognized cookie '%c'\n", c[0]);
 }
@@ -899,7 +900,6 @@
 		purple_util_chrreplace(m, '\r', '\n');
 
 		if (!strcmp(m, "<ding>")) {
-			PurpleBuddy *buddy;
 			PurpleAccount *account;
 			PurpleConversation *c;
 			char *username;
@@ -909,13 +909,8 @@
 			if (c == NULL)
 				c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, im->from);
 
-			if ((buddy = purple_find_buddy(account, im->from)) != NULL)
-				username = g_markup_escape_text(purple_buddy_get_alias(buddy), -1);
-			else
-				username = g_markup_escape_text(im->from, -1);
-
+			username = g_markup_escape_text(im->from, -1);
 			serv_got_attention(gc, username, YAHOO_BUZZ);
-
 			g_free(username);
 			g_free(m);
 			g_free(im);
@@ -2439,6 +2434,12 @@
 	case YAHOO_SERVICE_AUDIBLE:
 		yahoo_process_audible(gc, pkt);
 		break;
+	case YAHOO_SERVICE_Y7_FILETRANSFER:
+		yahoo_process_y7_filetransfer(gc, pkt);
+		break;
+	case YAHOO_SERVICE_Y7_FILETRANSFER_INFO:
+		yahoo_process_y7_filetransfer_info(gc, pkt);
+		break;
 	default:
 		purple_debug(PURPLE_DEBUG_ERROR, "yahoo",
 				   "Unhandled service 0x%02x\n", pkt->service);
@@ -3018,6 +3019,7 @@
 
 	g_free(yd->cookie_y);
 	g_free(yd->cookie_t);
+	g_free(yd->cookie_c);
 
 	if (yd->txhandler)
 		purple_input_remove(yd->txhandler);
--- a/libpurple/protocols/yahoo/yahoo.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.h	Sat Dec 22 23:18:08 2007 +0000
@@ -134,6 +134,7 @@
 	gsize auth_written;
 	char *cookie_y;
 	char *cookie_t;
+	char *cookie_c;
 	int session_id;
 	gboolean jp;
 	gboolean wm; /* connected w/ web messenger method */
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Sat Dec 22 23:18:08 2007 +0000
@@ -46,6 +46,10 @@
 	guint tx_handler;
 	gchar *rxqueue;
 	guint rxlen;
+
+	gboolean y7;	/* true for Y7 transfers (receive only for now) */
+	gchar *token;
+	gchar *tid;
 };
 
 static void yahoo_xfer_data_free(struct yahoo_xfer_data *xd)
@@ -53,11 +57,70 @@
 	g_free(xd->host);
 	g_free(xd->path);
 	g_free(xd->txbuf);
+	g_free(xd->token);
+	g_free(xd->tid);
 	if (xd->tx_handler)
 		purple_input_remove(xd->tx_handler);
 	g_free(xd);
 }
 
+
+static void yahoo_xfer_y7_request_next_file(PurpleXfer *xfer)
+{
+	struct yahoo_packet *pack;
+	struct yahoo_xfer_data *xd = xfer->data;
+	PurpleConnection *gc = xd->gc;
+	struct yahoo_data *yd = gc->proto_data;
+
+	g_return_if_fail(xd->y7);
+
+	pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, YAHOO_STATUS_AVAILABLE, 0);
+	yahoo_packet_hash(pack, "sssi",
+		1, purple_connection_get_display_name(xd->gc),
+		5, xfer->who,
+		265, xd->tid,
+		271, 1);
+	yahoo_packet_send_and_free(pack, yd);
+}
+
+static void yahoo_xfer_y7_cancel_receive(PurpleXfer *xfer)
+{
+	struct yahoo_packet *pack;
+	struct yahoo_xfer_data *xd = xfer->data;
+	PurpleConnection *gc = xd->gc;
+	struct yahoo_data *yd = gc->proto_data;
+
+	g_return_if_fail(xd->y7);
+
+	pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, -1, 0);
+	yahoo_packet_hash(pack, "sssi",
+		1, purple_connection_get_display_name(gc),
+		5, xfer->who,
+		265, xd->tid,
+		66, -1);
+	yahoo_packet_send_and_free(pack, yd);
+}
+
+static void yahoo_xfer_y7_accept_file(PurpleXfer *xfer)
+{
+	struct yahoo_packet *pack;
+	struct yahoo_xfer_data *xd = xfer->data;
+	PurpleConnection *gc = xd->gc;
+	struct yahoo_data *yd = gc->proto_data;
+
+	g_return_if_fail(xd->y7);
+
+	pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, YAHOO_STATUS_AVAILABLE, 0);
+	yahoo_packet_hash(pack, "ssssis",
+		1, purple_connection_get_display_name(gc),
+		5, xfer->who,				/* XXX this needs an accessor */
+		265, xd->tid,
+		27, purple_xfer_get_filename(xfer),	/* XXX this might be of incorrect encoding */
+		249, 3,
+		251, xd->token);
+	yahoo_packet_send_and_free(pack, yd);
+}
+
 static void yahoo_receivefile_send_cb(gpointer data, gint source, PurpleInputCondition condition)
 {
 	PurpleXfer *xfer;
@@ -97,6 +160,7 @@
 {
 	PurpleXfer *xfer;
 	struct yahoo_xfer_data *xd;
+	struct yahoo_data *yd;
 
 	purple_debug(PURPLE_DEBUG_INFO, "yahoo",
 			   "AAA - in yahoo_receivefile_connected\n");
@@ -112,11 +176,22 @@
 	}
 
 	xfer->fd = source;
+	yd = xd->gc->proto_data;
 
 	/* The first time we get here, assemble the tx buffer */
 	if (xd->txbuflen == 0) {
-		xd->txbuf = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n",
-			      xd->path, xd->host);
+		if (!xd->y7)
+			xd->txbuf = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n",
+				      xd->path, xd->host);
+		else
+			xd->txbuf = g_strdup_printf("GET /%s HTTP/1.0\r\n"
+				"Connection: close\r\n"
+				"Accept: */*\r\n"
+				"Host: %s\r\n"
+				"Cookie: Y=%s; T=%s\r\n"
+				"\r\n",
+				xd->path, xd->host, yd->cookie_y, yd->cookie_t);
+		purple_debug(PURPLE_DEBUG_INFO, "yahoo_filexfer", "HTTP request: [%s]\n", xd->txbuf);
 		xd->txbuflen = strlen(xd->txbuf);
 		xd->txbuf_written = 0;
 	}
@@ -281,6 +356,9 @@
 			}
 		}
 	} else {
+		if (xfer_data->y7)
+			yahoo_xfer_y7_accept_file(xfer);
+
 		xfer->fd = -1;
 		if (purple_proxy_connect(NULL, account, xfer_data->host, xfer_data->port,
 		                              yahoo_receivefile_connected, xfer) == NULL) {
@@ -340,6 +418,8 @@
 		if ((purple_xfer_get_size(xfer) > 0) &&
 		    (purple_xfer_get_bytes_sent(xfer) >= purple_xfer_get_size(xfer))) {
 			purple_xfer_set_completed(xfer, TRUE);
+			if (xd->y7)
+				yahoo_xfer_y7_request_next_file(xfer);
 			return 0;
 		} else
 			return -1;
@@ -430,11 +510,24 @@
 
 	xfer_data = xfer->data;
 
-	if (xfer_data)
+	if (xfer_data) {
+		if (xfer_data->y7)
+			yahoo_xfer_y7_cancel_receive(xfer);
 		yahoo_xfer_data_free(xfer_data);
+	}
 	xfer->data = NULL;
 }
 
+static void yahoo_xfer_request_denied(PurpleXfer *xfer)
+{
+	struct yahoo_xfer_data *xfer_data;
+
+	xfer_data = xfer->data;
+
+	if (xfer_data->y7)
+		yahoo_xfer_y7_cancel_receive(xfer);
+}
+
 void yahoo_process_p2pfilexfer(PurpleConnection *gc, struct yahoo_packet *pkt)
 {
 	GSList *l = pkt->hash;
@@ -628,6 +721,165 @@
 	}
 }
 
+void yahoo_process_y7_filetransfer(PurpleConnection *gc, struct yahoo_packet *pkt)
+{
+	struct yahoo_data *yd = gc->proto_data;
+	char *who = NULL, *name = NULL;
+	int ttype = 0;
+	char *tid = NULL;
+	GSList *l = pkt->hash;
+
+	while (l) {
+		struct yahoo_pair *pair = l->data;
+
+		switch (pair->key) {
+		case 4:
+			/* them */
+			who = pair->value;
+			break;
+		case 5:
+			/* us */
+			name = pair->value;
+			break;
+		case 222:
+			/* 1=send, 2=cancel, 3=accept, 4=reject */
+			if(pair->value)
+				ttype = atoi(pair->value);
+			break;
+		case 265:
+			/* transfer ID */
+			tid = pair->value;
+			break;
+		case 266:
+			/* number of files */
+			break;
+		case 27:
+			/* filename */
+			break;
+		case 28:
+			/* filesize */
+			break;
+		}
+
+		l = l->next;
+	}
+	if (ttype == 1 && tid) {
+		/* We auto-accept all offers here, and ask the user about each individual
+		 * file in yahoo_process_y7_filetransfer_info. This works fine for receiving
+		 * a single file; when receiving multiple canceling one in the middle
+		 * will also cancel the rest of them.
+		 * Maybe TODO: UI and API allowing transfer of multiple files as a package. */
+		struct yahoo_packet *pack;
+		pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER, YAHOO_STATUS_AVAILABLE, 0);
+		yahoo_packet_hash(pack, "sssi", 1, name, 5, who, 265, tid, 222, 3);
+		yahoo_packet_send_and_free(pack, yd);
+	}
+}
+
+void yahoo_process_y7_filetransfer_info(PurpleConnection *gc, struct yahoo_packet *pkt)
+{
+	struct yahoo_data *yd = gc->proto_data;
+	char *who = NULL, *name = NULL;
+	int medium = 0;
+	char *tid = NULL, *server_host = NULL, *server_token = NULL, *filename = NULL;
+	GSList *l = pkt->hash;
+	struct yahoo_packet *pack;
+	PurpleXfer *xfer;
+	struct yahoo_xfer_data *xfer_data;
+
+	while (l) {
+		struct yahoo_pair *pair = l->data;
+
+		switch (pair->key) {
+		case 4:
+			/* them */
+			who = pair->value;
+			break;
+		case 5:
+			/* us */
+			name = pair->value;
+			break;
+		case 249:
+			/* 1=p2p, 3=reflection server */
+			if(pair->value)
+				medium = atoi(pair->value);
+			break;
+		case 265:
+			/* transfer ID */
+			tid = pair->value;
+			break;
+		case 27:
+			filename = pair->value;
+			break;
+		case 250:
+			server_host = pair->value;
+			break;
+		case 251:
+			server_token = pair->value;
+			break;
+		}
+
+		l = l->next;
+	}
+	if (medium == 1) {
+		/* reject P2P transfers */
+		pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, YAHOO_STATUS_AVAILABLE, 0);
+		yahoo_packet_hash(pack, "sssi", 1, name, 5, who, 265, tid, 66, -3);
+		yahoo_packet_send_and_free(pack, yd);
+		return;
+	}
+
+	if (medium != 3) {
+		purple_debug_error("yahoo", "Unexpected medium %d.\n", medium);
+		/* weird */
+		return;
+	}
+
+	/* Setup the Yahoo-specific file transfer data */
+	xfer_data = g_new0(struct yahoo_xfer_data, 1);
+	xfer_data->gc = gc;
+	xfer_data->host = g_strdup(server_host);
+	xfer_data->token = g_strdup(server_token);
+	xfer_data->tid = g_strdup(tid);
+	xfer_data->port = 80;
+	xfer_data->y7 = TRUE;
+	
+	/* TODO: full urlencode here */
+	server_token = purple_strreplace(server_token, "\002", "%02");
+	xfer_data->path = g_strdup_printf("relay?token=%s&sender=%s&recver=%s",
+		server_token, who, name);
+	g_free(server_token);
+
+	purple_debug_misc("yahoo_filexfer", "Host is %s, port is %d, path is %s.\n",
+	                xfer_data->host, xfer_data->port, xfer_data->path);
+
+	/* Build the file transfer handle. */
+	xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, who);
+	xfer->data = xfer_data;
+
+	/* Set the info about the incoming file. */
+	{
+		char *utf8_filename = yahoo_string_decode(gc, filename, TRUE);
+		purple_xfer_set_filename(xfer, utf8_filename);
+		g_free(utf8_filename);
+	}
+
+	/* purple_xfer_set_size(xfer, filesize); */
+
+	/* Setup our I/O op functions */
+	purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init);
+	purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
+	purple_xfer_set_end_fnc(xfer,         yahoo_xfer_end);
+	purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send);
+	purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv);
+	purple_xfer_set_read_fnc(xfer,        yahoo_xfer_read);
+	purple_xfer_set_write_fnc(xfer,       yahoo_xfer_write);
+	purple_xfer_set_request_denied_fnc(xfer, yahoo_xfer_request_denied);
+
+	/* Now perform the request */
+	purple_xfer_request(xfer);
+}
+
 PurpleXfer *yahoo_new_xfer(PurpleConnection *gc, const char *who)
 {
 	PurpleXfer *xfer;
--- a/libpurple/protocols/yahoo/yahoo_filexfer.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.h	Sat Dec 22 23:18:08 2007 +0000
@@ -30,6 +30,16 @@
 void yahoo_process_p2pfilexfer( PurpleConnection *gc, struct yahoo_packet *pkt );
 
 /**
+ * Process ymsg version 7 file receive invites.
+ */
+void yahoo_process_y7_filetransfer(PurpleConnection *gc, struct yahoo_packet *pkt);
+
+/**
+ * Process ymsg version 7 file receive connection setups.
+ */
+void yahoo_process_y7_filetransfer_info(PurpleConnection *gc, struct yahoo_packet *pkt);
+
+/**
  * Process ymsg file receive invites.
  */
 void yahoo_process_filetransfer(PurpleConnection *gc, struct yahoo_packet *pkt);
--- a/libpurple/protocols/yahoo/yahoo_packet.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.h	Sat Dec 22 23:18:08 2007 +0000
@@ -99,9 +99,12 @@
 	YAHOO_SERVICE_VERIFY_ID_EXISTS = 0xc8,
 	YAHOO_SERVICE_AUDIBLE = 0xd0,
 	YAHOO_SERVICE_AUTH_REQ_15 = 0xd6,
+	YAHOO_SERVICE_Y7_FILETRANSFER = 0xdc,
+	YAHOO_SERVICE_Y7_FILETRANSFER_INFO = 0xdd,
+	YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT = 0xde,
 	YAHOO_SERVICE_CHGRP_15 = 0xe7,
 	YAHOO_SERVICE_STATUS_15 = 0xf0,
-	YAHOO_SERVICE_LIST_15 = 0Xf1,
+	YAHOO_SERVICE_LIST_15 = 0xf1,
 	YAHOO_SERVICE_WEBLOGIN = 0x0226,
 	YAHOO_SERVICE_SMS_MSG = 0x02ea
 };
--- a/libpurple/purple.h.in	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/purple.h.in	Sat Dec 22 23:18:08 2007 +0000
@@ -2,10 +2,11 @@
  * @file purple.h  Header files and defines
  * This file contains all the necessary preprocessor directives to include
  * libpurple's headers and other preprocessor directives required for plugins
- * or UIs to build.  Inlcuding this file eliminates the need to directly
+ * or UIs to build.  Including this file eliminates the need to directly
  * include any other libpurple files.
  *
  * @ingroup core libpurple
+ * @since 2.3.0
  */
 
 /* purple
--- a/libpurple/request.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/request.c	Sat Dec 22 23:18:08 2007 +0000
@@ -1208,6 +1208,7 @@
 
 	g_return_val_if_fail(ok_text != NULL,  NULL);
 	g_return_val_if_fail(ok_cb   != NULL,  NULL);
+	g_return_val_if_fail(cancel_text != NULL,  NULL);
 
 	ops = purple_request_get_ui_ops();
 
@@ -1296,6 +1297,7 @@
 	g_return_val_if_fail(fields  != NULL, NULL);
 	g_return_val_if_fail(ok_text != NULL, NULL);
 	g_return_val_if_fail(ok_cb   != NULL, NULL);
+	g_return_val_if_fail(cancel_text != NULL, NULL);
 
 	ops = purple_request_get_ui_ops();
 
--- a/libpurple/request.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/request.h	Sat Dec 22 23:18:08 2007 +0000
@@ -183,39 +183,53 @@
  */
 typedef struct
 {
+	/** @see purple_request_input(). */
 	void *(*request_input)(const char *title, const char *primary,
-						   const char *secondary, const char *default_value,
-						   gboolean multiline, gboolean masked, gchar *hint,
-						   const char *ok_text, GCallback ok_cb,
-						   const char *cancel_text, GCallback cancel_cb,
-						   PurpleAccount *account, const char *who, PurpleConversation *conv,
-						   void *user_data);
+	                       const char *secondary, const char *default_value,
+	                       gboolean multiline, gboolean masked, gchar *hint,
+	                       const char *ok_text, GCallback ok_cb,
+	                       const char *cancel_text, GCallback cancel_cb,
+	                       PurpleAccount *account, const char *who,
+	                       PurpleConversation *conv, void *user_data);
+
+	/** @see purple_request_choice_varg(). */
 	void *(*request_choice)(const char *title, const char *primary,
-							const char *secondary, int default_value,
-							const char *ok_text, GCallback ok_cb,
-							const char *cancel_text, GCallback cancel_cb,
-							PurpleAccount *account, const char *who, PurpleConversation *conv,
-							void *user_data, va_list choices);
+	                        const char *secondary, int default_value,
+	                        const char *ok_text, GCallback ok_cb,
+	                        const char *cancel_text, GCallback cancel_cb,
+	                        PurpleAccount *account, const char *who,
+	                        PurpleConversation *conv, void *user_data,
+	                        va_list choices);
+
+	/** @see purple_request_action_varg(). */
 	void *(*request_action)(const char *title, const char *primary,
-							const char *secondary, int default_action,
-							PurpleAccount *account, const char *who, PurpleConversation *conv,
-							void *user_data, size_t action_count,
-							va_list actions);
+	                        const char *secondary, int default_action,
+	                        PurpleAccount *account, const char *who,
+	                        PurpleConversation *conv, void *user_data,
+	                        size_t action_count, va_list actions);
+
+	/** @see purple_request_fields(). */
 	void *(*request_fields)(const char *title, const char *primary,
-							const char *secondary, PurpleRequestFields *fields,
-							const char *ok_text, GCallback ok_cb,
-							const char *cancel_text, GCallback cancel_cb,
-							PurpleAccount *account, const char *who, PurpleConversation *conv,
-							void *user_data);
+	                        const char *secondary, PurpleRequestFields *fields,
+	                        const char *ok_text, GCallback ok_cb,
+	                        const char *cancel_text, GCallback cancel_cb,
+	                        PurpleAccount *account, const char *who,
+	                        PurpleConversation *conv, void *user_data);
+
+	/** @see purple_request_file(). */
 	void *(*request_file)(const char *title, const char *filename,
-						  gboolean savedialog, GCallback ok_cb, GCallback cancel_cb,
-						  PurpleAccount *account, const char *who, PurpleConversation *conv,
-						  void *user_data);
+	                      gboolean savedialog, GCallback ok_cb,
+	                      GCallback cancel_cb, PurpleAccount *account,
+	                      const char *who, PurpleConversation *conv,
+	                      void *user_data);
+
 	void (*close_request)(PurpleRequestType type, void *ui_handle);
+
+	/** @see purple_request_folder(). */
 	void *(*request_folder)(const char *title, const char *dirname,
-							GCallback ok_cb, GCallback cancel_cb,
-							PurpleAccount *account, const char *who, PurpleConversation *conv,
-							void *user_data);
+	                        GCallback ok_cb, GCallback cancel_cb,
+	                        PurpleAccount *account, const char *who,
+	                        PurpleConversation *conv, void *user_data);
 
 	void (*_purple_reserved1)(void);
 	void (*_purple_reserved2)(void);
@@ -1159,198 +1173,247 @@
  * Prompts the user for text input.
  *
  * @param handle        The plugin or connection handle.  For some
- *                      things this is EXTREMELY important.  The
- *                      handle is used to programmatically close
- *                      the request dialog when it is no longer
- *                      needed.  For PRPLs this is often a pointer
- *                      to the PurpleConnection instance.  For plugins
- *                      this should be a similar, unique memory
- *                      location.  This value is important because
- *                      it allows a request to be closed, say, when
- *                      you sign offline.  If the request is NOT
- *                      closed it is VERY likely to cause a crash
- *                      whenever the callback handler functions are
- *                      triggered.
- * @param title         The title of the message.
- * @param primary       The main point of the message.
- * @param secondary     The secondary information.
+ *                      things this is <em>extremely</em> important.  The
+ *                      handle is used to programmatically close the request
+ *                      dialog when it is no longer needed.  For PRPLs this
+ *                      is often a pointer to the #PurpleConnection
+ *                      instance.  For plugins this should be a similar,
+ *                      unique memory location.  This value is important
+ *                      because it allows a request to be closed with
+ *                      purple_request_close_with_handle() when, for
+ *                      example, you sign offline.  If the request is
+ *                      <em>not</em> closed it is <strong>very</strong>
+ *                      likely to cause a crash whenever the callback
+ *                      handler functions are triggered.
+ * @param title         The title of the message, or @c NULL if it should have
+ *                      no title.
+ * @param primary       The main point of the message, or @c NULL if you're
+ *                      feeling enigmatic.
+ * @param secondary     Secondary information, or @c NULL if there is none.
  * @param default_value The default value.
- * @param multiline     TRUE if the inputted text can span multiple lines.
- * @param masked        TRUE if the inputted text should be masked in some way.
+ * @param multiline     @c TRUE if the inputted text can span multiple lines.
+ * @param masked        @c TRUE if the inputted text should be masked in some
+ *                      way (such as by displaying characters as stars).  This
+ *                      might be because the input is some kind of password.
  * @param hint          Optionally suggest how the input box should appear.
- *                      Use "html," for example, to allow the user to enter
+ *                      Use "html", for example, to allow the user to enter
  *                      HTML.
- * @param ok_text       The text for the @c OK button.
- * @param ok_cb         The callback for the @c OK button.
- * @param cancel_text   The text for the @c Cancel button.
- * @param cancel_cb     The callback for the @c Cancel button.
- * @param account		The PurpleAccount associated with this request, or NULL if none is
- * @param who			The username of the buddy assocaited with this request, or NULL if none is
- * @param conv			The PurpleConversation associated with this request, or NULL if none is
+ * @param ok_text       The text for the @c OK button, which may not be @c NULL.
+ * @param ok_cb         The callback for the @c OK button, which may not be @c
+ *                      NULL.
+ * @param cancel_text   The text for the @c Cancel button, which may not be @c
+ *                      NULL.
+ * @param cancel_cb     The callback for the @c Cancel button, which may be
+ *                      @c NULL.
+ * @param account       The #PurpleAccount associated with this request, or @c
+ *                      NULL if none is.
+ * @param who           The username of the buddy associated with this request,
+ *                      or @c NULL if none is.
+ * @param conv          The #PurpleConversation associated with this request, or
+ *                      @c NULL if none is.
  * @param user_data     The data to pass to the callback.
  *
  * @return A UI-specific handle.
  */
-void *purple_request_input(void *handle, const char *title,
-						 const char *primary, const char *secondary,
-						 const char *default_value,
-						 gboolean multiline, gboolean masked, gchar *hint,
-						 const char *ok_text, GCallback ok_cb,
-						 const char *cancel_text, GCallback cancel_cb,
-						 PurpleAccount *account, const char *who, PurpleConversation *conv,
-						 void *user_data);
+void *purple_request_input(void *handle, const char *title, const char *primary,
+	const char *secondary, const char *default_value, gboolean multiline,
+	gboolean masked, gchar *hint,
+	const char *ok_text, GCallback ok_cb,
+	const char *cancel_text, GCallback cancel_cb,
+	PurpleAccount *account, const char *who, PurpleConversation *conv,
+	void *user_data);
 
 /**
  * Prompts the user for multiple-choice input.
  *
- * @param handle        The plugin or connection handle.  For some
- *                      things this is EXTREMELY important.  See
- *                      the comments on purple_request_input.
- * @param title         The title of the message.
- * @param primary       The main point of the message.
- * @param secondary     The secondary information.
- * @param default_value The default value.
- * @param ok_text       The text for the @c OK button.
- * @param ok_cb         The callback for the @c OK button.
- * @param cancel_text   The text for the @c Cancel button.
- * @param cancel_cb     The callback for the @c Cancel button.
- * @param account		The PurpleAccount associated with this request, or NULL if none is
- * @param who			The username of the buddy assocaited with this request, or NULL if none is
- * @param conv			The PurpleConversation associated with this request, or NULL if none is
+ * @param handle        The plugin or connection handle.  For some things this
+ *                      is <em>extremely</em> important.  See the comments on
+ *                      purple_request_input().
+ * @param title         The title of the message, or @c NULL if it should have
+ *                      no title.
+ * @param primary       The main point of the message, or @c NULL if you're
+ *                      feeling enigmatic.
+ * @param secondary     Secondary information, or @c NULL if there is none.
+ * @param default_value The default choice; this should be one of the values
+ *                      listed in the varargs.
+ * @param ok_text       The text for the @c OK button, which may not be @c NULL.
+ * @param ok_cb         The callback for the @c OK button, which may not be @c
+ *                      NULL.
+ * @param cancel_text   The text for the @c Cancel button, which may not be @c
+ *                      NULL.
+ * @param cancel_cb     The callback for the @c Cancel button, or @c NULL to
+ *                      do nothing.
+ * @param account       The #PurpleAccount associated with this request, or @c
+ *                      NULL if none is.
+ * @param who           The username of the buddy associated with this request,
+ *                      or @c NULL if none is.
+ * @param conv          The #PurpleConversation associated with this request, or
+ *                      @c NULL if none is.
  * @param user_data     The data to pass to the callback.
- * @param ...           The choices.  This argument list should be
- *                      terminated with a NULL parameter.
+ * @param ...           The choices, which should be pairs of <tt>char *</tt>
+ *                      descriptions and <tt>int</tt> values, terminated with a
+ *                      @c NULL parameter.
  *
  * @return A UI-specific handle.
  */
-void *purple_request_choice(void *handle, const char *title,
-						  const char *primary, const char *secondary,
-						  int default_value,
-						  const char *ok_text, GCallback ok_cb,
-						  const char *cancel_text, GCallback cancel_cb,
-						  PurpleAccount *account, const char *who, PurpleConversation *conv,
-						  void *user_data, ...) G_GNUC_NULL_TERMINATED;
+void *purple_request_choice(void *handle, const char *title, const char *primary,
+	const char *secondary, int default_value,
+	const char *ok_text, GCallback ok_cb,
+	const char *cancel_text, GCallback cancel_cb,
+	PurpleAccount *account, const char *who, PurpleConversation *conv,
+	void *user_data, ...) G_GNUC_NULL_TERMINATED;
 
 /**
  * Prompts the user for multiple-choice input.
  *
- * @param handle        The plugin or connection handle.  For some
- *                      things this is EXTREMELY important.  See
- *                      the comments on purple_request_input.
- * @param title         The title of the message.
- * @param primary       The main point of the message.
- * @param secondary     The secondary information.
- * @param default_value The default value.
- * @param ok_text       The text for the @c OK button.
- * @param ok_cb         The callback for the @c OK button.
- * @param cancel_text   The text for the @c Cancel button.
- * @param cancel_cb     The callback for the @c Cancel button.
- * @param account		The PurpleAccount associated with this request, or NULL if none is
- * @param who			The username of the buddy assocaited with this request, or NULL if none is
- * @param conv			The PurpleConversation associated with this request, or NULL if none is
+ * @param handle        The plugin or connection handle.  For some things this
+ *                      is <em>extremely</em> important.  See the comments on
+ *                      purple_request_input().
+ * @param title         The title of the message, or @c NULL if it should have
+ *                      no title.
+ * @param primary       The main point of the message, or @c NULL if you're
+ *                      feeling enigmatic.
+ * @param secondary     Secondary information, or @c NULL if there is none.
+ * @param default_value The default choice; this should be one of the values
+ *                      listed in the varargs.
+ * @param ok_text       The text for the @c OK button, which may not be @c NULL.
+ * @param ok_cb         The callback for the @c OK button, which may not be @c
+ *                      NULL.
+ * @param cancel_text   The text for the @c Cancel button, which may not be @c
+ *                      NULL.
+ * @param cancel_cb     The callback for the @c Cancel button, or @c NULL to do
+ *                      nothing.
+ * @param account       The #PurpleAccount associated with this request, or @c
+ *                      NULL if none is
+ * @param who           The username of the buddy associated with this request,
+ *                      or @c NULL if none is
+ * @param conv          The #PurpleConversation associated with this request, or
+ *                      @c NULL if none is
  * @param user_data     The data to pass to the callback.
- * @param choices       The choices.  This argument list should be
- *                      terminated with a @c NULL parameter.
+ * @param choices       The choices, which should be pairs of <tt>char *</tt>
+ *                      descriptions and <tt>int</tt> values, terminated with a
+ *                      @c NULL parameter.
  *
  * @return A UI-specific handle.
  */
 void *purple_request_choice_varg(void *handle, const char *title,
-							   const char *primary, const char *secondary,
-							   int default_value,
-							   const char *ok_text, GCallback ok_cb,
-							   const char *cancel_text, GCallback cancel_cb,
-							   PurpleAccount *account, const char *who, PurpleConversation *conv,
-							   void *user_data, va_list choices);
+	const char *primary, const char *secondary, int default_value,
+	const char *ok_text, GCallback ok_cb,
+	const char *cancel_text, GCallback cancel_cb,
+	PurpleAccount *account, const char *who, PurpleConversation *conv,
+	void *user_data, va_list choices);
 
 /**
  * Prompts the user for an action.
  *
  * This is often represented as a dialog with a button for each action.
  *
- * @param handle         The plugin or connection handle.  For some
- *                       things this is EXTREMELY important.  See
- *                       the comments on purple_request_input.
- * @param title          The title of the message.
- * @param primary        The main point of the message.
- * @param secondary      The secondary information.
- * @param default_action The default value.
- * @param account		 The PurpleAccount associated with this request, or NULL if none is
- * @param who			 The username of the buddy assocaited with this request, or NULL if none is
- * @param conv			 The PurpleConversation associated with this request, or NULL if none is
+ * @param handle         The plugin or connection handle.  For some things this
+ *                       is <em>extremely</em> important.  See the comments on
+ *                       purple_request_input().
+ * @param title          The title of the message, or @c NULL if it should have
+ *                       no title.
+ * @param primary        The main point of the message, or @c NULL if you're
+ *                       feeling enigmatic.
+ * @param secondary      Secondary information, or @c NULL if there is none.
+ * @param default_action The default action, zero-indexed; if the third action
+ *                       supplied should be the default, supply <tt>2</tt>.
+ * @param account        The #PurpleAccount associated with this request, or @c
+ *                       NULL if none is.
+ * @param who            The username of the buddy associated with this request,
+ *                       or @c NULL if none is.
+ * @param conv           The #PurpleConversation associated with this request, or
+ *                       @c NULL if none is.
  * @param user_data      The data to pass to the callback.
  * @param action_count   The number of actions.
  * @param ...            A list of actions.  These are pairs of
  *                       arguments.  The first of each pair is the
- *                       string that appears on the button.  It should
+ *                       <tt>char *</tt> that appears on the button.  It should
  *                       have an underscore before the letter you want
  *                       to use as the accelerator key for the button.
- *                       The second of each pair is the callback
+ *                       The second of each pair is the <tt>GCallback</tt>
  *                       function to use when the button is clicked.
  *
  * @return A UI-specific handle.
  */
-void *purple_request_action(void *handle, const char *title,
-						  const char *primary, const char *secondary,
-						  int default_action,
-						  PurpleAccount *account, const char *who, PurpleConversation *conv,
-						  void *user_data, size_t action_count, ...);
+void *purple_request_action(void *handle, const char *title, const char *primary,
+	const char *secondary, int default_action, PurpleAccount *account,
+	const char *who, PurpleConversation *conv, void *user_data,
+	size_t action_count, ...);
 
 /**
  * Prompts the user for an action.
  *
  * This is often represented as a dialog with a button for each action.
  *
- * @param handle         The plugin or connection handle.  For some
- *                       things this is EXTREMELY important.  See
- *                       the comments on purple_request_input.
- * @param title          The title of the message.
- * @param primary        The main point of the message.
- * @param secondary      The secondary information.
- * @param default_action The default value.
- * @param account		 The PurpleAccount associated with this request, or NULL if none is
- * @param who			 The username of the buddy assocaited with this request, or NULL if none is
- * @param conv			 The PurpleConversation associated with this request, or NULL if none is
+ * @param handle         The plugin or connection handle.  For some things this
+ *                       is <em>extremely</em> important.  See the comments on
+ *                       purple_request_input().
+ * @param title          The title of the message, or @c NULL if it should have
+ *                       no title.
+ * @param primary        The main point of the message, or @c NULL if you're
+ *                       feeling enigmatic.
+ * @param secondary      Secondary information, or @c NULL if there is none.
+ * @param default_action The default action, zero-indexed; if the third action
+ *                       supplied should be the default, supply <tt>2</tt>.
+ * @param account        The #PurpleAccount associated with this request, or @c
+ *                       NULL if none is.
+ * @param who            The username of the buddy associated with this request,
+ *                       or @c NULL if none is.
+ * @param conv           The #PurpleConversation associated with this request, or
+ *                       @c NULL if none is.
  * @param user_data      The data to pass to the callback.
  * @param action_count   The number of actions.
- * @param actions        A list of actions and callbacks.
+ * @param actions        A list of actions.  These are pairs of
+ *                       arguments.  The first of each pair is the
+ *                       <tt>char *</tt> that appears on the button.  It should
+ *                       have an underscore before the letter you want
+ *                       to use as the accelerator key for the button.
+ *                       The second of each pair is the <tt>GCallback</tt>
+ *                       function to use when the button is clicked.
  *
  * @return A UI-specific handle.
  */
 void *purple_request_action_varg(void *handle, const char *title,
-							   const char *primary, const char *secondary,
-							   int default_action,
-							   PurpleAccount *account, const char *who, PurpleConversation *conv,
-							   void *user_data, size_t action_count,
-							   va_list actions);
+	const char *primary, const char *secondary, int default_action,
+	PurpleAccount *account, const char *who, PurpleConversation *conv,
+	void *user_data, size_t action_count, va_list actions);
 
 /**
  * Displays groups of fields for the user to fill in.
  *
- * @param handle      The plugin or connection handle.  For some
- *                    things this is EXTREMELY important.  See
- *                    the comments on purple_request_input.
- * @param title       The title of the message.
- * @param primary     The main point of the message.
- * @param secondary   The secondary information.
+ * @param handle      The plugin or connection handle.  For some things this
+ *                    is <em>extremely</em> important.  See the comments on
+ *                    purple_request_input().
+ * @param title       The title of the message, or @c NULL if it should have
+ *                    no title.
+ * @param primary     The main point of the message, or @c NULL if you're
+ *                    feeling enigmatic.
+ * @param secondary   Secondary information, or @c NULL if there is none.
  * @param fields      The list of fields.
- * @param ok_text     The text for the @c OK button.
- * @param ok_cb       The callback for the @c OK button.
- * @param cancel_text The text for the @c Cancel button.
- * @param cancel_cb   The callback for the @c Cancel button.
- * @param account	  The PurpleAccount associated with this request, or NULL if none is
- * @param who		  The username of the buddy associated with this request, or NULL if none is
- * @param conv		  The PurpleConversation associated with this request, or NULL if none is
+ * @param ok_text     The text for the @c OK button, which may not be @c NULL.
+ * @param ok_cb       The callback for the @c OK button, which may not be @c
+ *                    NULL.
+ * @param cancel_text The text for the @c Cancel button, which may not be @c
+ *                    NULL.
+ * @param cancel_cb   The callback for the @c Cancel button, which may be
+ *                    @c NULL.
+ * @param account     The #PurpleAccount associated with this request, or @c
+ *                    NULL if none is
+ * @param who         The username of the buddy associated with this request,
+ *                    or @c NULL if none is
+ * @param conv        The #PurpleConversation associated with this request, or
+ *                    @c NULL if none is
  * @param user_data   The data to pass to the callback.
  *
  * @return A UI-specific handle.
  */
-void *purple_request_fields(void *handle, const char *title,
-						  const char *primary, const char *secondary,
-						  PurpleRequestFields *fields,
-						  const char *ok_text, GCallback ok_cb,
-						  const char *cancel_text, GCallback cancel_cb,
-						  PurpleAccount *account, const char *who, PurpleConversation *conv,
-						  void *user_data);
+void *purple_request_fields(void *handle, const char *title, const char *primary,
+	const char *secondary, PurpleRequestFields *fields,
+	const char *ok_text, GCallback ok_cb,
+	const char *cancel_text, GCallback cancel_cb,
+	PurpleAccount *account, const char *who, PurpleConversation *conv,
+	void *user_data);
 
 /**
  * Closes a request.
@@ -1363,7 +1426,10 @@
 /**
  * Closes all requests registered with the specified handle.
  *
- * @param handle The handle.
+ * @param handle The handle, as supplied as the @a handle parameter to one of the
+ *               <tt>purple_request_*</tt> functions.
+ *
+ * @see purple_request_input().
  */
 void purple_request_close_with_handle(void *handle);
 
@@ -1401,50 +1467,57 @@
  * Displays a file selector request dialog.  Returns the selected filename to
  * the callback.  Can be used for either opening a file or saving a file.
  *
- * @param handle      The plugin or connection handle.  For some
- *                    things this is EXTREMELY important.  See
- *                    the comments on purple_request_input.
- * @param title       The title for the dialog (may be @c NULL)
+ * @param handle      The plugin or connection handle.  For some things this
+ *                    is <em>extremely</em> important.  See the comments on
+ *                    purple_request_input().
+ * @param title       The title of the message, or @c NULL if it should have
+ *                    no title.
  * @param filename    The default filename (may be @c NULL)
  * @param savedialog  True if this dialog is being used to save a file.
  *                    False if it is being used to open a file.
  * @param ok_cb       The callback for the @c OK button.
- * @param cancel_cb   The callback for the @c Cancel button.
- * @param account	  The PurpleAccount associated with this request, or NULL if none is
- * @param who		  The username of the buddy assocaited with this request, or NULL if none is
- * @param conv		  The PurpleConversation associated with this request, or NULL if none is
+ * @param cancel_cb   The callback for the @c Cancel button, which may be @c NULL.
+ * @param account     The #PurpleAccount associated with this request, or @c
+ *                    NULL if none is
+ * @param who         The username of the buddy associated with this request,
+ *                    or @c NULL if none is
+ * @param conv        The #PurpleConversation associated with this request, or
+ *                    @c NULL if none is
  * @param user_data   The data to pass to the callback.
  *
  * @return A UI-specific handle.
  */
 void *purple_request_file(void *handle, const char *title, const char *filename,
-						gboolean savedialog,
-						GCallback ok_cb, GCallback cancel_cb,
-						PurpleAccount *account, const char *who, PurpleConversation *conv,
-						void *user_data);
+	gboolean savedialog, GCallback ok_cb, GCallback cancel_cb,
+	PurpleAccount *account, const char *who, PurpleConversation *conv,
+	void *user_data);
 
 /**
  * Displays a folder select dialog. Returns the selected filename to
  * the callback.
  *
- * @param handle      The plugin or connection handle.  For some
- *                    things this is EXTREMELY important.  See
- *                    the comments on purple_request_input.
- * @param title       The title for the dialog (may be @c NULL)
+ * @param handle      The plugin or connection handle.  For some things this
+ *                    is <em>extremely</em> important.  See the comments on
+ *                    purple_request_input().
+ * @param title       The title of the message, or @c NULL if it should have
+ *                    no title.
  * @param dirname     The default directory name (may be @c NULL)
  * @param ok_cb       The callback for the @c OK button.
- * @param cancel_cb   The callback for the @c Cancel button.
- * @param account	  The PurpleAccount associated with this request, or NULL if none is
- * @param who		  The username of the buddy assocaited with this request, or NULL if none is
- * @param conv		  The PurpleConversation associated with this request, or NULL if none is
+ * @param cancel_cb   The callback for the @c Cancel button, which may be @c NULL.
+ * @param account     The #PurpleAccount associated with this request, or @c
+ *                    NULL if none is
+ * @param who         The username of the buddy associated with this request,
+ *                    or @c NULL if none is
+ * @param conv        The #PurpleConversation associated with this request, or
+ *                    @c NULL if none is
  * @param user_data   The data to pass to the callback.
  *
  * @return A UI-specific handle.
  */
 void *purple_request_folder(void *handle, const char *title, const char *dirname,
-						GCallback ok_cb, GCallback cancel_cb,
-						PurpleAccount *account, const char *who, PurpleConversation *conv,
-						void *user_data);
+	GCallback ok_cb, GCallback cancel_cb,
+	PurpleAccount *account, const char *who, PurpleConversation *conv,
+	void *user_data);
 
 /*@}*/
 
--- a/libpurple/savedstatuses.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/savedstatuses.c	Sat Dec 22 23:18:08 2007 +0000
@@ -243,7 +243,9 @@
 
 	child = xmlnode_new_child(node, "account");
 	xmlnode_set_attrib(child, "protocol", purple_account_get_protocol_id(substatus->account));
-	xmlnode_insert_data(child, purple_account_get_username(substatus->account), -1);
+	xmlnode_insert_data(child,
+			purple_normalize(substatus->account,
+				purple_account_get_username(substatus->account)), -1);
 
 	child = xmlnode_new_child(node, "state");
 	xmlnode_insert_data(child, purple_status_type_get_id(substatus->type), -1);
--- a/libpurple/status.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/status.c	Sat Dec 22 23:18:08 2007 +0000
@@ -607,13 +607,10 @@
 
 		if (old_status != NULL)
 		{
-			tmp = g_strdup_printf(_("%s changed status from %s to %s"), buddy_alias,
+			tmp = g_strdup_printf(_("%s (%s) changed status from %s to %s"), buddy_alias, buddy->name,
 			                      purple_status_get_name(old_status),
 			                      purple_status_get_name(new_status));
-			logtmp = g_strdup_printf(_("%s (%s) changed status from %s to %s"), buddy_alias, buddy->name,
-			                      purple_status_get_name(old_status),
-			                      purple_status_get_name(new_status));
-
+			logtmp = g_markup_escape_text(tmp, -1);
 		}
 		else
 		{
@@ -621,18 +618,15 @@
 
 			if (purple_status_is_active(new_status))
 			{
-				tmp = g_strdup_printf(_("%s is now %s"), buddy_alias,
+				tmp = g_strdup_printf(_("%s (%s) is now %s"), buddy_alias, buddy->name,
 				                      purple_status_get_name(new_status));
-				logtmp = g_strdup_printf(_("%s (%s) is now %s"), buddy_alias, buddy->name,
-				                      purple_status_get_name(new_status));
-
+				logtmp = g_markup_escape_text(tmp, -1);
 			}
 			else
 			{
-				tmp = g_strdup_printf(_("%s is no longer %s"), buddy_alias,
+				tmp = g_strdup_printf(_("%s (%s) is no longer %s"), buddy_alias, buddy->name,
 				                      purple_status_get_name(new_status));
-				logtmp = g_strdup_printf(_("%s (%s) is no longer %s"), buddy_alias, buddy->name,
-				                      purple_status_get_name(new_status));
+				logtmp = g_markup_escape_text(tmp, -1);
 			}
 		}
 
@@ -1244,12 +1238,15 @@
 
 			if (log != NULL)
 			{
-				char *tmp = g_strdup_printf(_("%s became idle"),
+				char *tmp, *tmp2;
+				tmp = g_strdup_printf(_("%s became idle"),
 				purple_buddy_get_alias(buddy));
+				tmp2 = g_markup_escape_text(tmp, -1);
+				g_free(tmp);
 
 				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
-				purple_buddy_get_alias(buddy), current_time, tmp);
-				g_free(tmp);
+				purple_buddy_get_alias(buddy), current_time, tmp2);
+				g_free(tmp2);
 			}
 		}
 	}
@@ -1261,12 +1258,15 @@
 
 			if (log != NULL)
 			{
-				char *tmp = g_strdup_printf(_("%s became unidle"),
+				char *tmp, *tmp2;
+				tmp = g_strdup_printf(_("%s became unidle"),
 				purple_buddy_get_alias(buddy));
+				tmp2 = g_markup_escape_text(tmp, -1);
+				g_free(tmp);
 
 				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
-				purple_buddy_get_alias(buddy), current_time, tmp);
-				g_free(tmp);
+				purple_buddy_get_alias(buddy), current_time, tmp2);
+				g_free(tmp2);
 			}
 		}
 	}
@@ -1321,13 +1321,15 @@
 
 			if (log != NULL)
 			{
-				char *msg;
+				char *msg, *tmp;
 
 				if (idle)
-					msg = g_strdup_printf(_("+++ %s became idle"), purple_account_get_username(account));
+					tmp = g_strdup_printf(_("+++ %s became idle"), purple_account_get_username(account));
 				else
-					msg = g_strdup_printf(_("+++ %s became unidle"), purple_account_get_username(account));
+					tmp = g_strdup_printf(_("+++ %s became unidle"), purple_account_get_username(account));
 
+				msg = g_markup_escape_text(tmp, -1);
+				g_free(tmp);
 				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
 				                 purple_account_get_username(account),
 				                 (idle ? idle_time : current_time), msg);
--- a/libpurple/tests/check_libpurple.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/tests/check_libpurple.c	Sat Dec 22 23:18:08 2007 +0000
@@ -1,11 +1,12 @@
 #include <glib.h>
 #include <stdlib.h>
 
+#include "tests.h"
+
 #include "../core.h"
 #include "../eventloop.h"
 #include "../util.h"
 
-#include "tests.h"
 
 /******************************************************************************
  * libpurple goodies
--- a/libpurple/tests/test_cipher.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/tests/test_cipher.c	Sat Dec 22 23:18:08 2007 +0000
@@ -190,6 +190,511 @@
 END_TEST
 
 /******************************************************************************
+ * DES Tests
+ *****************************************************************************/
+#define DES_TEST(in, keyz, out, len) { \
+	PurpleCipher *cipher = NULL; \
+	PurpleCipherContext *context = NULL; \
+	guchar answer[len+1]; \
+	gint ret = 0; \
+	guchar decrypt[len+1] = in; \
+	guchar key[8+1] = keyz;\
+	guchar encrypt[len+1] = out;\
+	size_t outlen; \
+	\
+	cipher = purple_ciphers_find_cipher("des"); \
+	context = purple_cipher_context_new(cipher, NULL); \
+	purple_cipher_context_set_key(context, key); \
+	\
+	ret = purple_cipher_context_encrypt(context, decrypt, len, answer, &outlen); \
+	fail_unless(ret == 0, NULL); \
+	fail_unless(outlen == (len), NULL); \
+	fail_unless(memcmp(encrypt, answer, len) == 0, NULL); \
+	\
+	ret = purple_cipher_context_decrypt(context, encrypt, len, answer, &outlen); \
+	fail_unless(ret == 0, NULL); \
+	fail_unless(outlen == (len), NULL); \
+	fail_unless(memcmp(decrypt, answer, len) == 0, NULL); \
+	\
+	purple_cipher_context_destroy(context); \
+}
+
+START_TEST(test_des_12345678) {
+	DES_TEST("12345678",
+	         "\x3b\x38\x98\x37\x15\x20\xf7\x5e",
+	         "\x06\x22\x05\xac\x6a\x0d\x55\xdd",
+	         8);
+}
+END_TEST
+
+START_TEST(test_des_abcdefgh) {
+	DES_TEST("abcdefgh",
+	         "\x3b\x38\x98\x37\x15\x20\xf7\x5e",
+	         "\x62\xe0\xc6\x8c\x48\xe4\x75\xed",
+	         8);
+}
+END_TEST
+
+/******************************************************************************
+ * DES3 Tests
+ * See http://csrc.nist.gov/groups/ST/toolkit/examples.html
+ * and some NULL things I made up
+ *****************************************************************************/
+
+#define DES3_TEST(in, key, iv, out, len, mode) { \
+	PurpleCipher *cipher = NULL; \
+	PurpleCipherContext *context = NULL; \
+	guchar answer[len+1]; \
+	guchar decrypt[len+1] = in; \
+	guchar encrypt[len+1] = out; \
+	size_t outlen; \
+	gint ret = 0; \
+	\
+	cipher = purple_ciphers_find_cipher("des3"); \
+	context = purple_cipher_context_new(cipher, NULL); \
+	purple_cipher_context_set_key(context, (guchar *)key); \
+	purple_cipher_context_set_batch_mode(context, (mode)); \
+	purple_cipher_context_set_iv(context, (guchar *)iv, 8); \
+	\
+	ret = purple_cipher_context_encrypt(context, decrypt, len, answer, &outlen); \
+	fail_unless(ret == 0, NULL); \
+	fail_unless(outlen == (len), NULL); \
+	fail_unless(memcmp(encrypt, answer, len) == 0, NULL); \
+	\
+	ret = purple_cipher_context_decrypt(context, encrypt, len, answer, &outlen); \
+	fail_unless(ret == 0, NULL); \
+	fail_unless(outlen == (len), NULL); \
+	fail_unless(memcmp(decrypt, answer, len) == 0, NULL); \
+	\
+	purple_cipher_context_destroy(context); \
+}
+
+START_TEST(test_des3_ecb_nist1) {
+	DES3_TEST(
+	          "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A"
+	          "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51",
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
+	          "\x23\x45\x67\x89\xAB\xCD\xEF\x01"
+	          "\x45\x67\x89\xAB\xCD\xEF\x01\x23",
+	          "00000000", /* ignored */
+	          "\x71\x47\x72\xF3\x39\x84\x1D\x34\x26\x7F\xCC\x4B\xD2\x94\x9C\xC3"
+	          "\xEE\x11\xC2\x2A\x57\x6A\x30\x38\x76\x18\x3F\x99\xC0\xB6\xDE\x87",
+	          32,
+	          PURPLE_CIPHER_BATCH_MODE_ECB);
+}
+END_TEST
+
+START_TEST(test_des3_ecb_nist2) {
+	DES3_TEST(
+	          "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A"
+	          "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51",
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
+	          "\x23\x45\x67\x89\xAB\xCD\xEF\x01"
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF",
+	          "00000000", /* ignored */
+	          "\x06\xED\xE3\xD8\x28\x84\x09\x0A\xFF\x32\x2C\x19\xF0\x51\x84\x86"
+	          "\x73\x05\x76\x97\x2A\x66\x6E\x58\xB6\xC8\x8C\xF1\x07\x34\x0D\x3D",
+	          32,
+	          PURPLE_CIPHER_BATCH_MODE_ECB);
+}
+END_TEST
+
+START_TEST(test_des3_ecb_null_key) {
+	DES3_TEST(
+	          "\x16\xf4\xb3\x77\xfd\x4b\x9e\xca",
+	          "\x38\x00\x88\x6a\xef\xcb\x00\xad"
+	          "\x5d\xe5\x29\x00\x7d\x98\x64\x4c"
+	          "\x86\x00\x7b\xd3\xc7\x00\x7b\x32",
+	          "00000000", /* ignored */
+	          "\xc0\x60\x30\xa1\xb7\x25\x42\x44",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_ECB);
+}
+END_TEST
+
+START_TEST(test_des3_ecb_null_text) {
+	DES3_TEST(
+	          "\x65\x73\x34\xc1\x19\x00\x79\x65",
+	          "\x32\x64\xda\x10\x13\x6a\xfe\x1e"
+	          "\x37\x54\xd1\x2c\x41\x04\x10\x40"
+	          "\xaf\x1c\x75\x2b\x51\x3a\x03\xf5",
+	          "00000000", /* ignored */
+	          "\xe5\x80\xf6\x12\xf8\x4e\xd9\x6c",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_ECB);
+}
+END_TEST
+
+START_TEST(test_des3_ecb_null_key_and_text) {
+	DES3_TEST(
+	          "\xdf\x7f\x00\x92\xe7\xc1\x49\xd2",
+	          "\x0e\x41\x00\xc4\x8b\xf0\x6e\xa1"
+	          "\x66\x49\x42\x63\x22\x00\xf0\x99"
+	          "\x6b\x22\xc1\x37\x9c\x00\xe4\x8f",
+	          "00000000", /* ignored */
+	          "\x73\xd8\x1f\x1f\x50\x01\xe4\x79",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_ECB);
+}
+END_TEST
+
+START_TEST(test_des3_cbc_nist1) {
+	DES3_TEST(
+	          "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A"
+	          "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51",
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
+	          "\x23\x45\x67\x89\xAB\xCD\xEF\x01"
+	          "\x45\x67\x89\xAB\xCD\xEF\x01\x23",
+	          "\xF6\x9F\x24\x45\xDF\x4F\x9B\x17",
+	          "\x20\x79\xC3\xD5\x3A\xA7\x63\xE1\x93\xB7\x9E\x25\x69\xAB\x52\x62"
+	          "\x51\x65\x70\x48\x1F\x25\xB5\x0F\x73\xC0\xBD\xA8\x5C\x8E\x0D\xA7",
+	          32,
+	          PURPLE_CIPHER_BATCH_MODE_CBC);
+}
+END_TEST
+
+START_TEST(test_des3_cbc_nist2) {
+	DES3_TEST(
+	          "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A"
+	          "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51",
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
+	          "\x23\x45\x67\x89\xAB\xCD\xEF\x01"
+	          "\x01\x23\x45\x67\x89\xAB\xCD\xEF",
+	          "\xF6\x9F\x24\x45\xDF\x4F\x9B\x17",
+	          "\x74\x01\xCE\x1E\xAB\x6D\x00\x3C\xAF\xF8\x4B\xF4\x7B\x36\xCC\x21"
+	          "\x54\xF0\x23\x8F\x9F\xFE\xCD\x8F\x6A\xCF\x11\x83\x92\xB4\x55\x81",
+	          32,
+	          PURPLE_CIPHER_BATCH_MODE_CBC);
+}
+END_TEST
+
+START_TEST(test_des3_cbc_null_key) {
+	DES3_TEST(
+	          "\x16\xf4\xb3\x77\xfd\x4b\x9e\xca",
+	          "\x38\x00\x88\x6a\xef\xcb\x00\xad"
+	          "\x5d\xe5\x29\x00\x7d\x98\x64\x4c"
+	          "\x86\x00\x7b\xd3\xc7\x00\x7b\x32",
+	          "\x31\x32\x33\x34\x35\x36\x37\x38",
+	          "\x52\xe7\xde\x96\x39\x87\x87\xdb",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_CBC);
+}
+END_TEST
+
+START_TEST(test_des3_cbc_null_text) {
+	DES3_TEST(
+	          "\x65\x73\x34\xc1\x19\x00\x79\x65",
+	          "\x32\x64\xda\x10\x13\x6a\xfe\x1e"
+	          "\x37\x54\xd1\x2c\x41\x04\x10\x40"
+	          "\xaf\x1c\x75\x2b\x51\x3a\x03\xf5",
+	          "\x7C\xAF\x0D\x57\x1E\x57\x10\xDA",
+	          "\x40\x12\x0e\x00\x85\xff\x6c\xc2",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_CBC);
+}
+END_TEST
+
+START_TEST(test_des3_cbc_null_key_and_text) {
+	DES3_TEST(
+	          "\xdf\x7f\x00\x92\xe7\xc1\x49\xd2",
+	          "\x0e\x41\x00\xc4\x8b\xf0\x6e\xa1"
+	          "\x66\x49\x42\x63\x22\x00\xf0\x99"
+	          "\x6b\x22\xc1\x37\x9c\x00\xe4\x8f",
+	          "\x01\x19\x0D\x2c\x40\x67\x89\x67",
+	          "\xa7\xc1\x10\xbe\x9b\xd5\x8a\x67",
+	          8,
+	          PURPLE_CIPHER_BATCH_MODE_CBC);
+}
+END_TEST
+
+/******************************************************************************
+ * HMAC Tests
+ * See RFC2202 and some other NULL tests I made up
+ *****************************************************************************/
+
+#define HMAC_TEST(data, data_len, key, key_len, type, digest) { \
+	PurpleCipher *cipher = NULL; \
+	PurpleCipherContext *context = NULL; \
+	gchar cdigest[41]; \
+	gboolean ret = FALSE; \
+	\
+	cipher = purple_ciphers_find_cipher("hmac"); \
+	context = purple_cipher_context_new(cipher, NULL); \
+	purple_cipher_context_set_option(context, "hash", type); \
+	purple_cipher_context_set_key_with_len(context, (guchar *)key, (key_len)); \
+	\
+	purple_cipher_context_append(context, (guchar *)(data), (data_len)); \
+	ret = purple_cipher_context_digest_to_str(context, sizeof(cdigest), cdigest, \
+	                                        NULL); \
+	\
+	fail_unless(ret == TRUE, NULL); \
+	fail_unless(strcmp((digest), cdigest) == 0, NULL); \
+	\
+	purple_cipher_context_destroy(context); \
+}
+
+/* HMAC MD5 */
+
+START_TEST(test_hmac_md5_Hi) {
+	HMAC_TEST("Hi There",
+	          8,
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+	          16,
+	          "md5",
+	          "9294727a3638bb1c13f48ef8158bfc9d");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_what) {
+	HMAC_TEST("what do ya want for nothing?",
+	          28,
+	          "Jefe",
+	          4,
+	          "md5",
+	          "750c783e6ab0b503eaa86e310a5db738");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_dd) {
+	HMAC_TEST("\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd",
+	          50,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          16,
+	          "md5",
+	          "56be34521d144c88dbb8c733f0e8b3f6");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_cd) {
+	HMAC_TEST("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd",
+	          50,
+	          "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
+	          "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+	          "\x15\x16\x17\x18\x19",
+	          25,
+	          "md5",
+	          "697eaf0aca3a3aea3a75164746ffaa79");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_truncation) {
+	HMAC_TEST("Test With Truncation",
+	          20,
+	          "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c",
+	          16,
+	          "md5",
+	          "56461ef2342edc00f9bab995690efd4c");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_large_key) {
+	HMAC_TEST("Test Using Larger Than Block-Size Key - Hash Key First",
+	          54,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          80,
+	          "md5",
+	          "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_large_key_and_data) {
+	HMAC_TEST("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
+	          73,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          80,
+	          "md5",
+	          "6f630fad67cda0ee1fb1f562db3aa53e");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_null_key) {
+	HMAC_TEST("Hi There",
+	          8,
+	          "\x0a\x0b\x00\x0d\x0e\x0f\x1a\x2f\x0b\x0b"
+	          "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b",
+	          20,
+	          "md5",
+	          "597bfd644b797a985561eeb03a169e59");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_null_text) {
+	HMAC_TEST("Hi\x00There",
+	          8,
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+	          20,
+	          "md5",
+	          "70be8e1b7b50dfcc335d6cd7992c564f");
+}
+END_TEST
+
+START_TEST(test_hmac_md5_null_key_and_text) {
+	HMAC_TEST("Hi\x00Th\x00re",
+	          8,
+	          "\x0c\x0d\x00\x0f\x10\x1a\x3a\x3a\xe6\x34"
+	          "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b",
+	          20,
+	          "md5",
+	          "b31bcbba35a33a067cbba9131cba4889");
+}
+END_TEST
+
+/* HMAC SHA1 */
+
+START_TEST(test_hmac_sha1_Hi) {
+	HMAC_TEST("Hi There",
+	          8,
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+	          20,
+	          "sha1",
+	          "b617318655057264e28bc0b6fb378c8ef146be00");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_what) {
+	HMAC_TEST("what do ya want for nothing?",
+	          28,
+	          "Jefe",
+	          4,
+	          "sha1",
+	          "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_dd) {
+	HMAC_TEST("\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+	          "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd",
+	          50,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          20,
+	          "sha1",
+	          "125d7342b9ac11cd91a39af48aa17b4f63f175d3");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_cd) {
+	HMAC_TEST("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+	          "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd",
+	          50,
+	          "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
+	          "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+	          "\x15\x16\x17\x18\x19",
+	          25,
+	          "sha1",
+	          "4c9007f4026250c6bc8414f9bf50c86c2d7235da");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_truncation) {
+	HMAC_TEST("Test With Truncation",
+	          20,
+	          "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+	          "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c",
+	          20,
+	          "sha1",
+	          "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_large_key) {
+	HMAC_TEST("Test Using Larger Than Block-Size Key - Hash Key First",
+	          54,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          80,
+	          "sha1",
+	          "aa4ae5e15272d00e95705637ce8a3b55ed402112");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_large_key_and_data) {
+	HMAC_TEST("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
+	          73,
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+	          "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	          80,
+	          "sha1",
+	          "e8e99d0f45237d786d6bbaa7965c7808bbff1a91");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_null_key) {
+	HMAC_TEST("Hi There",
+	          8,
+	          "\x0a\x0b\x00\x0d\x0e\x0f\x1a\x2f\x0b\x0b"
+	          "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b",
+	          20,
+	          "sha1",
+	          "eb62a2e0e33d300be669c52aab3f591bc960aac5");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_null_text) {
+	HMAC_TEST("Hi\x00There",
+	          8,
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+	          "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
+	          20,
+	          "sha1",
+	          "31ca58d849e971e418e3439de2c6f83144b6abb7");
+}
+END_TEST
+
+START_TEST(test_hmac_sha1_null_key_and_text) {
+	HMAC_TEST("Hi\x00Th\x00re",
+	          8,
+	          "\x0c\x0d\x00\x0f\x10\x1a\x3a\x3a\xe6\x34"
+	          "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b",
+	          20,
+	          "sha1",
+	          "e6b8e2fede87aa09dcb13e554df1435e056eae36");
+}
+END_TEST
+
+/******************************************************************************
  * Suite
  *****************************************************************************/
 Suite *
@@ -227,6 +732,53 @@
 	tcase_add_test(tc, test_sha1_1000_as_1000_times);
 	suite_add_tcase(s, tc);
 
+	/* des tests */
+	tc = tcase_create("DES");
+	tcase_add_test(tc, test_des_12345678);
+	tcase_add_test(tc, test_des_abcdefgh);
+	suite_add_tcase(s, tc);
+
+	/* des3 ecb tests */
+	tc = tcase_create("DES3 ECB");
+	tcase_add_test(tc, test_des3_ecb_nist1);
+	tcase_add_test(tc, test_des3_ecb_nist2);
+	tcase_add_test(tc, test_des3_ecb_null_key);
+	tcase_add_test(tc, test_des3_ecb_null_text);
+	tcase_add_test(tc, test_des3_ecb_null_key_and_text);
+	suite_add_tcase(s, tc);
+	/* des3 cbc tests */
+	tc = tcase_create("DES3 CBC");
+	tcase_add_test(tc, test_des3_cbc_nist1);
+	tcase_add_test(tc, test_des3_cbc_nist2);
+	tcase_add_test(tc, test_des3_cbc_null_key);
+	tcase_add_test(tc, test_des3_cbc_null_text);
+	tcase_add_test(tc, test_des3_cbc_null_key_and_text);
+	suite_add_tcase(s, tc);
+
+	/* hmac tests */
+	tc = tcase_create("HMAC");
+	tcase_add_test(tc, test_hmac_md5_Hi);
+	tcase_add_test(tc, test_hmac_md5_what);
+	tcase_add_test(tc, test_hmac_md5_dd);
+	tcase_add_test(tc, test_hmac_md5_cd);
+	tcase_add_test(tc, test_hmac_md5_truncation);
+	tcase_add_test(tc, test_hmac_md5_large_key);
+	tcase_add_test(tc, test_hmac_md5_large_key_and_data);
+	tcase_add_test(tc, test_hmac_md5_null_key);
+	tcase_add_test(tc, test_hmac_md5_null_text);
+	tcase_add_test(tc, test_hmac_md5_null_key_and_text);
+	tcase_add_test(tc, test_hmac_sha1_Hi);
+	tcase_add_test(tc, test_hmac_sha1_what);
+	tcase_add_test(tc, test_hmac_sha1_dd);
+	tcase_add_test(tc, test_hmac_sha1_cd);
+	tcase_add_test(tc, test_hmac_sha1_truncation);
+	tcase_add_test(tc, test_hmac_sha1_large_key);
+	tcase_add_test(tc, test_hmac_sha1_large_key_and_data);
+	tcase_add_test(tc, test_hmac_sha1_null_key);
+	tcase_add_test(tc, test_hmac_sha1_null_text);
+	tcase_add_test(tc, test_hmac_sha1_null_key_and_text);
+	suite_add_tcase(s, tc);
+
 	return s;
 }
 
--- a/libpurple/tests/tests.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/tests/tests.h	Sat Dec 22 23:18:08 2007 +0000
@@ -1,7 +1,8 @@
 #ifndef TESTS_H
 #  define TESTS_H
 
-#include <glib.h>
+#include "../purple.h"
+
 #include <check.h>
 
 /* define the test suites here */
--- a/libpurple/util.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/util.c	Sat Dec 22 23:18:08 2007 +0000
@@ -921,6 +921,7 @@
 {
 	const char *pln;
 	int len, pound;
+	char temp[2];
 
 	if (!text || *text != '&')
 		return NULL;
@@ -943,8 +944,9 @@
 		pln = "\302\256";      /* or use g_unichar_to_utf8(0xae); */
 	else if(IS_ENTITY("&apos;"))
 		pln = "\'";
-	else if(*(text+1) == '#' && (sscanf(text, "&#%u;", &pound) == 1) &&
-			pound != 0 && *(text+3+(gint)log10(pound)) == ';') {
+	else if(*(text+1) == '#' &&
+			(sscanf(text, "&#%u%1[;]", &pound, temp) == 2 || sscanf(text, "&#x%x%1[;]", &pound, temp) == 2) &&
+			pound != 0) {
 		static char buf[7];
 		int buflen = g_unichar_to_utf8((gunichar)pound, buf);
 		buf[buflen] = '\0';
@@ -4255,6 +4257,53 @@
 	return g_string_free(workstr, FALSE);
 }
 
+/*
+ * This function is copied from g_strerror() but changed to use
+ * gai_strerror().
+ */
+G_CONST_RETURN gchar *
+purple_gai_strerror(gint errnum)
+{
+	static GStaticPrivate msg_private = G_STATIC_PRIVATE_INIT;
+	char *msg;
+	int saved_errno = errno;
+
+	const char *msg_locale;
+
+	msg_locale = gai_strerror(errnum);
+	if (g_get_charset(NULL))
+	{
+		/* This string is already UTF-8--great! */
+		errno = saved_errno;
+		return msg_locale;
+	}
+	else
+	{
+		gchar *msg_utf8 = g_locale_to_utf8(msg_locale, -1, NULL, NULL, NULL);
+		if (msg_utf8)
+		{
+			/* Stick in the quark table so that we can return a static result */
+			GQuark msg_quark = g_quark_from_string(msg_utf8);
+			g_free(msg_utf8);
+
+			msg_utf8 = (gchar *)g_quark_to_string(msg_quark);
+			errno = saved_errno;
+			return msg_utf8;
+		}
+	}
+
+	msg = g_static_private_get(&msg_private);
+	if (!msg)
+	{
+		msg = g_new(gchar, 64);
+		g_static_private_set(&msg_private, msg, g_free);
+	}
+
+	sprintf(msg, "unknown error (%d)", errnum);
+
+	errno = saved_errno;
+	return msg;
+}
 
 char *
 purple_utf8_ncr_encode(const char *str)
--- a/libpurple/util.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/util.h	Sat Dec 22 23:18:08 2007 +0000
@@ -1110,6 +1110,18 @@
 gchar *purple_utf8_salvage(const char *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().
+ *
+ * @param errnum The error code.
+ *
+ * @return The UTF-8 error message.
+ * @since 2.4.0
+ */
+G_CONST_RETURN gchar *purple_gai_strerror(gint errnum);
+
+/**
  * Compares two UTF-8 strings case-insensitively.  This string is
  * more expensive than a simple g_utf8_collate() comparison because
  * it calls g_utf8_casefold() on each string, which allocates new
--- a/libpurple/version.h.in	Sat Dec 22 17:54:30 2007 +0000
+++ b/libpurple/version.h.in	Sat Dec 22 23:18:08 2007 +0000
@@ -49,6 +49,34 @@
  */
 const char *purple_version_check(guint required_major, guint required_minor, guint required_micro);
 
+/**
+ * The major version of the running libpurple.  Contrast with
+ * #PURPLE_MAJOR_VERSION, which expands at compile time to the major version of
+ * libpurple being compiled against.
+ *
+ * @since 2.4.0
+ */
+extern const guint purple_major_version;
+
+/**
+ * The minor version of the running libpurple.  Contrast with
+ * #PURPLE_MINOR_VERSION, which expands at compile time to the minor version of
+ * libpurple being compiled against.
+ *
+ * @since 2.4.0
+ */
+extern const guint purple_minor_version;
+
+/**
+ *
+ * The micro version of the running libpurple.  Contrast with
+ * #PURPLE_MICRO_VERSION, which expands at compile time to the micro version of
+ * libpurple being compiled against.
+ *
+ * @since 2.4.0
+ */
+extern const guint purple_micro_version;
+
 #ifdef __cplusplus
 }
 #endif
--- a/pidgin/Makefile.am	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/Makefile.am	Sat Dec 22 23:18:08 2007 +0000
@@ -119,7 +119,8 @@
 	gtkthemes.c \
 	gtkutils.c \
 	gtkwhiteboard.c \
-	minidialog.c
+	minidialog.c \
+	pidgintooltip.c
 
 pidgin_headers = \
 	eggtrayicon.h \
@@ -172,6 +173,7 @@
 	gtkutils.h \
 	gtkwhiteboard.h \
 	minidialog.h \
+	pidgintooltip.h \
 	pidgin.h
 
 pidginincludedir=$(includedir)/pidgin
--- a/pidgin/Makefile.mingw	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/Makefile.mingw	Sat Dec 22 23:18:08 2007 +0000
@@ -95,6 +95,7 @@
 			gtkwhiteboard.c \
 			minidialog.c \
 			pidginstock.c \
+			pidgintooltip.c \
 			win32/MinimizeToTray.c \
 			win32/gtkdocklet-win32.c \
 			win32/gtkwin32dep.c \
--- a/pidgin/gtkaccount.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkaccount.c	Sat Dec 22 23:18:08 2007 +0000
@@ -55,7 +55,6 @@
 	COLUMN_ENABLED,
 	COLUMN_PROTOCOL,
 	COLUMN_DATA,
-	COLUMN_PULSE_DATA,
 	NUM_COLUMNS
 };
 
@@ -139,18 +138,6 @@
 
 } AccountPrefsDialog;
 
-typedef struct
-{
-	GdkPixbuf *online_pixbuf;
-	gboolean pulse_to_grey;
-	float pulse_value;
-	int timeout;
-	PurpleAccount *account;
-	GtkTreeModel *model;
-
-} PidginPulseData;
-
-
 static AccountsWindow *accounts_window = NULL;
 static GHashTable *account_pref_wins;
 
@@ -1121,7 +1108,7 @@
 					 G_CALLBACK(proxy_type_changed_cb), dialog);
 }
 
-static void
+static gboolean
 account_win_destroy_cb(GtkWidget *w, GdkEvent *event,
 					   AccountPrefsDialog *dialog)
 {
@@ -1142,6 +1129,7 @@
 	purple_signals_disconnect_by_handle(dialog);
 
 	g_free(dialog);
+	return FALSE;
 }
 
 static void
@@ -1437,7 +1425,6 @@
 	GtkWidget *win;
 	GtkWidget *main_vbox;
 	GtkWidget *vbox;
-	GtkWidget *bbox;
 	GtkWidget *dbox;
 	GtkWidget *notebook;
 	GtkWidget *button;
@@ -1475,16 +1462,14 @@
 	if ((dialog->plugin = purple_find_prpl(dialog->protocol_id)) != NULL)
 		dialog->prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(dialog->plugin);
 
-	dialog->window = win = pidgin_create_window((type == PIDGIN_ADD_ACCOUNT_DIALOG) ? _("Add Account") : _("Modify Account"),
+	dialog->window = win = pidgin_create_dialog((type == PIDGIN_ADD_ACCOUNT_DIALOG) ? _("Add Account") : _("Modify Account"),
 		PIDGIN_HIG_BORDER, "account", FALSE);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(account_win_destroy_cb), dialog);
 
 	/* Setup the vbox */
-	main_vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), main_vbox);
-	gtk_widget_show(main_vbox);
+	main_vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	notebook = gtk_notebook_new();
 	gtk_box_pack_start(GTK_BOX(main_vbox), notebook, FALSE, FALSE, 0);
@@ -1511,8 +1496,6 @@
 	if (!dialog->prpl_info || !dialog->prpl_info->register_user)
 		gtk_widget_hide(button);
 
-
-
 	/* Setup the page with 'Advanced'. */
 	dialog->bottom_vbox = dbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
 	gtk_container_set_border_width(GTK_CONTAINER(dbox), PIDGIN_HIG_BORDER);
@@ -1524,30 +1507,13 @@
 	add_protocol_options(dialog, dbox);
 	add_proxy_options(dialog, dbox);
 
-	/* Setup the button box */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(main_vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
-
 	/* Cancel button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(cancel_account_prefs_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL, G_CALLBACK(cancel_account_prefs_cb), dialog);
 
 	/* Save button */
-	button = gtk_button_new_from_stock(GTK_STOCK_SAVE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE, G_CALLBACK(ok_account_prefs_cb), dialog);
 	if (dialog->account == NULL)
 		gtk_widget_set_sensitive(button, FALSE);
-
-	gtk_widget_show(button);
-
 	dialog->ok_button = button;
 
 	/* Set up DND */
@@ -1561,9 +1527,6 @@
 	g_signal_connect(G_OBJECT(dialog->window), "drag_data_received",
 			 G_CALLBACK(account_dnd_recv), dialog);
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(ok_account_prefs_cb), dialog);
-
 	/* Show the window. */
 	gtk_widget_show(win);
 }
@@ -1575,7 +1538,6 @@
 signed_on_off_cb(PurpleConnection *gc, gpointer user_data)
 {
 	PurpleAccount *account;
-	PidginPulseData *pulse_data;
 	GtkTreeModel *model;
 	GtkTreeIter iter;
 	GdkPixbuf *pixbuf;
@@ -1591,29 +1553,14 @@
 
 	if (gtk_tree_model_iter_nth_child(model, &iter, NULL, index))
 	{
-		gtk_tree_model_get(GTK_TREE_MODEL(accounts_window->model), &iter,
-						   COLUMN_PULSE_DATA, &pulse_data, -1);
-
-		if (pulse_data != NULL)
-		{
-			if (pulse_data->timeout > 0)
-				g_source_remove(pulse_data->timeout);
-
-			g_object_unref(G_OBJECT(pulse_data->online_pixbuf));
-
-			g_free(pulse_data);
-		}
-
 		pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM);
 		if ((pixbuf != NULL) && purple_account_is_disconnected(account))
 			gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
 
 		gtk_list_store_set(accounts_window->model, &iter,
 				   COLUMN_ICON, pixbuf,
-				   COLUMN_PULSE_DATA, NULL,
 				   -1);
 
-
 		if (pixbuf != NULL)
 			g_object_unref(G_OBJECT(pixbuf));
 	}
@@ -2313,7 +2260,6 @@
 	AccountsWindow *dialog;
 	GtkWidget *win;
 	GtkWidget *vbox;
-	GtkWidget *bbox;
 	GtkWidget *sw;
 	GtkWidget *button;
 	int width, height;
@@ -2328,7 +2274,7 @@
 	width  = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width");
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/height");
 
-	dialog->window = win = pidgin_create_window(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE);
+	dialog->window = win = pidgin_create_dialog(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
@@ -2337,57 +2283,28 @@
 					 G_CALLBACK(configure_cb), accounts_window);
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	/* Setup the scrolled window that will contain the list of accounts. */
 	sw = create_accounts_list(dialog);
 	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
 	gtk_widget_show(sw);
 
-	/* Button box. */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
-
 	/* Add button */
-	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(add_account_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD, G_CALLBACK(add_account_cb), dialog);
 
 	/* Modify button */
-	button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY, G_CALLBACK(modify_account_cb), dialog);
 	dialog->modify_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(modify_account_cb), dialog);
 
 	/* Delete button */
-	button = gtk_button_new_from_stock(GTK_STOCK_DELETE);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE, G_CALLBACK(ask_delete_account_cb), dialog);
 	dialog->delete_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(ask_delete_account_cb), dialog);
 
 	/* Close button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(close_accounts_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, G_CALLBACK(close_accounts_cb), dialog);
 
 	purple_signal_connect(pidgin_account_get_handle(), "account-modified",
 	                    accounts_window,
--- a/pidgin/gtkblist.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkblist.c	Sat Dec 22 23:18:08 2007 +0000
@@ -59,6 +59,7 @@
 #include "gtkscrollbook.h"
 #include "gtkutils.h"
 #include "pidgin/minidialog.h"
+#include "pidgin/pidgintooltip.h"
 
 #include <gdk/gdkkeysyms.h>
 #include <gtk/gtk.h>
@@ -153,6 +154,7 @@
 static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full);
 static const char *item_factory_translate_func (const char *path, gpointer func_data);
 static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter);
+static gboolean buddy_is_displayable(PurpleBuddy *buddy);
 static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender);
 static void pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node);
 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded);
@@ -2630,7 +2632,8 @@
 	return td;
 }
 
-static void pidgin_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, PurpleBlistNode *node)
+static gboolean
+pidgin_blist_paint_tip(GtkWidget *widget, gpointer null)
 {
 	GtkStyle *style;
 	int current_height, max_width;
@@ -2638,10 +2641,10 @@
 	int max_avatar_width;
 	GList *l;
 	int prpl_col = 0;
-        GtkTextDirection dir = gtk_widget_get_direction(widget);
+	GtkTextDirection dir = gtk_widget_get_direction(widget);
 
 	if(gtkblist->tooltipdata == NULL)
-		return;
+		return FALSE;
 
 	style = gtkblist->tipwindow->style;
 	gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
@@ -2740,10 +2743,11 @@
 
 		current_height += MAX(td->name_height + td->height, td->avatar_height) + TOOLTIP_BORDER;
 	}
-}
-
-
-void pidgin_blist_tooltip_destroy()
+	return FALSE;
+}
+
+static void
+pidgin_blist_destroy_tooltip_data()
 {
 	while(gtkblist->tooltipdata) {
 		struct tooltip_data *td = gtkblist->tooltipdata->data;
@@ -2759,12 +2763,62 @@
 		g_free(td);
 		gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata);
 	}
-
-	if (gtkblist->tipwindow == NULL)
-		return;
-
-	gtk_widget_destroy(gtkblist->tipwindow);
-	gtkblist->tipwindow = NULL;
+}
+
+void pidgin_blist_tooltip_destroy()
+{
+	pidgin_blist_destroy_tooltip_data();
+	pidgin_tooltip_destroy();
+}
+
+static gboolean
+pidgin_blist_create_tooltip_for_node(GtkWidget *widget, gpointer data, int *w, int *h)
+{
+	PurpleBlistNode *node = data;
+	int width, height;
+
+	gtkblist->tipwindow = widget;
+	if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		struct tooltip_data *td = create_tip_for_node(node, TRUE);
+		gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
+		width = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE +
+			MAX(td->width, td->name_width) + SMALL_SPACE + td->avatar_width + TOOLTIP_BORDER;
+		height = TOOLTIP_BORDER + MAX(td->height + td->name_height, MAX(STATUS_SIZE, td->avatar_height))
+			+ TOOLTIP_BORDER;
+	} else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleBlistNode *child;
+		PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node);
+		int max_text_width = 0;
+		int max_avatar_width = 0;
+		width = height = 0;
+
+		for(child = node->child; child; child = child->next)
+		{
+			if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) {
+				struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child));
+				if (b == (PurpleBuddy *)child) {
+					gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
+				} else {
+					gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
+				}
+				max_text_width = MAX(max_text_width, MAX(td->width, td->name_width));
+				max_avatar_width = MAX(max_avatar_width, td->avatar_width);
+				height += MAX(TOOLTIP_BORDER + MAX(STATUS_SIZE,td->avatar_height),
+						TOOLTIP_BORDER + td->height + td->name_height);
+			}
+		}
+		height += TOOLTIP_BORDER;
+		width = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
+	} else {
+		return FALSE;
+	}
+
+	if (w)
+		*w = width;
+	if (h)
+		*h = height;
+
+	return TRUE;
 }
 
 static gboolean pidgin_blist_expand_timeout(GtkWidget *tv)
@@ -2826,164 +2880,9 @@
 			 purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline")));
 }
 
-static gboolean pidgin_blist_tooltip_timeout(GtkWidget *tv)
-{
-	GtkTreePath *path;
-	GtkTreeIter iter;
-	PurpleBlistNode *node;
-	GValue val;
-	gboolean editable = FALSE;
-
-	/* If we're editing a cell (e.g. alias editing), don't show the tooltip */
-	g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL);
-	if (editable)
-		return FALSE;
-
-	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->tip_rect.x, gtkblist->tip_rect.y + (gtkblist->tip_rect.height/2), 
-		&path, NULL, NULL, NULL))
-		return FALSE;
-	gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
-	val.g_type = 0;
-	gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
-	node = g_value_get_pointer(&val);
-
-	pidgin_blist_draw_tooltip(node, gtkblist->window);
-
-	gtk_tree_path_free(path);
-	return FALSE;
-}
-
 void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget)
 {
-	int scr_w, scr_h, w, h, x, y;
-#if GTK_CHECK_VERSION(2,2,0)
-	int mon_num;
-	GdkScreen *screen = NULL;
-#endif
-	gboolean tooltip_top = FALSE;
-	struct _pidgin_blist_node *gtknode;
-	GdkRectangle mon_size;
-	int sig;
-	const char *name;
-	
-	if (node == NULL)
-		return;
-
-	/*
-	 * Attempt to free the previous tooltip.  I have a feeling
-	 * this is never needed... but just in case.
-	 */
-	pidgin_blist_tooltip_destroy();
-
-	gtkblist->tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
-
-	if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		struct tooltip_data *td = create_tip_for_node(node, TRUE);
-		gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
-		w = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE +
-			MAX(td->width, td->name_width) + SMALL_SPACE + td->avatar_width + TOOLTIP_BORDER;
-		h = TOOLTIP_BORDER + MAX(td->height + td->name_height, MAX(STATUS_SIZE, td->avatar_height))
-			+ TOOLTIP_BORDER;
-	} else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
-		PurpleBlistNode *child;
-		PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node);
-		int max_text_width = 0;
-		int max_avatar_width = 0;
-		w = h = 0;
-
-		for(child = node->child; child; child = child->next)
-		{
-			if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) {
-				struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child));
-				if (b == (PurpleBuddy *)child) {
-					gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td);
-				} else {
-					gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td);
-				}
-				max_text_width = MAX(max_text_width, MAX(td->width, td->name_width));
-				max_avatar_width = MAX(max_avatar_width, td->avatar_width);
-				h += MAX(TOOLTIP_BORDER + MAX(STATUS_SIZE,td->avatar_height),
-						TOOLTIP_BORDER + td->height + td->name_height);
-			}
-		}
-		h += TOOLTIP_BORDER;
-		w = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER;
-	} else {
-		gtk_widget_destroy(gtkblist->tipwindow);
-		gtkblist->tipwindow = NULL;
-		return;
-	}
-
-	if (gtkblist->tooltipdata == NULL) {
-		gtk_widget_destroy(gtkblist->tipwindow);
-		gtkblist->tipwindow = NULL;
-		return;
-	}
-
-	gtknode = node->ui_data;
-
-	name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget)));
-	gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE);
-	gtk_window_set_title(GTK_WINDOW(gtkblist->tipwindow), name ? name : _("Buddy List"));
-	gtk_window_set_resizable(GTK_WINDOW(gtkblist->tipwindow), FALSE);
-	gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips");
-	g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event",
-			G_CALLBACK(pidgin_blist_paint_tip), NULL);
-	gtk_widget_ensure_style (gtkblist->tipwindow);
-
-#if GTK_CHECK_VERSION(2,2,0)
-	gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
-	mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
-	gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
-
-	scr_w = mon_size.width + mon_size.x;
-	scr_h = mon_size.height + mon_size.y;
-#else
-	scr_w = gdk_screen_width();
-	scr_h = gdk_screen_height();
-	gdk_window_get_pointer(NULL, &x, &y, NULL);
-	mon_size.x = 0;
-	mon_size.y = 0;
-#endif
-
-#if GTK_CHECK_VERSION(2,2,0)
-	if (w > mon_size.width)
-	  w = mon_size.width - 10;
-
-	if (h > mon_size.height)
-	  h = mon_size.height - 10;
-#endif
-
-	x -= ((w >> 1) + 4);
-
-	if ((y + h + 4) > scr_h || tooltip_top)
-		y = y - h - 5;
-	else
-		y = y + 6;
-
-	if (y < mon_size.y)
-		y = mon_size.y;
-
-	if (y != mon_size.y) {
-		if ((x + w) > scr_w)
-			x -= (x + w + 5) - scr_w;
-		else if (x < mon_size.x)
-			x = mon_size.x;
-	} else {
-		x -= (w / 2 + 10);
-		if (x < mon_size.x)
-			x = mon_size.x;
-	}
-
-	gtk_widget_set_size_request(gtkblist->tipwindow, w, h);
-	gtk_window_move(GTK_WINDOW(gtkblist->tipwindow), x, y);
-	gtk_widget_show(gtkblist->tipwindow);
-
-	/* Hide the tooltip when the widget is destroyed */
-	sig = g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(pidgin_blist_tooltip_destroy), NULL);
-	g_signal_connect_swapped(G_OBJECT(gtkblist->tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig));
-
-	return;
+	pidgin_tooltip_show(widget, node, pidgin_blist_create_tooltip_for_node, pidgin_blist_paint_tip);
 }
 
 static gboolean pidgin_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context,
@@ -3033,31 +2932,34 @@
 	return FALSE;
 }
 
-static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
-{
-	GtkTreePath *path;
-	int delay;
-
-	delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
-
-	if (delay == 0)
+static gboolean
+pidgin_blist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
+		gpointer null, int *w, int *h)
+{
+	GtkTreeIter iter;
+	PurpleBlistNode *node;
+	GValue val;
+	gboolean editable = FALSE;
+
+	/* If we're editing a cell (e.g. alias editing), don't show the tooltip */
+	g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL);
+	if (editable)
 		return FALSE;
 
-	if (gtkblist->timeout) {
-		if ((event->y > gtkblist->tip_rect.y) && ((event->y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y))
-			return FALSE;
-		/* We've left the cell.  Remove the timeout and create a new one below */
-		pidgin_blist_tooltip_destroy();
-		g_source_remove(gtkblist->timeout);
-	}
-
-	gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
-	gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &gtkblist->tip_rect);
-
-	if (path)
-		gtk_tree_path_free(path);
-	gtkblist->timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_tooltip_timeout, tv);
-
+	if (gtkblist->tooltipdata) {
+		gtkblist->tipwindow = NULL;
+		pidgin_blist_destroy_tooltip_data();
+	}
+
+	gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
+	val.g_type = 0;
+	gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
+	node = g_value_get_pointer(&val);
+	return pidgin_blist_create_tooltip_for_node(widget, node, w, h);
+}
+
+static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null)
+{
 	if (gtkblist->mouseover_contact) {
 		if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) {
 			pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
@@ -3068,9 +2970,8 @@
 	return FALSE;
 }
 
-static void pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
-{
-
+static gboolean pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n)
+{
 	if (gtkblist->timeout) {
 		g_source_remove(gtkblist->timeout);
 		gtkblist->timeout = 0;
@@ -3081,14 +2982,13 @@
 		gtkblist->drag_timeout = 0;
 	}
 
-	pidgin_blist_tooltip_destroy();
-
 	if (gtkblist->mouseover_contact &&
 		!((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) &&
 		 (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) {
 			pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact);
 		gtkblist->mouseover_contact = NULL;
 	}
+	return FALSE;
 }
 
 static void
@@ -4643,6 +4543,13 @@
 }
 
 static void
+clear_elsewhere_errors(PidginMiniDialog *mini_dialog,
+                       gpointer unused)
+{
+	elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error);
+}
+
+static void
 ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist)
 {
 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
@@ -4657,6 +4564,12 @@
 	pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"),
 		reconnect_elsewhere_accounts, NULL);
 
+	/* Make dismissing the dialog clear the errors.  The "destroy" signal
+	 * does not appear to fire at quit, which is fortunate!
+	 */
+	g_signal_connect(G_OBJECT(mini_dialog), "destroy",
+		(GCallback) clear_elsewhere_errors, NULL);
+
 	add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog));
 
 	/* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
@@ -5152,12 +5065,14 @@
 #ifdef _WIN32
 	g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin), NULL);
 #endif
-
 	g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb), NULL);
+	g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL);
+	g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL);
 
 	/* Tooltips */
-	g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL);
-	g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL);
+	pidgin_tooltip_setup_for_treeview(gtkblist->treeview, NULL,
+			pidgin_blist_create_tooltip,
+			pidgin_blist_paint_tip);
 
 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE);
 
--- a/pidgin/gtkcertmgr.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkcertmgr.c	Sat Dec 22 23:18:08 2007 +0000
@@ -545,12 +545,13 @@
    So if it is set, don't open another one! */
 CertMgrDialog *certmgr_dialog = NULL;
 
-static void
+static gboolean
 certmgr_close_cb(GtkWidget *w, CertMgrDialog *dlg)
 {
 	/* TODO: Ignoring the arguments to this function may not be ideal,
 	   but there *should* only be "one dialog to rule them all" at a time*/
 	pidgin_certmgr_hide();
+	return FALSE;
 }
 
 void
@@ -559,7 +560,6 @@
 	CertMgrDialog *dlg;
 	GtkWidget *win;
 	GtkWidget *vbox;
-	GtkWidget *bbox;
 
 	/* Enumerate all the certificates on file */
 	{
@@ -599,7 +599,7 @@
 	dlg = certmgr_dialog = g_new0(CertMgrDialog, 1);
 
 	win = dlg->window =
-		pidgin_create_window(_("Certificate Manager"),/* Title */
+		pidgin_create_dialog(_("Certificate Manager"),/* Title */
 				     PIDGIN_HIG_BORDER, /*Window border*/
 				     "certmgr",         /* Role */
 				     TRUE); /* Allow resizing */
@@ -611,9 +611,7 @@
 	gtk_window_set_default_size(GTK_WINDOW(win), 400, 400);
 
 	/* Main vbox */
-	vbox = gtk_vbox_new( FALSE, PIDGIN_HIG_BORDER );
-	gtk_container_add(GTK_CONTAINER(win), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	/* Notebook of various certificate managers */
 	dlg->notebook = gtk_notebook_new();
@@ -622,19 +620,9 @@
 			   0);
 	gtk_widget_show(dlg->notebook);
 
-	/* Box for the close button */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
-
 	/* Close button */
-	dlg->closebutton = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), dlg->closebutton, FALSE, FALSE, 0);
-	gtk_widget_show(dlg->closebutton);
-	g_signal_connect(G_OBJECT(dlg->closebutton), "clicked",
-			 G_CALLBACK(certmgr_close_cb), dlg);
+	dlg->closebutton = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE,
+			G_CALLBACK(certmgr_close_cb), dlg);
 
 	/* Add the defined certificate managers */
 	/* TODO: Find a way of determining whether each is shown or not */
--- a/pidgin/gtkconn.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkconn.c	Sat Dec 22 23:18:08 2007 +0000
@@ -184,36 +184,39 @@
 
 static void pidgin_connection_network_connected ()
 {
-	GList *list = purple_accounts_get_all_active();
+	GList *list, *l;
 	PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist();
 
 	if(gtkblist)
 		pidgin_status_box_set_network_available(PIDGIN_STATUS_BOX(gtkblist->statusbox), TRUE);
 
-	while (list) {
-		PurpleAccount *account = (PurpleAccount*)list->data;
+	l = list = purple_accounts_get_all_active();
+	while (l) {
+		PurpleAccount *account = (PurpleAccount*)l->data;
 		g_hash_table_remove(auto_reconns, account);
 		if (purple_account_is_disconnected(account))
 			do_signon(account);
-		list = list->next;
+		l = l->next;
 	}
+	g_list_free(list);
 }
 
 static void pidgin_connection_network_disconnected ()
 {
-	GList *l = purple_accounts_get_all_active();
+	GList *list, *l;
 	PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist();
 	PurplePluginProtocolInfo *prpl_info = NULL;
 	PurpleConnection *gc = NULL;
-	
+
 	if(gtkblist)
 		pidgin_status_box_set_network_available(PIDGIN_STATUS_BOX(gtkblist->statusbox), FALSE);
 
+	l = list = purple_accounts_get_all_active();
 	while (l) {
 		PurpleAccount *a = (PurpleAccount*)l->data;
 		if (!purple_account_is_disconnected(a)) {
 			gc = purple_account_get_connection(a);
-			if (gc && gc->prpl) 
+			if (gc && gc->prpl)
 				prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
 			if (prpl_info) {
 				if (prpl_info->keepalive)
@@ -224,6 +227,7 @@
 		}
 		l = l->next;
 	}
+	g_list_free(list);
 }
 
 static void pidgin_connection_notice(PurpleConnection *gc, const char *text)
--- a/pidgin/gtkconv.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkconv.c	Sat Dec 22 23:18:08 2007 +0000
@@ -95,9 +95,10 @@
 
 #define	PIDGIN_CONV_ALL	((1 << 7) - 1)
 
-#define SEND_COLOR "#204a87"
-#define RECV_COLOR "#cc0000"
-#define HIGHLIGHT_COLOR "#AF7F00"
+#define DEFAULT_SEND_COLOR "#204a87"
+#define DEFAULT_RECV_COLOR "#cc0000"
+#define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
+#define DEFAULT_ACTION_COLOR "#062585"
 
 /* Undef this to turn off "custom-smiley" debug messages */
 #define DEBUG_CUSTOM_SMILEY
@@ -152,6 +153,7 @@
 static void conv_set_unseen(PurpleConversation *gtkconv, PidginUnseenState state);
 static void gtkconv_set_unseen(PidginConversation *gtkconv, PidginUnseenState state);
 static void update_typing_icon(PidginConversation *gtkconv);
+static void update_typing_message(PidginConversation *gtkconv, const char *message);
 static const char *item_factory_translate_func (const char *path, gpointer func_data);
 gboolean pidgin_conv_has_focus(PurpleConversation *conv);
 static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data);
@@ -163,7 +165,7 @@
 static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv);
 static gboolean infopane_press_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *conv);
 static gboolean pidgin_userlist_motion_cb (GtkWidget *w, GdkEventMotion *event, PidginConversation *gtkconv);
-static void pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv);
+static gboolean pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv);
 static void hide_conv(PidginConversation *gtkconv, gboolean closetimer);
 
 static void pidgin_conv_set_position_size(PidginWindow *win, int x, int y,
@@ -3376,6 +3378,34 @@
 }
 
 static void
+update_typing_message(PidginConversation *gtkconv, const char *message)
+{
+	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml));
+	GtkTextMark *stmark, *enmark;
+
+	stmark = gtk_text_buffer_get_mark(buffer, "typing-notification-start");
+	enmark = gtk_text_buffer_get_mark(buffer, "typing-notification-end");
+	if (stmark && enmark) {
+		GtkTextIter start, end;
+		gtk_text_buffer_get_iter_at_mark(buffer, &start, stmark);
+		gtk_text_buffer_get_iter_at_mark(buffer, &end, enmark);
+		gtk_text_buffer_delete_mark(buffer, stmark);
+		gtk_text_buffer_delete_mark(buffer, enmark);
+		gtk_text_buffer_delete(buffer, &start, &end);
+	} else if (message && *message == '\n' && !*(message + 1))
+		message = NULL;
+
+	if (message) {
+		GtkTextIter iter;
+		gtk_text_buffer_get_end_iter(buffer, &iter);
+		gtk_text_buffer_create_mark(buffer, "typing-notification-start", &iter, TRUE);
+		gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, message, -1, "TYPING-NOTIFICATION", NULL);
+		gtk_text_buffer_get_end_iter(buffer, &iter);
+		gtk_text_buffer_create_mark(buffer, "typing-notification-end", &iter, TRUE);
+	}
+}
+
+static void
 update_typing_icon(PidginConversation *gtkconv)
 {
 	PidginWindow *gtkwin;
@@ -3383,6 +3413,7 @@
 	PurpleConversation *conv = gtkconv->active_conv;
 	char *stock_id;
 	const char *tooltip;
+	char *message = NULL;
 
 	gtkwin = gtkconv->win;
 
@@ -3401,6 +3432,7 @@
 			g_source_remove(gtkconv->u.im->typing_timer);
 			gtkconv->u.im->typing_timer = 0;
 		}
+		update_typing_message(gtkconv, "\n");
 		return;
 	}
 
@@ -3410,9 +3442,11 @@
 		}
 		stock_id = PIDGIN_STOCK_ANIMATION_TYPING1;
 		tooltip = _("User is typing...");
+		message = g_strdup_printf(_("\n%s is typing..."), purple_conversation_get_title(conv));
 	} else {
 		stock_id = PIDGIN_STOCK_ANIMATION_TYPING5;
 		tooltip = _("User has typed something and stopped");
+		message = g_strdup_printf(_("\n%s has typed something and stopped"), purple_conversation_get_title(conv));
 		if (gtkconv->u.im->typing_timer != 0) {
 			g_source_remove(gtkconv->u.im->typing_timer);
 			gtkconv->u.im->typing_timer = 0;
@@ -3435,6 +3469,8 @@
 	}
 
 	gtk_widget_show(gtkwin->menu.typing_icon);
+	update_typing_message(gtkconv, message);
+	g_free(message);
 }
 
 static gboolean
@@ -3790,7 +3826,7 @@
 	if (is_me)
 	{
 		GdkColor send_color;
-		gdk_color_parse(SEND_COLOR, &send_color);
+		gdk_color_parse(DEFAULT_SEND_COLOR, &send_color);
 
 #if GTK_CHECK_VERSION(2,6,0)
 		gtk_list_store_insert_with_values(ls, &iter,
@@ -4343,14 +4379,15 @@
 
 	/* Show a maximum of 4 lines */
 	lines = MIN(lines, 4);
-	wrapped_lines = MIN(wrapped_lines, 4);
+	wrapped_lines = MIN(MAX(wrapped_lines, 2), 4);
 
 	pad_top = gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(gtkconv->entry));
 	pad_bottom = gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(gtkconv->entry));
 	pad_inside = gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(gtkconv->entry));
 
-	height = (oneline.height + pad_top + pad_bottom) * (lines + 1);
-	height += (oneline.height + pad_inside) * (wrapped_lines - lines);
+	height = (oneline.height + pad_top + pad_bottom) * lines;
+	if (wrapped_lines > lines)
+		height += (oneline.height + pad_inside) * (wrapped_lines - lines);
 
 	gtkconv->auto_resize = TRUE;
 	g_idle_add(reset_auto_resize_cb, gtkconv);
@@ -4375,7 +4412,7 @@
 	{
 		GtkWidget *hbox, *label;
 		PidginChatPane *gtkchat = gtkconv->u.chat;
-		
+
 		hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
 
@@ -4524,20 +4561,30 @@
 	conv = gtkconv->active_conv;
 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
 		node = (PurpleBlistNode*)(purple_blist_find_chat(conv->account, conv->name));
+#if 0
+		/* Using the transient blist nodes to show the tooltip doesn't quite work yet. */
+		if (!node)
+			node = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_chat");
+#endif
 	} else {
 		node = (PurpleBlistNode*)(purple_find_buddy(conv->account, conv->name));
-	}
-
-	if (node) 
+#if 0
+		if (!node)
+			node = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_buddy");
+#endif
+	}
+
+	if (node)
 		pidgin_blist_draw_tooltip(node, gtkconv->infopane);
 	return FALSE;
 }
 
-static void 
+static gboolean
 pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv)
 {
 	pidgin_blist_tooltip_destroy();
 	reset_tooltip();
+	return FALSE;
 }
 
 static gboolean 
@@ -5020,6 +5067,13 @@
 	g_signal_connect(G_OBJECT(gtkconv->entry), "drag_data_received",
 	                 G_CALLBACK(conv_dnd_recv), gtkconv);
 
+	gtk_text_buffer_create_tag(GTK_IMHTML(gtkconv->imhtml)->text_buffer, "TYPING-NOTIFICATION",
+			"foreground", "#888888",
+			"justification", GTK_JUSTIFY_LEFT,  /* XXX: RTL'ify */
+			"weight", PANGO_WEIGHT_BOLD,
+			"scale", PANGO_SCALE_SMALL,
+			NULL);
+
 	/* Setup the container for the tab. */
 	gtkconv->tab_cont = tab_cont = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	g_object_set_data(G_OBJECT(tab_cont), "PidginConversation", gtkconv);
@@ -5592,6 +5646,7 @@
 		}
 		else {
 			if (purple_message_meify(new_message, -1)) {
+				GdkColor *col;
 				str = g_malloc(1024);
 
 				if (flags & PURPLE_MESSAGE_AUTO_RESP) {
@@ -5604,9 +5659,20 @@
 				}
 
 				if (flags & PURPLE_MESSAGE_NICK)
-					strcpy(color, HIGHLIGHT_COLOR);
+					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "highlight-name-color", &col, NULL);
 				else
-					strcpy(color, "#062585");
+					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "action-name-color", &col, NULL);
+
+				if(col) {
+					g_snprintf(color, sizeof(color), "#%02X%02X%02X",
+						col->red >> 8, col->green >> 8, col->blue >> 8);
+				}
+				else {
+					if (flags & PURPLE_MESSAGE_NICK)
+						strcpy(color, DEFAULT_HIGHLIGHT_COLOR);
+					else
+						strcpy(color, DEFAULT_ACTION_COLOR);
+				}
 			}
 			else {
 				str = g_malloc(1024);
@@ -5618,19 +5684,46 @@
 					g_snprintf(str, 1024, "%s:", alias_escaped);
 					tag_end_offset = 1;
 				}
-				if (flags & PURPLE_MESSAGE_NICK)
-					strcpy(color, HIGHLIGHT_COLOR);
+				if (flags & PURPLE_MESSAGE_NICK) {
+					GdkColor *col;
+					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "highlight-name-color", &col, NULL);
+					if(col) {
+						g_snprintf(color, sizeof(color), "#%02X%02X%02X",
+							col->red >> 8, col->green >> 8, col->blue >> 8);
+					}
+					else {
+						strcpy(color, DEFAULT_HIGHLIGHT_COLOR);
+					}
+				}
 				else if (flags & PURPLE_MESSAGE_RECV) {
 					if (type == PURPLE_CONV_TYPE_CHAT) {
 						GdkColor *col = get_nick_color(gtkconv, name);
 
 						g_snprintf(color, sizeof(color), "#%02X%02X%02X",
 							   col->red >> 8, col->green >> 8, col->blue >> 8);
-					} else
-						strcpy(color, RECV_COLOR);
+					} else {
+						GdkColor *col;
+						gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "receive-name-color", &col, NULL);
+						if(col) {
+							g_snprintf(color, sizeof(color), "#%02X%02X%02X",
+								col->red >> 8, col->green >> 8, col->blue >> 8);
+						}
+						else {
+							strcpy(color, DEFAULT_RECV_COLOR);
+						}
+					}
 				}
-				else if (flags & PURPLE_MESSAGE_SEND)
-					strcpy(color, SEND_COLOR);
+				else if (flags & PURPLE_MESSAGE_SEND) {
+					GdkColor *col;
+					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "send-name-color", &col, NULL);
+					if(col) {
+						g_snprintf(color, sizeof(color), "#%02X%02X%02X",
+							col->red >> 8, col->green >> 8, col->blue >> 8);
+					}
+					else {
+						strcpy(color, DEFAULT_SEND_COLOR);
+					}
+				}
 				else {
 					purple_debug_error("gtkconv", "message missing flags\n");
 					strcpy(color, "#000000");
@@ -5751,6 +5844,7 @@
 		(type == PURPLE_CONV_TYPE_IM ? "displayed-im-msg" : "displayed-chat-msg"),
 		account, name, displaying, conv, flags);
 	g_free(displaying);
+	update_typing_message(gtkconv, NULL);
 }
 
 static void
@@ -7477,10 +7571,13 @@
 	purple_conversation_set_data(conv, "unseen-count", NULL);
 	purple_conversation_set_data(conv, "unseen-state", NULL);
 	purple_conversation_set_ui_ops(conv, pidgin_conversations_get_conv_ui_ops());
-	private_gtkconv_new(conv, FALSE);
+	if (!PIDGIN_CONVERSATION(conv))
+		private_gtkconv_new(conv, FALSE);
 	timer = GPOINTER_TO_INT(purple_conversation_get_data(conv, "close-timer"));
-	if (timer)
+	if (timer) {
 		purple_timeout_remove(timer);
+		purple_conversation_set_data(conv, "close-timer", NULL);
+	}
 }
 
 gboolean pidgin_conv_attach_to_conversation(PurpleConversation *conv)
@@ -7489,6 +7586,7 @@
 	PidginConversation *gtkconv;
 
 	if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) {
+		/* This is pretty much always the case now. */
 		gtkconv = PIDGIN_CONVERSATION(conv);
 		if (gtkconv->win != hidden_convwin)
 			return FALSE;
@@ -7496,6 +7594,11 @@
 		pidgin_conv_placement_place(gtkconv);
 		purple_signal_emit(pidgin_conversations_get_handle(),
 				"conversation-displayed", gtkconv);
+		list = gtkconv->convs;
+		while (list) {
+			pidgin_conv_attach(list->data);
+			list = list->next;
+		}
 		return TRUE;
 	}
 
@@ -7814,6 +7917,7 @@
 		/* Set default tab colors */
 		GString *str = g_string_new(NULL);
 		GtkSettings *settings = gtk_settings_get_default();
+		GtkStyle *parent = gtk_rc_get_style_by_paths(settings, "tab-container.tab-label*", NULL, G_TYPE_NONE), *now;
 		struct {
 			const char *stylename;
 			const char *labelname;
@@ -7828,8 +7932,9 @@
 		};
 		int iter;
 		for (iter = 0; styles[iter].stylename; iter++) {
-			if (!gtk_rc_get_style_by_paths(settings, styles[iter].labelname, NULL, G_TYPE_NONE))
-				/* Apparently both ACTIVE and NORMAL are required */
+			now = gtk_rc_get_style_by_paths(settings, styles[iter].labelname, NULL, G_TYPE_NONE);
+			if (parent == now ||
+					(parent && now && parent->rc_style == now->rc_style)) {
 				g_string_append_printf(str, "style \"%s\" {\n"
 						"fg[ACTIVE] = \"%s\"\n"
 						"}\n"
@@ -7837,6 +7942,7 @@
 						styles[iter].stylename,
 						styles[iter].color,
 						styles[iter].labelname, styles[iter].stylename);
+			}
 		}
 		gtk_rc_parse_string(str->str);
 		g_string_free(str, TRUE);
@@ -8259,7 +8365,7 @@
 	}
 	
 	if (e->button == 3) {
-		/* Right click was pressed. Popup the Send To menu. */
+		/* Right click was pressed. Popup the context menu. */
 		GtkWidget *menu = gtk_menu_new(), *sub;
 		gboolean populated = populate_menu_with_options(menu, gtkconv, TRUE);
 		sub = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv->win->menu.send_to));
@@ -9237,6 +9343,7 @@
 		gtkconv->tabby = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	else
 		gtkconv->tabby = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtk_widget_set_name(gtkconv->tabby, "tab-container");
 
 	/* select the correct ordering for verticle tabs */
 	if (angle == 90) {
@@ -9904,8 +10011,8 @@
 	GdkColor send_color;
 	time_t breakout_time;
 
-	gdk_color_parse(HIGHLIGHT_COLOR, &nick_highlight);
-	gdk_color_parse(SEND_COLOR, &send_color);
+	gdk_color_parse(DEFAULT_HIGHLIGHT_COLOR, &nick_highlight);
+	gdk_color_parse(DEFAULT_SEND_COLOR, &send_color);
 
 	srand(background.red + background.green + background.blue + 1);
 
--- a/pidgin/gtkdebug.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkdebug.c	Sat Dec 22 23:18:08 2007 +0000
@@ -985,6 +985,9 @@
 	REGISTER_G_LOG_HANDLER("GModule");
 	REGISTER_G_LOG_HANDLER("GLib-GObject");
 	REGISTER_G_LOG_HANDLER("GThread");
+#ifdef USE_GSTREAMER
+	REGISTER_G_LOG_HANDLER("GStreamer");
+#endif
 
 #ifdef _WIN32
 	if (!purple_debug_is_enabled())
--- a/pidgin/gtkdialogs.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkdialogs.c	Sat Dec 22 23:18:08 2007 +0000
@@ -89,6 +89,7 @@
 	{"Megan 'Cae' Schneider",       N_("support/QA"), NULL},
 	{"Evan Schoenberg",		N_("developer"), NULL},
 	{"Kevin 'SimGuy' Stange",	N_("developer & webmaster"),	NULL},
+	{"Will 'resiak' Thompson",	N_("developer"),	NULL},
 	{"Stu 'nosnilmot' Tomlinson",	N_("developer"), NULL},
 	{"Nathan 'faceprint' Walp",		N_("developer"), NULL},
 	{NULL, NULL, NULL}
@@ -98,8 +99,8 @@
 static const struct developer patch_writers[] = {
 	{"Dennis 'EvilDennisR' Ristuccia",	N_("Senior Contributor/QA"),	NULL},
 	{"Peter 'Fmoo' Ruibal",		NULL,	NULL},
+	{"Elliott 'QuLogic' Sales de Andrade",	NULL,	NULL},
 	{"Gabriel 'Nix' Schulhof", 	NULL, 	NULL},
-	{"Will 'resiak' Thompson",	NULL,	NULL},
 	{NULL, NULL, NULL}
 };
 
--- a/pidgin/gtkdocklet-x11.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkdocklet-x11.c	Sat Dec 22 23:18:08 2007 +0000
@@ -79,13 +79,14 @@
 	g_idle_add(docklet_x11_recreate_cb, NULL);
 }
 
-static void
+static gboolean
 docklet_x11_clicked_cb(GtkWidget *button, GdkEventButton *event, void *data)
 {
 	if (event->type != GDK_BUTTON_RELEASE)
-		return;
+		return FALSE;
 
 	pidgin_docklet_clicked(event->button);
+	return TRUE;
 }
 
 static void
--- a/pidgin/gtkft.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkft.c	Sat Dec 22 23:18:08 2007 +0000
@@ -745,7 +745,6 @@
 	PidginXferDialog *dialog;
 	GtkWidget *window;
 	GtkWidget *vbox1, *vbox2;
-	GtkWidget *bbox;
 	GtkWidget *sw;
 	GtkWidget *button;
 	GtkWidget *expander;
@@ -759,15 +758,13 @@
 		purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished");
 
 	/* Create the window. */
-	dialog->window = window = pidgin_create_window(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE);
+	dialog->window = window = pidgin_create_dialog(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE);
 
 	g_signal_connect(G_OBJECT(window), "delete_event",
 					 G_CALLBACK(delete_win_cb), dialog);
 
 	/* Create the parent vbox for everything. */
-	vbox1 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(window), vbox1);
-	gtk_widget_show(vbox1);
+	vbox1 = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER);
 
 	/* Create the main vbox for top half of the window. */
 	vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
@@ -812,71 +809,35 @@
 	gtk_container_add(GTK_CONTAINER(expander), table);
 	gtk_widget_show(table);
 
-	/* Now the button box for the buttons */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox1), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
-
 	/* Open button */
-	button = gtk_button_new_from_stock(GTK_STOCK_OPEN);
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_OPEN, G_CALLBACK(open_button_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
 	dialog->open_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(open_button_cb), dialog);
-
 	/* Pause button */
-	button = gtk_button_new_with_mnemonic(_("_Pause"));
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), _("_Pause"), G_CALLBACK(pause_button_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
 	dialog->pause_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(pause_button_cb), dialog);
-
 	/* Resume button */
-	button = gtk_button_new_with_mnemonic(_("_Resume"));
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), _("_Resume"), G_CALLBACK(resume_button_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
 	dialog->resume_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(resume_button_cb), dialog);
-
 	/* Remove button */
-	button = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_REMOVE, G_CALLBACK(remove_button_cb), dialog);
 	gtk_widget_hide(button);
 	dialog->remove_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(remove_button_cb), dialog);
-
 	/* Stop button */
-	button = gtk_button_new_from_stock(GTK_STOCK_STOP);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP, G_CALLBACK(stop_button_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
 	dialog->stop_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(stop_button_cb), dialog);
-
 	/* Close button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
+	button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, G_CALLBACK(close_button_cb), dialog);
 	dialog->close_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(close_button_cb), dialog);
-
 #ifdef _WIN32
 	g_signal_connect(G_OBJECT(dialog->window), "show",
 		G_CALLBACK(winpidgin_ensure_onscreen), dialog->window);
--- a/pidgin/gtkimhtml.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkimhtml.c	Sat Dec 22 23:18:08 2007 +0000
@@ -812,7 +812,6 @@
 static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data)
 {
 	GtkWidget *menuitem;
-	GtkWidget *mi, *img;
 
 	menuitem = gtk_menu_item_new_with_mnemonic(_("Paste as Plain _Text"));
 	gtk_widget_show(menuitem);
@@ -1012,7 +1011,7 @@
 static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext)
 {
 	GtkTextIter iter;
-	GtkIMHtmlOptions flags = plaintext ? 0 : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS);
+	GtkIMHtmlOptions flags = plaintext ? GTK_IMHTML_NO_SMILEY : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS);
 
 	if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL))
 		gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE);
@@ -1136,14 +1135,13 @@
 #ifdef _WIN32
 	/* If we're on windows, let's see if we can get data from the HTML Format
 	   clipboard before we try to paste from the GTK buffer */
-	if (!clipboard_paste_html_win32(imhtml)) {
+	if (!clipboard_paste_html_win32(imhtml))
 #endif
+	{
 	GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD);
 	gtk_clipboard_request_contents(clipboard, gdk_atom_intern("text/html", FALSE),
 				       paste_received_cb, imhtml);
-#ifdef _WIN32
 	}
-#endif
 	g_signal_stop_emission_by_name(imhtml, "paste-clipboard");
 }
 
@@ -1397,6 +1395,22 @@
 	                                        _("Hyperlink prelight color"),
 	                                        _("Color to draw hyperlinks when mouse is over them."),
 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("send-name-color",
+	                                        _("Sent Message Name Color"),
+	                                        _("Color to draw the name of a message you sent."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("receive-name-color",
+	                                        _("Received Message Name Color"),
+	                                        _("Color to draw the name of a message you received."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("highlight-name-color",
+	                                        _("\"Attention\" Name Color"),
+	                                        _("Color to draw the name of a message you received containing your name."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("action-name-color",
+	                                        _("Action Message Name Color"),
+	                                        _("Color to draw the name of an action message."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
 
 	binding_set = gtk_binding_set_by_class (parent_class);
 	gtk_binding_entry_add_signal (binding_set, GDK_b, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_BOLD);
@@ -2982,6 +2996,7 @@
 			pos += tlen;
 			g_free(tag); /* This was allocated back in VALID_TAG() */
 		} else if (imhtml->edit.link == NULL &&
+				!(options & GTK_IMHTML_NO_SMILEY) &&
 				gtk_imhtml_is_smiley(imhtml, fonts, c, &smilelen)) {
 			GtkIMHtmlFontDetail *fd;
 			gchar *sml = NULL;
@@ -3467,6 +3482,9 @@
 		return;
 	}
 #endif /* FILECHOOSER */
+#if 0 /* mismatched curly braces */
+	}
+#endif
 
 	/*
 	 * XXX - We should probably prompt the user to determine if they really
--- a/pidgin/gtkimhtml.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkimhtml.h	Sat Dec 22 23:18:08 2007 +0000
@@ -225,7 +225,8 @@
 	GTK_IMHTML_RETURN_LOG          = 1 << 7,
 	GTK_IMHTML_USE_POINTSIZE       = 1 << 8,
 	GTK_IMHTML_NO_FORMATTING       = 1 << 9,
-	GTK_IMHTML_USE_SMOOTHSCROLLING = 1 << 10
+	GTK_IMHTML_USE_SMOOTHSCROLLING = 1 << 10,
+	GTK_IMHTML_NO_SMILEY           = 1 << 11,
 } GtkIMHtmlOptions;
 
 enum {
--- a/pidgin/gtkimhtmltoolbar.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Sat Dec 22 23:18:08 2007 +0000
@@ -99,7 +99,7 @@
 	gtk_widget_grab_focus(toolbar->imhtml);
 }
 
-static void
+static gboolean
 destroy_toolbar_font(GtkWidget *widget, GdkEvent *event,
 					 GtkIMHtmlToolbar *toolbar)
 {
@@ -111,6 +111,7 @@
 		gtk_widget_destroy(toolbar->font_dialog);
 		toolbar->font_dialog = NULL;
 	}
+	return FALSE;
 }
 
 static void
@@ -191,7 +192,7 @@
 	gtk_widget_grab_focus(toolbar->imhtml);
 }
 
-static void
+static gboolean
 destroy_toolbar_fgcolor(GtkWidget *widget, GdkEvent *event,
 						GtkIMHtmlToolbar *toolbar)
 {
@@ -203,6 +204,7 @@
 		gtk_widget_destroy(toolbar->fgcolor_dialog);
 		toolbar->fgcolor_dialog = NULL;
 	}
+	return FALSE;
 }
 
 static void cancel_toolbar_fgcolor(GtkWidget *widget,
@@ -263,7 +265,7 @@
 	gtk_widget_grab_focus(toolbar->imhtml);
 }
 
-static void
+static gboolean
 destroy_toolbar_bgcolor(GtkWidget *widget, GdkEvent *event,
 						GtkIMHtmlToolbar *toolbar)
 {
@@ -279,6 +281,7 @@
 		gtk_widget_destroy(toolbar->bgcolor_dialog);
 		toolbar->bgcolor_dialog = NULL;
 	}
+	return FALSE;
 }
 
 static void
@@ -467,10 +470,11 @@
 	GtkTextMark *ins;
 
 #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
-	if (response != GTK_RESPONSE_ACCEPT) {
+	if (response != GTK_RESPONSE_ACCEPT)
 #else /* FILECHOOSER */
-	if (response != GTK_RESPONSE_OK) {
+	if (response != GTK_RESPONSE_OK)
 #endif /* FILECHOOSER */
+	{
 		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->image), FALSE);
 		return;
 	}
@@ -574,11 +578,12 @@
 	}
 }
 
-static void
+static gboolean
 close_smiley_dialog(GtkWidget *widget, GdkEvent *event,
 					GtkIMHtmlToolbar *toolbar)
 {
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smiley), FALSE);
+	return FALSE;
 }
 
 
@@ -1096,8 +1101,8 @@
 		{PIDGIN_STOCK_TOOLBAR_TEXT_SMALLER, do_small, &toolbar->smaller_size, _("Decrease Font Size")},
 		{"", NULL, NULL, NULL},
 		{PIDGIN_STOCK_TOOLBAR_FONT_FACE, toggle_font, &toolbar->font, _("Font Face")},
-		{PIDGIN_STOCK_TOOLBAR_FGCOLOR, toggle_bg_color, &toolbar->bgcolor, _("Background Color")},
-		{PIDGIN_STOCK_TOOLBAR_BGCOLOR, toggle_fg_color, &toolbar->fgcolor, _("Foreground Color")},
+		{PIDGIN_STOCK_TOOLBAR_BGCOLOR, toggle_bg_color, &toolbar->bgcolor, _("Background Color")},
+		{PIDGIN_STOCK_TOOLBAR_FGCOLOR, toggle_fg_color, &toolbar->fgcolor, _("Foreground Color")},
 		{"", NULL, NULL, NULL},
 		{PIDGIN_STOCK_CLEAR, clear_formatting_cb, &toolbar->clear, _("Reset Formatting")},
 		{"", NULL, NULL, NULL},
--- a/pidgin/gtkmain.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkmain.c	Sat Dec 22 23:18:08 2007 +0000
@@ -397,6 +397,7 @@
 	if (terse) {
 		text = g_strdup_printf(_("%s %s. Try `%s -h' for more information.\n"), PIDGIN_NAME, DISPLAY_VERSION, name);
 	} else {
+#ifndef WIN32
 		text = g_strdup_printf(_("%s %s\n"
 		       "Usage: %s [OPTION]...\n\n"
 		       "  -c, --config=DIR    use DIR for config files\n"
@@ -406,10 +407,20 @@
 		       "  -n, --nologin       don't automatically login\n"
 		       "  -l, --login[=NAME]  automatically login (optional argument NAME specifies\n"
 		       "                      account(s) to use, separated by commas)\n"
-#ifndef WIN32
 		       "  --display=DISPLAY   X display to use\n"
+		       "  -v, --version       display the current version and exit\n"), PIDGIN_NAME, DISPLAY_VERSION, name);
+#else
+		text = g_strdup_printf(_("%s %s\n"
+		       "Usage: %s [OPTION]...\n\n"
+		       "  -c, --config=DIR    use DIR for config files\n"
+		       "  -d, --debug         print debugging messages to stdout\n"
+		       "  -h, --help          display this help and exit\n"
+		       "  -m, --multiple      do not ensure single instance\n"
+		       "  -n, --nologin       don't automatically login\n"
+		       "  -l, --login[=NAME]  automatically login (optional argument NAME specifies\n"
+		       "                      account(s) to use, separated by commas)\n"
+		       "  -v, --version       display the current version and exit\n"), PIDGIN_NAME, DISPLAY_VERSION, name);
 #endif
-		       "  -v, --version       display the current version and exit\n"), PIDGIN_NAME, DISPLAY_VERSION, name);
 	}
 
 	purple_print_utf8_to_console(stdout, text);
--- a/pidgin/gtknotify.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtknotify.c	Sat Dec 22 23:18:08 2007 +0000
@@ -166,16 +166,18 @@
 	mail_dialog = NULL;
 }
 
-static void
+static gboolean
 formatted_close_cb(GtkWidget *win, GdkEvent *event, void *user_data)
 {
 	purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
+	return FALSE;
 }
 
-static void
+static gboolean
 searchresults_close_cb(PidginNotifySearchResultsData *data, GdkEvent *event, gpointer user_data)
 {
 	purple_notify_close(PURPLE_NOTIFY_SEARCHRESULTS, data);
+	return FALSE;
 }
 
 static void
@@ -284,6 +286,8 @@
 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
 
+	pidgin_auto_parent_window(dialog);
+
 	gtk_widget_show_all(dialog);
 
 	return dialog;
@@ -684,6 +688,8 @@
 	g_object_set_data(G_OBJECT(window), "info-widget", imhtml);
 
 	/* Show the window */
+	pidgin_auto_parent_window(window);
+
 	gtk_widget_show(window);
 
 	return window;
@@ -894,6 +900,8 @@
 	pidgin_notify_searchresults_new_rows(gc, results, data);
 
 	/* Show the window */
+	pidgin_auto_parent_window(window);
+
 	gtk_widget_show(window);
 	return data;
 }
--- a/pidgin/gtkpounce.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkpounce.c	Sat Dec 22 23:18:08 2007 +0000
@@ -1317,7 +1317,6 @@
 pidgin_pounces_manager_show(void)
 {
 	PouncesManager *dialog;
-	GtkWidget *bbox;
 	GtkWidget *button;
 	GtkWidget *list;
 	GtkWidget *vbox;
@@ -1334,7 +1333,7 @@
 	width  = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/width");
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/height");
 
-	dialog->window = win = pidgin_create_window(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE);
+	dialog->window = win = pidgin_create_dialog(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
@@ -1343,61 +1342,33 @@
 					 G_CALLBACK(pounces_manager_configure_cb), dialog);
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	/* List of saved buddy pounces */
 	list = create_pounces_list(dialog);
 	gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0);
 
-	/* Button box. */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
+	/* Add button */
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD, G_CALLBACK(pounces_manager_add_cb), dialog);
+	gtk_widget_set_sensitive(button, (purple_accounts_get_all() != NULL));
 
-	/* Add button */
-	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_set_sensitive(button, (purple_accounts_get_all() != NULL));
 	purple_signal_connect(purple_connections_get_handle(), "signed-on",
 						pounces_manager, PURPLE_CALLBACK(pounces_manager_connection_cb), button);
 	purple_signal_connect(purple_connections_get_handle(), "signed-off",
 						pounces_manager, PURPLE_CALLBACK(pounces_manager_connection_cb), button);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(pounces_manager_add_cb), dialog);
 
 	/* Modify button */
-	button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY);
-	dialog->modify_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY, G_CALLBACK(pounces_manager_modify_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(pounces_manager_modify_cb), dialog);
+	dialog->modify_button = button;
 
 	/* Delete button */
-	button = gtk_button_new_from_stock(GTK_STOCK_DELETE);
-	dialog->delete_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE, G_CALLBACK(pounces_manager_delete_cb), dialog);
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(pounces_manager_delete_cb), dialog);
+	dialog->delete_button = button;
 
 	/* Close button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(pounces_manager_close_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, G_CALLBACK(pounces_manager_close_cb), dialog);
 
 	gtk_widget_show(win);
 }
--- a/pidgin/gtkprefs.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkprefs.c	Sat Dec 22 23:18:08 2007 +0000
@@ -2150,7 +2150,6 @@
 void pidgin_prefs_show(void)
 {
 	GtkWidget *vbox;
-	GtkWidget *bbox;
 	GtkWidget *notebook;
 	GtkWidget *button;
 
@@ -2166,31 +2165,20 @@
 	/* Back to instant-apply! I win!  BU-HAHAHA! */
 
 	/* Create the window */
-	prefs = pidgin_create_window(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE);
+	prefs = pidgin_create_dialog(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE);
 	g_signal_connect(G_OBJECT(prefs), "destroy",
 					 G_CALLBACK(delete_prefs), NULL);
 
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(prefs), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(prefs), FALSE, PIDGIN_HIG_BORDER);
 
 	/* The notebook */
 	prefsnotebook = notebook = gtk_notebook_new ();
 	gtk_box_pack_start (GTK_BOX (vbox), notebook, FALSE, FALSE, 0);
 	gtk_widget_show(prefsnotebook);
 
-	/* The buttons to press! */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
-	gtk_widget_show (bbox);
-
-	button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
+	button = pidgin_dialog_add_button(GTK_DIALOG(prefs), GTK_STOCK_CLOSE, NULL, NULL);
 	g_signal_connect_swapped(G_OBJECT(button), "clicked",
 							 G_CALLBACK(gtk_widget_destroy), prefs);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
 
 	prefs_notebook_init();
 
--- a/pidgin/gtkprivacy.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkprivacy.c	Sat Dec 22 23:18:08 2007 +0000
@@ -45,6 +45,7 @@
 	GtkWidget *add_button;
 	GtkWidget *remove_button;
 	GtkWidget *clear_button;
+	GtkWidget *close_button;
 
 	GtkWidget *button_box;
 	GtkWidget *allow_widget;
@@ -259,19 +260,22 @@
 
 	gtk_widget_hide(dialog->allow_widget);
 	gtk_widget_hide(dialog->block_widget);
-	gtk_widget_hide(dialog->button_box);
+	gtk_widget_hide_all(dialog->button_box);
 
 	if (new_type == PURPLE_PRIVACY_ALLOW_USERS) {
 		gtk_widget_show(dialog->allow_widget);
-		gtk_widget_show(dialog->button_box);
+		gtk_widget_show_all(dialog->button_box);
 		dialog->in_allow_list = TRUE;
 	}
 	else if (new_type == PURPLE_PRIVACY_DENY_USERS) {
 		gtk_widget_show(dialog->block_widget);
-		gtk_widget_show(dialog->button_box);
+		gtk_widget_show_all(dialog->button_box);
 		dialog->in_allow_list = FALSE;
 	}
 
+	gtk_widget_show_all(dialog->close_button);
+	gtk_widget_show(dialog->button_box);
+
 	purple_blist_schedule_save();
 	pidgin_blist_refresh(purple_get_blist());
 }
@@ -355,7 +359,6 @@
 privacy_dialog_new(void)
 {
 	PidginPrivacyDialog *dialog;
-	GtkWidget *bbox;
 	GtkWidget *hbox;
 	GtkWidget *vbox;
 	GtkWidget *button;
@@ -367,15 +370,13 @@
 
 	dialog = g_new0(PidginPrivacyDialog, 1);
 
-	dialog->win = pidgin_create_window(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", TRUE);
+	dialog->win = pidgin_create_dialog(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", TRUE);
 
 	g_signal_connect(G_OBJECT(dialog->win), "delete_event",
 					 G_CALLBACK(destroy_cb), dialog);
 
 	/* Main vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(dialog->win), vbox);
-	gtk_widget_show(vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(dialog->win), FALSE, PIDGIN_HIG_BORDER);
 
 	/* Description label */
 	label = gtk_label_new(
@@ -433,52 +434,27 @@
 	gtk_box_pack_start(GTK_BOX(vbox), dialog->block_widget, TRUE, TRUE, 0);
 
 	/* Add the button box for Add, Remove, Clear */
-	dialog->button_box = bbox = gtk_hbutton_box_new();
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_SPREAD);
-	gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
+	dialog->button_box = pidgin_dialog_get_action_area(GTK_DIALOG(dialog->win));
 
 	/* Add button */
-	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
+	button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_ADD, G_CALLBACK(add_cb), dialog);
 	dialog->add_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(add_cb), dialog);
 
 	/* Remove button */
-	button = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
+	button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_REMOVE, G_CALLBACK(remove_cb), dialog);
 	dialog->remove_button = button;
 	gtk_widget_set_sensitive(button, FALSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(remove_cb), dialog);
 
 	/* Clear button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
+	button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_CLEAR, G_CALLBACK(clear_cb), dialog);
 	dialog->clear_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(clear_cb), dialog);
-
-	/* Another button box. */
-	bbox = gtk_hbutton_box_new();
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
-	gtk_widget_show(bbox);
 
 	/* Close button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
+	button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_CLOSE, G_CALLBACK(close_cb), dialog);
+	dialog->close_button = button;
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(close_cb), dialog);
-
+	type_changed_cb(GTK_OPTION_MENU(dialog->type_menu), dialog);
+#if 0
 	if (dialog->account->perm_deny == PURPLE_PRIVACY_ALLOW_USERS) {
 		gtk_widget_show(dialog->allow_widget);
 		gtk_widget_show(dialog->button_box);
@@ -489,7 +465,7 @@
 		gtk_widget_show(dialog->button_box);
 		dialog->in_allow_list = FALSE;
 	}
-
+#endif
 	return dialog;
 }
 
--- a/pidgin/gtkrequest.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkrequest.c	Sat Dec 22 23:18:08 2007 +0000
@@ -251,11 +251,12 @@
 	purple_request_close(PURPLE_REQUEST_FIELDS, data);
 }
 
-static void
+static gboolean
 destroy_multifield_cb(GtkWidget *dialog, GdkEvent *event,
 					  PidginRequestData *data)
 {
 	multifield_cancel_cb(NULL, data);
+	return FALSE;
 }
 
 
@@ -439,6 +440,8 @@
 	pidgin_set_accessible_label (entry, label);
 	data->u.input.entry = entry;
 
+	pidgin_auto_parent_window(dialog);
+
 	/* Show everything. */
 	gtk_widget_show(dialog);
 
@@ -546,6 +549,8 @@
 	g_object_set_data(G_OBJECT(dialog), "radio", radio);
 
 	/* Show everything. */
+	pidgin_auto_parent_window(dialog);
+
 	gtk_widget_show_all(dialog);
 
 	return data;
@@ -661,6 +666,8 @@
 		gtk_dialog_set_default_response(GTK_DIALOG(dialog), default_action);
 
 	/* Show everything. */
+	pidgin_auto_parent_window(dialog);
+
 	gtk_widget_show_all(dialog);
 
 	return data;
@@ -1059,7 +1066,6 @@
 	GtkWidget *vbox;
 	GtkWidget *vbox2;
 	GtkWidget *hbox;
-	GtkWidget *bbox;
 	GtkWidget *frame;
 	GtkWidget *label;
 	GtkWidget *table;
@@ -1089,9 +1095,9 @@
 
 
 #ifdef _WIN32
-	data->dialog = win = pidgin_create_window(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
+	data->dialog = win = pidgin_create_dialog(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
 #else /* !_WIN32 */
-	data->dialog = win = pidgin_create_window(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
+	data->dialog = win = pidgin_create_dialog(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
 #endif /* _WIN32 */
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
@@ -1099,7 +1105,7 @@
 
 	/* Setup the main horizontal box */
 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), hbox);
+	gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(win))), hbox);
 	gtk_widget_show(hbox);
 
 	/* Dialog icon. */
@@ -1382,39 +1388,21 @@
 
 	g_object_unref(sg);
 
-	/* Button box. */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
-
 	/* Cancel button */
-	button = gtk_button_new_from_stock(text_to_stock(cancel_text));
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(multifield_cancel_cb), data);
-
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), text_to_stock(cancel_text), G_CALLBACK(multifield_cancel_cb), data);
 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
 
 	/* OK button */
-	button = gtk_button_new_from_stock(text_to_stock(ok_text));
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), text_to_stock(ok_text), G_CALLBACK(multifield_ok_cb), data);
 	data->ok_button = button;
-
 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
 	gtk_window_set_default(GTK_WINDOW(win), button);
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(multifield_ok_cb), data);
-
 	if (!purple_request_fields_all_required_filled(fields))
 		gtk_widget_set_sensitive(button, FALSE);
 
+	pidgin_auto_parent_window(win);
+
 	gtk_widget_show(win);
 
 	return data;
@@ -1502,7 +1490,9 @@
 	}
 
 #endif /* FILECHOOSER */
-
+#if 0 /* mismatched curly braces */
+	}
+#endif
 	if ((data->u.file.savedialog == TRUE) &&
 		(g_file_test(data->u.file.name, G_FILE_TEST_EXISTS))) {
 		purple_request_action(data, NULL, _("That file already exists"),
@@ -1516,7 +1506,7 @@
 }
 
 #if !GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
-static void
+static gboolean
 file_cancel_cb(PidginRequestData *data)
 {
 	generic_response_start(data);
@@ -1525,6 +1515,7 @@
 		((PurpleRequestFileCb)data->cbs[0])(data->user_data, NULL);
 
 	purple_request_close(data->type, data);
+	return FALSE;
 }
 #endif /* FILECHOOSER */
 
@@ -1622,6 +1613,8 @@
 					 G_CALLBACK(file_ok_check_if_exists_cb), data);
 #endif /* FILECHOOSER */
 
+	pidgin_auto_parent_window(filesel);
+
 	data->dialog = filesel;
 	gtk_widget_show(filesel);
 
@@ -1673,6 +1666,8 @@
 #endif
 
 	data->dialog = dirsel;
+	pidgin_auto_parent_window(dirsel);
+
 	gtk_widget_show(dirsel);
 
 	return (void *)data;
--- a/pidgin/gtkroomlist.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkroomlist.c	Sat Dec 22 23:18:08 2007 +0000
@@ -28,6 +28,8 @@
 #include "pidgin.h"
 #include "gtkutils.h"
 #include "pidginstock.h"
+#include "pidgintooltip.h"
+
 #include "debug.h"
 #include "account.h"
 #include "connection.h"
@@ -340,33 +342,14 @@
 	}
 }
 
-static void pidgin_roomlist_tooltip_destroy(PidginRoomlist *grl)
-{
-	if ((grl == NULL) || (grl->tipwindow == NULL))
-		return;
-
-	gtk_widget_destroy(grl->tipwindow);
-	grl->tipwindow = NULL;
-}
-
-static void pidgin_roomlist_tooltip_destroy_cb(GObject *object, PidginRoomlist *grl)
-{
-	if ((grl == NULL) || (grl->tipwindow == NULL))
-		return;
-
-	if (grl->timeout)
-		g_source_remove(grl->timeout);
-	grl->timeout = 0;
-
-	pidgin_roomlist_tooltip_destroy(grl);
-}
-
 #define SMALL_SPACE 6
 #define TOOLTIP_BORDER 12
 
-static void pidgin_roomlist_paint_tip(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
+static gboolean
+pidgin_roomlist_paint_tooltip(GtkWidget *widget, gpointer user_data)
 {
-	PidginRoomlist *grl = (PidginRoomlist *)user_data;
+	PurpleRoomlist *list = user_data;
+	PidginRoomlist *grl = list->ui_data;
 	GtkStyle *style;
 	int current_height, max_width;
 	int max_text_width;
@@ -404,15 +387,13 @@
 				current_height + grl->tip_name_height,
 				grl->tip_layout);
 	}
-
+	return FALSE;
 }
 
-static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list)
+static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list, GtkTreePath *path)
 {
 	PidginRoomlist *grl = list->ui_data;
-	GtkWidget *tv = grl->tree;
 	PurpleRoomlistRoom *room;
-	GtkTreePath *path;
 	GtkTreeIter iter;
 	GValue val;
 	gchar *name, *tmp, *node_name;
@@ -421,10 +402,11 @@
 	gint j;
 	gboolean first = TRUE;
 
+#if 0
 	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2),
 		&path, NULL, NULL, NULL))
 		return FALSE;
-
+#endif
 	gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
 
 	val.g_type = 0;
@@ -460,8 +442,6 @@
 		g_free(label);
 	}
 
-	gtk_tree_path_free(path);
-
 	grl->tip_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL);
 	grl->tip_name_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL);
 
@@ -492,156 +472,22 @@
 	return TRUE;
 }
 
-static void pidgin_roomlist_draw_tooltip(PurpleRoomlist *list, GtkWidget *widget)
+static gboolean
+pidgin_roomlist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
+		gpointer data, int *w, int *h)
 {
+	PurpleRoomlist *list = data;
 	PidginRoomlist *grl = list->ui_data;
-	int scr_w, scr_h, w, h, x, y;
-#if GTK_CHECK_VERSION(2,2,0)
-	int mon_num;
-	GdkScreen *screen = NULL;
-#endif
-	GdkRectangle mon_size;
-	int sig;
-	const char *name;
-
-	pidgin_roomlist_tooltip_destroy(grl);
-	grl->tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
-	gtk_widget_ensure_style (grl->tipwindow);
-
-	if (!pidgin_roomlist_create_tip(list)) {
-		pidgin_roomlist_tooltip_destroy(grl);
-		return;
-	}
-
-	name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget)));
-	gtk_widget_set_app_paintable(grl->tipwindow, TRUE);
-	gtk_window_set_title(GTK_WINDOW(grl->tipwindow), name ? name : _("Room List"));
-	gtk_window_set_resizable(GTK_WINDOW(grl->tipwindow), FALSE);
-	gtk_widget_set_name(grl->tipwindow, "gtk-tooltips");
-	g_signal_connect(G_OBJECT(grl->tipwindow), "expose_event",
-			G_CALLBACK(pidgin_roomlist_paint_tip), grl);
-
-	w = TOOLTIP_BORDER + SMALL_SPACE +
-		MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER;
-	h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height
-		+ TOOLTIP_BORDER;
-
-#if GTK_CHECK_VERSION(2,2,0)
-	gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
-	mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
-	gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
-
-	scr_w = mon_size.width + mon_size.x;
-	scr_h = mon_size.height + mon_size.y;
-#else
-	scr_w = gdk_screen_width();
-	scr_h = gdk_screen_height();
-	gdk_window_get_pointer(NULL, &x, &y, NULL);
-	mon_size.x = 0;
-	mon_size.y = 0;
-#endif
-
-#if GTK_CHECK_VERSION(2,2,0)
-	if (w > mon_size.width)
-		w = mon_size.width - 10;
-
-	if (h > mon_size.height)
-		h = mon_size.height - 10;
-#endif
-	x -= ((w >> 1) + 4);
-
-	if ((y + h + 4) > scr_h)
-		y = y - h - 5;
-	else
-		y = y + 6;
-
-	if (y < mon_size.y)
-		y = mon_size.y;
-
-	if (y != mon_size.y) {
-		if ((x + w) > scr_w)
-			x -= (x + w + 5) - scr_w;
-		else if (x < mon_size.x)
-			x = mon_size.x;
-	} else {
-		x -= (w / 2 + 10);
-		if (x < mon_size.x)
-			x = mon_size.x;
-	}
-
-	gtk_widget_set_size_request(grl->tipwindow, w, h);
-	gtk_window_move(GTK_WINDOW(grl->tipwindow), x, y);
-	gtk_widget_show(grl->tipwindow);
-
-	/* Hide the tooltip when the widget is destroyed */
-	sig = g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(pidgin_roomlist_tooltip_destroy_cb), grl);
-	g_signal_connect_swapped(G_OBJECT(grl->tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig));
-}
-
-static gboolean pidgin_roomlist_tooltip_timeout(PurpleRoomlist *list)
-{
-	PidginRoomlist *grl = list->ui_data;
-	GtkWidget *tv = grl->tree;
-	GtkTreePath *path;
-
-	pidgin_roomlist_tooltip_destroy(grl);
-
-	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2),
-	                                   &path, NULL, NULL, NULL))
+	grl->tipwindow = widget;
+	if (!pidgin_roomlist_create_tip(data, path))
 		return FALSE;
-
-	pidgin_roomlist_draw_tooltip(list, GTK_WIDGET(grl->tree));
-
-	return FALSE;
-}
-
-static gboolean row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer user_data)
-{
-	PurpleRoomlist *list = user_data;
-	PidginRoomlist *grl = list->ui_data;
-	GtkTreePath *path;
-	int delay;
-
-	/* XXX: should this be using the blist delay pref? */
-	delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
-
-	if (delay == 0)
-		return FALSE;
-
-	if (grl->timeout) {
-		if ((event->y > grl->tip_rect.y) && ((event->y - grl->tip_rect.height) < grl->tip_rect.y))
-			return FALSE;
-		/* We've left the cell.  Remove the timeout and create a new one below */
-		pidgin_roomlist_tooltip_destroy(grl);
-	}
-
-	gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
-
-	if (path == NULL) {
-		pidgin_roomlist_tooltip_destroy(grl);
-		return FALSE;
-	}
-
-	gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &grl->tip_rect);
-
-	if (path)
-		gtk_tree_path_free(path);
-	grl->timeout = g_timeout_add(delay, (GSourceFunc)pidgin_roomlist_tooltip_timeout, list);
-
-	return FALSE;
-}
-
-static void row_leave_cb(GtkWidget *tv, GdkEventCrossing *e, gpointer user_data)
-{
-	PurpleRoomlist *list = user_data;
-	PidginRoomlist *grl = list->ui_data;
-
-	if (grl->timeout) {
-		g_source_remove(grl->timeout);
-		grl->timeout = 0;
-	}
-
-	pidgin_roomlist_tooltip_destroy(grl);
+	if (w)
+		*w = TOOLTIP_BORDER + SMALL_SPACE +
+			MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER;
+	if (h)
+		*h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height
+			+ TOOLTIP_BORDER;
+	return TRUE;
 }
 
 static gboolean account_filter_func(PurpleAccount *account)
@@ -685,15 +531,13 @@
 	dialog->account = account;
 
 	/* Create the window. */
-	dialog->window = window = pidgin_create_window(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE);
+	dialog->window = window = pidgin_create_dialog(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE);
 
 	g_signal_connect(G_OBJECT(window), "delete_event",
 					 G_CALLBACK(delete_win_cb), dialog);
 
 	/* Create the parent vbox for everything. */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(window), vbox);
-	gtk_widget_show(vbox);
+	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);
@@ -738,19 +582,14 @@
 	gtk_widget_show(dialog->progress);
 
 	/* button box */
-	bbox = gtk_hbutton_box_new();
+	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);
-	gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-	gtk_widget_show(bbox);
 
 	/* stop button */
-	dialog->stop_button = gtk_button_new_from_stock(GTK_STOCK_STOP);
-	gtk_box_pack_start(GTK_BOX(bbox), dialog->stop_button, FALSE, FALSE, 0);
-	g_signal_connect(G_OBJECT(dialog->stop_button), "clicked",
+	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);
-	gtk_widget_show(dialog->stop_button);
 
 	/* list button */
 	dialog->list_button = pidgin_pixbuf_button_from_stock(_("_Get List"), GTK_STOCK_REFRESH,
@@ -779,11 +618,8 @@
 	gtk_widget_show(dialog->join_button);
 
 	/* close button */
-	dialog->close_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), dialog->close_button, FALSE, FALSE, 0);
-	g_signal_connect(G_OBJECT(dialog->close_button), "clicked",
+	dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE,
 					 G_CALLBACK(close_button_cb), dialog);
-	gtk_widget_show(dialog->close_button);
 
 	/* show the dialog window and return the dialog */
 	gtk_widget_show(dialog->window);
@@ -967,6 +803,9 @@
 	g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), list);
 	g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), list);
 #endif
+	pidgin_tooltip_setup_for_treeview(tree, list,
+		pidgin_roomlist_create_tooltip,
+		pidgin_roomlist_paint_tooltip);
 
 	/* Enable CTRL+F searching */
 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), NAME_COLUMN);
--- a/pidgin/gtksavedstatuses.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtksavedstatuses.c	Sat Dec 22 23:18:08 2007 +0000
@@ -594,7 +594,7 @@
 	width  = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/width");
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/height");
 
-	dialog->window = win = pidgin_create_window(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE);
+	dialog->window = win = pidgin_create_dialog(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
@@ -603,18 +603,14 @@
 					 G_CALLBACK(configure_cb), dialog);
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	/* List of saved status states */
 	list = create_saved_status_list(dialog);
 	gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0);
 
 	/* Button box. */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
+	bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
 
 	/* Use button */
 	button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
@@ -627,36 +623,23 @@
 					 G_CALLBACK(status_window_use_cb), dialog);
 
 	/* Add button */
-	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_window_add_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD,
+			G_CALLBACK(status_window_add_cb), dialog);
 
 	/* Modify button */
-	button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY,
+			G_CALLBACK(status_window_modify_cb), dialog);
 	dialog->modify_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+
+	/* Delete button */
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE,
+			G_CALLBACK(status_window_delete_cb), dialog);
+	dialog->delete_button = button;
 	gtk_widget_set_sensitive(button, FALSE);
 
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_window_modify_cb), dialog);
-
-	/* Delete button */
-	button = gtk_button_new_from_stock(GTK_STOCK_DELETE);
-	dialog->delete_button = button;
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_set_sensitive(button, FALSE);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_window_delete_cb), dialog);
-
 	/* Close button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_window_close_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE,
+			G_CALLBACK(status_window_close_cb), dialog);
 
 	purple_signal_connect(purple_savedstatuses_get_handle(),
 			"savedstatus-changed", status_window,
@@ -1147,14 +1130,13 @@
 	if (edit)
 		dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status));
 
-	dialog->window = win = pidgin_create_window(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE);
+	dialog->window = win = pidgin_create_dialog(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(status_editor_destroy_cb), dialog);
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
@@ -1257,23 +1239,18 @@
 		(saved_status != NULL) && purple_savedstatus_has_substatuses(saved_status));
 
 	/* Button box */
-	bbox = gtk_hbutton_box_new();
+	bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
 	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
 	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
 
 	/* Cancel button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_editor_cancel_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
+			G_CALLBACK(status_editor_cancel_cb), dialog);
 
 	/* Use button */
 	button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
 										   PIDGIN_BUTTON_HORIZONTAL);
 	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
 	g_signal_connect(G_OBJECT(button), "clicked",
 					 G_CALLBACK(status_editor_ok_cb), dialog);
 
@@ -1284,19 +1261,15 @@
 	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
 	if (dialog->original_title == NULL)
 		gtk_widget_set_sensitive(button, FALSE);
-
 	g_signal_connect(G_OBJECT(button), "clicked",
 					 G_CALLBACK(status_editor_ok_cb), dialog);
 
 	/* Save button */
-	button = gtk_button_new_from_stock(GTK_STOCK_SAVE);
-	dialog->save_button = GTK_BUTTON(button);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE,
+			G_CALLBACK(status_editor_ok_cb), dialog);
 	if (dialog->original_title == NULL)
 		gtk_widget_set_sensitive(button, FALSE);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(status_editor_ok_cb), dialog);
+	dialog->save_button = GTK_BUTTON(button);
 
 	gtk_widget_show_all(win);
 	g_object_unref(sg);
@@ -1447,8 +1420,6 @@
 	char *tmp;
 	SubStatusEditor *dialog;
 	GtkSizeGroup *sg;
-	GtkWidget *bbox;
-	GtkWidget *button;
 	GtkWidget *combo;
 	GtkWidget *hbox;
 	GtkWidget *frame;
@@ -1486,15 +1457,14 @@
 	dialog->account = account;
 
 	tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account));
-	dialog->window = win = pidgin_create_window(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE);
+	dialog->window = win = pidgin_create_dialog(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE);
 	g_free(tmp);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(substatus_editor_destroy_cb), dialog);
 
 	/* Setup the vbox */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
-	gtk_container_add(GTK_CONTAINER(win), vbox);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
 
 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
@@ -1543,25 +1513,13 @@
 	dialog->toolbar = GTK_IMHTMLTOOLBAR(toolbar);
 	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
 
-	/* Button box */
-	bbox = gtk_hbutton_box_new();
-	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
-	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
-	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
-
 	/* Cancel button */
-	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(substatus_editor_cancel_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
+			G_CALLBACK(substatus_editor_cancel_cb), dialog);
 
 	/* OK button */
-	button = gtk_button_new_from_stock(GTK_STOCK_OK);
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-					 G_CALLBACK(substatus_editor_ok_cb), dialog);
+	pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_OK,
+			G_CALLBACK(substatus_editor_ok_cb), dialog);
 
 	/* Seed the input widgets with the current values */
 
--- a/pidgin/gtkscrollbook.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkscrollbook.c	Sat Dec 22 23:18:08 2007 +0000
@@ -64,7 +64,7 @@
 	return scroll_book_type;
 }
 
-static void
+static gboolean
 scroll_left_cb(PidginScrollBook *scroll_book)
 {
 	int index;
@@ -72,9 +72,10 @@
 
 	if (index > 0)
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(scroll_book->notebook), index - 1);
+	return TRUE;
 }
 
-static void
+static gboolean
 scroll_right_cb(PidginScrollBook *scroll_book)
 {
 	int index, count;
@@ -87,6 +88,7 @@
 
 	if (index + 1 < count)
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(scroll_book->notebook), index + 1);
+	return TRUE;
 }
 
 static void
@@ -136,10 +138,11 @@
 	refresh_scroll_box(scroll_book, index, count);
 }
 
-static void
+static gboolean
 scroll_close_cb(PidginScrollBook *scroll_book)
 {
 	gtk_widget_destroy(gtk_notebook_get_nth_page(GTK_NOTEBOOK(scroll_book->notebook), gtk_notebook_get_current_page(GTK_NOTEBOOK(scroll_book->notebook))));
+	return FALSE;
 }
 
 static void
--- a/pidgin/gtkstatusbox.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkstatusbox.c	Sat Dec 22 23:18:08 2007 +0000
@@ -1100,7 +1100,7 @@
 	return TRUE;
 }
 
-static int imhtml_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStatusBox *status_box)
+static gboolean imhtml_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStatusBox *status_box)
 {
 	if (event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab)
 	{
--- a/pidgin/gtkutils.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkutils.c	Sat Dec 22 23:18:08 2007 +0000
@@ -132,12 +132,9 @@
 	}
 }
 
-GtkWidget *
-pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
+static
+void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable)
 {
-	GtkWindow *wnd = NULL;
-
-	wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
 	if (title)
 		gtk_window_set_title(wnd, title);
 #ifdef _WIN32
@@ -148,11 +145,63 @@
 	if (role)
 		gtk_window_set_role(wnd, role);
 	gtk_window_set_resizable(wnd, resizable);
+}
+
+GtkWidget *
+pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
+{
+	GtkWindow *wnd = NULL;
+
+	wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+	pidgin_window_init(wnd, title, border_width, role, resizable);
+
+	return GTK_WIDGET(wnd);
+}
+
+GtkWidget *
+pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable)
+{
+	GtkWindow *wnd = NULL;
+
+	wnd = GTK_WINDOW(gtk_dialog_new());
+	pidgin_window_init(wnd, title, border_width, role, resizable);
+	g_object_set(G_OBJECT(wnd), "has-separator", FALSE, NULL);
 
 	return GTK_WIDGET(wnd);
 }
 
 GtkWidget *
+pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing)
+{
+	GtkBox *vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
+	gtk_box_set_homogeneous(vbox, homogeneous);
+	gtk_box_set_spacing(vbox, spacing);
+	return GTK_WIDGET(vbox);
+}
+
+GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog)
+{
+	return GTK_DIALOG(dialog)->vbox;
+}
+
+GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog)
+{
+	return GTK_DIALOG(dialog)->action_area;
+}
+
+GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
+		GCallback callback, gpointer callbackdata)
+{
+	GtkWidget *button = gtk_button_new_from_stock(label);
+	GtkWidget *bbox = pidgin_dialog_get_action_area(dialog);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	if (callback)
+		g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata);
+	gtk_widget_show(button);
+	return button;
+}
+
+GtkWidget *
 pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret)
 {
 	GtkWidget *frame;
@@ -1399,7 +1448,7 @@
 			char *str;
 
 			str = g_strdup_printf(_("The following error has occurred loading %s: %s"),
-						data->filename, strerror(errno));
+						data->filename, g_strerror(errno));
 			purple_notify_error(NULL, NULL,
 					  _("Failed to load image"),
 					  str);
@@ -2306,6 +2355,9 @@
 	}
 
 #endif /* FILECHOOSER */
+#if 0 /* mismatched curly braces */
+	}
+#endif
 	if (dialog->callback)
 		dialog->callback(filename, dialog->data);
 	gtk_widget_destroy(dialog->icon_filesel);
@@ -3269,3 +3321,107 @@
 	gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text));
 }
 
+gboolean pidgin_auto_parent_window(GtkWidget *widget)
+{
+#if 0
+	/* This looks at the most recent window that received focus, and makes
+	 * that the parent window. */
+#ifndef _WIN32
+	static GdkAtom _WindowTime = GDK_NONE;
+	static GdkAtom _Cardinal = GDK_NONE;
+	GList *windows = NULL;
+	GtkWidget *parent = NULL;
+	time_t window_time = 0;
+
+	windows = gtk_window_list_toplevels();
+
+	if (_WindowTime == GDK_NONE) {
+		_WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
+	}
+	if (_Cardinal == GDK_NONE) {
+		_Cardinal = gdk_atom_intern("CARDINAL", FALSE);
+	}
+
+	while (windows) {
+		GtkWidget *window = windows->data;
+		guchar *data = NULL;
+		int al = 0;
+		time_t value;
+
+		windows = g_list_delete_link(windows, windows);
+
+		if (window == widget ||
+				!GTK_WIDGET_VISIBLE(window))
+			continue;
+
+		if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
+				NULL, NULL, &al, &data))
+			continue;
+		value = *(time_t *)data;
+		if (window_time < value) {
+			window_time = value;
+			parent = window;
+		}
+		g_free(data);
+	}
+	if (windows)
+		g_list_free(windows);
+	if (parent) {
+		if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) {
+			/* The window is in focus, and the new window was not triggered by a keypress/click
+			 * event. So do not set it transient, to avoid focus stealing and all that.
+			 */
+			return FALSE;
+		}
+		gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
+		return TRUE;
+	}
+	return FALSE;
+#endif
+#else
+	/* This finds the currently active window and makes that the parent window. */
+	GList *windows = NULL;
+	GtkWidget *parent = NULL;
+	GdkEvent *event = gtk_get_current_event();
+	GdkWindow *menu = NULL;
+
+	if (event == NULL)
+		/* The window was not triggered by a user action. */
+		return FALSE;
+
+	/* We need to special case events from a popup menu. */
+	if (event->type == GDK_BUTTON_RELEASE) {
+		/* XXX: Neither of the following works:
+			menu = event->button.window;
+			menu = gdk_window_get_parent(event->button.window);
+			menu = gdk_window_get_toplevel(event->button.window);
+		*/
+	} else if (event->type == GDK_KEY_PRESS)
+		menu = event->key.window;
+
+	windows = gtk_window_list_toplevels();
+	while (windows) {
+		GtkWidget *window = windows->data;
+		windows = g_list_delete_link(windows, windows);
+
+		if (window == widget ||
+				!GTK_WIDGET_VISIBLE(window)) {
+			continue;
+		}
+
+		if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) ||
+				(menu && menu == window->window)) {
+			parent = window;
+			break;
+		}
+	}
+	if (windows)
+		g_list_free(windows);
+	if (parent) {
+		gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
+		return TRUE;
+	}
+	return FALSE;
+#endif
+}
+
--- a/pidgin/gtkutils.h	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/gtkutils.h	Sat Dec 22 23:18:08 2007 +0000
@@ -121,6 +121,61 @@
 GtkWidget *pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable);
 
 /**
+ * Creates a new dialog window
+ *
+ * @param title        The window title, or @c NULL
+ * @param border_width The window's desired border width
+ * @param role         A string indicating what the window is responsible for doing, or @c NULL
+ * @param resizable    Whether the window should be resizable (@c TRUE) or not (@c FALSE)
+ *
+ * @since 2.4.0
+ */
+GtkWidget *pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable);
+
+/**
+ * Retrieves the main content box (vbox) from a pidgin dialog window
+ *
+ * @param dialog       The dialog window
+ * @param homogeneous  TRUE if all children are to be given equal space allotments. 
+ * @param spacing      the number of pixels to place by default between children
+ *
+ * @since 2.4.0
+ */
+GtkWidget *pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing);
+
+/**
+ * Retrieves the main content box (vbox) from a pidgin dialog window
+ *
+ * @param dialog       The dialog window
+ *
+ * @since 2.4.0
+ */
+GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog);
+
+/**
+ * Add a button to a dialog created by #pidgin_create_dialog.
+ *
+ * @param dialog         The dialog window
+ * @param label          The stock-id or the label for the button
+ * @param callback       The callback function for the button
+ * @param callbackdata   The user data for the callback function
+ *
+ * @return The created button.
+ * @since 2.4.0
+ */
+GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
+		GCallback callback, gpointer callbackdata);
+
+/**
+ * Retrieves the action area (button box) from a pidgin dialog window
+ *
+ * @param dialog       The dialog window
+ *
+ * @since 2.4.0
+ */
+GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog);
+
+/**
  * Toggles the sensitivity of a widget.
  *
  * @param widget    @c NULL. Used for signal handlers.
@@ -725,5 +780,15 @@
  */
 void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text);
 
+/**
+ * Automatically make a window transient to a suitable parent window.
+ *
+ * @param window    The window to make transient.
+ *
+ * @return  Whether the window was made transient or not.
+ * @since 2.4.0
+ */
+gboolean pidgin_auto_parent_window(GtkWidget *window);
+
 #endif /* _PIDGINUTILS_H_ */
 
--- a/pidgin/minidialog.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/minidialog.c	Sat Dec 22 23:18:08 2007 +0000
@@ -35,7 +35,40 @@
 #include "pidgin/pidgin.h"
 #include "pidgin/pidginstock.h"
 
-G_DEFINE_TYPE (PidginMiniDialog, pidgin_mini_dialog, GTK_TYPE_VBOX)
+static void     pidgin_mini_dialog_init       (PidginMiniDialog      *self);
+static void     pidgin_mini_dialog_class_init (PidginMiniDialogClass *klass);
+
+static gpointer pidgin_mini_dialog_parent_class = NULL;
+
+static void
+pidgin_mini_dialog_class_intern_init (gpointer klass)
+{
+	pidgin_mini_dialog_parent_class = g_type_class_peek_parent (klass);
+	pidgin_mini_dialog_class_init ((PidginMiniDialogClass*) klass);
+}
+
+GType
+pidgin_mini_dialog_get_type (void)
+{
+	static GType g_define_type_id = 0;
+	if (G_UNLIKELY (g_define_type_id == 0))
+	{
+		static const GTypeInfo g_define_type_info = {
+			sizeof (PidginMiniDialogClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) pidgin_mini_dialog_class_intern_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,   /* class_data */
+			sizeof (PidginMiniDialog),
+			0,      /* n_preallocs */
+			(GInstanceInitFunc) pidgin_mini_dialog_init,
+		};
+		g_define_type_id = g_type_register_static (GTK_TYPE_VBOX,
+			"PidginMiniDialog", &g_define_type_info, 0);
+	}
+	return g_define_type_id;
+}
 
 enum
 {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidgintooltip.c	Sat Dec 22 23:18:08 2007 +0000
@@ -0,0 +1,299 @@
+/**
+ * @file pidgintooltip.c Pidgin Tooltip API
+ * @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 "prefs.h"
+#include "pidgin.h"
+#include "pidgintooltip.h"
+#include "debug.h"
+
+struct
+{
+	GtkWidget *widget;
+	int timeout;
+	GdkRectangle tip_rect;
+	GtkWidget *tipwindow;
+	PidginTooltipPaint paint_tooltip;
+} pidgin_tooltip;
+
+typedef struct
+{
+	GtkWidget *widget;
+	gpointer userdata;
+	PidginTooltipCreateForTree create_tooltip;
+	PidginTooltipPaint paint_tooltip;
+	GtkTreePath *path;
+} PidginTooltipData;
+
+static void
+destroy_tooltip_data(PidginTooltipData *data)
+{
+	gtk_tree_path_free(data->path);
+	g_free(data);
+}
+
+void pidgin_tooltip_destroy()
+{
+	if (pidgin_tooltip.timeout > 0) {
+		g_source_remove(pidgin_tooltip.timeout);
+		pidgin_tooltip.timeout = 0;
+	}
+	if (pidgin_tooltip.tipwindow) {
+		gtk_widget_destroy(pidgin_tooltip.tipwindow);
+		pidgin_tooltip.tipwindow = NULL;
+	}
+}
+
+static gboolean
+pidgin_tooltip_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+	if (pidgin_tooltip.paint_tooltip)
+		pidgin_tooltip.paint_tooltip(widget, data);
+	return FALSE;
+}
+
+static GtkWidget*
+setup_tooltip_window()
+{
+	const char *name;
+	GtkWidget *tipwindow;
+
+	tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
+	name = gtk_window_get_title(GTK_WINDOW(pidgin_tooltip.widget));
+#if GTK_CHECK_VERSION(2,10,0)
+	gtk_window_set_type_hint(GTK_WINDOW(tipwindow), GDK_WINDOW_TYPE_HINT_TOOLTIP);
+#endif
+	gtk_widget_set_app_paintable(tipwindow, TRUE);
+	gtk_window_set_title(GTK_WINDOW(tipwindow), name ? name : _("Pidgin Tooltip"));
+	gtk_window_set_resizable(GTK_WINDOW(tipwindow), FALSE);
+	gtk_widget_set_name(tipwindow, "gtk-tooltips");
+	gtk_widget_ensure_style(tipwindow);
+	gtk_widget_realize(tipwindow);
+	return tipwindow;
+}
+
+static void
+setup_tooltip_window_position(gpointer data, int w, int h)
+{
+	int sig;
+	int scr_w, scr_h, x, y;
+#if GTK_CHECK_VERSION(2,2,0)
+	int mon_num;
+	GdkScreen *screen = NULL;
+#endif
+	GdkRectangle mon_size;
+	GtkWidget *tipwindow = pidgin_tooltip.tipwindow;
+
+#if GTK_CHECK_VERSION(2,2,0)
+	gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
+	mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
+	gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
+
+	scr_w = mon_size.width + mon_size.x;
+	scr_h = mon_size.height + mon_size.y;
+#else
+	scr_w = gdk_screen_width();
+	scr_h = gdk_screen_height();
+	gdk_window_get_pointer(NULL, &x, &y, NULL);
+	mon_size.x = 0;
+	mon_size.y = 0;
+#endif
+
+#if GTK_CHECK_VERSION(2,2,0)
+	if (w > mon_size.width)
+		w = mon_size.width - 10;
+
+	if (h > mon_size.height)
+		h = mon_size.height - 10;
+#endif
+	x -= ((w >> 1) + 4);
+
+	if ((y + h + 4) > scr_h)
+		y = y - h - 5;
+	else
+		y = y + 6;
+
+	if (y < mon_size.y)
+		y = mon_size.y;
+
+	if (y != mon_size.y) {
+		if ((x + w) > scr_w)
+			x -= (x + w + 5) - scr_w;
+		else if (x < mon_size.x)
+			x = mon_size.x;
+	} else {
+		x -= (w / 2 + 10);
+		if (x < mon_size.x)
+			x = mon_size.x;
+	}
+
+	gtk_widget_set_size_request(tipwindow, w, h);
+	gtk_window_move(GTK_WINDOW(tipwindow), x, y);
+	gtk_widget_show(tipwindow);
+
+	g_signal_connect(G_OBJECT(tipwindow), "expose_event",
+			G_CALLBACK(pidgin_tooltip_expose_event), data);
+
+	/* Hide the tooltip when the widget is destroyed */
+	sig = g_signal_connect(G_OBJECT(pidgin_tooltip.widget), "destroy", G_CALLBACK(pidgin_tooltip_destroy), NULL);
+	g_signal_connect_swapped(G_OBJECT(tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig));
+}
+
+void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata,
+		PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip)
+{
+	GtkWidget *tipwindow;
+	int w, h;
+
+	pidgin_tooltip_destroy();
+
+	pidgin_tooltip.widget = gtk_widget_get_toplevel(widget);
+	pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window();
+	pidgin_tooltip.paint_tooltip = paint_tooltip;
+
+	if (!create_tooltip(tipwindow, userdata, &w, &h)) {
+		pidgin_tooltip_destroy();
+		return;
+	}
+	setup_tooltip_window_position(userdata, w, h);
+}
+
+static void
+reset_data_treepath(PidginTooltipData *data)
+{
+	gtk_tree_path_free(data->path);
+	data->path = NULL;
+}
+
+static void
+pidgin_tooltip_draw(PidginTooltipData *data)
+{
+	GtkWidget *tipwindow;
+	GtkTreePath *path = NULL;
+	int w, h;
+
+	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(data->widget),
+				pidgin_tooltip.tip_rect.x,
+				pidgin_tooltip.tip_rect.y + (pidgin_tooltip.tip_rect.height/2),
+				&path, NULL, NULL, NULL)) {
+		pidgin_tooltip_destroy();
+		return;
+	}
+
+	if (data->path) {
+		if (gtk_tree_path_compare(data->path, path) == 0) {
+			gtk_tree_path_free(path);
+			return;
+		}
+		gtk_tree_path_free(data->path);
+		data->path = NULL;
+	}
+
+	pidgin_tooltip_destroy();
+
+	pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget);
+	pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window();
+	pidgin_tooltip.paint_tooltip = data->paint_tooltip;
+
+	if (!data->create_tooltip(tipwindow, path, data->userdata, &w, &h)) {
+		pidgin_tooltip_destroy();
+		gtk_tree_path_free(path);
+		return;
+	}
+
+	setup_tooltip_window_position(data->userdata, w, h);
+
+	data->path = path;
+	g_signal_connect_swapped(G_OBJECT(tipwindow), "destroy",
+			G_CALLBACK(reset_data_treepath), data);
+}
+
+static gboolean
+pidgin_tooltip_timeout(gpointer data)
+{
+	pidgin_tooltip.timeout = 0;
+	pidgin_tooltip_draw(data);
+	return FALSE;
+}
+
+static gboolean
+row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer userdata)
+{
+	GtkTreePath *path;
+	int delay;
+
+	if (event->window != gtk_tree_view_get_bin_window(GTK_TREE_VIEW(tv)))
+		return FALSE;    /* The cursor is probably on the TreeView's header. */
+
+	/* XXX: probably use something more generic? */
+	delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
+	if (delay == 0)
+		return FALSE;
+
+	if (pidgin_tooltip.timeout) {
+		if ((event->y >= pidgin_tooltip.tip_rect.y) && ((event->y - pidgin_tooltip.tip_rect.height) <= pidgin_tooltip.tip_rect.y))
+			return FALSE;
+		/* We've left the cell.  Remove the timeout and create a new one below */
+		pidgin_tooltip_destroy();
+	}
+
+	gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
+
+	if (path == NULL) {
+		pidgin_tooltip_destroy();
+		return FALSE;
+	}
+
+	gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &pidgin_tooltip.tip_rect);
+	gtk_tree_path_free(path);
+
+	pidgin_tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_tooltip_timeout, userdata);
+
+	return FALSE;
+}
+
+static gboolean
+row_leave_cb(GtkWidget *tv, GdkEvent *event, gpointer userdata)
+{
+	pidgin_tooltip_destroy();
+	return FALSE;
+}
+
+gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata,
+		PidginTooltipCreateForTree create_tooltip, PidginTooltipPaint paint_tooltip)
+{
+	PidginTooltipData *tdata = g_new0(PidginTooltipData, 1);
+	tdata->widget = tree;
+	tdata->userdata = userdata;
+	tdata->create_tooltip = create_tooltip;
+	tdata->paint_tooltip = paint_tooltip;
+
+	g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), tdata);
+	g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), NULL);
+	g_signal_connect_swapped(G_OBJECT(tree), "destroy", G_CALLBACK(destroy_tooltip_data), tdata);
+	return TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidgintooltip.h	Sat Dec 22 23:18:08 2007 +0000
@@ -0,0 +1,97 @@
+/**
+ * @file pidgintooltip.h Pidgin Tooltip API
+ * @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
+ */
+#ifndef _PIDGIN_TOOLTIP_H_
+#define _PIDGIN_TOOLTIP_H_
+
+#include <gtk/gtk.h>
+
+/**
+ * @param tipwindow  The window for the tooltip.
+ * @param path       The GtkTreePath representing the row under the cursor.
+ * @param userdata   The userdata set during pidgin_tooltip_setup_for_treeview.
+ * @param w          The value of this should be set to the desired width of the tooltip window.
+ * @param h          The value of this should be set to the desired height of the tooltip window.
+ *
+ * @return  @c TRUE if the tooltip was created correctly, @c FALSE otherwise.
+ * @since 2.4.0
+ */
+typedef gboolean (*PidginTooltipCreateForTree)(GtkWidget *tipwindow,
+			GtkTreePath *path, gpointer userdata, int *w, int *h);
+
+/**
+ * @param tipwindow  The window for the tooltip.
+ * @param userdata   The userdata set during pidgin_tooltip_show.
+ * @param w          The value of this should be set to the desired width of the tooltip window.
+ * @param h          The value of this should be set to the desired height of the tooltip window.
+ *
+ * @return  @c TRUE if the tooltip was created correctly, @c FALSE otherwise.
+ * @since 2.4.0
+ */
+typedef gboolean (*PidginTooltipCreate)(GtkWidget *tipwindow,
+			gpointer userdata, int *w, int *h);
+
+/**
+ * @param  tipwindow   The window for the tooltip.
+ * @param  userdata    The userdata set during pidgin_tooltip_setup_for_treeview or pidgin_tooltip_show.
+ *
+ * @return  @c TRUE if the tooltip was painted correctly, @c FALSE otherwise.
+ * @since 2.4.0
+ */
+typedef gboolean (*PidginTooltipPaint)(GtkWidget *tipwindow, gpointer userdata);
+
+/**
+ * Setup tooltip drawing functions for a treeview.
+ *
+ * @param tree         The treeview
+ * @param userdata     The userdata to send to the callback functions
+ * @param create_cb    Callback function to create the tooltip for a GtkTreePath
+ * @param paint_cb     Callback function to paint the tooltip
+ *
+ * @return   @c TRUE if the tooltip callbacks were setup correctly.
+ * @since 2.4.0
+ */
+gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata,
+		PidginTooltipCreateForTree create_cb, PidginTooltipPaint paint_cb);
+
+/**
+ * Destroy the tooltip.
+ * @since 2.4.0
+ */
+void pidgin_tooltip_destroy(void);
+
+/**
+ * Create and show a tooltip.
+ *
+ * @param widget      The widget the tooltip is for
+ * @param userdata    The userdata to send to the callback functions
+ * @param create_cb    Callback function to create the tooltip from the GtkTreePath
+ * @param paint_cb     Callback function to paint the tooltip
+ *
+ * @since 2.4.0
+ */
+void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata,
+		PidginTooltipCreate create_cb, PidginTooltipPaint paint_cb);
+#endif
--- a/pidgin/plugins/crazychat/cc_network.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/plugins/crazychat/cc_network.c	Sat Dec 22 23:18:08 2007 +0000
@@ -529,7 +529,7 @@
 	while (total < len) {
 		n = send(s, buf + total, bytesleft, 0);
 		if (n == -1) {
-			Debug("ERROR: %s\n", strerror(errno));
+			Debug("ERROR: %s\n", g_strerror(errno));
 			return -1;
 		}
 		total += n;
--- a/pidgin/plugins/pidginrc.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/plugins/pidginrc.c	Sat Dec 22 23:18:08 2007 +0000
@@ -30,17 +30,29 @@
 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-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"
 };
 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-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"
 };
 static const gchar *color_names[] = {
 	N_("Cursor Color"),
 	N_("Secondary Cursor Color"),
-	N_("Hyperlink Color")
+	N_("Hyperlink Color"),
+	N_("Sent Message Name Color"),
+	N_("Received Message Name Color"),
+	N_("Highlighted Message Name Color"),
+	N_("Action Message Name Color")
 };
 static GtkWidget *color_widgets[G_N_ELEMENTS(color_prefs)];
 
--- a/pidgin/win32/gtkwin32dep.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/win32/gtkwin32dep.c	Sat Dec 22 23:18:08 2007 +0000
@@ -40,6 +40,7 @@
 
 #include "debug.h"
 #include "notify.h"
+#include "network.h"
 
 #include "resource.h"
 #include "idletrack.h"
@@ -51,6 +52,7 @@
 #include "gtkwin32dep.h"
 #include "win32dep.h"
 #include "gtkconv.h"
+#include "gtkconn.h"
 #include "util.h"
 #include "wspell.h"
 
@@ -64,6 +66,7 @@
 
 typedef BOOL (CALLBACK* LPFNFLASHWINDOWEX)(PFLASHWINFO);
 static LPFNFLASHWINDOWEX MyFlashWindowEx = NULL;
+static gboolean pwm_handles_connections = TRUE;
 
 
 /*
@@ -202,6 +205,43 @@
 #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13)
 #define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14)
 
+static void*
+winpidgin_netconfig_changed_cb(void *data)
+{
+	pwm_handles_connections = FALSE;
+
+	return NULL;
+}
+
+static void*
+winpidgin_get_handle(void)
+{
+	static int handle;
+
+	return &handle;
+}
+
+static gboolean
+winpidgin_pwm_reconnect()
+{
+	purple_signal_disconnect(purple_network_get_handle(), "network-configuration-changed",
+		winpidgin_get_handle(), PURPLE_CALLBACK(winpidgin_netconfig_changed_cb));
+
+	if (pwm_handles_connections == TRUE) {
+		PurpleConnectionUiOps *ui_ops = pidgin_connections_get_ui_ops();
+
+		purple_debug_info("winpidgin", "Resumed from standby, reconnecting accounts.\n");
+
+		if (ui_ops != NULL && ui_ops->network_connected != NULL)
+			ui_ops->network_connected();
+	} else {
+		purple_debug_info("winpidgin", "Resumed from standby, gtkconn will handle reconnecting.\n");
+		pwm_handles_connections = TRUE;
+	}
+
+	return FALSE;
+}
+
 static LRESULT CALLBACK message_window_handler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
 
 	if (msg == PIDGIN_WM_FOCUS_REQUEST) {
@@ -213,6 +253,28 @@
 		purple_debug_info("winpidgin", "Got protocol handler request: %s\n", proto_msg ? proto_msg : "");
 		purple_got_protocol_handler_uri(proto_msg);
 		return TRUE;
+	} else if (msg == WM_POWERBROADCAST) {
+		if (wparam == PBT_APMQUERYSUSPEND) {
+			purple_debug_info("winpidgin", "Windows requesting permission to suspend.\n");
+			return TRUE;
+		} else if (wparam == PBT_APMSUSPEND) {
+			PurpleConnectionUiOps *ui_ops = pidgin_connections_get_ui_ops();
+
+			purple_debug_info("winpidgin", "Entering system standby, disconnecting accounts.\n");
+
+			if (ui_ops != NULL && ui_ops->network_disconnected != NULL)
+				ui_ops->network_disconnected();
+
+			purple_signal_connect(purple_network_get_handle(), "network-configuration-changed", winpidgin_get_handle(),
+				PURPLE_CALLBACK(winpidgin_netconfig_changed_cb), NULL);
+
+			return TRUE;
+		} else if (wparam == PBT_APMRESUMESUSPEND) {
+			purple_debug_info("winpidgin", "Resuming from system standby.\n");
+			/* TODO: It seems like it'd be wise to use the NLA message, if possible, instead of this. */
+			purple_timeout_add_seconds(1, winpidgin_pwm_reconnect, NULL);
+			return TRUE;
+		}
 	}
 
 	return DefWindowProc(hwnd, msg, wparam, lparam);
@@ -242,7 +304,7 @@
 
 	/* Create the window */
 	if(!(win_hwnd = CreateWindow(wname, TEXT("WinpidginMsgWin"), 0, 0, 0, 0, 0,
-			HWND_MESSAGE, NULL, winpidgin_exe_hinstance(), 0))) {
+			NULL, NULL, winpidgin_exe_hinstance(), 0))) {
 		purple_debug_error("winpidgin",
 			"Unable to create message window.\n");
 		return NULL;
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Sat Dec 22 23:18:08 2007 +0000
@@ -1154,11 +1154,21 @@
 !macro RunCheckMacro UN
 Function ${UN}RunCheck
   Push $R0
-  System::Call 'kernel32::OpenMutex(i 2031617, b 0, t "pidgin_is_running") i .R0'
-  IntCmp $R0 0 done
-    MessageBox MB_OK|MB_ICONEXCLAMATION $(PIDGIN_IS_RUNNING) /SD IDOK
+  Push $R1
+
+  IntOp $R1 0 + 0
+  retry_runcheck:
+  ; Close the Handle (needed if we're retrying)
+  IntCmp $R1 0 +2
+    System::Call 'kernel32::CloseHandle(i $R1) i .R1'
+  System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_is_running") i .R1 ?e'
+  Pop $R0
+  IntCmp $R0 0 +3 ;This could check for ERROR_ALREADY_EXISTS(183), but lets just assume
+    MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(PIDGIN_IS_RUNNING) /SD IDCANCEL IDRETRY retry_runcheck
     Abort
+
   done:
+  Pop $R1
   Pop $R0
 FunctionEnd
 !macroend
@@ -1169,10 +1179,16 @@
   Push $R0
   Push $R1
   Push $R2
-  System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_installer_running") i .r1 ?e'
+
+  IntOp $R1 0 + 0
+  retry_runcheck:
+  ; Close the Handle (needed if we're retrying)
+  IntCmp $R1 0 +2
+    System::Call 'kernel32::CloseHandle(i $R1) i .R1'
+  System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_installer_running") i .R1 ?e'
   Pop $R0
-  StrCmp $R0 0 +3
-    MessageBox MB_OK|MB_ICONEXCLAMATION $(INSTALLER_IS_RUNNING) /SD IDOK
+  IntCmp $R0 0 +3 ;This could check for ERROR_ALREADY_EXISTS(183), but lets just assume
+    MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(INSTALLER_IS_RUNNING) /SD IDCANCEL IDRETRY retry_runcheck
     Abort
   Call RunCheck
   StrCpy $name "Pidgin ${PIDGIN_VERSION}"
--- a/pidgin/win32/winpidgin.c	Sat Dec 22 17:54:30 2007 +0000
+++ b/pidgin/win32/winpidgin.c	Sat Dec 22 23:18:08 2007 +0000
@@ -445,11 +445,12 @@
 #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13)
 #define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14)
 
-static BOOL winpidgin_set_running() {
+static BOOL winpidgin_set_running(BOOL fail_if_running) {
 	HANDLE h;
 
 	if ((h = CreateMutex(NULL, FALSE, "pidgin_is_running"))) {
-		if (GetLastError() == ERROR_ALREADY_EXISTS) {
+		DWORD err = GetLastError();
+		if (err == ERROR_ALREADY_EXISTS && fail_if_running) {
 			HWND msg_win;
 
 			printf("An instance of Pidgin is already running.\n");
@@ -465,7 +466,8 @@
 				NULL, MB_OK | MB_TOPMOST);
 
 			return FALSE;
-		}
+		} else
+			printf("Error (%u) accessing \"pidgin_is_running\" mutex.\n", (UINT) err);
 	}
 	return TRUE;
 }
@@ -628,8 +630,8 @@
 
 	winpidgin_set_locale();
 	/* If help, version or multiple flag used, do not check Mutex */
-	if (!strstr(lpszCmdLine, "-h") && !strstr(lpszCmdLine, "-v") && !strstr(lpszCmdLine, "-m"))
-		if (!getenv("PIDGIN_MULTI_INST") && !winpidgin_set_running())
+	if (!strstr(lpszCmdLine, "-h") && !strstr(lpszCmdLine, "-v"))
+		if (!winpidgin_set_running(getenv("PIDGIN_MULTI_INST") == NULL && strstr(lpszCmdLine, "-m") == NULL))
 			return 0;
 
 	/* Now we are ready for Pidgin .. */
--- a/po/POTFILES.in	Sat Dec 22 17:54:30 2007 +0000
+++ b/po/POTFILES.in	Sat Dec 22 23:18:08 2007 +0000
@@ -179,6 +179,7 @@
 libpurple/sslconn.c
 libpurple/status.c
 libpurple/util.c
+libpurple/win32/libc_interface.c
 pidgin.desktop.in
 pidgin/eggtrayicon.c
 pidgin/gtkaccount.c
--- a/po/de.po	Sat Dec 22 17:54:30 2007 +0000
+++ b/po/de.po	Sat Dec 22 23:18:08 2007 +0000
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-11-30 10:22+0100\n"
-"PO-Revision-Date: 2007-11-30 10:21+0100\n"
+"POT-Creation-Date: 2007-12-19 10:17+0100\n"
+"PO-Revision-Date: 2007-12-19 10:17+0100\n"
 "Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -1019,6 +1019,9 @@
 msgid "Open File..."
 msgstr "Datei öffnen..."
 
+msgid "Choose Location..."
+msgstr "Wählen Sie einen Ort..."
+
 msgid "Buddy logs in"
 msgstr "Buddy meldet sich an"
 
@@ -3412,12 +3415,12 @@
 msgid "Plaintext Authentication"
 msgstr "Klartext-Authentifizierung"
 
+msgid "Invalid response from server."
+msgstr "Ungültige Serverantwort."
+
 msgid "Server does not use any supported authentication method"
 msgstr "Der Server benutzt keine der unterstützten Authentifizierungsmethoden"
 
-msgid "Invalid response from server."
-msgstr "Ungültige Serverantwort."
-
 msgid ""
 "This server requires plaintext authentication over an unencrypted "
 "connection.  Allow this and continue authentication?"
@@ -3830,6 +3833,9 @@
 msgid "Write error"
 msgstr "Schreibfehler"
 
+msgid "Ping timeout"
+msgstr "Ping-Zeitüberschreitung"
+
 msgid "Read Error"
 msgstr "Fehler beim Lesen"
 
@@ -5766,6 +5772,12 @@
 msgid "Server port"
 msgstr "Server-Port"
 
+msgid "Could not join chat room"
+msgstr "Konnte Chatraum nicht betreten"
+
+msgid "Invalid chat room name"
+msgstr "Ungültiger Chatraumname"
+
 msgid "Server closed the connection."
 msgstr "Der Server hat die Verbindung beendet."
 
@@ -6569,9 +6581,6 @@
 msgid "_Exchange:"
 msgstr "A_ustausch:"
 
-msgid "Invalid chat name specified."
-msgstr "Ungültiger Chat-Name angegeben."
-
 msgid "Your IM Image was not sent. You cannot send IM Images in AIM chats."
 msgstr ""
 "Ihr IM-Bild wurde nicht gesendet. Sie können keine IM-Bilder in AIM-Chats "
@@ -6707,11 +6716,13 @@
 msgstr "Anzeigen, wie lange ich untätig war"
 
 msgid ""
-"Always use ICQ proxy server for file transfers\n"
-"(slower, but does not reveal your IP address)"
-msgstr ""
-"Benutze immer den AIM/ICQ-Proxyserver\n"
-"(langsamer, aber zeigt Ihre IP-Adresse nicht)"
+"Always use AIM/ICQ proxy server for\n"
+"file transfers and direct IM (slower,\n"
+"but does not reveal your IP address)"
+msgstr ""
+"Benutze immer den AIM/ICQ-Proxyserver für\n"
+"Dateiübertragungen und Direkt-IM (langsamer,\n"
+"aber zeigt Ihre IP-Adresse nicht)"
 
 #, c-format
 msgid "Asking %s to connect to us at %s:%hu for Direct IM."
@@ -9508,26 +9519,14 @@
 msgstr "Musik hören"
 
 #, c-format
-msgid "%s changed status from %s to %s"
-msgstr "%s hat den Status von %s zu %s geändert"
-
-#, c-format
 msgid "%s (%s) changed status from %s to %s"
 msgstr "%s (%s) hat den Status von %s zu %s geändert"
 
 #, c-format
-msgid "%s is now %s"
-msgstr "%s ist jetzt %s"
-
-#, c-format
 msgid "%s (%s) is now %s"
 msgstr "%s (%s) ist jetzt %s"
 
 #, c-format
-msgid "%s is no longer %s"
-msgstr "%s ist nicht mehr %s"
-
-#, c-format
 msgid "%s (%s) is no longer %s"
 msgstr "%s (%s) ist nicht mehr %s"
 
@@ -9633,6 +9632,28 @@
 msgid "Unable to connect to %s: %s"
 msgstr "Verbindung zu %s nicht möglich: %s"
 
+#. 10053
+#, c-format
+msgid "Connection interrupted by other software on your computer."
+msgstr ""
+"Die Verbindung wurde von einer anderen Software auf ihrem Computer "
+"unterbrochen."
+
+#. 10054
+#, fuzzy, c-format
+msgid "Remote host closed connection."
+msgstr "Der entfernte Host hat die Verbindung beendet."
+
+#. 10060
+#, c-format
+msgid "Connection timed out."
+msgstr "Verbindungsabbruch wegen Zeitüberschreitung."
+
+#. 10061
+#, c-format
+msgid "Connection refused."
+msgstr "Verbindung abgelehnt."
+
 msgid "Internet Messenger"
 msgstr "Internet-Sofortnachrichtendienst"
 
@@ -9995,6 +10016,17 @@
 "\n"
 "<b>Konto:</b> %s"
 
+#, c-format
+msgid ""
+"\n"
+"<b>Topic:</b> %s"
+msgstr ""
+"\n"
+"<b>Thema:</b> %s"
+
+msgid "(no topic set)"
+msgstr "(kein Thema gesetzt)"
+
 msgid "Buddy Alias"
 msgstr "Buddy-Alias"
 
@@ -10431,6 +10463,14 @@
 msgid "User has typed something and stopped"
 msgstr "Benutzer hat etwas getippt und wartet nun"
 
+#, c-format
+msgid ""
+"\n"
+"%s has typed something and stopped"
+msgstr ""
+"\n"
+"%s hat etwas getippt und wartet nun"
+
 #. Build the Send To menu
 msgid "S_end To"
 msgstr "S_enden an"
@@ -11055,15 +11095,6 @@
 msgid "_Reset formatting"
 msgstr "Formatierung _zurücksetzen"
 
-msgid "_Smile!"
-msgstr "_Lächeln!"
-
-msgid "_Insert"
-msgstr "_Einfügen"
-
-msgid "_Font"
-msgstr "_Schrift"
-
 msgid "Hyperlink color"
 msgstr "Hyperlink-Farbe"
 
@@ -11077,6 +11108,32 @@
 msgstr ""
 "Farbe zum Darstellen von Hyperlinks, wenn sich die Maus darüber befindet."
 
+#, fuzzy
+msgid "Sent Message Name Color"
+msgstr "Gesendete Nachrichten"
+
+msgid "Color to draw the name of a message you sent."
+msgstr ""
+
+#, fuzzy
+msgid "Received Message Name Color"
+msgstr "Empfangene Nachrichten"
+
+msgid "Color to draw the name of a message you received."
+msgstr ""
+
+msgid "\"Attention\" Name Color"
+msgstr ""
+
+msgid "Color to draw the name of a message you received containing your name."
+msgstr ""
+
+msgid "Action Message Name Color"
+msgstr ""
+
+msgid "Color to draw the name of an action message."
+msgstr ""
+
 msgid "_Copy E-Mail Address"
 msgstr "Kopiere _E-Mail-Adresse"
 
@@ -11130,6 +11187,15 @@
 msgid "_Save Image..."
 msgstr "Bild _speichern..."
 
+msgid "_Font"
+msgstr "_Schrift"
+
+msgid "_Insert"
+msgstr "_Einfügen"
+
+msgid "S_mile!"
+msgstr "_Lächeln!"
+
 msgid "Select Font"
 msgstr "Schriftart wählen"
 
@@ -11256,6 +11322,9 @@
 msgid "_Horizontal rule"
 msgstr "_Horizontale Linie"
 
+msgid "_Smile!"
+msgstr "_Lächeln!"
+
 #, c-format
 msgid ""
 "Are you sure you want to permanently delete the log of the conversation with "
@@ -11367,6 +11436,33 @@
 
 #, c-format
 msgid ""
+"%s %s\n"
+"Usage: %s [OPTION]...\n"
+"\n"
+"  -c, --config=DIR    use DIR for config files\n"
+"  -d, --debug         print debugging messages to stdout\n"
+"  -h, --help          display this help and exit\n"
+"  -m, --multiple      do not ensure single instance\n"
+"  -n, --nologin       don't automatically login\n"
+"  -l, --login[=NAME]  automatically login (optional argument NAME specifies\n"
+"                      account(s) to use, separated by commas)\n"
+"  -v, --version       display the current version and exit\n"
+msgstr ""
+"%s %s\n"
+"Benutzung: %s [OPTION]...\n"
+"\n"
+"  -c, --config=VERZ   benutze VERZ als Konfigurationsverzeichnis\n"
+"  -d, --debug         gibt Debugging-Meldungen nach stdout aus\n"
+"  -h, --help          zeigt diese Hilfe und beendet das Programm\n"
+"  -m, --multiple      mehrere Instanzen erlauben\n"
+"  -n, --nologin       nicht automatisch anmelden\n"
+"  -l, --login[=NAME]  automatische Anmeldung (optionales Argument \n"
+"                      NAME bestimmt Konto(n), die benutzt werden\n"
+"                      sollen, getrennt durch Kommata)\n"
+"  -v, --version       zeigt aktuelle Version und beendet das Programm\n"
+
+#, c-format
+msgid ""
 "%s %s has segfaulted and attempted to dump a core file.\n"
 "This is a bug in the software and has happened through\n"
 "no fault of your own.\n"
@@ -11675,8 +11771,8 @@
 "This is how your outgoing message text will appear when you use protocols "
 "that support formatting."
 msgstr ""
-"So wird der ausgehende Nachrichtentext aussehen, wenn Sie "
-"Protokollebenutzen, die Formatierung unterstützen."
+"So wird der ausgehende Nachrichtentext aussehen, wenn Sie Protokolle "
+"benutzen, die Formatierung unterstützen."
 
 msgid "Cannot start proxy configuration program."
 msgstr "Kann das Proxy-Konfigurationsprogramm nicht starten."
@@ -11699,6 +11795,9 @@
 msgid "Ports"
 msgstr "Ports"
 
+msgid "_Enable automatic router port forwarding"
+msgstr "Automatischer _Router-Port-Weiterleitung aktivieren"
+
 msgid "_Manually specify range of ports to listen on"
 msgstr "Port-Bereich, auf dem gehört werden soll, _manuell bestimmen"
 
@@ -12751,6 +12850,10 @@
 msgid "Hyperlink Color"
 msgstr "Hyperlink-Farbe"
 
+#, fuzzy
+msgid "Highlighted Message Name Color"
+msgstr "Hervorgehobene Nachrichten"
+
 msgid "GtkTreeView Horizontal Separation"
 msgstr "GtkTreeview horizontaler Abstand"