changeset 27746:e13759a83714

propagate from branch 'im.pidgin.pidgin' (head cff05fbceab1d88163770d13a4c7a6116bdeb8ee) to branch 'im.pidgin.pidgin.yaz' (head 4c2ca466febbc129edc2012fd6ce5769696116d0)
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Sat, 15 Dec 2007 05:15:31 +0000
parents b9197011ddd6 (current diff) a53f4b1813df (diff)
children 2be2eec7d273
files configure.ac libpurple/protocols/bonjour/mdns_howl.c libpurple/protocols/irc/irc.c libpurple/protocols/irc/parse.c libpurple/protocols/jabber/jabber.c libpurple/protocols/msn/msn.c libpurple/protocols/oscar/family_icbm.c libpurple/protocols/oscar/oscar.c libpurple/protocols/yahoo/yahoo.c libpurple/util.c pidgin/gtkconv.c pidgin/gtkimhtml.c pidgin/gtkmain.c pidgin/gtkprefs.c pidgin/gtkutils.c
diffstat 112 files changed, 2315 insertions(+), 1243 deletions(-) [+]
line wrap: on
line diff
--- a/.mtn-ignore	Sat Dec 15 05:12:24 2007 +0000
+++ b/.mtn-ignore	Sat Dec 15 05:15:31 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/COPYRIGHT	Sat Dec 15 05:12:24 2007 +0000
+++ b/COPYRIGHT	Sat Dec 15 05:15:31 2007 +0000
@@ -309,6 +309,7 @@
 Tim Ringenbach
 Dennis Ristuccia
 Lee Roach
+Eion Robb
 Rhett Robinson
 Luciano Miguel Ferreira Rocha
 Andrew Rodland
--- a/ChangeLog	Sat Dec 15 05:12:24 2007 +0000
+++ b/ChangeLog	Sat Dec 15 05:15:31 2007 +0000
@@ -1,5 +1,21 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.3.2 (??/??/????):
+	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.
+
+	Finch:
+	* Color is used in the buddylist to indicate status, and the conversation
+	  window to indicate various message attributes. Look at the sample gntrc
+	  file in the man-page for details.
+	* The default keybinding for dump-screen is now M-D and uses a file
+	  request dialog. M-d will properly delete-forward-word, and M-f has been
+	  fixed to imitate readline's behavior.
+
 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 15 05:12:24 2007 +0000
+++ b/ChangeLog.API	Sat Dec 15 05:15:31 2007 +0000
@@ -1,5 +1,16 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.3.2 (??/??/????):
+	Finch:
+		libgnt:
+		* Added gnt_tree_set_row_color to set the color for a row in a tree.
+		* Added gnt_style_get_string_list
+		* Added gnt_color_add_pair to define a new color.
+		* Added gnt_colors_get_color to get an ncurses color value from a
+		  string.
+		* Added gnt_style_get_color to get a color pair from an entry in
+		  ~/.gntrc
+
 version 2.3.0 (11/24/2007):
 	libpurple:
 		Added:
--- a/Makefile.am	Sat Dec 15 05:12:24 2007 +0000
+++ b/Makefile.am	Sat Dec 15 05:15:31 2007 +0000
@@ -9,6 +9,7 @@
 		README.MTN \
 		README.mingw \
 		config.h.mingw \
+		doxy2devhelp.xsl \
 		gaim.pc.in \
 		gaim-uninstalled.pc.in \
 		intltool-extract.in \
@@ -50,7 +51,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/configure.ac	Sat Dec 15 05:12:24 2007 +0000
+++ b/configure.ac	Sat Dec 15 05:15:31 2007 +0000
@@ -46,7 +46,7 @@
 m4_define([purple_lt_current], [3])
 m4_define([purple_major_version], [2])
 m4_define([purple_minor_version], [3])
-m4_define([purple_micro_version], [1])
+m4_define([purple_micro_version], [2])
 m4_define([purple_version_suffix], [devel])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
@@ -55,7 +55,7 @@
 m4_define([gnt_lt_current], [3])
 m4_define([gnt_major_version], [2])
 m4_define([gnt_minor_version], [3])
-m4_define([gnt_micro_version], [1])
+m4_define([gnt_micro_version], [2])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
@@ -328,7 +328,7 @@
 	enable_gtkui="$enableval", enable_gtkui="yes")
 AC_ARG_ENABLE(consoleui, [AC_HELP_STRING([--disable-consoleui],
 		[compile without console user interface])],
-	enable_consoleui=$enableval, enable_consoleui=yes)
+	[enable_consoleui=$enableval force_finch=$enableval], [enable_consoleui=yes force_finch=no])
 
 dnl #######################################################################
 dnl # Check for GTK+ 2.0 and other things used by the GTK UI
@@ -622,6 +622,14 @@
 	fi
 fi
 
+if test "x$force_finch" = "xyes" -a "x$enable_consoleui" != "xyes"; then
+	AC_MSG_ERROR([
+
+Finch will not be built. You need to install ncursesw (or ncurses) and its development headers.
+
+])
+fi
+
 AC_SUBST(GNT_LIBS)
 AC_SUBST(GNT_CFLAGS)
 AM_CONDITIONAL(ENABLE_GNT, test "x$enable_consoleui" = "xyes")
@@ -714,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
@@ -952,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/'`
@@ -1041,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/'`
@@ -1079,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")
@@ -1328,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 #######################################################################
@@ -1651,16 +1630,24 @@
 			])
 			mozilla_nspr="mozilla-nspr"
 			mozilla_nss="mozilla-nss"
-		else
-			if `$PKG_CONFIG --exists nss`; then
-				PKG_CHECK_MODULES(NSS, nss, [
-					have_nss="yes"
-				], [
-					AC_MSG_RESULT(no)
-				])
-				mozilla_nspr="nspr"
-				mozilla_nss="nss"
-			fi
+		elif `$PKG_CONFIG --exists nss`; then
+			PKG_CHECK_MODULES(NSS, nss, [
+				have_nss="yes"
+			], [
+				AC_MSG_RESULT(no)
+				have_nss="no"
+			])
+			mozilla_nspr="nspr"
+			mozilla_nss="nss"
+		elif `$PKG_CONFIG --exists microb-engine-nss`; then
+			PKG_CHECK_MODULES(NSS, microb-engine-nss, [
+				have_nss="yes"
+			], [
+				AC_MSG_RESULT(no)
+				have_nss="no"
+			])
+			mozilla_nspr="mozilla-nspr"
+			mozilla_nss="microb-engine-nss"
 		fi
 
 		if test "x$have_nss" = "xyes"; then
--- a/doc/account-signals.dox	Sat Dec 15 05:12:24 2007 +0000
+++ b/doc/account-signals.dox	Sat Dec 15 05:15:31 2007 +0000
@@ -9,6 +9,7 @@
   @signal account-setting-info
   @signal account-set-info
   @signal account-status-changed
+  @signal account-alias-changed
   @signal account-authorization-requested
   @signal account-authorization-denied
   @signal account-authorization-granted
--- a/doc/blist-signals.dox	Sat Dec 15 05:12:24 2007 +0000
+++ b/doc/blist-signals.dox	Sat Dec 15 05:15:31 2007 +0000
@@ -82,6 +82,14 @@
    Emitted when a new buddy is added to the buddy list.
   @endsignaldef
 
+ @signaldef buddy-removed
+  @signalproto
+void (*buddy_removed)(PurpleBuddy *buddy)
+  @endsignalproto
+  @signaldesc
+   Emitted when a buddy is removed from the buddy list.
+  @endsignaldef
+
  @signaldef buddy-icon-changed
   @signalproto
 void (*buddy_icon_changed)(PurpleBuddy *buddy)
@@ -90,14 +98,6 @@
    Emitted when a buddy's icon is set.
   @endsignaldef
 
- @signaldef buddy-removed
-  @signalproto
-void (*buddy_removed)(PurpleBuddy *buddy)
-  @endsignalproto
-  @signaldesc
-   Emitted when a buddy is removed from the buddy list.
-  @endsignaldef
-
  @signaldef blist-node-aliased
   @signalproto
 void (*blist_node_aliased)(PurpleBlistNode *node, const char *old_alias)
--- a/doc/finch.1.in	Sat Dec 15 05:12:24 2007 +0000
+++ b/doc/finch.1.in	Sat Dec 15 05:15:31 2007 +0000
@@ -135,6 +135,29 @@
 .TP
 A sample file looks like:
 .br
+[Finch]
+.br
+color-available = green; black
+.br
+color-away = blue; black
+.br
+color-idle = gray; black
+.br
+color-offline = red; black
+.br
+color-message-sent = cyan; default
+.br
+color-message-received = red; default
+.br
+color-message-highlight = black; green
+.br
+color-message-action = yellow; default
+.br
+color-timestamp = blue; default
+.br
+#See below for details on color
+.br
+
 [general]
 .br
 shadow = 0
--- a/doc/gtkconv-signals.dox	Sat Dec 15 05:12:24 2007 +0000
+++ b/doc/gtkconv-signals.dox	Sat Dec 15 05:15:31 2007 +0000
@@ -28,12 +28,14 @@
 
  @signaldef conversation-timestamp
   @signalproto
-char *(*conversation_timestamp)(PurpleConversation *conv, time_t when);
+char *(*conversation_timestamp)(PurpleConversation *conv, time_t when,
+                                gboolean show_date);
   @endsignalproto
   @signaldesc
    Emitted to allow plugins to customize the timestamp on a message.
-  @param conv The conversation the message belongs to.
-  @param when The time to be converted to a string.
+  @param conv      The conversation the message belongs to.
+  @param when      The time to be converted to a string.
+  @param show_date Whether the date should be displayed.
   @return A textual representation of the time, or @c NULL to use a
           default format.
  @endsignaldef
--- a/doc/log-signals.dox	Sat Dec 15 05:12:24 2007 +0000
+++ b/doc/log-signals.dox	Sat Dec 15 05:15:31 2007 +0000
@@ -10,13 +10,14 @@
 
  @signaldef log-timestamp
   @signalproto
-char *(*log_timestamp)(PurpleLog *log, time_t when);
+char *(*log_timestamp)(PurpleLog *log, time_t when, gboolean show_date);
   @endsignalproto
   @signaldesc
    Emitted to allow plugins to customize the timestamp on a message
    being logged.
-  @param log The log the message belongs to.
-  @param when The time to be converted to a string.
+  @param log       The log the message belongs to.
+  @param when      The time to be converted to a string.
+  @param show_date Whether the date should be displayed.
   @return A textual representation of the time, or @c NULL to use a
           default format.
   @note Plugins must be careful of logs with a type of PURPLE_LOG_SYSTEM.
--- a/doc/plugin-i18n.dox	Sat Dec 15 05:12:24 2007 +0000
+++ b/doc/plugin-i18n.dox	Sat Dec 15 05:15:31 2007 +0000
@@ -1,13 +1,19 @@
 /** @page plugin-i18n Third Party Plugin Translation Support
 
  @section Introduction
+  For the purpose of this document we're going to assume that your plugin:
+
+   - Is set up to use autotools.  It may be possible to add translation support
+     without autotools, but we have no idea how.  We may not want to know, either ;)
+   - Has an autogen.sh.  You may have also called this bootstrap.sh or similar.
+   - Resides in a source tree that has @c configure.ac and @c Makefile.am in the
+     top-level directory as well as a @c src directory in which the plugin's source
+     is located.  A @c Makefile.am should also exist in the @c src directory.
+
   For a plugin to have translation support there are a few steps that need to
   followed:
 
-   - The plugin must be setup to use autotools.  It may be possible to add
-     translation support without autotools, but I have no idea how.
-   - In your autogen.sh, bootstrap.sh, or whatever you called it, add the
-     following after your other utility checks:
+   - In your autogen.sh, add the following after your other utility checks:
      @code
 (intltoolize --version) < /dev/null > /dev/null 2>&1 || {
     echo;
@@ -16,12 +22,11 @@
     exit;
 }
      @endcode
-     Then before your call aclocal add:
+     Then before your call to aclocal add:
      @code
 intltoolize --force --copy
      @endcode
-   - Now edit configure.ac, configure.in, or whatever you may have called it
-     and add the following:
+   - Now edit configure.ac and add the following:
      @code
 AC_PROG_INTLTOOL
 
@@ -40,13 +45,13 @@
    - Create/edit the file 'POTFILE.in' in your favorite editor.  Each line
      should be the name of a file that could or does have strings marked for
 	 translating (we're getting to that step).  These file names should be
-	 relative to the top directory of your plugin.
-   - 'cd' back to the top directory of your plugin.
+	 relative to the top directory of your plugin's source tree.
+   - 'cd' back to the top directory of your plugin's source tree.
    - Open 'Makefile.am' and add 'po' to your 'SUBDIRS' variable.
-   - While still in the top directory of your plugin execute
+   - While still in the top directory of your plugin's source tree,  execute
      'intltool-prepare'.  This will setup anything extra that intltool needs.
-   - Fire off an 'autogen.sh' and when it's completed, verify that you have a
-     'po/POTFILES', notice the lack of a .in.  If you do, everything should be
+   - Fire off 'autogen.sh' and when it's completed, verify that you have a
+     'po/POTFILES' (notice the lack of a .in).  If you do, everything should be
 	 set on the autotools side.
    - Take a break, stretch your legs, smoke a cigarette, whatever, because
      we're done with the autotools part.
@@ -60,8 +65,10 @@
 #include <glib/gi18n-lib.h>
      @endcode
 	 Make sure that this include is after you include of your 'config.h',
-	 otherwise you will break your build.
-   - This is where things get a bit goofy.  libpurple is going to try and
+	 otherwise you will break your build.  Also note that if you wish to
+     maintain compatibility with older versions of GLib, you will need to
+     include additional preprocessor directives, which we won't cover here.
+   - This is where things get a bit goofy.  libpurple is going to try to
      translate our strings using the libpurple gettext package.  So we have to
      convert them before libpurple attempts to.
    - To do this, we're going to change the entries for name, summary, and
--- a/finch/gntblist.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/gntblist.c	Sat Dec 15 05:15:31 2007 +0000
@@ -37,6 +37,7 @@
 #include "debug.h"
 
 #include "gntbox.h"
+#include "gntcolors.h"
 #include "gntcombobox.h"
 #include "gntentry.h"
 #include "gntft.h"
@@ -46,6 +47,7 @@
 #include "gntmenuitem.h"
 #include "gntmenuitemcheck.h"
 #include "gntpounce.h"
+#include "gntstyle.h"
 #include "gnttree.h"
 #include "gntutils.h"
 #include "gntwindow.h"
@@ -124,6 +126,37 @@
 static int blist_node_compare_status(PurpleBlistNode *n1, PurpleBlistNode *n2);
 static int blist_node_compare_log(PurpleBlistNode *n1, PurpleBlistNode *n2);
 
+static int color_available;
+static int color_away;
+static int color_offline;
+static int color_idle;
+
+static int
+get_display_color(PurpleBlistNode  *node)
+{
+	PurpleBuddy *buddy;
+	int color = 0;
+
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node))
+		node = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact*)node);
+	if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
+		return 0;
+
+	buddy = (PurpleBuddy*)node;
+	if (purple_presence_is_idle(purple_buddy_get_presence(buddy))) {
+		color = color_idle;
+	} else if (purple_presence_is_available(purple_buddy_get_presence(buddy))) {
+		color = color_available;
+	} else if (purple_presence_is_online(purple_buddy_get_presence(buddy)) &&
+			!purple_presence_is_available(purple_buddy_get_presence(buddy))) {
+		color = color_away;
+	} else if (!purple_presence_is_online(purple_buddy_get_presence(buddy))) {
+		color = color_offline;
+	}
+
+	return color;
+}
+
 static gboolean
 is_contact_online(PurpleContact *contact)
 {
@@ -228,6 +261,7 @@
 		gnt_tree_change_text(GNT_TREE(ggblist->tree), node,
 				0, get_display_name(node));
 		gnt_tree_sort_row(GNT_TREE(ggblist->tree), node);
+		gnt_tree_set_row_color(GNT_TREE(ggblist->tree), node, get_display_color(node));
 	}
 
 	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
@@ -581,11 +615,11 @@
 
 	if (node->ui_data)
 		return;
-	
+
 	name = get_display_name(node);
 	if (name == NULL)
 		return;
-	
+
 	group = (PurpleGroup*)node->parent;
 	add_node((PurpleBlistNode*)group, ggblist);
 
@@ -601,6 +635,7 @@
 {
 	PurpleContact *contact;
 	PurpleBlistNode *node = (PurpleBlistNode *)buddy;
+	int color = 0;
 	if (node->ui_data)
 		return;
 
@@ -615,13 +650,11 @@
 	node->ui_data = gnt_tree_add_row_after(GNT_TREE(ggblist->tree), buddy,
 				gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
 				contact, NULL);
-	if (purple_presence_is_idle(purple_buddy_get_presence(buddy))) {
-		gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), buddy, GNT_TEXT_FLAG_DIM);
-		gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), contact, GNT_TEXT_FLAG_DIM);
-	} else {
-		gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), buddy, 0);
-		gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), contact, 0);
-	}
+
+	color = get_display_color((PurpleBlistNode*)buddy);
+	gnt_tree_set_row_color(GNT_TREE(ggblist->tree), buddy, color);
+	if (buddy == purple_contact_get_priority_buddy(contact))
+		gnt_tree_set_row_color(GNT_TREE(ggblist->tree), contact, color);
 }
 
 #if 0
@@ -1542,7 +1575,8 @@
 {
 	PurpleContact *contact;
 	GntTextFormatFlags bflag = 0, cflag = 0;
-	
+	int color = 0;
+
 	contact = purple_buddy_get_contact(buddy);
 
 	gnt_tree_change_text(GNT_TREE(ggblist->tree), buddy, 0, get_display_name((PurpleBlistNode*)buddy));
@@ -1556,19 +1590,17 @@
 	if (ggblist->tnode == (PurpleBlistNode*)buddy)
 		draw_tooltip(ggblist);
 
-	if (purple_presence_is_idle(purple_buddy_get_presence(buddy))) {
-		gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), buddy, bflag | GNT_TEXT_FLAG_DIM);
-		if (buddy == purple_contact_get_priority_buddy(contact))
-			gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), contact, cflag | GNT_TEXT_FLAG_DIM);
-		else
+	color = get_display_color((PurpleBlistNode*)buddy);
+	gnt_tree_set_row_color(GNT_TREE(ggblist->tree), buddy, color);
+	if (buddy == purple_contact_get_priority_buddy(contact))
+		gnt_tree_set_row_color(GNT_TREE(ggblist->tree), contact, color);
+
+	gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), buddy, bflag);
+	if (buddy == purple_contact_get_priority_buddy(contact))
+		gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), contact, cflag);
+
+	if (buddy != purple_contact_get_priority_buddy(contact))
 			update_buddy_display(purple_contact_get_priority_buddy(contact), ggblist);
-	} else {
-		gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), buddy, bflag);
-		if (buddy == purple_contact_get_priority_buddy(contact))
-			gnt_tree_set_row_flags(GNT_TREE(ggblist->tree), contact, cflag);
-		else
-			update_buddy_display(purple_contact_get_priority_buddy(contact), ggblist);
-	}
 }
 
 static void
@@ -1738,6 +1770,19 @@
 
 void finch_blist_init()
 {
+	color_available = gnt_style_get_color(NULL, "color-available");
+	if (!color_available)
+		color_available = gnt_color_add_pair(COLOR_GREEN, -1);
+	color_away = gnt_style_get_color(NULL, "color-away");
+	if (!color_away)
+		color_away = gnt_color_add_pair(COLOR_BLUE, -1);
+	color_idle = gnt_style_get_color(NULL, "color-idle");
+	if (!color_idle)
+		color_idle = gnt_color_add_pair(COLOR_CYAN, -1);
+	color_offline = gnt_style_get_color(NULL, "color-offline");
+	if (!color_offline)
+		color_offline = gnt_color_add_pair(COLOR_RED, -1);
+
 	purple_prefs_add_none(PREF_ROOT);
 	purple_prefs_add_none(PREF_ROOT "/size");
 	purple_prefs_add_int(PREF_ROOT "/size/width", 20);
@@ -2550,4 +2595,3 @@
 {
 	gnt_widget_set_size(ggblist->window, width, height);
 }
-
--- a/finch/gntconv.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/gntconv.c	Sat Dec 15 05:15:31 2007 +0000
@@ -49,6 +49,7 @@
 #include "gntmenu.h"
 #include "gntmenuitem.h"
 #include "gntmenuitemcheck.h"
+#include "gntstyle.h"
 #include "gnttextview.h"
 #include "gnttree.h"
 #include "gntutils.h"
@@ -64,6 +65,12 @@
 		const char *message, PurpleMessageFlags flags, time_t mtime);
 static void generate_send_to_menu(FinchConv *ggc);
 
+static int color_message_receive;
+static int color_message_send;
+static int color_message_highlight;
+static int color_message_action;
+static int color_timestamp;
+
 static PurpleBlistNode *
 get_conversation_blist_node(PurpleConversation *conv)
 {
@@ -753,7 +760,9 @@
 	/* Unnecessary to print the timestamp for delayed message */
 	if (purple_prefs_get_bool("/finch/conversations/timestamps"))
 		gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(ggconv->tv),
-					purple_utf8_strftime("(%H:%M:%S) ", localtime(&mtime)), GNT_TEXT_FLAG_DIM);
+					purple_utf8_strftime("(%H:%M:%S)", localtime(&mtime)), gnt_color_pair(color_timestamp));
+
+	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(ggconv->tv), " ", GNT_TEXT_FLAG_NORMAL);
 
 	if (flags & PURPLE_MESSAGE_AUTO_RESP)
 		gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(ggconv->tv),
@@ -763,23 +772,31 @@
 			!(flags & PURPLE_MESSAGE_NOTIFY))
 	{
 		char * name = NULL;
-
-		if (purple_message_meify((char*)message, -1))
-			name = g_strdup_printf("*** %s ", who);
-		else
-			name =  g_strdup_printf("%s: ", who);
+		GntTextFormatFlags msgflags = GNT_TEXT_FLAG_NORMAL;
+		gboolean me = FALSE;
 
+		if (purple_message_meify((char*)message, -1)) {
+			name = g_strdup_printf("*** %s", who);
+			msgflags = gnt_color_pair(color_message_action);
+			me = TRUE;
+		} else {
+			name =  g_strdup_printf("%s", who);
+			if (flags & PURPLE_MESSAGE_SEND)
+				msgflags = gnt_color_pair(color_message_send);
+			else if (flags & PURPLE_MESSAGE_NICK)
+				msgflags = gnt_color_pair(color_message_highlight);
+			else
+				msgflags = gnt_color_pair(color_message_receive);
+		}
 		gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(ggconv->tv),
-				name, GNT_TEXT_FLAG_BOLD);
+				name, msgflags);
+		gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(ggconv->tv), me ? " " : ": ", GNT_TEXT_FLAG_NORMAL);
 		g_free(name);
-	}
-	else
+	} else
 		fl = GNT_TEXT_FLAG_DIM;
 
 	if (flags & PURPLE_MESSAGE_ERROR)
 		fl |= GNT_TEXT_FLAG_BOLD;
-	if (flags & PURPLE_MESSAGE_NICK)
-		fl |= GNT_TEXT_FLAG_UNDERLINE;
 
 	/* XXX: Remove this workaround when textview can parse messages. */
 	newline = purple_strdup_withhtml(message);
@@ -1126,6 +1143,21 @@
 
 void finch_conversation_init()
 {
+	color_message_send = gnt_style_get_color(NULL, "color-message-sent");
+	if (!color_message_send)
+		color_message_send = gnt_color_add_pair(COLOR_CYAN, -1);
+	color_message_receive = gnt_style_get_color(NULL, "color-message-received");
+	if (!color_message_receive)
+		color_message_receive = gnt_color_add_pair(COLOR_RED, -1);
+	color_message_highlight = gnt_style_get_color(NULL, "color-message-highlight");
+	if (!color_message_highlight)
+		color_message_highlight = gnt_color_add_pair(COLOR_GREEN, -1);
+	color_timestamp = gnt_style_get_color(NULL, "color-timestamp");
+	if (!color_timestamp)
+		color_timestamp = gnt_color_add_pair(COLOR_BLUE, -1);
+	color_message_action = gnt_style_get_color(NULL, "color-message-action");
+	if (!color_message_action)
+		color_message_action = gnt_color_add_pair(COLOR_YELLOW, -1);
 	purple_prefs_add_none(PREF_ROOT);
 	purple_prefs_add_none(PREF_ROOT "/size");
 	purple_prefs_add_int(PREF_ROOT "/size/width", 70);
--- a/finch/gntdebug.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/gntdebug.c	Sat Dec 15 05:15:31 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 15 05:12:24 2007 +0000
+++ b/finch/gntplugin.c	Sat Dec 15 05:15:31 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/libgnt/Makefile.am	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/Makefile.am	Sat Dec 15 05:15:31 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 15 05:12:24 2007 +0000
+++ b/finch/libgnt/configure.ac	Sat Dec 15 05:15:31 2007 +0000
@@ -27,7 +27,7 @@
 m4_define([gnt_lt_current], [3])
 m4_define([gnt_major_version], [2])
 m4_define([gnt_minor_version], [3])
-m4_define([gnt_micro_version], [1])
+m4_define([gnt_micro_version], [2])
 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.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/gntcolors.c	Sat Dec 15 05:15:31 2007 +0000
@@ -33,6 +33,7 @@
 #include <string.h>
 
 static gboolean hascolors;
+static int custom_type = GNT_COLORS;
 static struct
 {
 	short r, g, b;
@@ -137,8 +138,8 @@
 }
 
 #if GLIB_CHECK_VERSION(2,6,0)
-static int
-get_color(char *key)
+int
+gnt_colors_get_color(char *key)
 {
 	int color;
 	gboolean custom = can_use_custom_color();
@@ -163,8 +164,12 @@
 		color = COLOR_MAGENTA;
 	else if (strcmp(key, "cyan") == 0)
 		color = COLOR_CYAN;
-	else
+	else if (strcmp(key, "default") == 0)
 		color = -1;
+	else {
+		g_warning("Invalid color name: %s\n", key);
+		color = -1;
+	}
 	return color;
 }
 
@@ -196,7 +201,7 @@
 				int color = -1;
 
 				key = g_ascii_strdown(key, -1);
-				color = get_color(key);
+				color = gnt_colors_get_color(key);
 				g_free(key);
 				if (color == -1)
 					continue;
@@ -237,8 +242,8 @@
 			GntColorType type = 0;
 			gchar *fgc = g_ascii_strdown(list[0], -1);
 			gchar *bgc = g_ascii_strdown(list[1], -1);
-			int fg = get_color(fgc);
-			int bg = get_color(bgc);
+			int fg = gnt_colors_get_color(fgc);
+			int bg = gnt_colors_get_color(bgc);
 			g_free(fgc);
 			g_free(bgc);
 			if (fg == -1 || bg == -1)
@@ -287,3 +292,8 @@
 		  pair == GNT_COLOR_TITLE_D || pair == GNT_COLOR_DISABLED) ? 0 : A_STANDOUT));
 }
 
+int gnt_color_add_pair(int fg, int bg)
+{
+	init_pair(custom_type, fg, bg);
+	return custom_type++;
+}
--- a/finch/libgnt/gntcolors.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/gntcolors.h	Sat Dec 15 05:15:31 2007 +0000
@@ -86,6 +86,16 @@
  */
 void gnt_color_pairs_parse(GKeyFile *kfile);
 
+/**
+ * Parse a string color
+ *
+ * @param kfile The string value
+ *
+ * @return A color
+ *
+ * @since 2.3.1 (gnt), 2.3.1 (pidgin)
+ */
+int gnt_colors_get_color(char *key);
 #endif
 
 /**
@@ -101,4 +111,15 @@
  */
 int gnt_color_pair(int color);
 
+/**
+ * Adds a color definition
+ *
+ * @param fg   Foreground
+ * @param bg   Background
+ *
+ * @return  A color pair
+ *
+ * @since 2.3.1
+ */
+int gnt_color_add_pair(int fg, int bg);
 #endif
--- a/finch/libgnt/gntentry.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/gntentry.c	Sat Dec 15 05:15:31 2007 +0000
@@ -140,7 +140,6 @@
 static gboolean
 complete_suggest(GntEntry *entry, const char *text)
 {
-	gboolean changed = FALSE;
 	int offstart = 0, offend = 0;
 
 	if (entry->word) {
@@ -148,27 +147,22 @@
 		const char *iter = text;
 		offstart = g_utf8_pointer_to_offset(entry->start, s);
 		while (*iter && toupper(*s) == toupper(*iter)) {
-			if (*s != *iter)
-				changed = TRUE;
 			*s++ = *iter++;
 		}
 		if (*iter) {
 			gnt_entry_key_pressed(GNT_WIDGET(entry), iter);
-			changed = TRUE;
 		}
 		offend = g_utf8_pointer_to_offset(entry->start, entry->cursor);
 	} else {
 		offstart = 0;
 		gnt_entry_set_text_internal(entry, text);
-		changed = TRUE;
 		offend = g_utf8_strlen(text, -1);
 	}
 
-	if (changed)
-		g_signal_emit(G_OBJECT(entry), signals[SIG_COMPLETION], 0,
-				entry->start + offstart, entry->start + offend);
+	g_signal_emit(G_OBJECT(entry), signals[SIG_COMPLETION], 0,
+			entry->start + offstart, entry->start + offend);
 	update_kill_ring(entry, ENTRY_JAIL, NULL, 0);
-	return changed;
+	return TRUE;
 }
 
 static int
--- a/finch/libgnt/gntmain.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/gntmain.c	Sat Dec 15 05:15:31 2007 +0000
@@ -679,8 +679,9 @@
 	g_free(cp);
 	clean_pid();
 	wm->mode = GNT_KP_MODE_NORMAL;
-	clear();
+	endwin();
 	setup_io();
+	refresh();
 	refresh_screen();
 }
 #endif
--- a/finch/libgnt/gntstyle.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/gntstyle.c	Sat Dec 15 05:15:31 2007 +0000
@@ -59,6 +59,42 @@
 #endif
 }
 
+int
+gnt_style_get_color(char *group, char *key)
+{
+#if GLIB_CHECK_VERSION(2,6,0)
+	int fg = 0, bg = 0;
+	gsize n;
+	char **vals;
+	int ret = 0;
+	vals = gnt_style_get_string_list(group, key, &n);
+	if (vals && n == 2) {
+		fg = gnt_colors_get_color(vals[0]);
+		bg = gnt_colors_get_color(vals[1]);
+		ret = gnt_color_add_pair(fg, bg);
+	}
+	g_strfreev(vals);
+	return ret;
+#else
+	return 0;
+#endif
+}
+
+char **gnt_style_get_string_list(const char *group, const char *key, gsize *length)
+{
+#if GLIB_CHECK_VERSION(2,6,0)
+	const char *prg = g_get_prgname();
+	if ((group == NULL || *group == '\0') && prg &&
+			g_key_file_has_group(gkfile, prg))
+		group = prg;
+	if (!group)
+		group = "general";
+	return g_key_file_get_string_list(gkfile, group, key, length, NULL);
+#else
+	return NULL;
+#endif
+}
+
 gboolean gnt_style_get_bool(GntStyle style, gboolean def)
 {
 	const char * str;
--- a/finch/libgnt/gntstyle.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/gntstyle.h	Sat Dec 15 05:15:31 2007 +0000
@@ -65,6 +65,33 @@
 char *gnt_style_get_from_name(const char *group, const char *key);
 
 /**
+ * Get the value of a preference in ~/.gntrc.
+ *
+ * @param group   The name of the group in the keyfile. If @c NULL, the prgname
+ *                will be used first, if available. Otherwise, "general" will be used.
+ * @param key     The key
+ * @param length  Return location for the number of strings returned, or NULL
+ *
+ * @return        NULL terminated string array. The array should be freed with g_strfreev().
+ *
+ * @since 2.3.2
+ */
+char **gnt_style_get_string_list(const char *group, const char *key, gsize *length);
+
+/**
+ * Get the value of a color pair in ~/.gntrc.
+ *
+ * @param group   The name of the group in the keyfile. If @c NULL, the prgname
+ *                will be used first, if available. Otherwise, "general" will be used.
+ * @param key     The key
+ *
+ * @return  The value of the color as an int, or 0 on error.
+ *
+ * @since 2.3.2
+ */
+int gnt_style_get_color(char *group, char *key);
+
+/**
  * Parse a boolean preference. For example, if 'value' is "false" (ignoring case)
  * or "0", the return value will be @c FALSE, otherwise @c TRUE.
  *
--- a/finch/libgnt/gnttextview.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/gnttextview.c	Sat Dec 15 05:15:31 2007 +0000
@@ -650,8 +650,10 @@
 		fl |= (A_DIM | gnt_color_pair(GNT_COLOR_DISABLED));
 	else if (flags & GNT_TEXT_FLAG_HIGHLIGHT)
 		fl |= (A_DIM | gnt_color_pair(GNT_COLOR_HIGHLIGHT));
+	else if ((flags & A_COLOR) == 0)
+		fl |= gnt_color_pair(GNT_COLOR_NORMAL);
 	else
-		fl |= gnt_color_pair(GNT_COLOR_NORMAL);
+		fl |= (flags & A_COLOR);
 
 	return fl;
 }
--- a/finch/libgnt/gnttree.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/gnttree.c	Sat Dec 15 05:15:31 2007 +0000
@@ -75,6 +75,7 @@
 	                               If choice is true, then child will be NULL */
 	gboolean isselected;
 	GntTextFormatFlags flags;
+	int color;
 
 	GntTreeRow *parent;
 	GntTreeRow *child;
@@ -522,9 +523,14 @@
 		else
 		{
 			if (flags & GNT_TEXT_FLAG_DIM)
-				attr |= (A_DIM | gnt_color_pair(GNT_COLOR_DISABLED));
+				if (row->color)
+					attr |= (A_DIM | gnt_color_pair(row->color));
+				else
+					attr |= (A_DIM | gnt_color_pair(GNT_COLOR_DISABLED));
 			else if (flags & GNT_TEXT_FLAG_HIGHLIGHT)
 				attr |= (A_DIM | gnt_color_pair(GNT_COLOR_HIGHLIGHT));
+			else if (row->color)
+				attr |= gnt_color_pair(row->color);
 			else
 				attr |= gnt_color_pair(GNT_COLOR_NORMAL);
 		}
@@ -1559,6 +1565,16 @@
 	redraw_tree(tree);	/* XXX: It shouldn't be necessary to redraw the whole darned tree */
 }
 
+void gnt_tree_set_row_color(GntTree *tree, void *key, int color)
+{
+	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
+	if (!row || row->color == color)
+		return;
+
+	row->color = color;
+	redraw_tree(tree);
+}
+
 void gnt_tree_set_selected(GntTree *tree , void *key)
 {
 	int dist;
--- a/finch/libgnt/gnttree.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/gnttree.h	Sat Dec 15 05:15:31 2007 +0000
@@ -325,6 +325,15 @@
 void gnt_tree_set_row_flags(GntTree *tree, void *key, GntTextFormatFlags flags);
 
 /**
+ * Set color for the text in a row in the tree.
+ *
+ * @param tree   The tree
+ * @param key    The key for the row
+ * @param color  The color
+ */
+void gnt_tree_set_row_color(GntTree *, void *, int);
+
+/**
  * Select a row.
  *
  * @param tree  The tree
--- a/finch/libgnt/gntwm.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/gntwm.c	Sat Dec 15 05:15:31 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>
@@ -692,6 +696,7 @@
 		{'\0', NULL}
 	};
 
+	gnt_widget_destroy(GNT_WIDGET(fs));
 
 	if ((file = g_fopen(path, "w+")) == NULL) {
 		return;
@@ -803,7 +808,6 @@
 	}
 	fprintf(file, "</pre>\n</body>");
 	fclose(file);
-	gnt_widget_destroy(GNT_WIDGET(fs));
 }
 
 static void
@@ -817,6 +821,11 @@
 {
 	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 enter the filename to save the screenshot."));
+	gnt_box_set_title(GNT_BOX(window), "Save Screenshot...");
+
 	gnt_file_sel_set_suggested_filename(sel, "dump.html");
 	g_signal_connect(G_OBJECT(sel), "file_selected", G_CALLBACK(dump_file_save), NULL);
 	g_signal_connect(G_OBJECT(sel->cancel), "activate", G_CALLBACK(dump_file_cancel), sel);
@@ -1193,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 {
@@ -1266,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
@@ -1436,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/finch/libgnt/test/tv.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/finch/libgnt/test/tv.c	Sat Dec 15 05:15:31 2007 +0000
@@ -112,8 +112,8 @@
 	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "plugins: ", GNT_TEXT_FLAG_BOLD);
 	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "this is the 2nd line\n", GNT_TEXT_FLAG_NORMAL);
 
-	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "plugins: ", GNT_TEXT_FLAG_BOLD);
-	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "this is the 3rd line\n", GNT_TEXT_FLAG_NORMAL);
+	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "plugins: ", GNT_TEXT_FLAG_BOLD | gnt_color_pair(GNT_COLOR_HIGHLIGHT));
+	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "this is the 3rd line\n", GNT_TEXT_FLAG_NORMAL | gnt_color_pair(GNT_COLOR_HIGHLIGHT));
 
 	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "plugins: ", GNT_TEXT_FLAG_BOLD);
 	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "this is the 4th line\n", GNT_TEXT_FLAG_NORMAL);
--- a/libpurple/Makefile.am	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/Makefile.am	Sat Dec 15 05:15:31 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 15 05:12:24 2007 +0000
+++ b/libpurple/account.c	Sat Dec 15 05:15:31 2007 +0000
@@ -336,8 +336,13 @@
 	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;
 }
@@ -741,7 +746,7 @@
 
 	current_error = g_new0(PurpleConnectionErrorInfo, 1);
 	current_error->type = type;
-	current_error->description = description;
+	current_error->description = g_strdup(description);
 
 	set_current_error(account, current_error);
 }
@@ -2529,22 +2534,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 15 05:12:24 2007 +0000
+++ b/libpurple/account.h	Sat Dec 15 05:15:31 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/blist.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/blist.c	Sat Dec 15 05:15:31 2007 +0000
@@ -1310,7 +1310,7 @@
 		g = (PurpleGroup *)((PurpleBlistNode *)c)->parent;
 	} else {
 		if (group) {
-			/* Add chat to blist if isn't already on it. Fixes #2752. */
+			/* Add group to blist if isn't already on it. Fixes #2752. */
 			if (!purple_find_group(group->name)) {
 				purple_blist_add_group(group,
 						purple_blist_get_last_sibling(purplebuddylist->root));
--- a/libpurple/connection.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/connection.c	Sat Dec 15 05:15:31 2007 +0000
@@ -38,6 +38,8 @@
 #include "signals.h"
 #include "util.h"
 
+#define KEEPALIVE_INTERVAL 30
+
 static GList *connections = NULL;
 static GList *connections_connecting = NULL;
 static PurpleConnectionUiOps *connection_ui_ops = NULL;
@@ -73,7 +75,7 @@
 	if (on && !gc->keepalive)
 	{
 		purple_debug_info("connection", "Activating keepalive.\n");
-		gc->keepalive = purple_timeout_add_seconds(30, send_keepalive, gc);
+		gc->keepalive = purple_timeout_add_seconds(KEEPALIVE_INTERVAL, send_keepalive, gc);
 	}
 	else if (!on && gc->keepalive > 0)
 	{
@@ -219,9 +221,6 @@
 {
 	PurpleAccount *account;
 	GSList *buddies;
-#if 0
-	GList *wins;
-#endif
 	PurplePluginProtocolInfo *prpl_info = NULL;
 	gboolean remove = FALSE;
 
@@ -269,19 +268,6 @@
 
 	purple_signal_emit(purple_connections_get_handle(), "signed-off", gc);
 
-#if 0
-	/* see comment later in file on if 0'd same code */
-	/*
-	 * XXX This is a hack! Remove this and replace it with a better event
-	 *     notification system.
-	 */
-	for (wins = purple_get_windows(); wins != NULL; wins = wins->next) {
-		PurpleConvWindow *win = (PurpleConvWindow *)wins->data;
-		purple_conversation_update(purple_conv_window_get_conversation_at(win, 0),
-								 PURPLE_CONV_ACCOUNT_OFFLINE);
-	}
-#endif
-
 	purple_account_request_close_with_account(account);
 	purple_request_close_with_handle(gc);
 	purple_notify_close_with_handle(gc);
--- a/libpurple/ft.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/ft.c	Sat Dec 15 05:15:31 2007 +0000
@@ -1306,8 +1306,12 @@
 }
 
 void
-purple_xfers_uninit(void) {
-	purple_signals_disconnect_by_handle(purple_xfers_get_handle());
+purple_xfers_uninit(void)
+{
+	void *handle = purple_xfers_get_handle();
+
+	purple_signals_disconnect_by_handle(handle);
+	purple_signals_unregister_by_instance(handle);
 }
 
 void
--- a/libpurple/network.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/network.c	Sat Dec 15 05:15:31 2007 +0000
@@ -182,22 +182,22 @@
 		/* Make sure the IP address entered by the user is valid */
 		if ((ip != NULL) && (purple_network_ip_atoi(ip) != NULL))
 			return ip;
-	}
-
-	/* Check if STUN discovery was already done */
-	stun = purple_stun_discover(NULL);
-	if ((stun != NULL) && (stun->status == PURPLE_STUN_STATUS_DISCOVERED))
-		return stun->publicip;
+	} else {
+		/* Check if STUN discovery was already done */
+		stun = purple_stun_discover(NULL);
+		if ((stun != NULL) && (stun->status == PURPLE_STUN_STATUS_DISCOVERED))
+			return stun->publicip;
 
-	/* Attempt to get the IP from a NAT device using UPnP */
-	ip = purple_upnp_get_public_ip();
-	if (ip != NULL)
-	  return ip;
+		/* Attempt to get the IP from a NAT device using UPnP */
+		ip = purple_upnp_get_public_ip();
+		if (ip != NULL)
+			return ip;
 
-	/* Attempt to get the IP from a NAT device using NAT-PMP */
-	ip = purple_pmp_get_public_ip();
-	if (ip != NULL)
-		return ip;
+		/* Attempt to get the IP from a NAT device using NAT-PMP */
+		ip = purple_pmp_get_public_ip();
+		if (ip != NULL)
+			return ip;
+	}
 
 	/* Just fetch the IP of the local system */
 	return purple_network_get_local_system_ip(fd);
@@ -362,7 +362,7 @@
 	listen_data->cb_data = cb_data;
 	listen_data->socket_type = socket_type;
 
-	if (!listen_map_external)
+	if (!listen_map_external || !purple_prefs_get_bool("/purple/network/map_ports"))
 	{
 		purple_debug_info("network", "Skipping external port mapping.\n");
 		/* The pmp_map_cb does what we want to do */
@@ -677,11 +677,13 @@
 	purple_prefs_add_none  ("/purple/network");
 	purple_prefs_add_bool  ("/purple/network/auto_ip", TRUE);
 	purple_prefs_add_string("/purple/network/public_ip", "");
+	purple_prefs_add_bool  ("/purple/network/map_ports", TRUE);
 	purple_prefs_add_bool  ("/purple/network/ports_range_use", FALSE);
 	purple_prefs_add_int   ("/purple/network/ports_range_start", 1024);
 	purple_prefs_add_int   ("/purple/network/ports_range_end", 2048);
 
-	purple_upnp_discover(NULL, NULL);
+	if(purple_prefs_get_bool("/purple/network/map_ports") || purple_prefs_get_bool("/purple/network/auto_ip"))
+		purple_upnp_discover(NULL, NULL);
 
 #ifdef HAVE_LIBNM
 	nm_context = libnm_glib_init();
@@ -708,4 +710,7 @@
 	if(nm_context)
 		libnm_glib_shutdown(nm_context);
 #endif
+
+	purple_signal_unregister(purple_network_get_handle(),
+	                         "network-configuration-changed");
 }
--- a/libpurple/plugin.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugin.c	Sat Dec 15 05:15:31 2007 +0000
@@ -1175,7 +1175,7 @@
 purple_plugins_init(void) {
 	void *handle = purple_plugins_get_handle();
 
-        purple_plugins_add_search_path(LIBDIR);
+	purple_plugins_add_search_path(LIBDIR);
 
 	purple_signal_register(handle, "plugin-load",
 						 purple_marshal_VOID__POINTER,
@@ -1190,8 +1190,12 @@
 }
 
 void
-purple_plugins_uninit(void) {
-	purple_signals_disconnect_by_handle(purple_plugins_get_handle());
+purple_plugins_uninit(void)
+{
+	void *handle = purple_plugins_get_handle();
+
+	purple_signals_disconnect_by_handle(handle);
+	purple_signals_unregister_by_instance(handle);
 }
 
 /**************************************************************************
--- a/libpurple/plugins/autoaccept.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/autoaccept.c	Sat Dec 15 05:15:31 2007 +0000
@@ -114,22 +114,33 @@
 		case FT_ACCEPT:
 			if (ensure_path_exists(pref))
 			{
-				dirname = g_build_filename(pref, xfer->who, NULL);
+				int count = 1;
+				const char *escape;
+				dirname = g_build_filename(pref, purple_normalize(account, xfer->who), NULL);
 
 				if (!ensure_path_exists(dirname))
 				{
 					g_free(dirname);
 					break;
 				}
-				
-				filename = g_build_filename(dirname, xfer->filename, NULL);
+
+				escape = purple_escape_filename(xfer->filename);
+				filename = g_build_filename(dirname, escape, NULL);
+
+				/* Make sure the file doesn't exist. Do we want some better checking than this? */
+				while (g_file_test(filename, G_FILE_TEST_EXISTS)) {
+					char *file = g_strdup_printf("%s-%d", escape, count++);
+					g_free(filename);
+					filename = g_build_filename(dirname, file, NULL);
+					g_free(file);
+				}
 
 				purple_xfer_request_accepted(xfer, filename);
 
 				g_free(dirname);
 				g_free(filename);
 			}
-			
+
 			purple_signal_connect(purple_xfers_get_handle(), "file-recv-complete", handle,
 								PURPLE_CALLBACK(auto_accept_complete_cb), xfer);
 			break;
--- a/libpurple/plugins/newline.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/newline.c	Sat Dec 15 05:15:31 2007 +0000
@@ -31,6 +31,12 @@
 addnewline_msg_cb(PurpleAccount *account, char *sender, char **message,
 					 PurpleConversation *conv, int *flags, void *data)
 {
+	if (((purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) &&
+		 !purple_prefs_get_bool("/plugins/core/newline/im")) ||
+		((purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) &&
+		 !purple_prefs_get_bool("/plugins/core/newline/chat")))
+		return FALSE;
+
 	if (g_ascii_strncasecmp(*message, "/me ", strlen("/me "))) {
 		char *tmp = g_strdup_printf("<br/>%s", *message);
 		g_free(*message);
@@ -40,6 +46,25 @@
 	return FALSE;
 }
 
+static PurplePluginPrefFrame *
+get_plugin_pref_frame(PurplePlugin *plugin) {
+	PurplePluginPrefFrame *frame;
+	PurplePluginPref *ppref;
+
+	frame = purple_plugin_pref_frame_new();
+
+	ppref = purple_plugin_pref_new_with_name_and_label(
+			"/plugins/core/newline/im", _("Add new line in IMs"));
+	purple_plugin_pref_frame_add(frame, ppref);
+
+	ppref = purple_plugin_pref_new_with_name_and_label(
+			"/plugins/core/newline/chat", _("Add new line in Chats"));
+	purple_plugin_pref_frame_add(frame, ppref);
+
+	return frame;
+}
+
+
 static gboolean
 plugin_load(PurplePlugin *plugin)
 {
@@ -53,6 +78,17 @@
 	return TRUE;
 }
 
+static PurplePluginUiInfo prefs_info = {
+	get_plugin_pref_frame,
+	0,   /* page_num (Reserved) */
+	NULL, /* frame (Reserved) */
+	/* Padding */
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
 static PurplePluginInfo info =
 {
 	PURPLE_PLUGIN_MAGIC,							/**< magic			*/
@@ -80,7 +116,7 @@
 
 	NULL,											/**< ui_info		*/
 	NULL,											/**< extra_info		*/
-	NULL,											/**< prefs_info		*/
+	&prefs_info,									/**< prefs_info		*/
 	NULL,											/**< actions		*/
 
 	/* padding */
@@ -92,6 +128,9 @@
 
 static void
 init_plugin(PurplePlugin *plugin) {
+	purple_prefs_add_none("/plugins/core/newline");
+	purple_prefs_add_bool("/plugins/core/newline/im", TRUE);
+	purple_prefs_add_bool("/plugins/core/newline/chat", TRUE);
 }
 
 PURPLE_INIT_PLUGIN(lastseen, init_plugin, info)
--- a/libpurple/plugins/perl/common/BuddyList.xs	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/perl/common/BuddyList.xs	Sat Dec 15 05:15:31 2007 +0000
@@ -44,11 +44,13 @@
 	Purple::Account account
 	const char * name
 PREINIT:
-	GSList *l;
+	GSList *l, *ll;
 PPCODE:
-	for (l = purple_find_buddies(account, name); l != NULL; l = l->next) {
+	ll = purple_find_buddies(account, name);
+	for (l = ll; l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::BuddyList::Buddy")));
 	}
+	g_slist_free(ll);
 
 Purple::BuddyList::Group
 purple_find_group(name)
@@ -101,11 +103,13 @@
 purple_group_get_accounts(group)
 	Purple::BuddyList::Group  group
 PREINIT:
-	GSList *l;
+	GSList *l, *ll;
 PPCODE:
-	for (l = purple_group_get_accounts(group); l != NULL; l = l->next) {
+	ll = purple_group_get_accounts(group);
+	for (l = ll; l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Account")));
 	}
+	g_slist_free(ll);
 
 gboolean
 purple_group_on_account(group, account)
@@ -268,11 +272,15 @@
 purple_blist_node_get_extended_menu(node)
 	Purple::BuddyList::Node node
 PREINIT:
-	GList *l;
+	GList *l, *ll;
 PPCODE:
-	for (l = purple_blist_node_get_extended_menu(node); l != NULL; l = g_list_delete_link(l, l)) {
+	ll = purple_blist_node_get_extended_menu(node);
+	for (l = ll; l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Menu::Action")));
 	}
+	/* We can free the list here but the script needs to free the
+	 * Purple::Menu::Action 'objects' itself. */
+	g_list_free(ll);
 
 void
 purple_blist_node_set_bool(node, key, value)
--- a/libpurple/plugins/perl/common/Cmds.xs	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/perl/common/Cmds.xs	Sat Dec 15 05:15:31 2007 +0000
@@ -66,21 +66,23 @@
 	Purple::Conversation conv
 	const gchar *command
 PREINIT:
-	GList *l;
+	GList *l, *ll;
 PPCODE:
-	for (l = purple_cmd_help(conv, command); l != NULL; l = l->next) {
+	for (l = ll = purple_cmd_help(conv, command); l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
 	}
+	g_list_free(ll);
 
 void
 purple_cmd_list(conv)
 	Purple::Conversation conv
 PREINIT:
-	GList *l;
+	GList *l, *ll;
 PPCODE:
-	for (l = purple_cmd_list(conv); l != NULL; l = l->next) {
+	for (l = ll = purple_cmd_list(conv); l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
 	}
+	g_list_free(ll);
 
 Purple::Cmd::Id
 purple_cmd_register(plugin, command, args, priority, flag, prpl_id, func, helpstr, data = 0)
--- a/libpurple/plugins/perl/common/Conversation.xs	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/perl/common/Conversation.xs	Sat Dec 15 05:15:31 2007 +0000
@@ -464,6 +464,10 @@
 
 	purple_conv_chat_add_users(chat, t_GL_users, t_GL_extra_msgs, t_GL_flags, new_arrivals);
 
+	g_list_free(t_GL_users);
+	g_list_free(t_GL_extra_msgs);
+	g_list_free(t_GL_flags);
+
 gboolean
 purple_conv_chat_find_user(chat, user)
 	Purple::Conversation::Chat chat
--- a/libpurple/plugins/perl/common/Log.xs	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/perl/common/Log.xs	Sat Dec 15 05:15:31 2007 +0000
@@ -65,11 +65,15 @@
 	const char *name
 	Purple::Account account
 PREINIT:
-	GList *l;
+	GList *l, *ll;
 PPCODE:
-	for (l = purple_log_get_logs(type, name, account); l != NULL; l = l->next) {
-		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::ListEntry")));
+	ll = purple_log_get_logs(type, name, account);
+	for (l = ll; l != NULL; l = l->next) {
+		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Log")));
 	}
+	/* We can free the list here but the script needs to free the
+	 * Purple::Log 'objects' itself. */
+	g_list_free(ll);
 
 int
 purple_log_get_size(log)
@@ -79,11 +83,15 @@
 purple_log_get_system_logs(account)
 	Purple::Account account
 PREINIT:
-	GList *l;
+	GList *l, *ll;
 PPCODE:
-	for (l = purple_log_get_system_logs(account); l != NULL; l = l->next) {
-		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::ListEntry")));
+	ll = purple_log_get_system_logs(account);
+	for (l = ll; l != NULL; l = l->next) {
+		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Log")));
 	}
+	/* We can free the list here but the script needs to free the
+	 * Purple::Log 'objects' itself. */
+	g_list_free(ll);
 
 int
 purple_log_get_total_size(type, name, account)
@@ -101,11 +109,14 @@
 void
 purple_log_logger_get_options()
 PREINIT:
-	GList *l;
+	GList *l, *ll;
 PPCODE:
-	for (l = purple_log_logger_get_options(); l != NULL; l = l->next) {
-		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::ListEntry")));
+	/* This might want to be massaged to a hash, since that's essentially
+	 * what the key/value list is emulating. */
+	for (l = ll = purple_log_logger_get_options(); l != NULL; l = l->next) {
+		XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
 	}
+	g_list_free(ll);
 
 gchar_own *
 purple_log_read(log, flags)
--- a/libpurple/plugins/perl/common/Pounce.xs	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/perl/common/Pounce.xs	Sat Dec 15 05:15:31 2007 +0000
@@ -106,6 +106,18 @@
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Pounce")));
 	}
 
+void
+purple_pounces_get_all_for_ui(ui)
+	const char *ui
+PREINIT:
+	GList *l, *ll;
+PPCODE:
+	ll = purple_pounces_get_all_for_ui(ui);
+	for (l = ll; l != NULL; l = l->next) {
+		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Pounce")));
+	}
+	g_list_free(ll);
+
 Purple::Handle
 purple_pounces_get_handle()
 
--- a/libpurple/plugins/perl/common/Prefs.xs	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/perl/common/Prefs.xs	Sat Dec 15 05:15:31 2007 +0000
@@ -57,6 +57,7 @@
 		t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(value), i, 0), t_sl));
 	}
 	purple_prefs_add_string_list(name, t_GL);
+	g_list_free(t_GL);
 
 void
 purple_prefs_destroy()
@@ -159,6 +160,7 @@
 		t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(value), i, 0), t_sl));
 	}
 	purple_prefs_set_string_list(name, t_GL);
+	g_list_free(t_GL);
 
 void
 purple_prefs_trigger_callback(name)
--- a/libpurple/plugins/perl/common/Prpl.xs	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/perl/common/Prpl.xs	Sat Dec 15 05:15:31 2007 +0000
@@ -21,13 +21,15 @@
 	Purple::Account account
 	Purple::Presence presence
 PREINIT:
-	GList *l;
+	GList *l, *ll;
 PPCODE:
-	for (l = purple_prpl_get_statuses(account,presence); l != NULL; l = l->next) {
-		/* XXX Someone please test and make sure this is the right
-		 * type for these things. */
+	ll = purple_prpl_get_statuses(account,presence);
+	for (l = ll; l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Status")));
 	}
+	/* We can free the list here but the script needs to free the
+	 * Purple::Status 'objects' itself. */
+	g_list_free(ll);
 
 void
 purple_prpl_got_account_idle(account, idle, idle_time)
--- a/libpurple/plugins/perl/common/SavedStatuses.xs	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/perl/common/SavedStatuses.xs	Sat Dec 15 05:15:31 2007 +0000
@@ -140,11 +140,13 @@
 purple_savedstatuses_get_popular(how_many)
 	unsigned int how_many
 PREINIT:
-	GList *l;
+	GList *l, *ll;
 PPCODE:
-	for (l = purple_savedstatuses_get_popular(how_many); l != NULL; l = l->next) {
+	ll = purple_savedstatuses_get_popular(how_many);
+	for (l = ll; l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::SavedStatus")));
 	}
+	g_list_free(ll);
 
 Purple::Handle
 purple_savedstatuses_get_handle()
--- a/libpurple/plugins/perl/common/Status.xs	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/plugins/perl/common/Status.xs	Sat Dec 15 05:15:31 2007 +0000
@@ -90,6 +90,7 @@
 		t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(source_list), i, 0), t_sl));
 	}
 	purple_presence_add_list(presence, t_GL);
+	g_list_free(t_GL);
 
 void
 purple_presence_add_status(presence, status)
@@ -361,28 +362,6 @@
 purple_status_type_destroy(status_type)
 	Purple::StatusType status_type
 
-Purple::StatusType
-purple_status_type_find_with_id(status_types, id)
-	SV *status_types
-	const char *id
-PREINIT:
-/* XXX Check that this function actually works, I think it might need a */
-/* status_type as it's first argument to work as $status_type->find_with_id */
-/* properly. */
-	GList *t_GL;
-	int i, t_len;
-CODE:
-	t_GL = NULL;
-	t_len = av_len((AV *)SvRV(status_types));
-
-	for (i = 0; i < t_len; i++) {
-		STRLEN t_sl;
-		t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(status_types), i, 0), t_sl));
-	}
-	RETVAL = (PurpleStatusType *)purple_status_type_find_with_id(t_GL, id);
-OUTPUT:
-	RETVAL
-
 Purple::StatusAttr
 purple_status_type_get_attr(status_type, id)
 	Purple::StatusType status_type
@@ -398,6 +377,26 @@
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::StatusAttr")));
 	}
 
+Purple::StatusType
+purple_status_type_find_with_id(status_types, id)
+	SV *status_types
+	const char *id
+PREINIT:
+	GList *t_GL;
+	int i, t_len;
+CODE:
+	t_GL = NULL;
+	t_len = av_len((AV *)SvRV(status_types));
+
+	for (i = 0; i < t_len; i++) {
+		STRLEN t_sl;
+		t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(status_types), i, 0), t_sl));
+	}
+	RETVAL = (PurpleStatusType *)purple_status_type_find_with_id(t_GL, id);
+	g_list_free(t_GL);
+OUTPUT:
+	RETVAL
+
 const char *
 purple_status_type_get_id(status_type)
 	Purple::StatusType status_type
--- a/libpurple/pounce.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/pounce.c	Sat Dec 15 05:15:31 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/protocols/bonjour/Makefile.am	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/bonjour/Makefile.am	Sat Dec 15 05:15:31 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_ft.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour_ft.c	Sat Dec 15 05:15:31 2007 +0000
@@ -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");
--- a/libpurple/protocols/bonjour/buddy.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Sat Dec 15 05:15:31 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 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Sat Dec 15 05:15:31 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/issues.txt	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/bonjour/issues.txt	Sat Dec 15 05:15:31 2007 +0000
@@ -2,5 +2,4 @@
 ============= Known issues ===============
 ==========================================
 
-* File transfers
 * Typing notifications
--- a/libpurple/protocols/bonjour/jabber.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Sat Dec 15 05:15:31 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>
@@ -62,34 +68,15 @@
 #define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" \
 		"<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" from=\"%s\" to=\"%s\">"
 
+enum sent_stream_start_types {
+	NOT_SENT       = 0,
+	PARTIALLY_SENT = 1,
+	FULLY_SENT     = 2
+};
+
 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) {
 
@@ -100,6 +87,8 @@
 	bconv->rx_handler = 0;
 	bconv->pb = pb;
 
+	bonjour_parser_setup(bconv);
+
 	return bconv;
 }
 
@@ -129,34 +118,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");
@@ -164,49 +167,41 @@
 					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 {
@@ -225,13 +220,24 @@
 	/*
 	 * 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)
 	{
 		bb = pb->proto_data;
-		if ((bb != NULL) && (g_ascii_strcasecmp(bb->ip, cbba->address) == 0))
-			*(cbba->pb) = pb;
+		if (bb != NULL) {
+			const char *ip;
+			GSList *tmp = bb->ips;
+
+			while(tmp) {
+				ip = tmp->data;
+				if (ip != NULL && g_ascii_strcasecmp(ip, cbba->address) == 0) {
+					*(cbba->pb) = pb;
+					break;
+				}
+				tmp = tmp->next;
+			}
+		}
 	}
 }
 
@@ -289,7 +295,7 @@
 	/* If we're not ready to actually send, append it to the buffer */
 	if (bconv->tx_handler != 0
 			|| bconv->connect_data != NULL
-			|| !bconv->sent_stream_start
+			|| bconv->sent_stream_start != FULLY_SENT
 			|| !bconv->recv_stream_start
 			|| purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
 		ret = -1;
@@ -319,7 +325,8 @@
 	}
 
 	if (ret < len) {
-		if (bconv->tx_handler == 0)
+		/* Don't interfere with the stream starting */
+		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);
@@ -409,20 +416,6 @@
 	}
 }
 
-void bonjour_jabber_stream_started(PurpleBuddy *pb) {
-	BonjourBuddy *bb = pb->proto_data;
-	BonjourJabberConversation *bconv = bb->conversation;
-
-	/* If the stream has been completely started, we can start doing stuff */
-	if (bconv->sent_stream_start && bconv->recv_stream_start && 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);
-		/* We can probably write the data right now. */
-		_send_data_write_cb(pb, bconv->socket, PURPLE_INPUT_WRITE);
-	}
-
-}
 
 struct _stream_start_data {
 	char *msg;
@@ -448,9 +441,14 @@
 	else if (ret <= 0) {
 		const char *err = g_strerror(errno);
 		PurpleConversation *conv;
+		const char *ip = NULL;
+
+		/* For better or worse, use the first IP*/
+		if (bb->ips)
+			ip = bb->ips->data;
 
 		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), ip ? ip : "(null)", bb->port_p2pj, err ? err : "(null)");
 
 		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
 		if (conv != NULL)
@@ -478,14 +476,13 @@
 
 	/* Stream started; process the send buffer if there is one */
 	purple_input_remove(bconv->tx_handler);
-	bconv->tx_handler= 0;
-	bconv->sent_stream_start = TRUE;
+	bconv->tx_handler = 0;
+	bconv->sent_stream_start = FULLY_SENT;
 
 	bonjour_jabber_stream_started(pb);
-
 }
 
-static gboolean bonjour_jabber_stream_init(PurpleBuddy *pb, int client_socket)
+static gboolean bonjour_jabber_send_stream_init(PurpleBuddy *pb, int client_socket)
 {
 	int ret, len;
 	char *stream_start;
@@ -495,6 +492,8 @@
 								   purple_buddy_get_name(pb));
 	len = strlen(stream_start);
 
+	bb->conversation->sent_stream_start = PARTIALLY_SENT;
+
 	/* Start the stream */
 	ret = send(client_socket, stream_start, len, 0);
 
@@ -502,9 +501,14 @@
 		ret = 0;
 	else if (ret <= 0) {
 		const char *err = g_strerror(errno);
+		const char *ip = NULL;
+
+		/* For better or worse, use the first IP*/
+		if (bb->ips)
+			ip = bb->ips->data;
 
 		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), ip ? ip : "(null)", bb->port_p2pj, err ? err : "(null)");
 
 		close(client_socket);
 		g_free(stream_start);
@@ -521,18 +525,60 @@
 		bb->conversation->tx_handler = purple_input_add(client_socket,
 			PURPLE_INPUT_WRITE, _start_stream, pb);
 	} else
-		bb->conversation->sent_stream_start = TRUE;
+		bb->conversation->sent_stream_start = FULLY_SENT;
 
 	g_free(stream_start);
 
-	/* setup the parser fresh for each stream */
-	bonjour_parser_setup(bb->conversation);
+	return TRUE;
+}
+
+static gboolean
+_async_bonjour_jabber_close_conversation(gpointer data) {
+	BonjourJabberConversation *bconv = data;
+	bonjour_jabber_close_conversation(bconv);
+	return FALSE;
+}
+
+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)) {
+		const char *err = g_strerror(errno);
+		PurpleConversation *conv;
+		const char *ip = NULL;
+
+		/* For better or worse, use the first IP*/
+		if (bb->ips)
+			ip = bb->ips->data;
 
-	bb->conversation->socket = client_socket;
-	bb->conversation->rx_handler = purple_input_add(client_socket,
-		PURPLE_INPUT_READ, _client_socket_handler, pb);
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
+				   purple_buddy_get_name(pb), ip ? ip : "(null)", bb->port_p2pj, 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));
 
-	return TRUE;
+		close(bconv->socket);
+		/* 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;
+		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) {
+		/* 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);
+		/* We can probably write the data right now. */
+		_send_data_write_cb(pb, bconv->socket, PURPLE_INPUT_WRITE);
+	}
+
 }
 
 static void
@@ -576,14 +622,15 @@
 	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);
 
-		if (!bonjour_jabber_stream_init(pb, client_socket)) {
-			close(client_socket);
-			return;
-		}
+		/* 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);
 
 	} else {
 		purple_debug_warning("bonjour", "Ignoring incoming connection because an existing connection exists.\n");
@@ -620,7 +667,7 @@
 	}
 
 	memset(&my_addr, 0, sizeof(struct sockaddr_in));
-	my_addr.sin_family = PF_INET;
+	my_addr.sin_family = AF_INET;
 
 	/* Attempt to find a free port */
 	bind_successful = FALSE;
@@ -681,9 +728,14 @@
 
 	if (source < 0) {
 		PurpleConversation *conv;
+		const char *ip = NULL;
+
+		/* For better or worse, use the first IP*/
+		if (bb->ips)
+			ip = bb->ips->data;
 
 		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), ip ? ip : "(null)", bb->port_p2pj, error ? error : "(null)");
 
 		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
 		if (conv != NULL)
@@ -696,12 +748,17 @@
 		return;
 	}
 
-	if (!bonjour_jabber_stream_init(pb, source)) {
+	if (!bonjour_jabber_send_stream_init(pb, source)) {
 		const char *err = g_strerror(errno);
 		PurpleConversation *conv;
+		const char *ip = NULL;
+
+		/* For better or worse, use the first IP*/
+		if (bb->ips)
+			ip = bb->ips->data;
 
 		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), ip ? ip : "(null)", bb->port_p2pj, err ? err : "(null)");
 
 		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
 		if (conv != NULL)
@@ -714,6 +771,11 @@
 		bb->conversation = NULL;
 		return;
 	}
+
+	/* 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);
 }
 
 static PurpleBuddy *
@@ -737,6 +799,11 @@
 	{
 		PurpleProxyConnectData *connect_data;
 		PurpleProxyInfo *proxy_info;
+		const char *ip = NULL;
+
+		/* For better or worse, use the first IP*/
+		if (bb->ips)
+			ip = bb->ips->data;
 
 		purple_debug_info("bonjour", "Starting conversation with %s\n", to);
 
@@ -749,8 +816,8 @@
 		}
 		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, data->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);
@@ -843,7 +910,7 @@
 		/* Close the socket and remove the watcher */
 		if (bconv->socket >= 0) {
 			/* Send the end of the stream to the other end of the conversation */
-			if (bconv->sent_stream_start)
+			if (bconv->sent_stream_start == FULLY_SENT)
 				send(bconv->socket, STREAM_END, strlen(STREAM_END), 0);
 			/* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
 			close(bconv->socket);
--- a/libpurple/protocols/bonjour/jabber.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.h	Sat Dec 15 05:15:31 2007 +0000
@@ -47,7 +47,7 @@
 	guint rx_handler;
 	guint tx_handler;
 	PurpleCircBuffer *tx_buf;
-	gboolean sent_stream_start;
+	int sent_stream_start; /* 0 = Unsent, 1 = Partial, 2 = Complete */
 	gboolean recv_stream_start;
 	PurpleProxyConnectData *connect_data;
 	gpointer stream_data;
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Sat Dec 15 05:15:31 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);
@@ -155,7 +244,7 @@
 			/* Make sure it isn't us */
 			if (purple_utf8_strcasecmp(name, account->username) != 0) {
 				if (!avahi_service_resolver_new(avahi_service_browser_get_client(b),
-						interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC,
+						interface, protocol, name, type, domain, AVAHI_PROTO_INET,
 						0, _resolver_callback, account)) {
 					purple_debug_warning("bonjour", "_browser_callback -- Error initiating resolver: %s\n",
 						avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
@@ -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;
+	}
 }
 
 /****************************
@@ -315,14 +443,14 @@
 		case PUBLISH_START:
 			publish_result = avahi_entry_group_add_service_strlst(
 				idata->group, AVAHI_IF_UNSPEC,
-				AVAHI_PROTO_UNSPEC, 0,
+				AVAHI_PROTO_INET, 0,
 				purple_account_get_username(data->account),
 				ICHAT_SERVICE, NULL, NULL, data->port_p2pj, lst);
 			break;
 		case PUBLISH_UPDATE:
 			publish_result = avahi_entry_group_update_service_txt_strlst(
 				idata->group, AVAHI_IF_UNSPEC,
-				AVAHI_PROTO_UNSPEC, 0,
+				AVAHI_PROTO_INET, 0,
 				purple_account_get_username(data->account),
 				ICHAT_SERVICE, NULL, lst);
 			break;
@@ -354,7 +482,7 @@
 
 	g_return_val_if_fail(idata != NULL, FALSE);
 
-	idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, ICHAT_SERVICE, NULL, 0, _browser_callback, data->account);
+	idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, ICHAT_SERVICE, NULL, 0, _browser_callback, data->account);
 	if (!idata->sb) {
 
 		purple_debug_error("bonjour",
@@ -400,7 +528,7 @@
 				purple_account_get_username(data->account));
 
 		ret = avahi_entry_group_add_record(idata->buddy_icon_group, AVAHI_IF_UNSPEC,
-			AVAHI_PROTO_UNSPEC, flags, svc_name,
+			AVAHI_PROTO_INET, flags, svc_name,
 			AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 120, avatar_data, avatar_len);
 
 		g_free(svc_name);
@@ -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);
 
@@ -486,7 +617,7 @@
 
 	name = g_strdup_printf("%s." ICHAT_SERVICE "local", buddy->name);
 	idata->buddy_icon_rec_browser = avahi_record_browser_new(session_idata->client, AVAHI_IF_UNSPEC,
-		AVAHI_PROTO_UNSPEC, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0,
+		AVAHI_PROTO_INET, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0,
 		_buddy_icon_record_cb, buddy);
 	g_free(name);
 
--- a/libpurple/protocols/bonjour/mdns_howl.c	Sat Dec 15 05:12:24 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 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Sat Dec 15 05:15:31 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/irc/irc.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/irc/irc.c	Sat Dec 15 05:15:31 2007 +0000
@@ -184,9 +184,14 @@
 /* XXX I don't like messing directly with these buddies */
 gboolean irc_blist_timeout(struct irc_conn *irc)
 {
-	GString *string = g_string_sized_new(512);
+	GString *string;
 	char *list, *buf;
 
+	if (irc->ison_outstanding)
+		return TRUE;
+
+	string = g_string_sized_new(512);
+
 	g_hash_table_foreach(irc->buddies, (GHFunc)irc_buddy_append, (gpointer)string);
 
 	list = g_string_free(string, FALSE);
@@ -200,6 +205,8 @@
 	irc_send(irc, buf);
 	g_free(buf);
 
+	irc->ison_outstanding = TRUE;
+
 	return TRUE;
 }
 
--- a/libpurple/protocols/irc/irc.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/irc/irc.h	Sat Dec 15 05:15:31 2007 +0000
@@ -56,6 +56,8 @@
 	guint timer;
 	GHashTable *buddies;
 
+	gboolean ison_outstanding;
+
 	char *inbuf;
 	int inbuflen;
 	int inbufused;
--- a/libpurple/protocols/irc/msgs.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/irc/msgs.c	Sat Dec 15 05:15:31 2007 +0000
@@ -707,6 +707,7 @@
 	g_strfreev(nicks);
 
 	g_hash_table_foreach(irc->buddies, (GHFunc)irc_buddy_status, (gpointer)irc);
+	irc->ison_outstanding = FALSE;
 }
 
 static void irc_buddy_status(char *name, struct irc_buddy *ib, struct irc_conn *irc)
--- a/libpurple/protocols/irc/parse.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/irc/parse.c	Sat Dec 15 05:15:31 2007 +0000
@@ -64,7 +64,7 @@
 	{ "318", "nt:", irc_msg_endwhois },	/* End of WHOIS			*/
 	{ "319", "nn:", irc_msg_whois },	/* Whois channels		*/
 	{ "320", "nn:", irc_msg_whois },	/* Whois (fn ident)		*/
-	{ "314", "nnvvv:", irc_msg_whois },	/* Whowas user			*/
+	{ "314", "nnnvv:", irc_msg_whois },	/* Whowas user			*/
 	{ "369", "nt:", irc_msg_endwhois },	/* End of WHOWAS		*/
 	{ "321", "*", irc_msg_list },		/* Start of list		*/
 	{ "322", "ncv:", irc_msg_list },	/* List.			*/
--- a/libpurple/protocols/jabber/caps.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/jabber/caps.c	Sat Dec 15 05:15:31 2007 +0000
@@ -257,18 +257,23 @@
 
 /* this function assumes that all information is available locally */
 static JabberCapsClientInfo *jabber_caps_collect_info(const char *node, const char *ver, GList *ext) {
-	JabberCapsClientInfo *result = g_new0(JabberCapsClientInfo, 1);
+	JabberCapsClientInfo *result;
 	JabberCapsKey *key = g_new0(JabberCapsKey, 1);
 	JabberCapsValue *caps;
 	GList *iter;
-	
+
 	key->node = (char *)node;
 	key->ver = (char *)ver;
-	
+
 	caps = g_hash_table_lookup(capstable,key);
-	
+
 	g_free(key);
-	
+
+	if (caps == NULL)
+		return NULL;
+
+	result = g_new0(JabberCapsClientInfo, 1);
+
 	/* join all information */
 	for(iter = caps->identities; iter; iter = g_list_next(iter)) {
 		JabberCapsIdentity *id = iter->data;
--- a/libpurple/protocols/jabber/jabber.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sat Dec 15 05:15:31 2007 +0000
@@ -537,12 +537,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)
@@ -1280,6 +1281,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 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sat Dec 15 05:15:31 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/presence.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sat Dec 15 05:15:31 2007 +0000
@@ -376,23 +376,26 @@
 static void jabber_presence_set_capabilities(JabberCapsClientInfo *info, gpointer user_data) {
 	JabberPresenceCapabilities *userdata = user_data;
 	GList *iter;
-	
+
 	if(userdata->jbr->caps)
 		jabber_caps_free_clientinfo(userdata->jbr->caps);
 	userdata->jbr->caps = info;
-	
-	for(iter = info->features; iter; iter = g_list_next(iter)) {
-		if(!strcmp((const char*)iter->data, "http://jabber.org/protocol/commands")) {
-			JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
-			xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items");
-			xmlnode_set_attrib(iq->node, "to", userdata->from);
-			xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/commands");
-			
-			jabber_iq_set_callback(iq, jabber_adhoc_disco_result_cb, NULL);
-			jabber_iq_send(iq);
-			break;
+
+	if (info) {
+		for(iter = info->features; iter; iter = g_list_next(iter)) {
+			if(!strcmp((const char*)iter->data, "http://jabber.org/protocol/commands")) {
+				JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
+				xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items");
+				xmlnode_set_attrib(iq->node, "to", userdata->from);
+				xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/commands");
+
+				jabber_iq_set_callback(iq, jabber_adhoc_disco_result_cb, NULL);
+				jabber_iq_send(iq);
+				break;
+			}
 		}
 	}
+
 	g_free(userdata->from);
 	g_free(userdata);
 }
--- a/libpurple/protocols/msn/command.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msn/command.c	Sat Dec 15 05:15:31 2007 +0000
@@ -117,6 +117,7 @@
 		cmd->trId = 0;
 	}
 
+	/* khc: Huh! */
 	/*add payload Length checking*/
 	msn_set_payload_len(cmd);
 	purple_debug_info("MSNP14","get payload len:%d\n",cmd->payload_len);
--- a/libpurple/protocols/msn/notification.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msn/notification.c	Sat Dec 15 05:15:31 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);
@@ -1777,7 +1778,7 @@
 			passport = msn_user_get_passport(session->user);
 			url = session->passport_info.file;
 
-			purple_notify_emails(gc, atoi(unread), FALSE, NULL, NULL,
+			purple_notify_emails(gc, count, FALSE, NULL, NULL,
 							   &passport, &url, NULL, NULL);
 		}
 	}
@@ -1850,7 +1851,7 @@
 			passport = msn_user_get_passport(session->user);
 			url = session->passport_info.file;
 
-			purple_notify_emails(gc, atoi(unread), FALSE, NULL, NULL,
+			purple_notify_emails(gc, count, FALSE, NULL, NULL,
 							   &passport, &url, NULL, NULL);
 		}
 	}
--- a/libpurple/protocols/msn/oim.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msn/oim.c	Sat Dec 15 05:15:31 2007 +0000
@@ -488,10 +488,12 @@
 		char *unread = xmlnode_get_data(iu_node);
 		const char *passport = msn_user_get_passport(session->user);
 		const char *url = session->passport_info.file;
+		int count = atoi(unread);
 
 		/* XXX/khc: pretty sure this is wrong */
-		purple_notify_emails(session->account->gc, atoi(unread), FALSE, NULL,
-			NULL, &passport, &url, NULL, NULL);
+		if (count > 0)
+			purple_notify_emails(session->account->gc, count, FALSE, NULL,
+				NULL, &passport, &url, NULL, NULL);
 		g_free(unread);
 	}
 
--- a/libpurple/protocols/msn/session.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msn/session.c	Sat Dec 15 05:15:31 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/state.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msn/state.c	Sat Dec 15 05:15:31 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 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msn/user.c	Sat Dec 15 05:15:31 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/msnp9/directconn.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msnp9/directconn.c	Sat Dec 15 05:15:31 2007 +0000
@@ -81,6 +81,7 @@
 create_listener(int port)
 {
 	int fd;
+	int flags;
 	const int on = 1;
 
 #if 0
@@ -156,7 +157,8 @@
 		return -1;
 	}
 
-	fcntl(fd, F_SETFL, O_NONBLOCK);
+	flags = fcntl(fd, F_GETFL);
+	fcntl(fd, F_SETFL, flags | O_NONBLOCK);
 
 	return fd;
 }
@@ -353,7 +355,7 @@
 }
 
 static void
-connect_cb(gpointer data, gint source, const gchar *error_message)
+connect_cb(gpointer data, gint source, PurpleInputCondition cond)
 {
 	MsnDirectConn* directconn;
 	int fd;
@@ -405,6 +407,15 @@
 	}
 }
 
+static void
+directconn_connect_cb(gpointer data, gint source, const gchar *error_message)
+{
+	if (error_message)
+		purple_debug_error("msn", "Error making direct connection: %s\n", error_message);
+
+	connect_cb(data, source, PURPLE_INPUT_READ);
+}
+
 gboolean
 msn_directconn_connect(MsnDirectConn *directconn, const char *host, int port)
 {
@@ -424,14 +435,9 @@
 #endif
 
 	directconn->connect_data = purple_proxy_connect(NULL, session->account,
-			host, port, connect_cb, directconn);
+			host, port, directconn_connect_cb, directconn);
 
-	if (directconn->connect_data != NULL)
-	{
-		return TRUE;
-	}
-	else
-		return FALSE;
+	return (directconn->connect_data != NULL);
 }
 
 #if 0
--- a/libpurple/protocols/msnp9/notification.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msnp9/notification.c	Sat Dec 15 05:15:31 2007 +0000
@@ -235,6 +235,8 @@
 		/* OK */
 		const char *friendly = purple_url_decode(cmd->params[3]);
 
+		session->passport_info.verified = atoi(cmd->params[4]);
+
 		purple_connection_set_display_name(gc, friendly);
 
 		msn_session_set_login_step(session, MSN_LOGIN_STEP_SYN);
--- a/libpurple/protocols/msnp9/servconn.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msnp9/servconn.c	Sat Dec 15 05:15:31 2007 +0000
@@ -468,6 +468,7 @@
 create_listener(int port)
 {
 	int fd;
+	int flags;
 	const int on = 1;
 
 #if 0
@@ -543,7 +544,8 @@
 		return -1;
 	}
 
-	fcntl(fd, F_SETFL, O_NONBLOCK);
+	flags = fcntl(fd, F_GETFL);
+	fcntl(fd, F_SETFL, flags | O_NONBLOCK);
 
 	return fd;
 }
--- a/libpurple/protocols/msnp9/session.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msnp9/session.h	Sat Dec 15 05:15:31 2007 +0000
@@ -114,7 +114,7 @@
 		char *file;
 		char *client_ip;
 		int client_port;
-
+		int verified;
 	} passport_info;
 };
 
--- a/libpurple/protocols/msnp9/user.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/msnp9/user.c	Sat Dec 15 05:15:31 2007 +0000
@@ -126,16 +126,20 @@
 msn_user_set_friendly_name(MsnUser *user, const char *name)
 {
 	MsnCmdProc *cmdproc;
+	MsnSession *session;
 	const char *encoded;
 
 	g_return_if_fail(user != NULL);
 
 	encoded = purple_url_encode(name);
+	session = user->userlist->session;
 
-	if (user->friendly_name && strcmp(user->friendly_name, name) && (strlen(encoded) < 387)) {
+	if (user->friendly_name && strcmp(user->friendly_name, name)
+		&& (strlen(encoded) < 387) && session->passport_info.verified &&
+		(user->list_op & MSN_LIST_FL_OP)) {
 		/* copy the new name to the server list, but only when new */
 		/* should we check this more thoroughly? */
-		cmdproc = user->userlist->session->notification->cmdproc;
+		cmdproc = session->notification->cmdproc;
 		msn_cmdproc_send(cmdproc, "REA", "%s %s",
 						 user->passport,
 						 encoded);
--- a/libpurple/protocols/myspace/myspace.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Sat Dec 15 05:15:31 2007 +0000
@@ -1047,15 +1047,18 @@
 msim_set_status(PurpleAccount *account, PurpleStatus *status)
 {
 	PurpleStatusType *type;
+	PurplePresence *pres;
 	MsimSession *session;
 	guint status_code;
-	gchar *statstring;
+	const gchar *message;
+	gchar *stripped;
 
 	session = (MsimSession *)account->gc->proto_data;
 
 	g_return_if_fail(MSIM_SESSION_VALID(session));
 
 	type = purple_status_get_type(status);
+	pres = purple_status_get_presence(status);
 
 	switch (purple_status_type_get_primitive(type)) {
 		case PURPLE_STATUS_AVAILABLE:
@@ -1083,16 +1086,20 @@
 			break;
 	}
 
-	statstring = (gchar *)purple_status_get_attr_string(status, "message");
-
-	if (!statstring) {
-		statstring = "";
-	}
+	message = purple_status_get_attr_string(status, "message");
 
 	/* Status strings are plain text. */
-	statstring = purple_markup_strip_html(statstring);
-
-	msim_set_status_code(session, status_code, statstring);
+	if (message != NULL)
+		stripped = purple_markup_strip_html(message);
+	else
+		stripped = g_strdup("");
+
+	msim_set_status_code(session, status_code, stripped);
+
+	/* If we should be idle, set that status. Time is irrelevant here. */
+	if (purple_presence_is_idle(pres) && status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN)
+		msim_set_idle(account->gc, 1);
+
 }
 
 /** Go idle. */
@@ -1100,6 +1107,7 @@
 msim_set_idle(PurpleConnection *gc, int time)
 {
 	MsimSession *session;
+	PurpleStatus *status;
 
 	g_return_if_fail(gc != NULL);
 
@@ -1107,16 +1115,30 @@
 
 	g_return_if_fail(MSIM_SESSION_VALID(session));
 
+	status = purple_account_get_active_status(session->account);
+
 	if (time == 0) {
 		/* Going back from idle. In msim, idle is mutually exclusive 
 		 * from the other states (you can only be away or idle, but not
-		 * both, for example), so by going non-idle I go online.
+		 * both, for example), so by going non-idle I go back to what
+		 * libpurple says I should be.
 		 */
-		/* TODO: find out how to keep old status string? */
-		msim_set_status_code(session, MSIM_STATUS_CODE_ONLINE, g_strdup(""));
+		msim_set_status(session->account, status);
 	} else {
+		const gchar *message;
+		gchar *stripped;
+
+		/* Set the idle message to the status message from the real
+		 * current status.
+		 */
+		message = purple_status_get_attr_string(status, "message");
+		if (message != NULL)
+			stripped = purple_markup_strip_html(message);
+		else
+			stripped = g_strdup("");
+
 		/* msim doesn't support idle time, so just go idle */
-		msim_set_status_code(session, MSIM_STATUS_CODE_IDLE, g_strdup(""));
+		msim_set_status_code(session, MSIM_STATUS_CODE_IDLE, stripped);
 	}
 }
 
@@ -1347,7 +1369,9 @@
 	msim_msg_dump("msim_check_inbox_cb: reply=%s\n", reply);
 
 	body = msim_msg_get_dictionary(reply, "body");
-	g_return_if_fail(body != NULL);
+
+	if (body == NULL)
+		return;
 
 	old_inbox_status = session->inbox_status;
 
@@ -1411,6 +1435,11 @@
 
 	session = (MsimSession *)data;
 
+	if (!MSIM_SESSION_VALID(session)) {
+		purple_debug_info("msim", "msim_check_inbox: session invalid, stopping the mail check.\n");
+		return FALSE;
+	}
+
 	purple_debug_info("msim", "msim_check_inbox: checking mail\n");
 	g_return_val_if_fail(msim_send(session, 
 			"persist", MSIM_TYPE_INTEGER, 1,
@@ -1623,7 +1652,7 @@
 
 	/* Check mail if they want to. */
 	if (purple_account_get_check_mail(session->account)) {
-		purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, 
+		session->inbox_handle = purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, 
 				(GSourceFunc)msim_check_inbox, session);
 		msim_check_inbox(session);
 	}
@@ -1894,7 +1923,7 @@
 		purple_debug_info("msim", "msim_status: found buddy %s\n", username);
 	}
 
-	if (status_headline) {
+	if (status_headline && strcmp(status_headline, "") != 0) {
 		/* The status headline is plaintext, but libpurple treats it as HTML,
 		 * so escape any HTML characters to their entity equivalents. */
 		status_headline_escaped = g_markup_escape_text(status_headline, strlen(status_headline));
@@ -1925,8 +1954,8 @@
 			break;
 
 		case MSIM_STATUS_CODE_IDLE:
-			/* will be handled below */
-			purple_status_code = -1;
+			/* Treat idle as an available status. */
+			purple_status_code = PURPLE_STATUS_AVAILABLE;
 			break;
 
 		default:
--- a/libpurple/protocols/myspace/session.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/myspace/session.c	Sat Dec 15 05:15:31 2007 +0000
@@ -63,6 +63,7 @@
 	session->next_rid = 1;
 	session->last_comm = time(NULL);
 	session->inbox_status = 0;
+	session->inbox_handle = 0;
 	
 	return session;
 }
@@ -90,6 +91,11 @@
 		msim_msg_free(session->server_info);
 	}
 	
+	/* Stop checking the inbox at the end of the session. */
+	if (session->inbox_handle) {
+		purple_timeout_remove(session->inbox_handle);
+	}
+
 	g_free(session);
 }
 
--- a/libpurple/protocols/myspace/session.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/myspace/session.h	Sat Dec 15 05:15:31 2007 +0000
@@ -45,6 +45,7 @@
 	guint next_rid;                     /**< Next request/response ID */
 	time_t last_comm;                   /**< Time received last communication */
 	guint inbox_status;                 /**< Bit field of inbox notifications */
+	guint inbox_handle;                 /**< The handle for the mail check timer */
 } MsimSession;
 
 /* Check if an MsimSession is valid */
--- a/libpurple/protocols/oscar/family_chatnav.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/oscar/family_chatnav.c	Sat Dec 15 05:15:31 2007 +0000
@@ -29,6 +29,49 @@
 
 #include "oscar.h"
 
+static int
+error(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs)
+{
+	int ret = 0;
+	aim_snac_t *snac2;
+	guint16 error, chatnav_error;
+	GSList *tlvlist;
+
+	if (!(snac2 = aim_remsnac(od, snac->id))) {
+		purple_debug_warning("oscar", "chatnav error: received response to unknown request (%08lx)\n", snac->id);
+		return 0;
+	}
+
+	if (snac2->family != 0x000d) {
+		purple_debug_warning("oscar", "chatnav error: received response that maps to corrupt request (fam=%04x)\n", snac2->family);
+		return 0;
+	}
+
+	/*
+	 * We now know what the original SNAC subtype was.
+	 */
+	if (snac2->type == 0x0008) /* create room */
+	{
+		error = byte_stream_get16(bs);
+		tlvlist = aim_tlvlist_read(bs);
+		chatnav_error = aim_tlv_get16(tlvlist, 0x0008, 1);
+
+		purple_debug_warning("oscar",
+				"Could not join room, error=0x%04hx, chatnav_error=0x%04hx\n",
+				error, chatnav_error);
+		purple_notify_error(od->gc, NULL, _("Could not join chat room"),
+				chatnav_error == 0x0033 ? _("Invalid chat room name") : _("Unknown error"));
+
+		ret = 1;
+	}
+
+	if (snac2)
+		g_free(snac2->data);
+	g_free(snac2);
+
+	return ret;
+}
+
 /*
  * Subtype 0x0002
  *
@@ -451,7 +494,9 @@
 static int
 snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs)
 {
-	if (snac->subtype == 0x0009)
+	if (snac->subtype == 0x0001)
+		return error(od, conn, mod, frame, snac, bs);
+	else if (snac->subtype == 0x0009)
 		return parseinfo(od, conn, mod, frame, snac, bs);
 
 	return 0;
--- a/libpurple/protocols/oscar/family_feedbag.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/oscar/family_feedbag.c	Sat Dec 15 05:15:31 2007 +0000
@@ -115,14 +115,10 @@
 	gboolean exists;
 	struct aim_ssi_item *cur, *new;
 
-	new = (struct aim_ssi_item *)g_malloc(sizeof(struct aim_ssi_item));
+	new = g_new(struct aim_ssi_item, 1);
 
 	/* Set the name */
-	if (name) {
-		new->name = (char *)g_malloc((strlen(name)+1)*sizeof(char));
-		strcpy(new->name, name);
-	} else
-		new->name = NULL;
+	new->name = g_strdup(name);
 
 	/* Set the group ID# and buddy ID# */
 	new->gid = gid;
@@ -345,13 +341,9 @@
  */
 struct aim_ssi_item *aim_ssi_itemlist_exists(struct aim_ssi_item *list, const char *sn)
 {
-	struct aim_ssi_item *cur;
-	if (!list || !sn)
+	if (!sn)
 		return NULL;
-	for (cur=list; cur; cur=cur->next)
-		if ((cur->type == AIM_SSI_TYPE_BUDDY) && (cur->name) && (!aim_sncmp(cur->name, sn)))
-			return cur;
-	return NULL;
+	return aim_ssi_itemlist_finditem(list, NULL, sn, AIM_SSI_TYPE_BUDDY);
 }
 
 /**
@@ -510,7 +502,7 @@
 		for (cur1=od->ssi.official; cur1 && (n < 15); cur1=cur1->next) {
 			if (!aim_ssi_itemlist_find(od->ssi.local, cur1->gid, cur1->bid)) {
 				n++;
-				new = (struct aim_ssi_tmp *)g_malloc(sizeof(struct aim_ssi_tmp));
+				new = g_new(struct aim_ssi_tmp, 1);
 				new->action = SNAC_SUBTYPE_FEEDBAG_DEL;
 				new->ack = 0xffff;
 				new->name = NULL;
@@ -530,7 +522,7 @@
 		for (cur1=od->ssi.local; cur1 && (n < 15); cur1=cur1->next) {
 			if (!aim_ssi_itemlist_find(od->ssi.official, cur1->gid, cur1->bid)) {
 				n++;
-				new = (struct aim_ssi_tmp *)g_malloc(sizeof(struct aim_ssi_tmp));
+				new = g_new(struct aim_ssi_tmp, 1);
 				new->action = SNAC_SUBTYPE_FEEDBAG_ADD;
 				new->ack = 0xffff;
 				new->name = NULL;
@@ -551,7 +543,7 @@
 			cur2 = aim_ssi_itemlist_find(od->ssi.official, cur1->gid, cur1->bid);
 			if (cur2 && (aim_ssi_itemlist_cmp(cur1, cur2))) {
 				n++;
-				new = (struct aim_ssi_tmp *)g_malloc(sizeof(struct aim_ssi_tmp));
+				new = g_new(struct aim_ssi_tmp, 1);
 				new->action = SNAC_SUBTYPE_FEEDBAG_MOD;
 				new->ack = 0xffff;
 				new->name = NULL;
@@ -1028,8 +1020,7 @@
 		return -EINVAL;
 
 	g_free(group->name);
-	group->name = (char *)g_malloc((strlen(newgn)+1)*sizeof(char));
-	strcpy(group->name, newgn);
+	group->name = g_strdup(newgn);
 
 	/* Sync our local list with the server list */
 	return aim_ssi_sync(od);
@@ -1461,11 +1452,7 @@
 		if ((item = aim_ssi_itemlist_find(od->ssi.local, gid, bid))) {
 			item->type = type;
 			g_free(item->name);
-			if (name) {
-				item->name = (char *)g_malloc((strlen(name)+1)*sizeof(char));
-				strcpy(item->name, name);
-			} else
-				item->name = NULL;
+			item->name = g_strdup(name);
 			aim_tlvlist_free(item->data);
 			item->data = aim_tlvlist_copy(data);
 		}
@@ -1473,11 +1460,7 @@
 		if ((item = aim_ssi_itemlist_find(od->ssi.official, gid, bid))) {
 			item->type = type;
 			g_free(item->name);
-			if (name) {
-				item->name = (char *)g_malloc((strlen(name)+1)*sizeof(char));
-				strcpy(item->name, name);
-			} else
-				item->name = NULL;
+			item->name = g_strdup(name);
 			aim_tlvlist_free(item->data);
 			item->data = aim_tlvlist_copy(data);
 		}
@@ -1555,10 +1538,7 @@
 				/* Remove the item from the local list */
 				/* Make sure cur->item is still valid memory */
 				if (aim_ssi_itemlist_valid(od->ssi.local, cur->item)) {
-					if (cur->item->name) {
-						cur->name = (char *)g_malloc((strlen(cur->item->name)+1)*sizeof(char));
-						strcpy(cur->name, cur->item->name);
-					}
+					cur->name = g_strdup(cur->item->name);
 					aim_ssi_itemlist_del(&od->ssi.local, cur->item);
 				}
 				cur->item = NULL;
@@ -1569,11 +1549,7 @@
 					struct aim_ssi_item *cur1;
 					if ((cur1 = aim_ssi_itemlist_find(od->ssi.official, cur->item->gid, cur->item->bid))) {
 						g_free(cur->item->name);
-						if (cur1->name) {
-							cur->item->name = (char *)g_malloc((strlen(cur1->name)+1)*sizeof(char));
-							strcpy(cur->item->name, cur1->name);
-						} else
-							cur->item->name = NULL;
+						cur->item->name = g_strdup(cur1->name);
 						aim_tlvlist_free(cur->item->data);
 						cur->item->data = aim_tlvlist_copy(cur1->data);
 					}
@@ -1603,11 +1579,7 @@
 					struct aim_ssi_item *cur1;
 					if ((cur1 = aim_ssi_itemlist_find(od->ssi.official, cur->item->gid, cur->item->bid))) {
 						g_free(cur1->name);
-						if (cur->item->name) {
-							cur1->name = (char *)g_malloc((strlen(cur->item->name)+1)*sizeof(char));
-							strcpy(cur1->name, cur->item->name);
-						} else
-							cur1->name = NULL;
+						cur1->name = g_strdup(cur->item->name);
 						aim_tlvlist_free(cur1->data);
 						cur1->data = aim_tlvlist_copy(cur->item->data);
 					}
--- a/libpurple/protocols/oscar/family_icbm.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/oscar/family_icbm.c	Sat Dec 15 05:15:31 2007 +0000
@@ -213,7 +213,6 @@
  */
 static int aim_im_paraminfo(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs)
 {
-	aim_rxcallback_t userfunc;
 	struct aim_icbmparameters params;
 
 	params.maxchan = byte_stream_get16(bs);
@@ -223,8 +222,11 @@
 	params.maxrecverwarn = byte_stream_get16(bs);
 	params.minmsginterval = byte_stream_get32(bs);
 
-	if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
-		return userfunc(od, conn, frame, &params);
+	params.flags = 0x0000000b;
+	params.maxmsglen = 8000;
+	params.minmsginterval = 0;
+
+	aim_im_setparams(od, &params);
 
 	return 0;
 }
@@ -292,7 +294,7 @@
 		if (!args->msg || (args->msglen <= 0))
 			return -EINVAL;
 
-		if (args->msglen >= MAXMSGLEN)
+		if (args->msglen > MAXMSGLEN)
 			return -E2BIG;
 	}
 
--- a/libpurple/protocols/oscar/oscar.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Sat Dec 15 05:15:31 2007 +0000
@@ -176,7 +176,6 @@
 static int purple_parse_locaterights(OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_parse_buddyrights(OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_parse_locerr     (OscarData *, FlapConnection *, FlapFrame *, ...);
-static int purple_icbm_param_info  (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_parse_genericerr (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_memrequest       (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_selfinfo         (OscarData *, FlapConnection *, FlapFrame *, ...);
@@ -1249,7 +1248,6 @@
 	oscar_data_addhandler(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_RECVAUTHREQ, purple_ssi_authrequest, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_RECVAUTHREP, purple_ssi_authreply, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_ADDED, purple_ssi_gotadded, 0);
-	oscar_data_addhandler(od, SNAC_FAMILY_ICBM, 0x0005, purple_icbm_param_info, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_ICBM, SNAC_SUBTYPE_ICBM_INCOMING, purple_parse_incoming_im, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_ICBM, SNAC_SUBTYPE_ICBM_MISSEDCALL, purple_parse_misses, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_ICBM, SNAC_SUBTYPE_ICBM_CLIENTAUTORESP, purple_parse_clientauto, 0);
@@ -1854,9 +1852,6 @@
 		signon = info->onlinesince;
 	else if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN)
 		signon = time(NULL) - info->sessionlen;
-	if (!aim_sncmp(purple_account_get_username(account), info->sn)) {
-		purple_connection_set_display_name(gc, info->sn);
-	}
 	purple_prpl_got_user_login_time(account, info->sn, signon);
 
 	/* Idle time stuff */
@@ -3463,6 +3458,8 @@
 	info = va_arg(ap, aim_userinfo_t *);
 	va_end(ap);
 
+	purple_connection_set_display_name(od->gc, info->sn);
+
 	/*
 	 * What's with the + 0.5?
 	 * The 0.5 is basically poor-man's rounding.  Normally
@@ -3524,32 +3521,6 @@
 	return 1;
 }
 
-static int purple_icbm_param_info(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) {
-	struct aim_icbmparameters *params;
-	va_list ap;
-
-	va_start(ap, fr);
-	params = va_arg(ap, struct aim_icbmparameters *);
-	va_end(ap);
-
-	/* XXX - evidently this crashes on solaris. i have no clue why
-	purple_debug_misc("oscar", "ICBM Parameters: maxchannel = %hu, default flags = 0x%08lx, max msg len = %hu, "
-			"max sender evil = %f, max receiver evil = %f, min msg interval = %u\n",
-			params->maxchan, params->flags, params->maxmsglen,
-			((float)params->maxsenderwarn)/10.0, ((float)params->maxrecverwarn)/10.0,
-			params->minmsginterval);
-	*/
-
-	/* Maybe senderwarn and recverwarn should be user preferences... */
-	params->flags = 0x0000000b;
-	params->maxmsglen = 8000;
-	params->minmsginterval = 0;
-
-	aim_im_setparams(od, params);
-
-	return 1;
-}
-
 static int purple_parse_locaterights(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
 {
 	PurpleConnection *gc = od->gc;
@@ -5391,6 +5362,7 @@
 
 	if (chat_name != NULL)
 		g_hash_table_insert(defaults, "room", g_strdup(chat_name));
+	g_hash_table_insert(defaults, "exchange", g_strdup("4"));
 
 	return defaults;
 }
@@ -5407,26 +5379,29 @@
 	OscarData *od = (OscarData *)gc->proto_data;
 	FlapConnection *conn;
 	char *name, *exchange;
+	int exchange_int;
 
 	name = g_hash_table_lookup(data, "room");
 	exchange = g_hash_table_lookup(data, "exchange");
 
-	if ((name == NULL) || (*name == '\0')) {
-		purple_notify_error(gc, NULL, _("Invalid chat name specified."), NULL);
-		return;
-	}
+	g_return_if_fail(name != NULL && *name != '\0');
+	g_return_if_fail(exchange != NULL);
+
+	errno = 0;
+	exchange_int = strtol(exchange, NULL, 10);
+	g_return_if_fail(errno == 0);
 
 	purple_debug_info("oscar", "Attempting to join chat room %s.\n", name);
 
 	if ((conn = flap_connection_getbytype(od, SNAC_FAMILY_CHATNAV)))
 	{
 		purple_debug_info("oscar", "chatnav exists, creating room\n");
-		aim_chatnav_createroom(od, conn, name, atoi(exchange));
+		aim_chatnav_createroom(od, conn, name, exchange_int);
 	} else {
 		/* this gets tricky */
 		struct create_room *cr = g_new0(struct create_room, 1);
 		purple_debug_info("oscar", "chatnav does not exist, opening chatnav\n");
-		cr->exchange = atoi(exchange);
+		cr->exchange = exchange_int;
 		cr->name = g_strdup(name);
 		od->create_rooms = g_slist_prepend(od->create_rooms, cr);
 		aim_srv_requestnew(od, SNAC_FAMILY_CHATNAV);
@@ -6752,7 +6727,7 @@
 	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
 
 	option = purple_account_option_bool_new(
-		_("Always use ICQ proxy server for file transfers\n(slower, but does not reveal your IP address)"), "always_use_rv_proxy",
+		_("Always use AIM/ICQ proxy server for\nfile transfers and direct IM (slower,\nbut does not reveal your IP address)"), "always_use_rv_proxy",
 		OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY);
 	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
 
--- a/libpurple/protocols/oscar/oscar.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Sat Dec 15 05:15:31 2007 +0000
@@ -94,11 +94,8 @@
  * for WinAIM clients (up through the latest (4.0.1957)) to
  * send any more than 1kb.  Amaze all your windows friends
  * with utterly oversized instant messages!
- *
- * TODO: the real limit is the total SNAC size at 8192. Fix this.
- *
  */
-#define MAXMSGLEN 7987
+#define MAXMSGLEN 2544
 
 /*
  * Maximum size of a Buddy Icon.
--- a/libpurple/protocols/qq/header_info.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/qq/header_info.h	Sat Dec 15 05:15:31 2007 +0000
@@ -33,7 +33,7 @@
 #define QQ_PACKET_TAG           0x02	/* all QQ text packets starts with it */
 #define QQ_PACKET_TAIL          0x03	/* all QQ text packets end with it */
 
-#define QQ_CLIENT       0x0E1B
+#define QQ_CLIENT       0x0d55
 
 /* list of known QQ commands */
 enum {
--- a/libpurple/protocols/yahoo/yahoo.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Sat Dec 15 05:15:31 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 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.h	Sat Dec 15 05:15:31 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 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Sat Dec 15 05:15:31 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 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.h	Sat Dec 15 05:15:31 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 15 05:12:24 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.h	Sat Dec 15 05:15:31 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/prpl.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/prpl.h	Sat Dec 15 05:15:31 2007 +0000
@@ -292,6 +292,13 @@
 	void (*chat_whisper)(PurpleConnection *, int id,
 						 const char *who, const char *message);
 	int  (*chat_send)(PurpleConnection *, int id, const char *message, PurpleMessageFlags flags);
+
+	/** If implemented, this will be called regularly for this prpl's
+	 *  active connections.  You'd want to do this if you need to repeatedly
+	 *  send some kind of keepalive packet to the server to avoid being
+	 *  disconnected.  ("Regularly" is defined by
+	 *  <code>KEEPALIVE_INTERVAL</code> in <tt>libpurple/connection.c</tt>.)
+	 */
 	void (*keepalive)(PurpleConnection *);
 
 	/** new user registration */
--- a/libpurple/purple.h.in	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/purple.h.in	Sat Dec 15 05:15:31 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/savedstatuses.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/savedstatuses.c	Sat Dec 15 05:15:31 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/tests/check_libpurple.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/tests/check_libpurple.c	Sat Dec 15 05:15:31 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/tests.h	Sat Dec 15 05:12:24 2007 +0000
+++ b/libpurple/tests/tests.h	Sat Dec 15 05:15:31 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 15 05:12:24 2007 +0000
+++ b/libpurple/util.c	Sat Dec 15 05:15:31 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';
--- a/pidgin.spec.in	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin.spec.in	Sat Dec 15 05:15:31 2007 +0000
@@ -14,6 +14,9 @@
 %define pidginver @VERSION@
 %endif
 
+# define the minimum API version required, so we can use it for plugin deps
+%define apiver %(echo "@VERSION@"|awk -F. '{print $1"."$2}')
+
 Summary:    A GTK+ based multiprotocol instant messaging client
 Name:       @PACKAGE@
 Version:    %pidginver
@@ -29,7 +32,7 @@
 BuildRequires: gtk2-devel
 
 %{!?_without_startupnotification:BuildRequires: startup-notification-devel}
-%{?_with_avahi:BuildRequires: avahi-compat-howl-devel}
+%{?_with_avahi:BuildRequires: avahi-glib-devel}
 %{!?_without_gtkspell:BuildRequires: gtkspell-devel}
 %{?_with_howl:BuildRequires: howl-devel}
 %{?_with_meanwhile:BuildRequires: meanwhile-devel}
@@ -118,21 +121,21 @@
 %package -n libpurple-bonjour
 Summary:    Bonjour plugin for Pidgin
 Group:      Applications/Internet
-Requires:   libpurple = %{version}
+Requires:   libpurple >= %{apiver}
 %endif
 
 %if 0%{?_with_meanwhile:1}
 %package -n libpurple-meanwhile
 Summary:    Lotus Sametime plugin for Pidgin using the Meanwhile library
 Group:      Applications/Internet
-Requires:   libpurple = %{version}
+Requires:   libpurple >= %{apiver}
 %endif
 
 %if 0%{?_with_mono:1}
 %package -n libpurple-mono
 Summary:    Mono .NET plugin support for Pidgin
 Group:      Applications/Internet
-Requires:   libpurple = %{version}
+Requires:   libpurple >= %{apiver}
 %endif
 
 %if 0%{!?_without_text:1}
@@ -462,6 +465,12 @@
 %endif
 
 %changelog
+* Wed Dec  5 2007 Stu Tomlinson <stu@nosnilmot.com>
+- When building with avahi, use native avahi instead of howl compatability
+  headers
+- Make the split out plugins depend only on the minimum necessary API
+  version of libpurple
+
 * Tue Oct 23 2007 Stu Tomlinson <stu@nosnilmot.com>
 - Add finch.pc to finch-devel
 
--- a/pidgin/gtkblist.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin/gtkblist.c	Sat Dec 15 05:15:31 2007 +0000
@@ -1398,7 +1398,7 @@
 	pidgin_append_blist_node_proto_menu(menu, buddy->account->gc, node);
 	pidgin_append_blist_node_extended_menu(menu, node);
 
-	if (!contact_expanded)
+	if (!contact_expanded && contact != NULL)
 		pidgin_append_blist_node_move_to_menu(menu, (PurpleBlistNode *)contact);
 
 	if (node->parent && node->parent->child->next && 
@@ -3170,6 +3170,7 @@
 		GList *cur;
 		struct proto_chat_entry *pce;
 		char *name, *value;
+		PidginBlistNode *bnode = node->ui_data;
 
 		chat = (PurpleChat *)node;
 		prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
@@ -3182,6 +3183,13 @@
 			g_free(tmp);
 		}
 
+		if (bnode && bnode->conv.conv &&
+				prpl_info && (prpl_info->options & OPT_PROTO_CHAT_TOPIC) &&
+				!purple_conv_chat_has_left(PURPLE_CONV_CHAT(bnode->conv.conv))) {
+			const char *topic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(bnode->conv.conv));
+			g_string_append_printf(str, _("\n<b>Topic:</b> %s"), topic ? topic : _("(no topic set)"));
+		}
+
 		if (prpl_info->chat_info != NULL)
 			cur = prpl_info->chat_info(chat->account->gc);
 		else
@@ -3251,7 +3259,7 @@
 		/* Alias */
 		/* If there's not a contact alias, the node is being displayed with
 		 * this alias, so there's no point in showing it in the tooltip. */
-		if (full && b->alias != NULL && b->alias[0] != '\0' &&
+		if (full && c && b->alias != NULL && b->alias[0] != '\0' &&
 		    (c->alias != NULL && c->alias[0] != '\0') &&
 		    strcmp(c->alias, b->alias) != 0)
 		{
@@ -3302,13 +3310,13 @@
 		}
 
 		/* Last Seen */
-		if (full && !PURPLE_BUDDY_IS_ONLINE(b))
+		if (full && c && !PURPLE_BUDDY_IS_ONLINE(b))
 		{
 			struct _pidgin_blist_node *gtknode = ((PurpleBlistNode *)c)->ui_data;
 			PurpleBlistNode *bnode;
 			int lastseen = 0;
 
-			if (!gtknode->contact_expanded || PURPLE_BLIST_NODE_IS_CONTACT(node))
+			if (gtknode && (!gtknode->contact_expanded || PURPLE_BLIST_NODE_IS_CONTACT(node)))
 			{
 				/* We're either looking at a buddy for a collapsed contact or
 				 * an expanded contact itself so we show the most recent
@@ -3371,6 +3379,34 @@
 	return g_string_free(str, FALSE);
 }
 
+static GHashTable *cached_emblems;
+
+static void _cleanup_cached_emblem(gpointer data, GObject *obj) {
+	g_hash_table_remove(cached_emblems, data);
+}
+
+static GdkPixbuf * _pidgin_blist_get_cached_emblem(gchar *path) {
+	GdkPixbuf *pb = g_hash_table_lookup(cached_emblems, path);
+
+	if (pb != NULL) {
+		/* The caller gets a reference */
+		g_object_ref(pb);
+		g_free(path);
+	} else {
+		pb = gdk_pixbuf_new_from_file(path, NULL);
+		if (pb != NULL) {
+			/* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
+			/* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
+			g_object_weak_ref(G_OBJECT(pb), _cleanup_cached_emblem, path);
+			g_hash_table_insert(cached_emblems, path, pb);
+		} else
+			g_free(path);
+	}
+
+	return pb;
+}
+
+
 GdkPixbuf *
 pidgin_blist_get_emblem(PurpleBlistNode *node)
 {
@@ -3381,7 +3417,6 @@
 	PurplePluginProtocolInfo *prpl_info;
 	const char *name = NULL;
 	char *filename, *path;
-	GdkPixbuf *ret;
 	PurplePresence *p;
 
 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
@@ -3394,11 +3429,9 @@
 		gtkbuddynode = node->ui_data;
 		p = purple_buddy_get_presence(buddy);
 		if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
-			path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", 
+			path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems",
 						"16", "mobile.png", NULL);
-			ret = gdk_pixbuf_new_from_file(path, NULL);
-			g_free(path);
-			return ret;
+			return _pidgin_blist_get_cached_emblem(path);
 		}
 
 		if (((struct _pidgin_blist_node*)(node->parent->ui_data))->contact_expanded) {
@@ -3410,26 +3443,22 @@
 		return NULL;
 	}
 
+	g_return_val_if_fail(buddy != NULL, NULL);
+
 	if (!purple_privacy_check(buddy->account, purple_buddy_get_name(buddy))) {
 		path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "blocked.png", NULL);
-		ret = gdk_pixbuf_new_from_file(path, NULL);
-		g_free(path);
-		return ret;
+		return _pidgin_blist_get_cached_emblem(path);
 	}
 
 	p = purple_buddy_get_presence(buddy);
 	if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
 		path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "mobile.png", NULL);
-		ret = gdk_pixbuf_new_from_file(path, NULL);
-		g_free(path);
-		return ret;
+		return _pidgin_blist_get_cached_emblem(path);
 	}
 
 	if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_TUNE)) {
 		path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", "music.png", NULL);
-		ret = gdk_pixbuf_new_from_file(path, NULL);
-		g_free(path);
-		return ret;
+		return _pidgin_blist_get_cached_emblem(path);
 	}
 
 	prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
@@ -3446,12 +3475,10 @@
 	filename = g_strdup_printf("%s.png", name);
 
 	path = g_build_filename(DATADIR, "pixmaps", "pidgin", "emblems", "16", filename, NULL);
-	ret = gdk_pixbuf_new_from_file(path, NULL);
-
 	g_free(filename);
-	g_free(path);
-
-	return ret;
+
+	/* _pidgin_blist_get_cached_emblem() assumes ownership of path */
+	return _pidgin_blist_get_cached_emblem(path);
 }
 
 
@@ -4072,7 +4099,7 @@
 		PurpleConversation *conv, PurpleMessageFlags flag, PurpleBlistNode *node)
 {
 	PidginBlistNode *ui = node->ui_data;
-	if (ui->conv.conv != conv || PIDGIN_CONVERSATION(conv) ||
+	if (ui->conv.conv != conv || !pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv)) ||
 			!(flag & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)))
 		return;
 	ui->conv.flags |= PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE;
@@ -4081,11 +4108,10 @@
 }
 
 static void
-displayed_msg_update_ui_cb(PurpleAccount *account, const char *who, const char *message,
-		PurpleConversation *conv, PurpleMessageFlags flag, PurpleBlistNode *node)
+displayed_msg_update_ui_cb(PidginConversation *gtkconv, PurpleBlistNode *node)
 {
 	PidginBlistNode *ui = node->ui_data;
-	if (ui->conv.conv != conv)
+	if (ui->conv.conv != gtkconv->active_conv)
 		return;
 	ui->conv.flags &= ~PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE;
 	pidgin_blist_update(purple_get_blist(), node);
@@ -4111,10 +4137,11 @@
 							ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
 					purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg",
 							ui, PURPLE_CALLBACK(written_msg_update_ui_cb), buddy);
-					purple_signal_connect(pidgin_conversations_get_handle(), "displayed-im-msg",
+					purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
 							ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), buddy);
 				}
 			}
+			break;
 		case PURPLE_CONV_TYPE_CHAT:
 			{
 				PurpleChat *chat = purple_blist_find_chat(conv->account, conv->name);
@@ -4131,9 +4158,10 @@
 						ui, PURPLE_CALLBACK(conversation_deleted_update_ui_cb), ui);
 				purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg",
 						ui, PURPLE_CALLBACK(written_msg_update_ui_cb), chat);
-				purple_signal_connect(pidgin_conversations_get_handle(), "displayed-chat-msg",
+				purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
 						ui, PURPLE_CALLBACK(displayed_msg_update_ui_cb), chat);
 			}
+			break;
 		default:
 			break;
 	}
@@ -5691,7 +5719,6 @@
 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded)
 {
 	PurpleGroup *group;
-	GdkColor textcolor;
 	gboolean selected;
 	char group_count[12] = "";
 	char *mark, *esc;
@@ -5699,7 +5726,6 @@
 	GtkTreeIter iter;
 
 	group = (PurpleGroup*)gnode;
-	textcolor = gtkblist->treeview->style->fg[GTK_STATE_ACTIVE];
 
 	if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview)), NULL, &iter)) {
 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter,
@@ -5714,12 +5740,7 @@
 	}
 
 	esc = g_markup_escape_text(group->name, -1);
-	if (selected)
-		mark = g_strdup_printf("<span weight='bold'>%s</span>%s", esc, group_count);
-	else
-		mark = g_strdup_printf("<span color='#%02x%02x%02x' weight='bold'>%s</span>%s",
-				       textcolor.red>>8, textcolor.green>>8, textcolor.blue>>8,
-				       esc, group_count);
+	mark = g_strdup_printf("<span weight='bold'>%s</span>%s", esc ? esc : "", group_count);
 
 	g_free(esc);
 	return mark;
@@ -5949,7 +5970,8 @@
 
 		ui = node->ui_data;
 		conv = ui->conv.conv;
-		hidden = (conv && (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE));
+		hidden = (conv && (ui->conv.flags & PIDGIN_BLIST_NODE_HAS_PENDING_MESSAGE) &&
+				pidgin_conv_is_hidden(PIDGIN_CONVERSATION(conv)));
 
 		status = pidgin_blist_get_status_icon(node,
 				 biglist ? PIDGIN_STATUS_ICON_LARGE : PIDGIN_STATUS_ICON_SMALL);
@@ -6897,6 +6919,8 @@
 {
 	void *gtk_blist_handle = pidgin_blist_get_handle();
 
+	cached_emblems = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
 	purple_signal_connect(purple_connections_get_handle(), "signed-on",
 						gtk_blist_handle, PURPLE_CALLBACK(account_signon_cb),
 						NULL);
@@ -6948,6 +6972,8 @@
 
 void
 pidgin_blist_uninit(void) {
+	g_hash_table_destroy(cached_emblems);
+
 	purple_signals_unregister_by_instance(pidgin_blist_get_handle());
 	purple_signals_disconnect_by_handle(pidgin_blist_get_handle());
 }
--- a/pidgin/gtkconv.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin/gtkconv.c	Sat Dec 15 05:15:31 2007 +0000
@@ -129,6 +129,7 @@
 static GtkWidget *invite_dialog = NULL;
 static GtkWidget *warn_close_dialog = NULL;
 
+static PidginWindow *hidden_convwin = NULL;
 static GList *window_list = NULL;
 
 /* Lists of status icons at all available sizes for use as window icons */
@@ -1379,7 +1380,13 @@
 			timer = purple_timeout_add_seconds(CLOSE_CONV_TIMEOUT_SECS, close_already, conv);
 			purple_conversation_set_data(conv, "close-timer", GINT_TO_POINTER(timer));
 		}
+#if 0
+		/* I will miss you */
 		purple_conversation_set_ui_ops(conv, NULL);
+#else
+		pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv);
+		pidgin_conv_window_add_gtkconv(hidden_convwin, gtkconv);
+#endif
 	}
 }
 
@@ -2820,6 +2827,9 @@
 	if (gtkconv == NULL) {
 		pidgin_conv_attach_to_conversation(conv);
 		gtkconv = PIDGIN_CONVERSATION(conv);
+	} else if (gtkconv->win == hidden_convwin) {
+		pidgin_conv_window_remove_gtkconv(hidden_convwin, gtkconv);
+		pidgin_conv_placement_place(gtkconv);
 	}
 
 	pidgin_conv_switch_active_conversation(conv);
@@ -2852,20 +2862,15 @@
 		PurpleConversation *conv = (PurpleConversation*)l->data;
 		PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
 
-		if (gtkconv != NULL && gtkconv->active_conv != conv)
+		if(gtkconv == NULL || gtkconv->active_conv != conv)
 			continue;
-		if (gtkconv == NULL) {
-			if (!purple_conversation_get_data(conv, "unseen-count") ||
-				!purple_conversation_get_data(conv, "unseen-state") ||
-				GPOINTER_TO_INT(purple_conversation_get_data(conv, "unseen-state"))<min_state)
-				continue;
+
+		if (gtkconv->unseen_state >= min_state
+			&& (!hidden_only ||
+				(hidden_only && gtkconv->win == hidden_convwin))) {
+
 			r = g_list_prepend(r, conv);
 			c++;
-		} else {
-			if (gtkconv->unseen_state >= min_state && !hidden_only) {
-				r = g_list_prepend(r, conv);
-				c++;
-			}
 		}
 	}
 
@@ -2908,8 +2913,8 @@
 		GdkPixbuf *pbuf = pidgin_conv_get_icon(conv, icon, PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC);
 		GtkWidget *item;
 		gchar *text = g_strdup_printf("%s (%d)",
-				gtkconv ? gtk_label_get_text(GTK_LABEL(gtkconv->tab_label)) : purple_conversation_get_name(conv),
-				gtkconv ? gtkconv->unseen_count : GPOINTER_TO_INT(purple_conversation_get_data(conv, "unseen-count")));
+				gtk_label_get_text(GTK_LABEL(gtkconv->tab_label)),
+				gtkconv->unseen_count);
 
 		gtk_image_set_from_pixbuf(GTK_IMAGE(icon), pbuf);
 		g_object_unref(pbuf);
@@ -3183,7 +3188,7 @@
 	PurpleConversation *conv;
 	GtkWidget *item;
 
-	if (win->window == NULL)
+	if (win->window == NULL || win == hidden_convwin)
 		return;
 
 	gtkconv = pidgin_conv_window_get_active_gtkconv(win);
@@ -4423,7 +4428,7 @@
 	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;
+	height = (oneline.height + pad_top + pad_bottom) * MAX(lines, 2);
 	height += (oneline.height + pad_inside) * (wrapped_lines - lines);
 
 	gtkconv->auto_resize = TRUE;
@@ -4455,7 +4460,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);
 
@@ -4604,16 +4609,25 @@
 	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 void
 pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv)
 {
 	pidgin_blist_tooltip_destroy();
@@ -5050,9 +5064,6 @@
 	GtkWidget *tab_cont;
 	PurpleBlistNode *convnode;
 
-	if (hidden)
-		return;
-
 	if (conv_type == PURPLE_CONV_TYPE_IM && (gtkconv = pidgin_conv_find_gtkconv(conv))) {
 		conv->ui_data = gtkconv;
 		if (!g_list_find(gtkconv->convs, conv))
@@ -5152,7 +5163,10 @@
 	                         G_CALLBACK(gtk_widget_grab_focus),
 	                         gtkconv->entry);
 
-	pidgin_conv_placement_place(gtkconv);
+	if (hidden)
+		pidgin_conv_window_add_gtkconv(hidden_convwin, gtkconv);
+	else
+		pidgin_conv_placement_place(gtkconv);
 
 	if (nick_colors == NULL) {
 		nbr_nick_colors = NUM_NICK_COLORS;
@@ -5160,13 +5174,11 @@
 	}
 }
 
-#if 0
 static void
 pidgin_conv_new_hidden(PurpleConversation *conv)
 {
 	private_gtkconv_new(conv, TRUE);
 }
-#endif
 
 void
 pidgin_conv_new(PurpleConversation *conv)
@@ -5182,24 +5194,30 @@
 				   PurpleConversation *conv, PurpleMessageFlags flags)
 {
 	PurpleConversationUiOps *ui_ops = pidgin_conversations_get_conv_ui_ops();
+	gboolean hide = FALSE;
 
 	/* create hidden conv if hide_new pref is always */
-	/* or if hide_new pref is away and account is away */
-	if ((strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "always") == 0) ||
-		(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "away") == 0 &&
-		 !purple_status_is_available(purple_account_get_active_status(account)))) {
-		if (!conv) {
-			ui_ops->create_conversation = NULL;
-			conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender);
-			purple_conversation_set_ui_ops(conv, NULL);
-			ui_ops->create_conversation = pidgin_conv_new;
-		} else {
-			/* TODO: update the unseen_state data on the conv here */
-		}
-	} else {
-		/* new message for an IM */
-		if (conv && conv->type == PURPLE_CONV_TYPE_IM)
-			pidgin_conv_attach_to_conversation(conv);
+	if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "always") == 0)
+		hide = TRUE;
+
+	/* create hidden conv if hide_new pref is away and account is away */
+	if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "away") == 0 &&
+	    !purple_status_is_available(purple_account_get_active_status(account)))
+		hide = TRUE;
+
+	if (conv && PIDGIN_IS_PIDGIN_CONVERSATION(conv) && !hide) {
+		PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
+		if (gtkconv->win == hidden_convwin) {
+			pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv);
+			pidgin_conv_placement_place(gtkconv);
+		}
+		return;
+	}
+
+	if (hide) {
+		ui_ops->create_conversation = pidgin_conv_new_hidden;
+		purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender);
+		ui_ops->create_conversation = pidgin_conv_new;
 	}
 }
 
@@ -5208,9 +5226,6 @@
 {
 	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
 
-	if (!gtkconv)
-		return;
-
 	gtkconv->convs = g_list_remove(gtkconv->convs, conv);
 	/* Don't destroy ourselves until all our convos are gone */
 	if (gtkconv->convs) {
@@ -6553,7 +6568,6 @@
 		gboolean ellipsis = FALSE;
 		/* I think this is a little longer than it needs to be but I'm lazy. */
 		char *style;
-		gboolean bold = FALSE;
 
 		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
 			im = PURPLE_CONV_IM(conv);
@@ -6575,8 +6589,29 @@
 				markup = title;
 			}
 		} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
-			const char *topic = gtk_entry_get_text(GTK_ENTRY(gtkconv->u.chat->topic_text));
-			char *esc = topic ? g_markup_escape_text(topic, -1) : NULL;
+			const char *topic = gtkconv->u.chat->topic_text ? gtk_entry_get_text(GTK_ENTRY(gtkconv->u.chat->topic_text)) : NULL;
+			char *esc = NULL;
+#if GTK_CHECK_VERSION(2,6,0)
+			esc = topic ? g_markup_escape_text(topic, -1) : NULL;
+#else
+			/* GTK < 2.6 doesn't have auto ellipsization, so we do a crude
+			 * trucation to prevent forcing the window to be as wide as the topic */
+			int len = 0;
+			char *c, *tmp = g_strdup(topic);
+			c = tmp;
+			while(*c && len < 72) {
+				c = g_utf8_next_char(c);
+				len++;
+			}
+			if (len == 72) {
+				*c = '\0';
+				c = g_strdup_printf("%s...", tmp);
+				g_free(tmp);
+				tmp = c;
+			}
+			esc = tmp ? g_markup_escape_text(tmp, -1) : NULL;
+			g_free(tmp);
+#endif
 			markup = g_strdup_printf("%s%s<span color='%s' size='smaller'>%s</span>",
 						purple_conversation_get_title(conv),
 						esc  && *esc ? "\n" : "",
@@ -6588,7 +6623,7 @@
 				CONV_TEXT_COLUMN, markup, -1);
 	        /* XXX seanegan Why do I have to do this? */
 		gtk_widget_queue_draw(gtkconv->infopane);
-	
+
 		if (title != markup)
 			g_free(markup);
 
@@ -6599,47 +6634,43 @@
 		if (im != NULL &&
 		    purple_conv_im_get_typing_state(im) == PURPLE_TYPING) {
 			atk_object_set_description(accessibility_obj, _("Typing"));
-			style = "color=\"#4e9a06\"";
+			style = "tab-label-typing";
 		} else if (im != NULL &&
 		         purple_conv_im_get_typing_state(im) == PURPLE_TYPED) {
 			atk_object_set_description(accessibility_obj, _("Stopped Typing"));
-			style = "color=\"#c4a000\"";
+			style = "tab-label-typed";
 		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK)	{
 			atk_object_set_description(accessibility_obj, _("Nick Said"));
-			style = "color=\"#204a87\"";
+			style = "tab-label-attention";
 		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT)	{
 			atk_object_set_description(accessibility_obj, _("Unread Messages"));
 			if (gtkconv->active_conv->type == PURPLE_CONV_TYPE_CHAT)
-				style = "color=\"#cc0000\"";
+				style = "tab-label-unreadchat";
 			else
-				style = "color=\"#204a87\"";
+				style = "tab-label-attention";
 		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT) {
 			atk_object_set_description(accessibility_obj, _("New Event"));
-			style = "color=\"#888a85\"";
+			style = "tab-label-event";
 		} else {
 			style = NULL;
 		}
-		
+
+		gtk_widget_set_name(gtkconv->tab_label, style);
+		gtk_label_set_text(GTK_LABEL(gtkconv->tab_label), title);
+		gtk_widget_set_state(gtkconv->tab_label, GTK_STATE_ACTIVE);
+
 		if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT ||
 				gtkconv->unseen_state == PIDGIN_UNSEEN_NICK ||
-				gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT)
-			bold = TRUE;
-
-		if (style || bold)
-		{
-			char *html_title,*label;
-
-			html_title = g_markup_escape_text(title_tmp, -1);
-			label = g_strdup_printf("<span %s %s>%s</span>",
-			                        style ? style : "",
-			                        bold ? "weight=\"bold\"" : "",
-			                        html_title);
-			g_free(html_title);
-			gtk_label_set_markup(GTK_LABEL(gtkconv->tab_label), label);
-			g_free(label);
-		}
-		else
-			gtk_label_set_text(GTK_LABEL(gtkconv->tab_label), title_tmp);
+				gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT) {
+			PangoAttrList *list = pango_attr_list_new();
+			PangoAttribute *attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
+			attr->start_index = 0;
+			attr->end_index = -1;
+			pango_attr_list_insert(list, attr);
+			gtk_label_set_attributes(GTK_LABEL(gtkconv->tab_label), list);
+			pango_attr_list_unref(list);
+		} else
+			gtk_label_set_attributes(GTK_LABEL(gtkconv->tab_label), NULL);
 
 		if (pidgin_conv_window_is_active_conversation(conv))
 			update_typing_icon(gtkconv);
@@ -6720,7 +6751,6 @@
 			unseen = PIDGIN_UNSEEN_TEXT;
 
 		conv_set_unseen(conv, unseen);
-		purple_conversation_update(conv, PURPLE_CONV_UPDATE_UNSEEN);
 	}
 }
 
@@ -7244,7 +7274,6 @@
 account_status_changed_cb(PurpleAccount *account, PurpleStatus *oldstatus,
                           PurpleStatus *newstatus)
 {
-#if 0
 	GList *l;
 	PurpleConversation *conv = NULL;
 	PidginConversation *gtkconv;
@@ -7254,7 +7283,27 @@
 
 	if(purple_status_is_available(oldstatus) || !purple_status_is_available(newstatus))
 		return;
-#endif
+
+	while ((l = hidden_convwin->gtkconvs) != NULL)
+	{
+		gtkconv = l->data;
+
+		conv = gtkconv->active_conv;
+
+		while(l && !purple_status_is_available(
+					purple_account_get_active_status(
+					purple_conversation_get_account(conv))))
+			l = l->next;
+		if (!l)
+			break;
+
+		pidgin_conv_window_remove_gtkconv(hidden_convwin, gtkconv);
+		pidgin_conv_placement_place(gtkconv);
+
+		/* TODO: do we need to do anything for any other conversations that are in the same gtkconv here?
+		 * I'm a little concerned that not doing so will cause the "pending" indicator in the gtkblist not to be cleared. -DAA*/
+		purple_conversation_update(conv, PURPLE_CONV_UPDATE_UNSEEN);
+	}
 }
 
 static void
@@ -7262,25 +7311,35 @@
 				 gconstpointer value, gpointer data)
 {
 	GList *l;
+	PurpleConversation *conv = NULL;
+	PidginConversation *gtkconv;
 	gboolean when_away = FALSE;
 
+	if(!hidden_convwin)
+		return;
+
 	if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "always")==0)
 		return;
 
 	if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "away")==0)
 		when_away = TRUE;
 
-	for (l = purple_get_conversations(); l; l = l->next)
+	for (l = hidden_convwin->gtkconvs; l; )
 	{
-		PurpleConversation *conv = l->data;
-		PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
-		if (gtkconv)
+		gtkconv = l->data;
+		l = l->next;
+
+		conv = gtkconv->active_conv;
+
+		if (conv->type == PURPLE_CONV_TYPE_CHAT ||
+				gtkconv->unseen_count == 0 ||
+				(when_away && !purple_status_is_available(
+							purple_account_get_active_status(
+							purple_conversation_get_account(conv)))))
 			continue;
-		if(when_away && !purple_status_is_available(
-							purple_account_get_active_status(
-							purple_conversation_get_account(conv))))
-			continue;
-		pidgin_conv_attach_to_conversation(conv);
+
+		pidgin_conv_window_remove_gtkconv(hidden_convwin, gtkconv);
+		pidgin_conv_placement_place(gtkconv);
 	}
 }
 
@@ -7550,10 +7609,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)
@@ -7561,8 +7623,22 @@
 	GList *list;
 	PidginConversation *gtkconv;
 
-	if (PIDGIN_IS_PIDGIN_CONVERSATION(conv))
-		return FALSE;
+	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;
+		pidgin_conv_window_remove_gtkconv(hidden_convwin, gtkconv);
+		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;
+	}
 
 	pidgin_conv_attach(conv);
 	gtkconv = PIDGIN_CONVERSATION(conv);
@@ -7783,17 +7859,17 @@
 						 purple_value_new(PURPLE_TYPE_INT));
 
 	purple_signal_register(handle, "conversation-switched",
-						 purple_marshal_VOID__POINTER_POINTER, NULL, 1,
+						 purple_marshal_VOID__POINTER, NULL, 1,
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
 										PURPLE_SUBTYPE_CONVERSATION));
 
 	purple_signal_register(handle, "conversation-hiding",
-						 purple_marshal_VOID__POINTER_POINTER, NULL, 1,
+						 purple_marshal_VOID__POINTER, NULL, 1,
 						 purple_value_new(PURPLE_TYPE_BOXED,
 										"PidginConversation *"));
 
 	purple_signal_register(handle, "conversation-displayed",
-						 purple_marshal_VOID__POINTER_POINTER, NULL, 1,
+						 purple_marshal_VOID__POINTER, NULL, 1,
 						 purple_value_new(PURPLE_TYPE_BOXED,
 										"PidginConversation *"));
 
@@ -7835,6 +7911,9 @@
 
 	purple_conversations_set_ui_ops(&conversation_ui_ops);
 
+	hidden_convwin = pidgin_conv_window_new();
+	window_list = g_list_remove(window_list, hidden_convwin);
+
 	purple_signal_connect(purple_accounts_get_handle(), "account-status-changed",
                         handle, PURPLE_CALLBACK(account_status_changed_cb), NULL);
 
@@ -7874,6 +7953,41 @@
 			PURPLE_CALLBACK(wrote_msg_update_unseen_cb), NULL);
 	purple_signal_connect(purple_conversations_get_handle(), "wrote-chat-msg", handle,
 			PURPLE_CALLBACK(wrote_msg_update_unseen_cb), NULL);
+
+	{
+		/* Set default tab colors */
+		GString *str = g_string_new(NULL);
+		GtkSettings *settings = gtk_settings_get_default();
+		struct {
+			const char *stylename;
+			const char *labelname;
+			const char *color;
+		} styles[] = {
+			{"pidgin_tab_label_typing_default", "tab-label-typing", "#4e9a06"},
+			{"pidgin_tab_label_typed_default", "tab-label-typed", "#c4a000"},
+			{"pidgin_tab_label_attention_default", "tab-label-attention", "#006aff"},
+			{"pidgin_tab_label_unreadchat_default", "tab-label-unreadchat", "#cc0000"},
+			{"pidgin_tab_label_event_default", "tab-label-event", "#888a85"},
+			{NULL, NULL, NULL}
+		};
+		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 */
+				g_string_append_printf(str, "style \"%s\" {\n"
+						"fg[ACTIVE] = \"%s\"\n"
+						"}\n"
+						"widget \"*%s\" style \"%s\"\n",
+						styles[iter].stylename,
+						styles[iter].color,
+						styles[iter].labelname, styles[iter].stylename);
+		}
+		gtk_rc_parse_string(str->str);
+		g_string_free(str, TRUE);
+#if GTK_CHECK_VERSION(2,4,0)
+		gtk_rc_reset_styles(settings);
+#endif
+	}
 }
 
 void
@@ -8708,7 +8822,7 @@
                                                  gtk_entry_get_text(entry));
 		}
 		serv_alias_buddy(buddy);
-	} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {	        
+	} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
 		gtk_entry_set_text(GTK_ENTRY(gtkconv->u.chat->topic_text), gtk_entry_get_text(entry));
 		topic_callback(NULL, gtkconv);
 	}
@@ -8719,7 +8833,7 @@
 infopane_entry_activate(PidginConversation *gtkconv)
 {
 	GtkWidget *entry = NULL;
-        PurpleConversation *conv = gtkconv->active_conv;
+	PurpleConversation *conv = gtkconv->active_conv;
 	const char *text = NULL;
 
 	if (!GTK_WIDGET_VISIBLE(gtkconv->tab_label)) {
@@ -8735,9 +8849,21 @@
 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
 		PurpleBuddy *buddy = purple_find_buddy(gtkconv->active_conv->account, gtkconv->active_conv->name);
 		if (!buddy)
+			/* This buddy isn't in your buddy list, so we can't alias him */
 			return FALSE;
+
 		text = purple_buddy_get_contact_alias(buddy);
 	} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
+		PurpleConnection *gc;
+		PurplePluginProtocolInfo *prpl_info = NULL;
+
+		gc = purple_conversation_get_gc(conv);
+		if (gc != NULL)
+			prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+		if (prpl_info && prpl_info->set_chat_topic == NULL)
+			/* This protocol doesn't support setting the chat room topic */
+			return FALSE;
+
 		text = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv));
 	}
 
@@ -8756,10 +8882,9 @@
 	g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(alias_cb), gtkconv);
 	g_signal_connect(G_OBJECT(entry), "focus-out-event", G_CALLBACK(alias_focus_cb), gtkconv);
 	g_signal_connect(G_OBJECT(entry), "key-press-event", G_CALLBACK(alias_key_press_cb), gtkconv);
-	
-	
-
-	gtk_entry_set_text(GTK_ENTRY(entry), text);
+
+	if (text != NULL)
+		gtk_entry_set_text(GTK_ENTRY(entry), text);
 	gtk_widget_show(entry);
 	gtk_widget_hide(gtkconv->infopane);
 	gtk_widget_grab_focus(entry);
@@ -9049,8 +9174,6 @@
 	if (win->dialogs.search)
 		gtk_widget_destroy(win->dialogs.search);
 
-	gtk_widget_hide_all(win->window);
-
 	if (win->gtkconvs) {
 		while (win->gtkconvs) {
 			gboolean last = (win->gtkconvs->next == NULL);
@@ -9104,7 +9227,7 @@
 		ptr = gdk_cursor_new(GDK_LEFT_PTR);
 	}
 
-	gtk_label_set_markup(label, "×");
+	gtk_label_set_markup(label, "×");
 	gdk_window_set_cursor(event->window, ptr);
 	return FALSE;
 }
@@ -9117,7 +9240,7 @@
 		hand = gdk_cursor_new(GDK_HAND2);
 	}
 
-	gtk_label_set_markup(label, "<u>×</u>");
+	gtk_label_set_markup(label, "<u>×</u>");
 	gdk_window_set_cursor(event->window, hand);
 	return FALSE;
 }
@@ -9147,7 +9270,7 @@
 	gtk_event_box_set_visible_window(GTK_EVENT_BOX(gtkconv->close), FALSE);
 #endif
 	gtk_widget_set_events(gtkconv->close, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
-	close_image = gtk_label_new("×");
+	close_image = gtk_label_new("×");
 	g_signal_connect(G_OBJECT(gtkconv->close), "enter-notify-event", G_CALLBACK(close_button_entered_cb), close_image);
 	g_signal_connect(G_OBJECT(gtkconv->close), "leave-notify-event", G_CALLBACK(close_button_left_cb), close_image);
 	gtk_widget_show(close_image);
@@ -9176,6 +9299,7 @@
 
 	/* Tab label. */
 	gtkconv->tab_label = gtk_label_new(tmp_lab = purple_conversation_get_title(conv));
+	gtk_widget_set_name(gtkconv->tab_label, "tab-label");
 
 	gtkconv->menu_tabby = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 	gtkconv->menu_label = gtk_label_new(tmp_lab);
@@ -9335,7 +9459,7 @@
 	if (win->gtkconvs && win->gtkconvs->next == NULL)
 		pidgin_conv_tab_pack(win, win->gtkconvs->data);
 
-	if (!win->gtkconvs)
+	if (!win->gtkconvs && win != hidden_convwin)
 		pidgin_conv_window_destroy(win);
 }
 
@@ -9874,7 +9998,9 @@
 gboolean
 pidgin_conv_is_hidden(PidginConversation *gtkconv)
 {
-	return (gtkconv == NULL);
+	g_return_val_if_fail(gtkconv != NULL, FALSE);
+
+	return (gtkconv->win == hidden_convwin);
 }
 
 
@@ -9975,5 +10101,3 @@
 
 	return colors;
 }
-
-
--- a/pidgin/gtkdebug.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin/gtkdebug.c	Sat Dec 15 05:15:31 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/gtkimhtml.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin/gtkimhtml.c	Sat Dec 15 05:15:31 2007 +0000
@@ -846,28 +846,6 @@
 	gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, 5);
 
 	g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(clear_formatting_cb), imhtml);
-	
-	mi = gtk_menu_item_new();
-	gtk_widget_show(mi);
-	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
-
-	img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_SMILEY, GTK_ICON_SIZE_MENU);
-	mi = gtk_image_menu_item_new_with_label(_("_Smile!"));
-	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
-	gtk_widget_show(mi);
-	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
-
-	img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_INSERT, GTK_ICON_SIZE_MENU);
-	mi = gtk_image_menu_item_new_with_label(_("_Insert"));
-	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
-	gtk_widget_show(mi);
-	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
-
-	img = gtk_image_new_from_stock(GTK_STOCK_BOLD, GTK_ICON_SIZE_MENU);
-	mi = gtk_image_menu_item_new_with_label(_("_Font"));
-	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
-	gtk_widget_show(mi);
-	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
 }
 
 static char *
@@ -4298,19 +4276,19 @@
 	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
 
 	img = gtk_image_new_from_stock(GTK_STOCK_BOLD, GTK_ICON_SIZE_MENU);
-	mi = gtk_image_menu_item_new_with_label(_("_Font"));
+	mi = gtk_image_menu_item_new_with_mnemonic(_("_Font"));
 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
 	gtk_widget_show(mi);
 	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
 
 	img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_INSERT, GTK_ICON_SIZE_MENU);
-	mi = gtk_image_menu_item_new_with_label(_("_Insert"));
+	mi = gtk_image_menu_item_new_with_mnemonic(_("_Insert"));
 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
 	gtk_widget_show(mi);
 	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
 
 	img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_SMILEY, GTK_ICON_SIZE_MENU);
-	mi = gtk_image_menu_item_new_with_label(_("_Smile!"));
+	mi = gtk_image_menu_item_new_with_mnemonic(_("S_mile!"));
 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
 	gtk_widget_show(mi);
 	gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
--- a/pidgin/gtkimhtmltoolbar.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Sat Dec 15 05:15:31 2007 +0000
@@ -1315,7 +1315,9 @@
 
 	purple_prefs_connect_callback(toolbar, PIDGIN_PREFS_ROOT "/conversations/toolbar/wide",
 			imhtmltoolbar_view_pref_changed, toolbar);
-	purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT "/conversations/toolbar/wide");
+	g_signal_connect_data(G_OBJECT(toolbar), "realize",
+			G_CALLBACK(purple_prefs_trigger_callback), PIDGIN_PREFS_ROOT "/conversations/toolbar/wide",
+			NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
 
 #if GTK_CHECK_VERSION(2,4,0)
 	gtk_event_box_set_visible_window(GTK_EVENT_BOX(event), FALSE);
--- a/pidgin/gtkmain.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin/gtkmain.c	Sat Dec 15 05:15:31 2007 +0000
@@ -611,7 +611,7 @@
 #ifndef _WIN32
 				  "c:dhmnl::s:v",
 #else
-				  "c:dhnl::v",
+				  "c:dhmnl::v",
 #endif
 				  long_options, NULL)) != -1) {
 		switch (opt) {
--- a/pidgin/gtkprefs.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin/gtkprefs.c	Sat Dec 15 05:15:31 2007 +0000
@@ -1214,6 +1214,9 @@
 	vbox = pidgin_make_frame (ret, _("Ports"));
 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
+	pidgin_prefs_checkbox(_("_Enable automatic router port forwarding"),
+			"/purple/network/map_ports", vbox);
+
 	ports_checkbox = pidgin_prefs_checkbox(_("_Manually specify range of ports to listen on"),
 			"/purple/network/ports_range_use", vbox);
 
--- a/pidgin/gtkroomlist.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin/gtkroomlist.c	Sat Dec 15 05:15:31 2007 +0000
@@ -963,8 +963,10 @@
 	g_signal_connect(G_OBJECT(tree), "button-press-event", G_CALLBACK(room_click_cb), list);
 	g_signal_connect(G_OBJECT(tree), "row-expanded", G_CALLBACK(row_expanded_cb), list);
 	g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(row_activated_cb), list);
+#if 0 /* uncomment this when the tooltips are slightly less annoying and more well behaved */
 	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
 
 	/* Enable CTRL+F searching */
 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), NAME_COLUMN);
--- a/pidgin/gtkutils.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin/gtkutils.c	Sat Dec 15 05:15:31 2007 +0000
@@ -2451,7 +2451,13 @@
 	g_signal_connect(G_OBJECT(dialog->icon_filesel), "destroy",
 					 G_CALLBACK(icon_filesel_delete_cb), dialog);
 #endif /* FILECHOOSER */
-	return 	dialog->icon_filesel;
+
+#ifdef _WIN32
+	g_signal_connect(G_OBJECT(dialog->icon_filesel), "show",
+		G_CALLBACK(winpidgin_ensure_onscreen), dialog->icon_filesel);
+#endif
+
+	return dialog->icon_filesel;
 }
 
 
--- a/pidgin/pidginstock.c	Sat Dec 15 05:12:24 2007 +0000
+++ b/pidgin/pidginstock.c	Sat Dec 15 05:15:31 2007 +0000
@@ -347,7 +347,7 @@
 	size_t i;
 	GtkWidget *win;
 	GtkIconSize microscopic, extra_small, small, medium, large, huge;
-	
+
 	if (stock_initted)
 		return;
 
@@ -372,7 +372,7 @@
 		{
 			/* GTK+ Stock icon */
 			iconset = gtk_style_lookup_icon_set(gtk_widget_get_style(win),
-												stock_icons[i].filename);
+					stock_icons[i].filename);
 		}
 		else
 		{
@@ -386,11 +386,11 @@
 			gtk_icon_source_set_direction_wildcarded(source, TRUE);
 			gtk_icon_source_set_size_wildcarded(source, TRUE);
 			gtk_icon_source_set_state_wildcarded(source, TRUE);
-			
+
 
 			iconset = gtk_icon_set_new();
 			gtk_icon_set_add_source(iconset, source);
-			
+
 			gtk_icon_source_free(source);
 			g_free(filename);
 		}
@@ -401,7 +401,7 @@
 	}
 
 	/* register custom icon sizes */
-	
+
 	microscopic =  gtk_icon_size_register(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC, 11, 11);
 	extra_small =  gtk_icon_size_register(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL, 16, 16);
 	small =        gtk_icon_size_register(PIDGIN_ICON_SIZE_TANGO_SMALL, 22, 22);
@@ -414,61 +414,41 @@
 		GtkIconSet *iconset;
 
 		iconset = gtk_icon_set_new();
-		if (sized_stock_icons[i].microscopic)
-			add_sized_icon(iconset, microscopic,
-					sized_stock_icons[i].dir, sized_stock_icons[i].rtl,
-					"11", sized_stock_icons[i].filename);
-		if (sized_stock_icons[i].extra_small)
-			add_sized_icon(iconset, extra_small,
-				       sized_stock_icons[i].dir, sized_stock_icons[i].rtl,
-				       "16", sized_stock_icons[i].filename);
-               if (sized_stock_icons[i].small)
-                        add_sized_icon(iconset, small,
-				       sized_stock_icons[i].dir,  sized_stock_icons[i].rtl,
-                                       "22", sized_stock_icons[i].filename);
-               if (sized_stock_icons[i].medium)
-                        add_sized_icon(iconset, medium,
-			               sized_stock_icons[i].dir,  sized_stock_icons[i].rtl,
-                                       "32", sized_stock_icons[i].filename);
-	       if (sized_stock_icons[i].large)
-		       add_sized_icon(iconset, large,
-                                      sized_stock_icons[i].dir, sized_stock_icons[i].rtl,
-                                      "48", sized_stock_icons[i].filename);
-               if (sized_stock_icons[i].huge)
-                        add_sized_icon(iconset, huge,
-	                               sized_stock_icons[i].dir,  sized_stock_icons[i].rtl,
-                                       "64", sized_stock_icons[i].filename);
+
+#define ADD_SIZED_ICON(name, size) do { \
+		if (sized_stock_icons[i].name)  \
+			add_sized_icon(iconset, name,  \
+					sized_stock_icons[i].dir, sized_stock_icons[i].rtl, \
+					size, sized_stock_icons[i].filename); \
+		} while (0)
+		ADD_SIZED_ICON(microscopic, "11");
+		ADD_SIZED_ICON(extra_small, "16");
+		ADD_SIZED_ICON(small, "22");
+		ADD_SIZED_ICON(medium, "32");
+		ADD_SIZED_ICON(large, "48");
+		ADD_SIZED_ICON(huge, "64");
+#undef ADD_SIZED_ICON
 
 		gtk_icon_factory_add(icon_factory, sized_stock_icons[i].name, iconset);
 		gtk_icon_set_unref(iconset);
 
 		if (sized_stock_icons[i].translucent_name) {
 			iconset = gtk_icon_set_new();
-			if (sized_stock_icons[i].microscopic)
-				add_translucent_sized_icon(iconset, microscopic,
-						sized_stock_icons[i].dir, sized_stock_icons[i].rtl,
-						"11", sized_stock_icons[i].filename);
-			if (sized_stock_icons[i].extra_small)
-				add_translucent_sized_icon(iconset, extra_small,
-					       sized_stock_icons[i].dir, sized_stock_icons[i].rtl,
-					       "16", sized_stock_icons[i].filename);
-	               if (sized_stock_icons[i].small)
-        	                add_translucent_sized_icon(iconset, small,
-					       sized_stock_icons[i].dir,  sized_stock_icons[i].rtl,
-	                                       "22", sized_stock_icons[i].filename);
-	               if (sized_stock_icons[i].medium)
-	                        add_translucent_sized_icon(iconset, medium,
-				               sized_stock_icons[i].dir,  sized_stock_icons[i].rtl,
-	                                       "32", sized_stock_icons[i].filename);
-		       if (sized_stock_icons[i].large)
-			       add_translucent_sized_icon(iconset, large,
-	                                      sized_stock_icons[i].dir, sized_stock_icons[i].rtl,
-	                                      "48", sized_stock_icons[i].filename);
-	               if (sized_stock_icons[i].huge)
-	                        add_translucent_sized_icon(iconset, huge,
-		                               sized_stock_icons[i].dir,  sized_stock_icons[i].rtl,
-	                                       "64", sized_stock_icons[i].filename);
-	
+
+#define ADD_TRANS_ICON(name, size) do { \
+			if (sized_stock_icons[i].name) \
+				add_translucent_sized_icon(iconset, name, \
+						sized_stock_icons[i].dir, sized_stock_icons[i].rtl, \
+						size, sized_stock_icons[i].filename); \
+			} while (0)
+			ADD_TRANS_ICON(microscopic, "11");
+			ADD_TRANS_ICON(extra_small, "16");
+			ADD_TRANS_ICON(small, "22");
+			ADD_TRANS_ICON(medium, "32");
+			ADD_TRANS_ICON(large, "48");
+			ADD_TRANS_ICON(huge, "64");
+#undef ADD_TRANS_ICON
+
 			gtk_icon_factory_add(icon_factory, sized_stock_icons[i].translucent_name, iconset);
 			gtk_icon_set_unref(iconset);
 		}
--- a/po/ChangeLog	Sat Dec 15 05:12:24 2007 +0000
+++ b/po/ChangeLog	Sat Dec 15 05:15:31 2007 +0000
@@ -1,5 +1,8 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.3.1
+	* German translation updated (Jochen Kemnade, Björn Voigt)
+
 version 2.3.0
 	* Afrikaans translation added (Friedel Wolff)
 	* Belarusian Latin translation updated (Ihar Hrachyshka)
--- a/po/POTFILES.in	Sat Dec 15 05:12:24 2007 +0000
+++ b/po/POTFILES.in	Sat Dec 15 05:15:31 2007 +0000
@@ -118,6 +118,7 @@
 libpurple/protocols/myspace/zap.c
 libpurple/protocols/novell/nmuser.c
 libpurple/protocols/novell/novell.c
+libpurple/protocols/oscar/family_chatnav.c
 libpurple/protocols/oscar/flap_connection.c
 libpurple/protocols/oscar/libaim.c
 libpurple/protocols/oscar/libicq.c
--- a/po/de.po	Sat Dec 15 05:12:24 2007 +0000
+++ b/po/de.po	Sat Dec 15 05:15:31 2007 +0000
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-11-28 10:03+0100\n"
-"PO-Revision-Date: 2007-11-28 10:03+0100\n"
+"POT-Creation-Date: 2007-12-06 15:10+0100\n"
+"PO-Revision-Date: 2007-12-06 15:10+0100\n"
 "Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -2336,6 +2336,12 @@
 msgid "Loads .NET plugins with Mono."
 msgstr "Lädt .NET-Plugins mit Mono."
 
+msgid "Add new line in IMs"
+msgstr "Neue Zeile in IMs einfügen"
+
+msgid "Add new line in Chats"
+msgstr "Neue Zeile in Chats einfügen"
+
 #. *< magic
 #. *< major version
 #. *< minor version
@@ -5760,6 +5766,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."
 
@@ -6563,9 +6575,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 "
@@ -6701,11 +6710,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."
@@ -9989,6 +10000,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"
 
@@ -11049,15 +11071,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"
 
@@ -11124,6 +11137,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"
 
@@ -11250,6 +11272,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 "
@@ -11693,6 +11718,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"