changeset 21889:a3f0c30eb0bf

propagate from branch 'im.pidgin.pidgin' (head 1716154a473ab645c7477f4285579df56d955fde) to branch 'im.pidgin.pidgin.next.minor' (head 457a3a60abf75010d7855c1f25254e8118d38466)
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Tue, 18 Dec 2007 05:50:43 +0000
parents c117352a6088 (diff) c88a3f2dbb52 (current diff)
children 5814ae85e756
files ChangeLog.API libpurple/protocols/bonjour/mdns_howl.c pidgin/gtkconv.c pidgin/gtkimhtml.c pidgin/gtkprefs.c pidgin/gtkroomlist.c pidgin/gtkutils.c
diffstat 139 files changed, 3228 insertions(+), 1539 deletions(-) [+]
line wrap: on
line diff
--- a/.mtn-ignore	Wed Nov 28 01:54:34 2007 +0000
+++ b/.mtn-ignore	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/COPYRIGHT	Tue Dec 18 05:50:43 2007 +0000
@@ -309,6 +309,7 @@
 Tim Ringenbach
 Dennis Ristuccia
 Lee Roach
+Eion Robb
 Rhett Robinson
 Luciano Miguel Ferreira Rocha
 Andrew Rodland
--- a/ChangeLog	Wed Nov 28 01:54:34 2007 +0000
+++ b/ChangeLog	Tue Dec 18 05:50:43 2007 +0000
@@ -1,5 +1,47 @@
 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.
+
+	Pidgin:
+	* Added the ability to theme conversation name colors (red and blue)
+	  through your GTK+ theme, and exposed those theme settings to the 
+	  Pidgin GTK+ Theme Control plugin (Dustin Howett)
+
+	Finch:
+	* Color is used in the buddylist to indicate status, and the conversation
+	  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.1 (12/7/2007):
+	http://developer.pidgin.im/query?status=closed&milestone=2.3.1
+		NOTE: Due to the way this release was made, it is possible that
+		      bugs marked as fixed in 2.3.1 will not be fixed until the
+		      next release.
+	
+	* Fixed a number of MSN bugs introduced in 2.3.0, resolving problems
+	  connecting to MSN and random local display name changes
+	* Going idle on MySpaceIM will no longer clear your status and message.
+	* Idle MySpaceIM buddies should now appear online at login.
+	* Fixed crashes in XMPP when discovering a client's capabilities
+	* Don't set the current tune title if it's NULL (XMPP/Google Talk)
+	* Don't allow buddies to be manually added to Bonjour
+	* Don't advertise IPv6 on Bonjour because we don't support it
+	* Compile fixes for FreeBSD and Solaris
+	* Update QQ client version so some accounts can connect again
+	* Do not allow ISON requests to stack in IRC, preventing flooding IRC
+	  servers when temporary network outages are restored
+	* Plug several leaks in the perl plugin loader
+	* Prevent autoaccept plugin overwriting existing files
+
 version 2.3.0 (11/24/2007):
 	http://developer.pidgin.im/query?status=closed&milestone=2.3.0
 		NOTE: Some bugs marked fixed in 2.2.1, 2.2.2 or 2.2.3 may not
--- a/ChangeLog.API	Wed Nov 28 01:54:34 2007 +0000
+++ b/ChangeLog.API	Tue Dec 18 05:50:43 2007 +0000
@@ -18,6 +18,17 @@
 		* pidgin_auto_parent_window to make a window transient for a suitable
 		  parent window.
 
+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	Wed Nov 28 01:54:34 2007 +0000
+++ b/Makefile.am	Tue Dec 18 05:50:43 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/NEWS	Wed Nov 28 01:54:34 2007 +0000
+++ b/NEWS	Tue Dec 18 05:50:43 2007 +0000
@@ -1,5 +1,19 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+2.3.1 (12/7/2007):
+	Stu: I'm sorry for the MSN problems and the plugin crashes in 2.3.0.
+	Hopefully this will redeem us. This fixes a number of bugs. I'm a
+	bit late but I'd like to welcome John to the team. Enjoy!
+
+	Luke: I've done absolutely nothing in the last 2 weeks, except watch
+	others commit bug and, more, leak fixes.  People should be noticing
+	remarkably fewer memory leaks now than 2 or more releases ago.
+
+	Kevin: I'm not quite sure what happened to our MySpaceIM Summer of
+	Code student, but I fixed a few MySpace bugs with idle and status.
+	I will try to fix some of the other more significant bugs, after I
+	figure out the protocol, especially including grouping issues.
+
 2.3.0 (11/20/2007):
 	Luke: While this does not have the new MSN code, rest assured that
 	we are working on it and that it is nearing release.  This contains
--- a/configure.ac	Wed Nov 28 01:54:34 2007 +0000
+++ b/configure.ac	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/doc/account-signals.dox	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/doc/blist-signals.dox	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/doc/finch.1.in	Tue Dec 18 05:50:43 2007 +0000
@@ -88,9 +88,8 @@
 Start resizing a window. Press the cursor keys to resize the window. When you
 are done, press \fBEnter\fR or \fBEscape\fR.
 .TP
-.B Alt \+ d
-Dump the contents of the screen in HTML format in a file named "dump.html" in
-working directory.
+.B Alt \+ D
+Dump the contents of the screen in HTML format.
 .TP
 .B Alt \+ .
 Move the position of the current window in the window list one place to the
@@ -107,8 +106,7 @@
 Jump to the 1st, 2nd ... 10th window.
 .TP
 .B Ctrl \+ o
-Bring up the menu (if there is one) for a window. Note that currently only the
-buddylist has a menu.
+Bring up the menu (if there is one) for a window.
 .TP
 .B Alt \+ /
 Show a list of available key-bindings for the current widget in focus.
@@ -137,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
@@ -494,7 +515,7 @@
 .SH BUGS
 Known bugs are listed at
 .br
-\fIhttp://sourceforge.net/tracker/?group_id=235&atid=100235\fR
+\fIhttp://developer.pidgin.im/query?status=new&status=assigned&status=reopened&component=finch+%28gnt%2Fncurses%29&order=priority\fR
 
 Before sending a bug report, please verify that you have the latest
 version of \fBfinch\fR and \fBlibpurple\fR.  Many bugs (major and minor) are
--- a/doc/gtkconv-signals.dox	Wed Nov 28 01:54:34 2007 +0000
+++ b/doc/gtkconv-signals.dox	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/doc/log-signals.dox	Tue Dec 18 05:50:43 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/pidgin.1.in	Wed Nov 28 01:54:34 2007 +0000
+++ b/doc/pidgin.1.in	Tue Dec 18 05:50:43 2007 +0000
@@ -88,8 +88,8 @@
 .TP
 .B Add Buddy Pounce
 A Buddy Pounce is a configurable automated action to be performed when the
-buddy's state changes.  This will open the \fBBuddy Pounce\fR dialog to be
-discussed later.
+buddy's state changes.  This will open the \fBBuddy Pounce\fR dialog, which
+will be discussed later.
 .TP
 .B View Log
 Pidgin is capable of automatically logging messages.  These logs are
@@ -118,7 +118,7 @@
 
 .SH ACCOUNT EDITOR
 The account editor consists of a list of accounts and information about
-them.  It can be accessed by selecting \fBManage\fR from the Tools menu.
+them.  It can be accessed by selecting \fBManage\fR from the Accounts menu.
 Clicking \fIDelete\fR will delete the currently selected account.
 Clicking \fIAdd\fR or \fIModify\fR will invoke a \fBModify Account\fR
 window.  Here, the user  can add or alter account information.  When creating
--- a/doc/plugin-i18n.dox	Wed Nov 28 01:54:34 2007 +0000
+++ b/doc/plugin-i18n.dox	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/gntblist.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/gntconv.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/gntdebug.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/gntplugin.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/Makefile.am	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/configure.ac	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gntcolors.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gntcolors.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gntentry.c	Tue Dec 18 05:50:43 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
@@ -575,15 +569,16 @@
 next_begin_word(const char *text, const char *end)
 {
 	gunichar ch = 0;
+
+	while (text && text < end && g_unichar_isspace(g_utf8_get_char(text)))
+		text = g_utf8_find_next_char(text, end);
+
 	ch = g_utf8_get_char(text);
 	while ((text = g_utf8_find_next_char(text, end)) != NULL && text <= end) {
 		gunichar cur = g_utf8_get_char(text);
 		if (!SAME(ch, cur))
 			break;
 	}
-
-	while (text && text < end && g_unichar_isspace(g_utf8_get_char(text)))
-		text = g_utf8_find_next_char(text, end);
 	return (text ? text : end);
 }
 
--- a/finch/libgnt/gntmain.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gntmain.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gntstyle.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gntstyle.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gnttextview.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gnttree.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gnttree.h	Tue Dec 18 05:50:43 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/gntwindow.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gntwindow.c	Tue Dec 18 05:50:43 2007 +0000
@@ -62,7 +62,8 @@
 	if (window->menu)
 		gnt_widget_destroy(GNT_WIDGET(window->menu));
 	if (window->priv) {
-		g_hash_table_destroy(window->priv->accels);
+		if (window->priv->accels)
+			g_hash_table_destroy(window->priv->accels);
 		g_free(window->priv);
 	}
 	org_destroy(widget);
--- a/finch/libgnt/gntwm.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/gntwm.c	Tue Dec 18 05:50:43 2007 +0000
@@ -20,14 +20,19 @@
  * 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>
 #include <ctype.h>
 #include <gmodule.h>
 #include <stdlib.h>
@@ -41,6 +46,7 @@
 #include "gntbox.h"
 #include "gntbutton.h"
 #include "gntentry.h"
+#include "gntfilesel.h"
 #include "gntlabel.h"
 #include "gntmenu.h"
 #include "gnttextview.h"
@@ -663,12 +669,12 @@
 	return TRUE;
 }
 
-static gboolean
-dump_screen(GntBindable *bindable, GList *null)
+static void
+dump_file_save(GntFileSel *fs, const char *path, const char *f, gpointer n)
 {
+	FILE *file;
 	int x, y;
 	chtype old = 0, now = 0;
-	FILE *file = fopen("dump.html", "w");
 	struct {
 		char ascii;
 		char *unicode;
@@ -690,6 +696,12 @@
 		{'\0', NULL}
 	};
 
+	gnt_widget_destroy(GNT_WIDGET(fs));
+
+	if ((file = g_fopen(path, "w+")) == NULL) {
+		return;
+	}
+
 	fprintf(file, "<head>\n  <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />\n</head>\n<body>\n");
 	fprintf(file, "<pre>");
 	for (y = 0; y < getmaxy(stdscr); y++) {
@@ -796,6 +808,28 @@
 	}
 	fprintf(file, "</pre>\n</body>");
 	fclose(file);
+}
+
+static void
+dump_file_cancel(GntWidget *w, GntFileSel *fs)
+{
+	gnt_widget_destroy(GNT_WIDGET(fs));
+}
+
+static gboolean
+dump_screen(GntBindable *b, GList *null)
+{
+	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);
+	gnt_widget_show(window);
 	return TRUE;
 }
 
@@ -1168,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 {
@@ -1241,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
@@ -1366,7 +1441,7 @@
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "window-list", window_list,
 				"\033" "w", NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "dump-screen", dump_screen,
-				"\033" "d", NULL);
+				"\033" "D", NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "shift-left", shift_left,
 				"\033" ",", NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "shift-right", shift_right,
@@ -1411,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	Wed Nov 28 01:54:34 2007 +0000
+++ b/finch/libgnt/test/tv.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/Makefile.am	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/account.c	Tue Dec 18 05:50:43 2007 +0000
@@ -331,13 +331,24 @@
 	if(err == NULL)
 		return node;
 
+	/* It doesn't make sense to have transient errors persist across a
+	 * restart.
+	 */
+	if(!purple_connection_error_is_fatal (err->type))
+		return node;
+
 	child = xmlnode_new_child(node, "type");
 	snprintf(type_str, sizeof(type_str), "%u", err->type);
 	xmlnode_insert_data(child, type_str, -1);
 
 	child = xmlnode_new_child(node, "description");
-	if(err->description)
-		xmlnode_insert_data(child, err->description, -1);
+	if(err->description) {
+		char *utf8ized = purple_utf8_try_convert(err->description);
+		if(utf8ized == NULL)
+			utf8ized = purple_utf8_salvage(err->description);
+		xmlnode_insert_data(child, utf8ized, -1);
+		g_free(utf8ized);
+	}
 
 	return node;
 }
@@ -741,7 +752,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 +2540,19 @@
 
 	g_return_val_if_fail(name != NULL, NULL);
 
-	who = g_strdup(purple_normalize(NULL, name));
-
 	for (l = purple_accounts_get_all(); l != NULL; l = l->next) {
 		account = (PurpleAccount *)l->data;
 
-		if (!strcmp(purple_normalize(NULL, purple_account_get_username(account)), who) &&
+		who = g_strdup(purple_normalize(account, name));
+		if (!strcmp(purple_normalize(account, purple_account_get_username(account)), who) &&
 			(!protocol_id || !strcmp(account->protocol_id, protocol_id))) {
-
+			g_free(who);
 			break;
 		}
-
+		g_free(who);
 		account = NULL;
 	}
 
-	g_free(who);
-
 	return account;
 }
 
--- a/libpurple/account.h	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/account.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/blist.c	Tue Dec 18 05:50:43 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/buddyicon.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/buddyicon.c	Tue Dec 18 05:50:43 2007 +0000
@@ -98,8 +98,7 @@
 {
 	const char *dirname;
 	char *path;
-	FILE *file = NULL;
-
+	
 	g_return_if_fail(img != NULL);
 
 	if (!purple_buddy_icons_is_caching())
@@ -120,24 +119,12 @@
 		}
 	}
 
-	if ((file = g_fopen(path, "wb")) != NULL)
-	{
-		if (!fwrite(purple_imgstore_get_data(img), purple_imgstore_get_size(img), 1, file))
-		{
-			purple_debug_error("buddyicon", "Error writing %s: %s\n",
-			                   path, g_strerror(errno));
-		}
-		else
-			purple_debug_info("buddyicon", "Wrote cache file: %s\n", path);
-
-		fclose(file);
-	}
-	else
-	{
+	if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
+		purple_util_write_data_to_file_absolute(path, purple_imgstore_get_data(img),
+							purple_imgstore_get_size(img));	
+	} else 	{
 		purple_debug_error("buddyicon", "Unable to create file %s: %s\n",
 		                   path, g_strerror(errno));
-		g_free(path);
-		return;
 	}
 	g_free(path);
 }
--- a/libpurple/connection.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/connection.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/ft.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/network.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugin.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/autoaccept.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/newline.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/perl/common/BuddyList.xs	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/perl/common/Cmds.xs	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/perl/common/Conversation.xs	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/perl/common/Log.xs	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/perl/common/Pounce.xs	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/perl/common/Prefs.xs	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/perl/common/Prpl.xs	Tue Dec 18 05:50:43 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)
@@ -52,3 +54,20 @@
 	Purple::Account account
 	const char *name
 	time_t login_time
+
+int
+purple_prpl_send_raw(gc, str)
+	Purple::Connection gc
+	const char *str
+PREINIT:
+	PurplePluginProtocolInfo *prpl_info;
+CODE:
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+	if (prpl_info && prpl_info->send_raw != NULL) {
+		RETVAL = prpl_info->send_raw(gc, str, strlen(str));
+	} else {
+		RETVAL = 0;
+	}
+OUTPUT:
+	RETVAL
+
--- a/libpurple/plugins/perl/common/SavedStatuses.xs	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/perl/common/SavedStatuses.xs	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/perl/common/Status.xs	Tue Dec 18 05:50:43 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/plugins/ssl/ssl-gnutls.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Tue Dec 18 05:50:43 2007 +0000
@@ -512,9 +512,8 @@
 	dt.data = (unsigned char *) buf;
 	dt.size = buf_sz;
 
-	/* Perform the conversion */
-	crt = x509_import_from_datum(dt,
-				     GNUTLS_X509_FMT_PEM); // files should be in PEM format
+	/* Perform the conversion; files should be in PEM format */
+	crt = x509_import_from_datum(dt, GNUTLS_X509_FMT_PEM);
 
 	/* Cleanup */
 	g_free(buf);
--- a/libpurple/pounce.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/pounce.c	Tue Dec 18 05:50:43 2007 +0000
@@ -181,7 +181,8 @@
 
 	child = xmlnode_new_child(node, "account");
 	xmlnode_set_attrib(child, "protocol", pouncer->protocol_id);
-	xmlnode_insert_data(child, purple_account_get_username(pouncer), -1);
+	xmlnode_insert_data(child,
+			purple_normalize(pouncer, purple_account_get_username(pouncer)), -1);
 
 	child = xmlnode_new_child(node, "pouncee");
 	xmlnode_insert_data(child, purple_pounce_get_pouncee(pounce), -1);
--- a/libpurple/prefs.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/prefs.c	Tue Dec 18 05:50:43 2007 +0000
@@ -914,7 +914,7 @@
 	if(pref) {
 		if(pref->type != PURPLE_PREF_PATH) {
 			purple_debug_error("prefs",
-					"purple_prefs_set_path: %s not a string pref\n", name);
+					"purple_prefs_set_path: %s not a path pref\n", name);
 			return;
 		}
 
--- a/libpurple/protocols/bonjour/Makefile.am	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/Makefile.am	Tue Dec 18 05:50:43 2007 +0000
@@ -13,6 +13,7 @@
 	buddy.h \
 	jabber.c \
 	jabber.h \
+	mdns_avahi.c \
 	mdns_common.c \
 	mdns_common.h \
 	mdns_interface.h \
@@ -22,14 +23,6 @@
 	bonjour_ft.c \
 	bonjour_ft.h 
 
-if MDNS_AVAHI
-  BONJOURSOURCES += mdns_avahi.c
-else
-if MDNS_HOWL
-    BONJOURSOURCES += mdns_howl.c
-endif
-endif
-
 AM_CFLAGS = $(st)
 
 libbonjour_la_LDFLAGS = -module -avoid-version
@@ -40,29 +33,14 @@
 noinst_LIBRARIES     = libbonjour.a
 libbonjour_a_SOURCES = $(BONJOURSOURCES)
 libbonjour_a_CFLAGS  = $(AM_CFLAGS)
-libbonjour_a_LIBADD  =
-
-if MDNS_AVAHI
-  libbonjour_a_LIBADD  += $(AVAHI_LIBS)
-else
-if MDNS_HOWL
-    libbonjour_a_LIBADD  += $(HOWL_LIBS)
-endif
-endif
+libbonjour_a_LIBADD  = $(AVAHI_LIBS)
 
 else
 
 st =
 pkg_LTLIBRARIES       = libbonjour.la
 libbonjour_la_SOURCES = $(BONJOURSOURCES)
-libbonjour_la_LIBADD  = $(GLIB_LIBS) $(LIBXML_LIBS)
-if MDNS_AVAHI
-  libbonjour_la_LIBADD  += $(AVAHI_LIBS)
-else
-if MDNS_HOWL
-    libbonjour_la_LIBADD  += $(HOWL_LIBS)
-endif
-endif
+libbonjour_la_LIBADD  = $(GLIB_LIBS) $(LIBXML_LIBS) $(AVAHI_LIBS)
 
 endif
 
@@ -72,13 +50,6 @@
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
 	$(DEBUG_CFLAGS) \
-	$(LIBXML_CFLAGS)
+	$(LIBXML_CFLAGS) \
+	$(AVAHI_CFLAGS)
 
-if MDNS_AVAHI
-  AM_CPPFLAGS += $(AVAHI_CFLAGS)
-else
-if MDNS_HOWL
-    AM_CPPFLAGS += $(HOWL_CFLAGS)
-endif
-endif
-
--- a/libpurple/protocols/bonjour/bonjour.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Tue Dec 18 05:50:43 2007 +0000
@@ -109,7 +109,7 @@
 	gc->proto_data = bd = g_new0(BonjourData, 1);
 
 	/* Start waiting for jabber connections (iChat style) */
-	bd->jabber_data = g_new(BonjourJabber, 1);
+	bd->jabber_data = g_new0(BonjourJabber, 1);
 	bd->jabber_data->port = BONJOUR_DEFAULT_PORT_INT;
 	bd->jabber_data->account = account;
 
--- a/libpurple/protocols/bonjour/bonjour.h	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.h	Tue Dec 18 05:50:43 2007 +0000
@@ -44,7 +44,7 @@
 {
 	BonjourDnsSd *dns_sd_data;
 	BonjourJabber *jabber_data;
-	GList *xfer_lists;
+	GSList *xfer_lists;
 } BonjourData;
 
 #endif /* _BONJOUR_H_ */
--- a/libpurple/protocols/bonjour/bonjour_ft.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour_ft.c	Tue Dec 18 05:50:43 2007 +0000
@@ -149,7 +149,7 @@
 static PurpleXfer*
 bonjour_si_xfer_find(BonjourData *bd, const char *sid, const char *from)
 {
-	GList *xfers = NULL;
+	GSList *xfers = NULL;
 	PurpleXfer *xfer = NULL;
 	XepXfer *xf = NULL;
 
@@ -309,7 +309,7 @@
 	if(xf != NULL) {
 		bd = (BonjourData*)xf->data;
 		if(bd != NULL) {
-			bd->xfer_lists = g_list_remove(bd->xfer_lists, xfer);
+			bd->xfer_lists = g_slist_remove(bd->xfer_lists, xfer);
 			purple_debug_info("bonjour", "B free xfer from lists(%p).\n", bd->xfer_lists);
 		}
 		if (xf->proxy_connection != NULL)
@@ -359,7 +359,7 @@
 	purple_xfer_set_cancel_send_fnc(xfer, bonjour_xfer_cancel_send);
 	purple_xfer_set_end_fnc(xfer, bonjour_xfer_end);
 
-	bd->xfer_lists = g_list_append(bd->xfer_lists, xfer);
+	bd->xfer_lists = g_slist_append(bd->xfer_lists, xfer);
 
 	return xfer;
 }
@@ -387,7 +387,7 @@
 bonjour_xfer_init(PurpleXfer *xfer)
 {
 	PurpleBuddy *buddy = NULL;
-	BonjourBuddy *bd = NULL;
+	BonjourBuddy *bb = NULL;
 	XepXfer *xf = NULL;
 
 	xf = (XepXfer*)xfer->data;
@@ -401,8 +401,10 @@
 	if (buddy == NULL)
 		return;
 
-	bd = (BonjourBuddy *)buddy->proto_data;
-	xf->buddy_ip = g_strdup(bd->ip);
+	bb = (BonjourBuddy *)buddy->proto_data;
+	/* Assume it is the first IP. We could do something like keep track of which one is in use or something. */
+	if (bb->ips)
+		xf->buddy_ip = g_strdup(bb->ips->data);
 	if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
 		/* initiate file transfer, send SI offer. */
 		purple_debug_info("bonjour", "Bonjour xfer type is PURPLE_XFER_SEND.\n");
@@ -603,7 +605,7 @@
 	purple_xfer_set_cancel_recv_fnc(xfer, bonjour_xfer_cancel_recv);
 	purple_xfer_set_end_fnc(xfer, bonjour_xfer_end);
 
-	bd->xfer_lists = g_list_append(bd->xfer_lists, xfer);
+	bd->xfer_lists = g_slist_append(bd->xfer_lists, xfer);
 
 	purple_xfer_request(xfer);
 }
--- a/libpurple/protocols/bonjour/buddy.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/issues.txt	Tue Dec 18 05:50:43 2007 +0000
@@ -2,5 +2,4 @@
 ============= Known issues ===============
 ==========================================
 
-* File transfers
 * Typing notifications
--- a/libpurple/protocols/bonjour/jabber.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Tue Dec 18 05:50:43 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,36 +68,17 @@
 #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) {
+bonjour_jabber_conv_new(PurpleBuddy *pb, PurpleAccount *account, const char *ip) {
 
 	BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1);
 	bconv->socket = -1;
@@ -99,11 +86,14 @@
 	bconv->tx_handler = 0;
 	bconv->rx_handler = 0;
 	bconv->pb = pb;
+	bconv->account = account;
+	bconv->ip = g_strdup(ip);
+
+	bonjour_parser_setup(bconv);
 
 	return bconv;
 }
 
-
 static const char *
 _font_size_ichat_to_purple(int size)
 {
@@ -129,34 +119,48 @@
 {
 	xmlnode *body_node, *html_node, *events_node;
 	PurpleConnection *gc = pb->account->gc;
-	char *body, *html_body = NULL;
-	const char *ichat_balloon_color = NULL;
-	const char *ichat_text_color = NULL;
-	const char *font_face = NULL;
-	const char *font_size = NULL;
-	const char *font_color = NULL;
+	gchar *body = NULL;
 	gboolean composing_event = FALSE;
 
 	body_node = xmlnode_get_child(message_node, "body");
-	if (body_node == NULL)
+	html_node = xmlnode_get_child(message_node, "html");
+
+	if (body_node == NULL && html_node == NULL) {
+		purple_debug_error("bonjour", "No body or html node found, discarding message.\n");
 		return;
-	body = xmlnode_get_data(body_node);
+	}
 
-	html_node = xmlnode_get_child(message_node, "html");
-	if (html_node != NULL)
-	{
+	events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event");
+	if (events_node != NULL) {
+		if (xmlnode_get_child(events_node, "composing") != NULL)
+			composing_event = TRUE;
+		if (xmlnode_get_child(events_node, "id") != NULL) {
+			/* The user is just typing */
+			/* TODO: Deal with typing notification */
+			return;
+		}
+	}
+
+	if (html_node != NULL) {
 		xmlnode *html_body_node;
 
 		html_body_node = xmlnode_get_child(html_node, "body");
-		if (html_body_node != NULL)
-		{
+		if (html_body_node != NULL) {
 			xmlnode *html_body_font_node;
+			const char *ichat_balloon_color = NULL;
+			const char *ichat_text_color = NULL;
+			const char *font_face = NULL;
+			const char *font_size = NULL;
+			const char *font_color = NULL;
 
 			ichat_balloon_color = xmlnode_get_attrib(html_body_node, "ichatballooncolor");
 			ichat_text_color = xmlnode_get_attrib(html_body_node, "ichattextcolor");
 			html_body_font_node = xmlnode_get_child(html_body_node, "font");
-			if (html_body_font_node != NULL)
-			{ /* Types of messages sent by iChat */
+
+			/* Types of messages sent by iChat */
+			if (html_body_font_node != NULL) {
+				gchar *html_body;
+
 				font_face = xmlnode_get_attrib(html_body_font_node, "face");
 				/* The absolute iChat font sizes should be converted to 1..7 range */
 				font_size = xmlnode_get_attrib(html_body_font_node, "ABSZ");
@@ -164,74 +168,74 @@
 					font_size = _font_size_ichat_to_purple(atoi(font_size));
 				font_color = xmlnode_get_attrib(html_body_font_node, "color");
 				html_body = xmlnode_get_data(html_body_font_node);
+
 				if (html_body == NULL)
 					/* This is the kind of formated messages that Purple creates */
 					html_body = xmlnode_to_str(html_body_font_node, NULL);
+
+				if (html_body != NULL) {
+					/* Use some sane defaults */
+					if (font_face == NULL) font_face = "Helvetica";
+					if (font_size == NULL) font_size = "3";
+					if (ichat_text_color == NULL) ichat_text_color = "#000000";
+					if (ichat_balloon_color == NULL) ichat_balloon_color = "#FFFFFF";
+
+					body = g_strdup_printf("<font face='%s' size='%s' color='%s' back='%s'>%s</font>",
+								   font_face, font_size, ichat_text_color, ichat_balloon_color,
+								   html_body);
+
+					g_free(html_body);
+				}
 			}
 		}
 	}
 
-	events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event");
-	if (events_node != NULL)
-	{
-		if (xmlnode_get_child(events_node, "composing") != NULL)
-			composing_event = TRUE;
-		if (xmlnode_get_child(events_node, "id") != NULL)
-		{
-			/* The user is just typing */
-			/* TODO: Deal with typing notification */
-			g_free(body);
-			g_free(html_body);
-			return;
-		}
-	}
+	/* Compose the message */
+	if (body == NULL && body_node != NULL)
+		body = xmlnode_get_data(body_node);
 
-	/* Compose the message */
-	if (html_body != NULL)
-	{
-		g_free(body);
-
-		if (font_face == NULL) font_face = "Helvetica";
-		if (font_size == NULL) font_size = "3";
-		if (ichat_text_color == NULL) ichat_text_color = "#000000";
-		if (ichat_balloon_color == NULL) ichat_balloon_color = "#FFFFFF";
-		body = g_strdup_printf("<font face='%s' size='%s' color='%s' back='%s'>%s</font>",
-				       font_face, font_size, ichat_text_color, ichat_balloon_color,
-				       html_body);
+	if (body == NULL) {
+		purple_debug_error("bonjour", "No html body or regular body found.\n");
+		return;
 	}
 
-	/* TODO: Should we do something with "composing_event" here? */
-
 	/* Send the message to the UI */
 	serv_got_im(gc, pb->name, body, 0, time(NULL));
 
 	g_free(body);
-	g_free(html_body);
 }
 
-struct _check_buddy_by_address_t {
+struct _match_buddies_by_address_t {
 	const char *address;
-	PurpleBuddy **pb;
-	BonjourJabber *bj;
+	GSList *matched_buddies;
+	BonjourJabber *jdata;
 };
 
 static void
-_check_buddy_by_address(gpointer key, gpointer value, gpointer data)
+_match_buddies_by_address(gpointer key, gpointer value, gpointer data)
 {
 	PurpleBuddy *pb = value;
-	BonjourBuddy *bb;
-	struct _check_buddy_by_address_t *cbba = data;
+	struct _match_buddies_by_address_t *mbba = data;
 
 	/*
 	 * If the current PurpleBuddy's data is not null and the PurpleBuddy's account
 	 * is the same as the account requesting the check then continue to determine
-	 * whether the buddies IP matches the target IP.
+	 * whether one of the buddies IPs matches the target IP.
 	 */
-	if (cbba->bj->account == pb->account)
+	if (mbba->jdata->account == pb->account && pb->proto_data != NULL)
 	{
-		bb = pb->proto_data;
-		if ((bb != NULL) && (g_ascii_strcasecmp(bb->ip, cbba->address) == 0))
-			*(cbba->pb) = pb;
+		const char *ip;
+		BonjourBuddy *bb = pb->proto_data;
+		GSList *tmp = bb->ips;
+
+		while(tmp) {
+			ip = tmp->data;
+			if (ip != NULL && g_ascii_strcasecmp(ip, mbba->address) == 0) {
+				mbba->matched_buddies = g_slist_prepend(mbba->matched_buddies, pb);
+				break;
+			}
+			tmp = tmp->next;
+		}
 	}
 }
 
@@ -243,8 +247,6 @@
 	BonjourJabberConversation *bconv = bb->conversation;
 	int ret, writelen;
 
-	/* TODO: Make sure that the stream has been established before sending */
-
 	writelen = purple_circ_buffer_get_max_read(bconv->tx_buf);
 
 	if (writelen == 0) {
@@ -289,7 +291,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 +321,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);
@@ -331,6 +334,7 @@
 void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet) {
 
 	g_return_if_fail(packet != NULL);
+	g_return_if_fail(pb != NULL);
 
 	if (!strcmp(packet->name, "message"))
 		_jabber_parse_and_write_message_to_ui(packet, pb);
@@ -344,31 +348,31 @@
 static void
 _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition)
 {
-	PurpleBuddy *pb = data;
+	BonjourJabberConversation *bconv = data;
 	gint len, message_length;
 	static char message[4096];
 
-	/*TODO: use a static buffer */
-
 	/* Read the data from the socket */
 	if ((len = recv(socket, message, sizeof(message) - 1, 0)) == -1) {
 		/* There have been an error reading from the socket */
 		if (errno != EAGAIN) {
-			BonjourBuddy *bb = pb->proto_data;
 			const char *err = g_strerror(errno);
 
 			purple_debug_warning("bonjour", "receive error: %s\n", err ? err : "(null)");
 
-			bonjour_jabber_close_conversation(bb->conversation);
-			bb->conversation = NULL;
+			bonjour_jabber_close_conversation(bconv);
+			if (bconv->pb != NULL) {
+				BonjourBuddy *bb = bconv->pb->proto_data;
+				bb->conversation = NULL;
+			}
 
 			/* I guess we really don't need to notify the user.
 			 * If they try to send another message it'll reconnect */
 		}
 		return;
 	} else if (len == 0) { /* The other end has closed the socket */
-		purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name ? pb->name : "(null)");
-		bonjour_jabber_stream_ended(pb);
+		purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", (bconv->pb && bconv->pb->name) ? bconv->pb->name : "(unknown)");
+		bonjour_jabber_stream_ended(bconv);
 		return;
 	} else {
 		message_length = len;
@@ -382,47 +386,37 @@
 
 	purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", message, len);
 
-	bonjour_parser_process(pb, message, message_length);
+	bonjour_parser_process(bconv, message, message_length);
 }
 
-void bonjour_jabber_stream_ended(PurpleBuddy *pb) {
-	BonjourBuddy *bb = pb->proto_data;
+void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv) {
 
-	purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", pb->name);
-
-	g_return_if_fail(bb != NULL);
+	purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", bconv->pb ? bconv->pb->name : "(unknown)");
 
 	/* Inform the user that the conversation has been closed */
-	if (bb->conversation != NULL) {
+	if (bconv != NULL) {
+		BonjourBuddy *bb = NULL;
+
+		if(bconv->pb != NULL)
+			bb = bconv->pb->proto_data;
 #if 0
-		PurpleConversation *conv;
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account);
-		if (conv != NULL) {
-			char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
-			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
-			g_free(tmp);
+		if(bconv->pb != NULL) {
+			PurpleConversation *conv;
+			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bconv->pb->name, bconv->pb->account);
+			if (conv != NULL) {
+				char *tmp = g_strdup_printf(_("%s has closed the conversation."), bconv->pb->name);
+				purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+				g_free(tmp);
+			}
 		}
 #endif
 		/* Close the socket, clear the watcher and free memory */
-		bonjour_jabber_close_conversation(bb->conversation);
-		bb->conversation = NULL;
+		bonjour_jabber_close_conversation(bconv);
+		if(bb)
+			bb->conversation = NULL;
 	}
 }
 
-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;
@@ -432,9 +426,7 @@
 static void
 _start_stream(gpointer data, gint source, PurpleInputCondition condition)
 {
-	PurpleBuddy *pb = data;
-	BonjourBuddy *bb = pb->proto_data;
-	BonjourJabberConversation *bconv = bb->conversation;
+	BonjourJabberConversation *bconv = data;
 	struct _stream_start_data *ss = bconv->stream_data;
 	int len, ret;
 
@@ -448,18 +440,26 @@
 	else if (ret <= 0) {
 		const char *err = g_strerror(errno);
 		PurpleConversation *conv;
+		const char *bname = bconv->buddy_name;
+		BonjourBuddy *bb = NULL;
 
-		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
-				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+		if(bconv->pb) {
+			bb = bconv->pb->proto_data;
+			bname = purple_buddy_get_name(bconv->pb);
+		}
 
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n",
+				   bname ? bname : "(unknown)", bconv->ip, err ? err : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->account);
 		if (conv != NULL)
 			purple_conversation_write(conv, NULL,
 				  _("Unable to send the message, the conversation couldn't be started."),
 				  PURPLE_MESSAGE_SYSTEM, time(NULL));
 
 		bonjour_jabber_close_conversation(bconv);
-		bb->conversation = NULL;
+		if(bb != NULL)
+			bb->conversation = NULL;
 
 		return;
 	}
@@ -478,23 +478,31 @@
 
 	/* 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);
-
+	bonjour_jabber_stream_started(bconv);
 }
 
-static gboolean bonjour_jabber_stream_init(PurpleBuddy *pb, int client_socket)
+static gboolean bonjour_jabber_send_stream_init(BonjourJabberConversation *bconv, int client_socket)
 {
 	int ret, len;
 	char *stream_start;
-	BonjourBuddy *bb = pb->proto_data;
+	const char *bname = bconv->buddy_name;
+
+	if (bconv->pb != NULL)
+		bname = purple_buddy_get_name(bconv->pb);
 
-	stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
-								   purple_buddy_get_name(pb));
+	/* If we have no idea who "to" is, use an empty string.
+	 * If we don't know now, it is because the other side isn't playing nice, so they can't complain. */
+	if (bname == NULL)
+		bname = "";
+
+	stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(bconv->account), bname);
 	len = strlen(stream_start);
 
+	bconv->sent_stream_start = PARTIALLY_SENT;
+
 	/* Start the stream */
 	ret = send(client_socket, stream_start, len, 0);
 
@@ -503,8 +511,17 @@
 	else if (ret <= 0) {
 		const char *err = g_strerror(errno);
 
-		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
-				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n",
+				   (*bname) ? bname : "(unknown)", bconv->ip, err ? err : "(null)");
+
+		if (bconv->pb) {
+			PurpleConversation *conv;
+			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->account);
+			if (conv != NULL)
+				purple_conversation_write(conv, NULL,
+					  _("Unable to send the message, the conversation couldn't be started."),
+					  PURPLE_MESSAGE_SYSTEM, time(NULL));
+		}
 
 		close(client_socket);
 		g_free(stream_start);
@@ -516,37 +533,76 @@
 	if (ret < len) {
 		struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
 		ss->msg = g_strdup(stream_start + ret);
-		bb->conversation->stream_data = ss;
+		bconv->stream_data = ss;
 		/* Finish sending the stream start */
-		bb->conversation->tx_handler = purple_input_add(client_socket,
-			PURPLE_INPUT_WRITE, _start_stream, pb);
+		bconv->tx_handler = purple_input_add(client_socket,
+			PURPLE_INPUT_WRITE, _start_stream, bconv);
 	} else
-		bb->conversation->sent_stream_start = TRUE;
+		bconv->sent_stream_start = FULLY_SENT;
 
 	g_free(stream_start);
 
-	/* setup the parser fresh for each stream */
-	bonjour_parser_setup(bb->conversation);
+	return TRUE;
+}
+
+/* This gets called when we've successfully sent our <stream:stream />
+ * AND when we've recieved a <stream:stream /> */
+void bonjour_jabber_stream_started(BonjourJabberConversation *bconv) {
+
+	if (bconv->sent_stream_start == NOT_SENT && !bonjour_jabber_send_stream_init(bconv, bconv->socket)) {
+		const char *err = g_strerror(errno);
+		const char *bname = bconv->buddy_name;
+
+		if (bconv->pb)
+			bname = purple_buddy_get_name(bconv->pb);
+
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n",
+				   bname ? bname : "(unknown)", bconv->ip, err ? err : "(null)");
 
-	bb->conversation->socket = client_socket;
-	bb->conversation->rx_handler = purple_input_add(client_socket,
-		PURPLE_INPUT_READ, _client_socket_handler, pb);
+		if (bconv->pb) {
+			PurpleConversation *conv;
+			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->account);
+			if (conv != NULL)
+				purple_conversation_write(conv, NULL,
+					  _("Unable to send the message, the conversation couldn't be started."),
+					  PURPLE_MESSAGE_SYSTEM, time(NULL));
+		}
+
+		/* We don't want to recieve anything else */
+		close(bconv->socket);
+		bconv->socket = -1;
 
-	return TRUE;
+		/* This must be asynchronous because it destroys the parser and we
+		 * may be in the middle of parsing.
+		 */
+		async_bonjour_jabber_close_conversation(bconv);
+		return;
+	}
+
+	/* If the stream has been completely started and we know who we're talking to, we can start doing stuff. */
+	/* I don't think the circ_buffer can actually contain anything without a buddy being associated, but lets be explicit. */
+	if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start
+			&& bconv->pb && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
+		/* Watch for when we can write the buffered messages */
+		bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
+			_send_data_write_cb, bconv->pb);
+		/* We can probably write the data right now. */
+		_send_data_write_cb(bconv->pb, bconv->socket, PURPLE_INPUT_WRITE);
+	}
+
 }
 
 static void
 _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition)
 {
-	PurpleBuddy *pb = NULL;
+	BonjourJabber *jdata = data;
 	struct sockaddr_in their_addr; /* connector's address information */
 	socklen_t sin_size = sizeof(struct sockaddr);
 	int client_socket;
 	int flags;
-	BonjourBuddy *bb;
 	char *address_text = NULL;
-	PurpleBuddyList *bl = purple_get_blist();
-	struct _check_buddy_by_address_t *cbba;
+	struct _match_buddies_by_address_t *mbba;
+	BonjourJabberConversation *bconv;
 
 	/* Check that it is a read condition */
 	if (condition != PURPLE_INPUT_READ)
@@ -561,38 +617,35 @@
 	/* Look for the buddy that has opened the conversation and fill information */
 	address_text = inet_ntoa(their_addr.sin_addr);
 	purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text);
-	cbba = g_new0(struct _check_buddy_by_address_t, 1);
-	cbba->address = address_text;
-	cbba->pb = &pb;
-	cbba->bj = data;
-	g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba);
-	g_free(cbba);
-	if (pb == NULL)
-	{
+	mbba = g_new0(struct _match_buddies_by_address_t, 1);
+	mbba->address = address_text;
+	mbba->jdata = jdata;
+	g_hash_table_foreach(purple_get_blist()->buddies, _match_buddies_by_address, mbba);
+
+	if (mbba->matched_buddies == NULL) {
 		purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheros comic\n");
+		g_slist_free(mbba->matched_buddies);
+		g_free(mbba);
 		close(client_socket);
 		return;
 	}
-	bb = pb->proto_data;
 
-	/* Check if the conversation has been previously started */
-	if (bb->conversation == NULL)
-	{
-		bb->conversation = bonjour_jabber_conv_new(pb);
+	g_slist_free(mbba->matched_buddies);
+	g_free(mbba);
 
-		if (!bonjour_jabber_stream_init(pb, client_socket)) {
-			close(client_socket);
-			return;
-		}
+	/* We've established that this *could* be from one of our buddies.
+	 * Wait for the stream open to see if that matches too before assigning it.
+	 */
+	bconv = bonjour_jabber_conv_new(NULL, jdata->account, address_text);
 
-	} else {
-		purple_debug_warning("bonjour", "Ignoring incoming connection because an existing connection exists.\n");
-		close(client_socket);
-	}
+	/* We wait for the stream start before doing anything else */
+	bconv->socket = client_socket;
+	bconv->rx_handler = purple_input_add(client_socket, PURPLE_INPUT_READ, _client_socket_handler, bconv);
+
 }
 
 gint
-bonjour_jabber_start(BonjourJabber *data)
+bonjour_jabber_start(BonjourJabber *jdata)
 {
 	struct sockaddr_in my_addr;
 	int yes = 1;
@@ -600,56 +653,56 @@
 	gboolean bind_successful;
 
 	/* Open a listening socket for incoming conversations */
-	if ((data->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0)
+	if ((jdata->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0)
 	{
 		purple_debug_error("bonjour", "Cannot open socket: %s\n", g_strerror(errno));
-		purple_connection_error_reason (data->account->gc,
+		purple_connection_error_reason (jdata->account->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Cannot open socket"));
 		return -1;
 	}
 
 	/* Make the socket reusable */
-	if (setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0)
+	if (setsockopt(jdata->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0)
 	{
 		purple_debug_error("bonjour", "Error setting socket options: %s\n", g_strerror(errno));
-		purple_connection_error_reason (data->account->gc,
+		purple_connection_error_reason (jdata->account->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Error setting socket options"));
 		return -1;
 	}
 
 	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;
 	for (i = 0; i < 10; i++)
 	{
-		my_addr.sin_port = htons(data->port);
-		if (bind(data->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == 0)
+		my_addr.sin_port = htons(jdata->port);
+		if (bind(jdata->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == 0)
 		{
 			bind_successful = TRUE;
 			break;
 		}
-		data->port++;
+		jdata->port++;
 	}
 
 	/* On no!  We tried 10 ports and could not bind to ANY of them */
 	if (!bind_successful)
 	{
 		purple_debug_error("bonjour", "Cannot bind socket: %s\n", g_strerror(errno));
-		purple_connection_error_reason (data->account->gc,
+		purple_connection_error_reason (jdata->account->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Could not bind socket to port"));
 		return -1;
 	}
 
 	/* Attempt to listen on the bound socket */
-	if (listen(data->socket, 10) != 0)
+	if (listen(jdata->socket, 10) != 0)
 	{
 		purple_debug_error("bonjour", "Cannot listen on socket: %s\n", g_strerror(errno));
-		purple_connection_error_reason (data->account->gc,
+		purple_connection_error_reason (jdata->account->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Could not listen on socket"));
 		return -1;
@@ -657,18 +710,18 @@
 
 #if 0
 	/* TODO: Why isn't this being used? */
-	data->socket = purple_network_listen(data->port, SOCK_STREAM);
+	data->socket = purple_network_listen(jdata->port, SOCK_STREAM);
 
-	if (data->socket == -1)
+	if (jdata->socket == -1)
 	{
 		purple_debug_error("bonjour", "No se ha podido crear el socket\n");
 	}
 #endif
 
 	/* Open a watcher in the socket we have just opened */
-	data->watcher_id = purple_input_add(data->socket, PURPLE_INPUT_READ, _server_socket_handler, data);
+	jdata->watcher_id = purple_input_add(jdata->socket, PURPLE_INPUT_READ, _server_socket_handler, jdata);
 
-	return data->port;
+	return jdata->port;
 }
 
 static void
@@ -683,7 +736,7 @@
 		PurpleConversation *conv;
 
 		purple_debug_error("bonjour", "Error connecting to buddy %s at %s:%d error: %s\n",
-				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, error ? error : "(null)");
+				   purple_buddy_get_name(pb), bb->conversation->ip, bb->port_p2pj, error ? error : "(null)");
 
 		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
 		if (conv != NULL)
@@ -696,12 +749,12 @@
 		return;
 	}
 
-	if (!bonjour_jabber_stream_init(pb, source)) {
+	if (!bonjour_jabber_send_stream_init(bb->conversation, source)) {
 		const char *err = g_strerror(errno);
 		PurpleConversation *conv;
 
 		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
-				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+				   purple_buddy_get_name(pb), bb->conversation->ip, bb->port_p2pj, err ? err : "(null)");
 
 		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
 		if (conv != NULL)
@@ -714,18 +767,120 @@
 		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, bb->conversation);
+}
+
+void
+bonjour_jabber_conv_match_by_name(BonjourJabberConversation *bconv) {
+	PurpleBuddy *pb;
+
+	g_return_if_fail(bconv->ip != NULL);
+	g_return_if_fail(bconv->pb == NULL);
+
+	pb = purple_find_buddy(bconv->account, bconv->buddy_name);
+	if (pb && pb->proto_data) {
+		BonjourBuddy *bb = pb->proto_data;
+		const char *ip;
+		GSList *tmp = bb->ips;
+
+		purple_debug_info("bonjour", "Found buddy %s for incoming conversation \"from\" attrib.\n",
+			purple_buddy_get_name(pb));
+
+		/* Check that one of the buddy's IPs matches */
+		while(tmp) {
+			ip = tmp->data;
+			if (ip != NULL && g_ascii_strcasecmp(ip, bconv->ip) == 0) {
+				BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data;
+
+				purple_debug_info("bonjour", "Matched buddy %s to incoming conversation \"from\" attrib and IP (%s)\n",
+					purple_buddy_get_name(pb), bconv->ip);
+
+				/* Attach conv. to buddy and remove from pending list */
+				jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv);
+
+				/* Check if the buddy already has a conversation and, if so, replace it */
+				if(bb->conversation != NULL && bb->conversation != bconv)
+					bonjour_jabber_close_conversation(bb->conversation);
+
+				bconv->pb = pb;
+				bb->conversation = bconv;
+
+				break;
+			}
+			tmp = tmp->next;
+		}
+	}
+
+	/* We've failed to match a buddy - give up */
+	if (bconv->pb == NULL) {
+		/* This must be asynchronous because it destroys the parser and we
+		 * may be in the middle of parsing.
+		 */
+		async_bonjour_jabber_close_conversation(bconv);
+	}
+}
+
+
+void
+bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv) {
+	BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data;
+	struct _match_buddies_by_address_t *mbba;
+
+	mbba = g_new0(struct _match_buddies_by_address_t, 1);
+	mbba->address = bconv->ip;
+	mbba->jdata = jdata;
+	g_hash_table_foreach(purple_get_blist()->buddies, _match_buddies_by_address, mbba);
+
+	/* If there is exactly one match, use it */
+	if(mbba->matched_buddies != NULL) {
+		if(mbba->matched_buddies->next != NULL)
+			purple_debug_error("bonjour", "More than one buddy matched for ip %s.\n", bconv->ip);
+		else {
+			PurpleBuddy *pb = mbba->matched_buddies->data;
+			BonjourBuddy *bb = pb->proto_data;
+
+			purple_debug_info("bonjour", "Matched buddy %s to incoming conversation using IP (%s)\n",
+				purple_buddy_get_name(pb), bconv->ip);
+
+			/* Attach conv. to buddy and remove from pending list */
+			jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv);
+
+			/* Check if the buddy already has a conversation and, if so, replace it */
+			if (bb->conversation != NULL && bb->conversation != bconv)
+				bonjour_jabber_close_conversation(bb->conversation);
+
+			bconv->pb = pb;
+			bb->conversation = bconv;
+		}
+	} else
+		purple_debug_error("bonjour", "No buddies matched for ip %s.\n", bconv->ip);
+
+	/* We've failed to match a buddy - give up */
+	if (bconv->pb == NULL) {
+		/* This must be asynchronous because it destroys the parser and we
+		 * may be in the middle of parsing.
+		 */
+		async_bonjour_jabber_close_conversation(bconv);
+	}
+
+	g_slist_free(mbba->matched_buddies);
+	g_free(mbba);
 }
 
 static PurpleBuddy *
-_find_or_start_conversation(BonjourJabber *data, const gchar *to)
+_find_or_start_conversation(BonjourJabber *jdata, const gchar *to)
 {
 	PurpleBuddy *pb = NULL;
 	BonjourBuddy *bb = NULL;
 
-	g_return_val_if_fail(data != NULL, NULL);
+	g_return_val_if_fail(jdata != NULL, NULL);
 	g_return_val_if_fail(to != NULL, NULL);
 
-	pb = purple_find_buddy(data->account, to);
+	pb = purple_find_buddy(jdata->account, to);
 	if (pb == NULL || pb->proto_data == NULL)
 		/* You can not send a message to an offline buddy */
 		return NULL;
@@ -737,27 +892,29 @@
 	{
 		PurpleProxyConnectData *connect_data;
 		PurpleProxyInfo *proxy_info;
+		/* For better or worse, use the first IP*/
+		const char *ip = bb->ips->data;
 
 		purple_debug_info("bonjour", "Starting conversation with %s\n", to);
 
 		/* Make sure that the account always has a proxy of "none".
 		 * This is kind of dirty, but proxy_connect_none() isn't exposed. */
-		proxy_info = purple_account_get_proxy_info(data->account);
+		proxy_info = purple_account_get_proxy_info(jdata->account);
 		if (proxy_info == NULL) {
 			proxy_info = purple_proxy_info_new();
-			purple_account_set_proxy_info(data->account, proxy_info);
+			purple_account_set_proxy_info(jdata->account, proxy_info);
 		}
 		purple_proxy_info_set_type(proxy_info, PURPLE_PROXY_NONE);
 
-		connect_data = purple_proxy_connect(data->account->gc, data->account,
-						    bb->ip, bb->port_p2pj, _connected_to_buddy, pb);
+		connect_data = purple_proxy_connect(NULL, jdata->account,
+						    ip, bb->port_p2pj, _connected_to_buddy, pb);
 
 		if (connect_data == NULL) {
 			purple_debug_error("bonjour", "Unable to connect to buddy (%s).\n", to);
 			return NULL;
 		}
 
-		bb->conversation = bonjour_jabber_conv_new(pb);
+		bb->conversation = bonjour_jabber_conv_new(pb, jdata->account, ip);
 		bb->conversation->connect_data = connect_data;
 		/* We don't want _send_data() to register the tx_handler;
 		 * that neeeds to wait until we're actually connected. */
@@ -767,7 +924,7 @@
 }
 
 int
-bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body)
+bonjour_jabber_send_message(BonjourJabber *jdata, const gchar *to, const gchar *body)
 {
 	xmlnode *message_node, *node, *node2;
 	gchar *message;
@@ -775,7 +932,7 @@
 	BonjourBuddy *bb;
 	int ret;
 
-	pb = _find_or_start_conversation(data, to);
+	pb = _find_or_start_conversation(jdata, to);
 	if (pb == NULL) {
 		purple_debug_info("bonjour", "Can't send a message to an offline buddy (%s).\n", to);
 		/* You can not send a message to an offline buddy */
@@ -786,7 +943,7 @@
 
 	message_node = xmlnode_new("message");
 	xmlnode_set_attrib(message_node, "to", bb->name);
-	xmlnode_set_attrib(message_node, "from", purple_account_get_username(data->account));
+	xmlnode_set_attrib(message_node, "from", purple_account_get_username(jdata->account));
 	xmlnode_set_attrib(message_node, "type", "chat");
 
 	/* Enclose the message from the UI within a "font" node */
@@ -818,32 +975,63 @@
 	return ret;
 }
 
+static gboolean
+_async_bonjour_jabber_close_conversation_cb(gpointer data) {
+	BonjourJabberConversation *bconv = data;
+	bonjour_jabber_close_conversation(bconv);
+	return FALSE;
+}
+
+void
+async_bonjour_jabber_close_conversation(BonjourJabberConversation *bconv) {
+	BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data;
+
+	jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv);
+
+	/* Disconnect this conv. from the buddy here so it can't be disposed of twice.*/
+	if(bconv->pb != NULL) {
+		BonjourBuddy *bb = bconv->pb->proto_data;
+		if (bb->conversation == bconv)
+			bb->conversation = NULL;
+	}
+
+	purple_timeout_add(0, _async_bonjour_jabber_close_conversation_cb, bconv);
+}
+
 void
 bonjour_jabber_close_conversation(BonjourJabberConversation *bconv)
 {
 	if (bconv != NULL) {
-		GList *xfers, *tmp_next;
-		BonjourData *bd = bconv->pb->account->gc->proto_data;
+		BonjourData *bd = NULL;
+
+		if(PURPLE_CONNECTION_IS_VALID(bconv->account->gc)) {
+			bd = bconv->account->gc->proto_data;
+			bd->jabber_data->pending_conversations = g_slist_remove(bd->jabber_data->pending_conversations, bconv);
+		}
 
 		/* Cancel any file transfers that are waiting to begin */
-		xfers = bd->xfer_lists;
-		while(xfers != NULL) {
-			PurpleXfer *xfer = xfers->data;
-			tmp_next = xfers->next;
-			/* We only need to cancel this if it hasn't actually started transferring. */
-			/* This will change if we ever support IBB transfers. */
-			if (strcmp(xfer->who, bconv->pb->name) == 0
-					&& (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED
-					    || purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN)) {
-				purple_xfer_cancel_remote(xfer);
+		/* There wont be any transfers if it hasn't been attached to a buddy */
+		if (bconv->pb != NULL && bd != NULL) {
+			GSList *xfers, *tmp_next;
+			xfers = bd->xfer_lists;
+			while(xfers != NULL) {
+				PurpleXfer *xfer = xfers->data;
+				tmp_next = xfers->next;
+				/* We only need to cancel this if it hasn't actually started transferring. */
+				/* This will change if we ever support IBB transfers. */
+				if (strcmp(xfer->who, bconv->pb->name) == 0
+						&& (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED
+							|| purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN)) {
+					purple_xfer_cancel_remote(xfer);
+				}
+				xfers = tmp_next;
 			}
-			xfers = tmp_next;
 		}
 
 		/* Close the socket and remove the watcher */
 		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);
@@ -866,25 +1054,26 @@
 		if (bconv->context != NULL)
 			bonjour_parser_setup(bconv);
 
+		g_free(bconv->buddy_name);
+		g_free(bconv->ip);
 		g_free(bconv);
 	}
 }
 
 void
-bonjour_jabber_stop(BonjourJabber *data)
+bonjour_jabber_stop(BonjourJabber *jdata)
 {
 	/* Close the server socket and remove the watcher */
-	if (data->socket >= 0)
-		close(data->socket);
-	if (data->watcher_id > 0)
-		purple_input_remove(data->watcher_id);
+	if (jdata->socket >= 0)
+		close(jdata->socket);
+	if (jdata->watcher_id > 0)
+		purple_input_remove(jdata->watcher_id);
 
 	/* Close all the conversation sockets and remove all the watchers after sending end streams */
-	if (data->account->gc != NULL)
-	{
+	if (jdata->account->gc != NULL) {
 		GSList *buddies, *l;
 
-		buddies = purple_find_buddies(data->account, purple_account_get_username(data->account));
+		buddies = purple_find_buddies(jdata->account, NULL);
 		for (l = buddies; l; l = l->next) {
 			BonjourBuddy *bb = ((PurpleBuddy*) l->data)->proto_data;
 			bonjour_jabber_close_conversation(bb->conversation);
@@ -893,6 +1082,11 @@
 
 		g_slist_free(buddies);
 	}
+
+	while (jdata->pending_conversations != NULL) {
+		bonjour_jabber_close_conversation(jdata->pending_conversations->data);
+		jdata->pending_conversations = g_slist_delete_link(jdata->pending_conversations, jdata->pending_conversations);
+	}
 }
 
 XepIq *
@@ -989,7 +1183,7 @@
 	PurpleBuddy *pb = NULL;
 
 	/* start the talk, reuse the message socket  */
-	pb = _find_or_start_conversation ((BonjourJabber*)iq->data, iq->to);
+	pb = _find_or_start_conversation((BonjourJabber*) iq->data, iq->to);
 	/* Send the message */
 	if (pb != NULL) {
 		/* Convert xml node into stream */
--- a/libpurple/protocols/bonjour/jabber.h	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.h	Tue Dec 18 05:50:43 2007 +0000
@@ -38,7 +38,8 @@
 	gint port;
 	gint socket;
 	gint watcher_id;
-	PurpleAccount* account;
+	PurpleAccount *account;
+	GSList *pending_conversations;
 } BonjourJabber;
 
 typedef struct _BonjourJabberConversation
@@ -47,13 +48,18 @@
 	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;
 	xmlParserCtxt *context;
 	xmlnode *current;
 	PurpleBuddy *pb;
+	PurpleAccount *account;
+
+	/* The following are only needed before attaching to a PurpleBuddy */
+	gchar *buddy_name;
+	gchar *ip;
 } BonjourJabberConversation;
 
 /**
@@ -68,14 +74,20 @@
 
 void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv);
 
-void bonjour_jabber_stream_started(PurpleBuddy *pb);
+void async_bonjour_jabber_close_conversation(BonjourJabberConversation *bconv);
 
-void bonjour_jabber_stream_ended(PurpleBuddy *pb);
+void bonjour_jabber_stream_started(BonjourJabberConversation *bconv);
+
+void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv);
 
 void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet);
 
 void bonjour_jabber_stop(BonjourJabber *data);
 
+void bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv);
+
+void bonjour_jabber_conv_match_by_name(BonjourJabberConversation *bconv);
+
 typedef enum {
 	XEP_IQ_SET,
 	XEP_IQ_GET,
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Tue Dec 18 05:50:43 2007 +0000
@@ -28,17 +28,7 @@
 #include "dnsquery.h"
 #include "mdns_common.h"
 
-
-/* data structure for the resolve callback */
-typedef struct _ResolveCallbackArgs {
-	DNSServiceRef resolver;
-	guint resolver_handler;
-	gchar *full_service_name;
-
-	PurpleDnsQueryData *query;
-
-	BonjourBuddy* buddy;
-} ResolveCallbackArgs;
+static GSList *pending_buddies = NULL;
 
 /* data used by win32 bonjour implementation */
 typedef struct _win32_session_impl_data {
@@ -50,20 +40,69 @@
 	guint browser_handler;
 } Win32SessionImplData;
 
-typedef struct _win32_buddy_impl_data {
+typedef struct _win32_buddy_service_resolver_data {
 	DNSServiceRef txt_query;
 	guint txt_query_handler;
+	uint32_t if_idx;
+	gchar *name;
+	gchar *type;
+	gchar *domain;
+	/* This is a reference to the entry in BonjourBuddy->ips */
+	const char *ip;
+} Win32SvcResolverData;
+
+typedef struct _win32_buddy_impl_data {
+	GSList *resolvers;
 	DNSServiceRef null_query;
 	guint null_query_handler;
 } Win32BuddyImplData;
 
+/* data structure for the resolve callback */
+typedef struct _ResolveCallbackArgs {
+	DNSServiceRef resolver;
+	guint resolver_handler;
+	PurpleAccount *account;
+	BonjourBuddy *bb;
+	Win32SvcResolverData *res_data;
+	gchar *full_service_name;
+	PurpleDnsQueryData *query;
+} ResolveCallbackArgs;
+
+static gint
+_find_resolver_data(gconstpointer a, gconstpointer b) {
+	const Win32SvcResolverData *rd_a = a;
+	const Win32SvcResolverData *rd_b = b;
+	gint ret = 1;
+
+	if(rd_a->if_idx == rd_b->if_idx
+			&& !strcmp(rd_a->name, rd_b->name)
+			&& !strcmp(rd_a->type, rd_b->type)
+			&& !strcmp(rd_a->domain, rd_b->domain)) {
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static void
+_cleanup_resolver_data(Win32SvcResolverData *rd) {
+	if (rd->txt_query != NULL) {
+		purple_input_remove(rd->txt_query_handler);
+		DNSServiceRefDeallocate(rd->txt_query);
+	}
+	g_free(rd->name);
+	g_free(rd->type);
+	g_free(rd->domain);
+	g_free(rd);
+}
+
 static void
 _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) {
 	DNSServiceProcessResult((DNSServiceRef) data);
 }
 
 static void
-_mdns_parse_text_record(BonjourBuddy* buddy, const char* record, uint16_t record_len)
+_mdns_parse_text_record(BonjourBuddy *buddy, const char *record, uint16_t record_len)
 {
 	const char *txt_entry;
 	uint8_t txt_len;
@@ -114,35 +153,44 @@
 static void
 _mdns_resolve_host_callback(GSList *hosts, gpointer data, const char *error_message)
 {
-	ResolveCallbackArgs* args = (ResolveCallbackArgs*)data;
-	BonjourBuddy* bb = args->buddy;
+	ResolveCallbackArgs* args = (ResolveCallbackArgs*) data;
+	Win32BuddyImplData *idata = args->bb->mdns_impl_data;
+	gboolean delete_buddy = FALSE;
+	PurpleBuddy *pb;
+
+	if ((pb = purple_find_buddy(args->account, args->bb->name)))
+		if (pb->proto_data != args->bb)
+			purple_debug_error("bonjour", "Found purple buddy for %s not matching bonjour buddy record. "
+				"This is going to be ugly!.\n", args->bb->name);
 
 	if (!hosts || !hosts->data) {
 		purple_debug_error("bonjour", "host resolution - callback error.\n");
-		bonjour_buddy_delete(bb);
+		delete_buddy = TRUE;
 	} else {
-		struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1);
-		Win32BuddyImplData *idata = bb->mdns_impl_data;
-
-		g_return_if_fail(idata != NULL);
-
-		g_free(bb->ip);
-		bb->ip = g_strdup(inet_ntoa(addr->sin_addr));
+		struct sockaddr_in *addr = g_slist_nth_data(hosts, 1);
 
 		/* finally, set up the continuous txt record watcher, and add the buddy to purple */
 
-		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, kDNSServiceFlagsLongLivedQuery,
+		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&args->res_data->txt_query, kDNSServiceFlagsLongLivedQuery,
 				kDNSServiceInterfaceIndexAny, args->full_service_name, kDNSServiceType_TXT,
-				kDNSServiceClass_IN, _mdns_record_query_callback, bb)) {
+				kDNSServiceClass_IN, _mdns_record_query_callback, args->bb)) {
 
-			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", bb->name, bb->ip, bb->port_p2pj);
+			const char *ip = inet_ntoa(addr->sin_addr);
+
+			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", args->bb->name, ip, args->bb->port_p2pj);
+
 
-			idata->txt_query_handler = purple_input_add(DNSServiceRefSockFD(idata->txt_query),
-				PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query);
+			args->bb->ips = g_slist_prepend(args->bb->ips, g_strdup(ip));
+			args->res_data->ip = args->bb->ips->data;
+
+			args->res_data->txt_query_handler = purple_input_add(DNSServiceRefSockFD(args->res_data->txt_query),
+				PURPLE_INPUT_READ, _mdns_handle_event, args->res_data->txt_query);
 
-			bonjour_buddy_add_to_purple(bb, NULL);
-		} else
-			bonjour_buddy_delete(bb);
+			bonjour_buddy_add_to_purple(args->bb, NULL);
+		} else {
+			purple_debug_error("bonjour", "Unable to set up record watcher for buddy %s\n", args->bb->name);
+			delete_buddy = TRUE;
+		}
 
 	}
 
@@ -153,6 +201,26 @@
 		hosts = g_slist_remove(hosts, hosts->data);
 	}
 
+	if (delete_buddy) {
+		idata->resolvers = g_slist_remove(idata->resolvers, args->res_data);
+		_cleanup_resolver_data(args->res_data);
+
+		/* If this was the last resolver, remove the buddy */
+		if (idata->resolvers == NULL) {
+			if (pb) {
+				purple_account_remove_buddy(args->account, pb, NULL);
+				purple_blist_remove_buddy(pb);
+			} else
+				bonjour_buddy_delete(args->bb);
+
+			/* Remove from the pending list */
+			pending_buddies = g_slist_remove(pending_buddies, args->bb);
+		}
+	} else {
+		/* Remove from the pending list */
+		pending_buddies = g_slist_remove(pending_buddies, args->bb);
+	}
+
 	/* free the remaining args memory */
 	g_free(args->full_service_name);
 	g_free(args);
@@ -163,37 +231,54 @@
     const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context)
 {
 	ResolveCallbackArgs *args = (ResolveCallbackArgs*)context;
+	Win32BuddyImplData *idata = args->bb->mdns_impl_data;
 
 	/* remove the input fd and destroy the service ref */
 	purple_input_remove(args->resolver_handler);
+	args->resolver_handler = 0;
 	DNSServiceRefDeallocate(args->resolver);
+	args->resolver = NULL;
 
 	if (kDNSServiceErr_NoError != errorCode)
-	{
 		purple_debug_error("bonjour", "service resolver - callback error.\n");
-		bonjour_buddy_delete(args->buddy);
-		g_free(args);
-	}
-	else
-	{
-		args->buddy->port_p2pj = ntohs(port);
+	else {
+		/* set more arguments, and start the host resolver */
+
+		if ((args->query =
+				purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args)) != NULL) {
+
+			args->full_service_name = g_strdup(fullname);
+
+			/* TODO: Should this be per resolver? */
+			args->bb->port_p2pj = ntohs(port);
 
-		/* parse the text record */
-		_mdns_parse_text_record(args->buddy, txtRecord, txtLen);
+			/* We don't want to hit the cleanup code */
+			return;
+		} else
+			purple_debug_error("bonjour", "service resolver - host resolution failed.\n");
+	}
 
-		/* set more arguments, and start the host resolver */
-		args->full_service_name = g_strdup(fullname);
+	/* If we get this far, clean up */
+
+	idata->resolvers = g_slist_remove(idata->resolvers, args->res_data);
+	_cleanup_resolver_data(args->res_data);
 
-		if (!(args->query =
-			purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args)))
-		{
-			purple_debug_error("bonjour", "service resolver - host resolution failed.\n");
-			bonjour_buddy_delete(args->buddy);
-			g_free(args->full_service_name);
-			g_free(args);
+	/* If this was the last resolver, remove the buddy */
+	if (idata->resolvers == NULL) {
+		PurpleBuddy *pb;
+		/* See if this is now attached to a PurpleBuddy */
+		if ((pb = purple_find_buddy(args->account, args->bb->name))) {
+			purple_account_remove_buddy(args->account, pb, NULL);
+			purple_blist_remove_buddy(pb);
+		} else {
+			/* Remove from the pending list */
+			pending_buddies = g_slist_remove(pending_buddies, args->bb);
+			bonjour_buddy_delete(args->bb);
 		}
 	}
 
+	g_free(args);
+
 }
 
 static void DNSSD_API
@@ -212,7 +297,6 @@
     DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context)
 {
 	PurpleAccount *account = (PurpleAccount*)context;
-	PurpleBuddy *pb = NULL;
 
 	if (kDNSServiceErr_NoError != errorCode)
 		purple_debug_error("bonjour", "service browser - callback error\n");
@@ -221,28 +305,118 @@
 		if (purple_utf8_strcasecmp(serviceName, account->username) != 0) {
 			/* OK, lets go ahead and resolve it to add to the buddy list */
 			ResolveCallbackArgs *args = g_new0(ResolveCallbackArgs, 1);
-			args->buddy = bonjour_buddy_new(serviceName, account);
+
+			purple_debug_info("bonjour", "Received new record for '%s' on iface %u (%s, %s)\n",
+							  serviceName, interfaceIndex, regtype ? regtype : "",
+							  replyDomain ? replyDomain : "");
+
+			if (kDNSServiceErr_NoError == DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype,
+					replyDomain, _mdns_service_resolve_callback, args)) {
+				GSList *tmp = pending_buddies;
+				PurpleBuddy *pb;
+				BonjourBuddy* bb = NULL;
+				Win32SvcResolverData *rd;
+				Win32BuddyImplData *idata;
+				gint fd;
+
+				/* Is there an existing buddy? */
+				if ((pb = purple_find_buddy(account, serviceName)))
+					bb = pb->proto_data;
+				/* Is there a pending buddy? */
+				else {
+					while (tmp) {
+						BonjourBuddy *bb_tmp = tmp->data;
+						if (!strcmp(bb_tmp->name, serviceName)) {
+							bb = bb_tmp;
+							break;
+						}
+						tmp = tmp->next;
+					}
+				}
 
-			if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype,
-					replyDomain, _mdns_service_resolve_callback, args)) {
-				bonjour_buddy_delete(args->buddy);
+				if (bb == NULL) {
+					bb = bonjour_buddy_new(serviceName, account);
+
+					/* This is only necessary for the wacky case where someone previously manually added a buddy. */
+					if (pb == NULL)
+						pending_buddies = g_slist_prepend(pending_buddies, bb);
+					else
+						pb->proto_data = bb;
+				}
+
+
+				rd = g_new0(Win32SvcResolverData, 1);
+				rd->if_idx = interfaceIndex;
+				rd->name = g_strdup(serviceName);
+				rd->type = g_strdup(regtype);
+				rd->domain = g_strdup(replyDomain);
+
+				idata = bb->mdns_impl_data;
+				idata->resolvers = g_slist_prepend(idata->resolvers, rd);
+
+				args->bb = bb;
+				args->res_data = rd;
+				args->account = account;
+
+				/* get a file descriptor for this service ref, and add it to the input list */
+				fd = DNSServiceRefSockFD(args->resolver);
+				args->resolver_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver);
+			} else {
+				purple_debug_error("bonjour", "service browser - failed to resolve service.\n");
 				g_free(args);
-				purple_debug_error("bonjour", "service browser - failed to resolve service.\n");
-			} else {
-				/* get a file descriptor for this service ref, and add it to the input list */
-				gint fd = DNSServiceRefSockFD(args->resolver);
-				args->resolver_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver);
 			}
 		}
 	} else {
+		PurpleBuddy *pb = NULL;
+
 		/* A peer has sent a goodbye packet, remove them from the buddy list */
-		purple_debug_info("bonjour", "service browser - remove notification\n");
+		purple_debug_info("bonjour", "Received remove notification for '%s' on iface %u (%s, %s)\n",
+						  serviceName, interfaceIndex, regtype ? regtype : "",
+						  replyDomain ? replyDomain : "");
+
 		pb = purple_find_buddy(account, serviceName);
 		if (pb != NULL) {
-			purple_account_remove_buddy(account, pb, NULL);
-			purple_blist_remove_buddy(pb);
-		} else
+			GSList *l;
+			/* There may be multiple presences, we should only get rid of this one */
+			Win32SvcResolverData *rd_search;
+			BonjourBuddy *bb = pb->proto_data;
+			Win32BuddyImplData *idata;
+
+			g_return_if_fail(bb != NULL);
+
+			idata = bb->mdns_impl_data;
+
+			rd_search = g_new0(Win32SvcResolverData, 1);
+			rd_search->if_idx = interfaceIndex;
+			rd_search->name = (gchar *) serviceName;
+			rd_search->type = (gchar *) regtype;
+			rd_search->domain = (gchar *) replyDomain;
+
+			l = g_slist_find_custom(idata->resolvers, rd_search, _find_resolver_data);
+
+			g_free(rd_search);
+
+			if (l != NULL) {
+				Win32SvcResolverData *rd = l->data;
+				idata->resolvers = g_slist_delete_link(idata->resolvers, l);
+				/* This IP is no longer available */
+				if (rd->ip != NULL) {
+					bb->ips = g_slist_remove(bb->ips, rd->ip);
+					g_free((gchar *) rd->ip);
+				}
+				_cleanup_resolver_data(rd);
+
+				/* If this was the last resolver, remove the buddy */
+				if (idata->resolvers == NULL) {
+					purple_debug_info("bonjour", "Removed last presence for buddy '%s'; removing buddy.\n", serviceName);
+					purple_account_remove_buddy(account, pb, NULL);
+					purple_blist_remove_buddy(pb);
+				}
+			}
+		} else {
 			purple_debug_warning("bonjour", "Unable to find buddy (%s) to remove\n", serviceName ? serviceName : "(null)");
+			/* TODO: Should we look in the pending buddies list? */
+		}
 	}
 }
 
@@ -385,9 +559,10 @@
 
 	g_return_if_fail(idata != NULL);
 
-	if (idata->txt_query != NULL) {
-		purple_input_remove(idata->txt_query_handler);
-		DNSServiceRefDeallocate(idata->txt_query);
+	while (idata->resolvers) {
+		Win32SvcResolverData *rd = idata->resolvers->data;
+		_cleanup_resolver_data(rd);
+		idata->resolvers = g_slist_delete_link(idata->resolvers, idata->resolvers);
 	}
 
 	if (idata->null_query != NULL) {
--- a/libpurple/protocols/bonjour/parser.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/parser.c	Tue Dec 18 05:50:43 2007 +0000
@@ -31,26 +31,59 @@
 #include "util.h"
 #include "xmlnode.h"
 
+static gboolean
+parse_from_attrib_and_find_buddy(BonjourJabberConversation *bconv, int nb_attributes, const xmlChar **attributes) {
+	int i;
+
+	/* If the "from" attribute is specified, attach it to the conversation. */
+	for(i=0; i < nb_attributes * 5; i+=5) {
+		if(!xmlStrcmp(attributes[i], (xmlChar*) "from")) {
+			int len = attributes[i+4] - attributes[i+3];
+			bconv->buddy_name = g_strndup(attributes[i+3], len);
+			bonjour_jabber_conv_match_by_name(bconv);
+
+			return (bconv->pb != NULL);
+		}
+	}
+
+	return FALSE;
+}
+
 static void
 bonjour_parser_element_start_libxml(void *user_data,
 				   const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace,
 				   int nb_namespaces, const xmlChar **namespaces,
 				   int nb_attributes, int nb_defaulted, const xmlChar **attributes)
 {
-	PurpleBuddy *pb = user_data;
-	BonjourBuddy *bb = pb->proto_data;
-	BonjourJabberConversation *bconv = bb->conversation;
+	BonjourJabberConversation *bconv = user_data;
 
 	xmlnode *node;
 	int i;
 
-	if(!element_name) {
-		return;
-	} else if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
-		bconv->recv_stream_start = TRUE;
-		bonjour_jabber_stream_started(pb);
+	g_return_if_fail(element_name != NULL);
+
+	if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
+		if(!bconv->recv_stream_start) {
+			bconv->recv_stream_start = TRUE;
+
+			if (bconv->pb == NULL)
+				parse_from_attrib_and_find_buddy(bconv, nb_attributes, attributes);
+
+			bonjour_jabber_stream_started(bconv);
+		}
 	} else {
 
+		/* If we haven't yet attached a buddy and this isn't "<stream:features />",
+		 * try to get a "from" attribute as a last resort to match our buddy. */
+		if(bconv->pb == NULL
+				&& !(prefix && !xmlStrcmp(prefix, (xmlChar*) "stream")
+					&& !xmlStrcmp(element_name, (xmlChar*) "features"))
+				&& !parse_from_attrib_and_find_buddy(bconv, nb_attributes, attributes))
+			/* We've run out of options for finding who the conversation is from
+			   using explicitly specified stuff; see if we can make a good match
+			   by using the IP */
+			bonjour_jabber_conv_match_by_ip(bconv);
+
 		if(bconv->current)
 			node = xmlnode_new_child(bconv->current, (const char*) element_name);
 		else
@@ -82,27 +115,19 @@
 	}
 }
 
-static gboolean _async_bonjour_jabber_stream_ended_cb(gpointer data) {
-	bonjour_jabber_stream_ended((PurpleBuddy *) data);
-	return FALSE;
-}
-
 static void
 bonjour_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
 				 const xmlChar *prefix, const xmlChar *namespace)
 {
-	PurpleBuddy *pb = user_data;
-	BonjourBuddy *bb = pb->proto_data;
-	BonjourJabberConversation *bconv = bb->conversation;
+	BonjourJabberConversation *bconv = user_data;
 
 	if(!bconv->current) {
 		/* We don't keep a reference to the start stream xmlnode,
 		 * so we have to check for it here to close the conversation */
-		if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
+		if(!xmlStrcmp(element_name, (xmlChar*) "stream"))
 			/* Asynchronously close the conversation to prevent bonjour_parser_setup()
 			 * being called from within this context */
-			purple_timeout_add(0, _async_bonjour_jabber_stream_ended_cb, pb);
-		}
+			async_bonjour_jabber_close_conversation(bconv);
 		return;
 	}
 
@@ -112,7 +137,7 @@
 	} else {
 		xmlnode *packet = bconv->current;
 		bconv->current = NULL;
-		bonjour_jabber_process_packet(pb, packet);
+		bonjour_jabber_process_packet(bconv->pb, packet);
 		xmlnode_free(packet);
 	}
 }
@@ -120,9 +145,7 @@
 static void
 bonjour_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len)
 {
-	PurpleBuddy *pb = user_data;
-	BonjourBuddy *bb = pb->proto_data;
-	BonjourJabberConversation *bconv = bb->conversation;
+	BonjourJabberConversation *bconv = user_data;
 
 	if(!bconv->current)
 		return;
@@ -184,21 +207,17 @@
 }
 
 
-void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len)
+void bonjour_parser_process(BonjourJabberConversation *bconv, const char *buf, int len)
 {
-	BonjourBuddy *bb = pb->proto_data;
 
-	g_return_if_fail(bb != NULL);
-	g_return_if_fail(bb->conversation != NULL);
-
-	if (bb->conversation->context ==  NULL) {
+	if (bconv->context == NULL) {
 		/* libxml inconsistently starts parsing on creating the
 		 * parser, so do a ParseChunk right afterwards to force it. */
-		bb->conversation->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, pb, buf, len, NULL);
-		xmlParseChunk(bb->conversation->context, "", 0, 0);
-	} else if (xmlParseChunk(bb->conversation->context, buf, len, 0) < 0) {
+		bconv->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, bconv, buf, len, NULL);
+		xmlParseChunk(bconv->context, "", 0, 0);
+	} else if (xmlParseChunk(bconv->context, buf, len, 0) < 0)
 		/* TODO: What should we do here - I assume we should display an error or something (maybe just print something to the conv?) */
 		purple_debug_error("bonjour", "Error parsing xml.\n");
-	}
+
 }
 
--- a/libpurple/protocols/bonjour/parser.h	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/bonjour/parser.h	Tue Dec 18 05:50:43 2007 +0000
@@ -28,6 +28,6 @@
 #include "jabber.h"
 
 void bonjour_parser_setup(BonjourJabberConversation *bconv);
-void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len);
+void bonjour_parser_process(BonjourJabberConversation *bconv, const char *buf, int len);
 
 #endif /* _PURPLE_BONJOUR_PARSER_H_ */
--- a/libpurple/protocols/irc/irc.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/irc/irc.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/irc/irc.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/irc/msgs.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/irc/parse.c	Tue Dec 18 05:50:43 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/auth.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/jabber/auth.c	Tue Dec 18 05:50:43 2007 +0000
@@ -330,14 +330,21 @@
 							disallow_plaintext_auth);
 					g_free(msg);
 					return;
-				/* Everything else has failed, so fail the
-				 * connection. Should probably have a better
-				 * error here.
-				 */
+
 				} else {
-					purple_connection_error_reason (js->gc,
-						PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
-						_("Server does not use any supported authentication method"));
+					/* We have no mechs which can work.
+					 * Try falling back on the old jabber:iq:auth method. We get here if the server supports
+					 * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of
+					 * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect
+					 * jabber:iq:auth in this situation.  iChat Server in particular offers SASL GSSAPI by default, which is often
+					 * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails.
+					 *
+					 * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However,
+					 * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms.
+					 * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
+					 * which would connect without issue otherwise. -evands
+					 */
+					jabber_auth_start_old(js);
 					return;
 				}
 				/* not reached */
@@ -563,6 +570,75 @@
 	}
 }
 
+/*!
+ * @brief Given the server challenge (message) and the key (password), calculate the HMAC-MD5 digest
+ *
+ * This is the crammd5 response.  Inspired by cyrus-sasl's _sasl_hmac_md5()
+ */
+static void
+auth_hmac_md5(const char *challenge, size_t challenge_len, const char *key, size_t key_len, guchar *digest)
+{
+	PurpleCipher *cipher;
+	PurpleCipherContext *context;
+	int i;
+	/* inner padding - key XORd with ipad */
+	unsigned char k_ipad[65];    
+	/* outer padding - key XORd with opad */
+	unsigned char k_opad[65];    
+
+	cipher = purple_ciphers_find_cipher("md5");
+
+	/* if key is longer than 64 bytes reset it to key=MD5(key) */
+	if (strlen(key) > 64) {
+		guchar keydigest[16];
+
+		context = purple_cipher_context_new(cipher, NULL);
+		purple_cipher_context_append(context, (const guchar *)key, strlen(key));
+		purple_cipher_context_digest(context, 16, keydigest, NULL);
+		purple_cipher_context_destroy(context);
+
+		key = (char *)keydigest;
+		key_len = 16;
+	} 
+
+	/*
+	 * the HMAC_MD5 transform looks like:
+	 *
+	 * MD5(K XOR opad, MD5(K XOR ipad, text))
+	 *
+	 * where K is an n byte key
+	 * ipad is the byte 0x36 repeated 64 times
+	 * opad is the byte 0x5c repeated 64 times
+	 * and text is the data being protected
+	 */
+
+	/* start out by storing key in pads */
+	memset(k_ipad, '\0', sizeof k_ipad);
+	memset(k_opad, '\0', sizeof k_opad);
+	memcpy(k_ipad, (void *)key, key_len);
+	memcpy(k_opad, (void *)key, key_len);
+
+	/* XOR key with ipad and opad values */
+	for (i=0; i<64; i++) {
+		k_ipad[i] ^= 0x36;
+		k_opad[i] ^= 0x5c;
+	}
+
+	/* perform inner MD5 */
+	context = purple_cipher_context_new(cipher, NULL);
+	purple_cipher_context_append(context, k_ipad, 64); /* start with inner pad */
+	purple_cipher_context_append(context, (const guchar *)challenge, challenge_len); /* then text of datagram */
+	purple_cipher_context_digest(context, 16, digest, NULL); /* finish up 1st pass */
+	purple_cipher_context_destroy(context);
+
+	/* perform outer MD5 */	
+	context = purple_cipher_context_new(cipher, NULL);
+	purple_cipher_context_append(context, k_opad, 64); /* start with outer pad */
+	purple_cipher_context_append(context, digest, 16); /* then results of 1st hash */
+	purple_cipher_context_digest(context, 16, digest, NULL); /* finish up 2nd pass */
+	purple_cipher_context_destroy(context);
+}
+
 static void auth_old_cb(JabberStream *js, xmlnode *packet, gpointer data)
 {
 	JabberIq *iq;
@@ -608,6 +684,33 @@
 			jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
 			jabber_iq_send(iq);
 
+		} else if(js->stream_id && xmlnode_get_child(query, "crammd5")) {
+			const char *challenge;
+			guchar digest[16];
+			char h[17], *p;
+			int i;
+
+			iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
+			query = xmlnode_get_child(iq->node, "query");
+
+			x = xmlnode_new_child(query, "username");
+			xmlnode_insert_data(x, js->user->node, -1);
+			x = xmlnode_new_child(query, "resource");
+			xmlnode_insert_data(x, js->user->resource, -1);
+
+			x = xmlnode_new_child(query, "crammd5");
+			challenge = xmlnode_get_attrib(xmlnode_get_child(query, "crammd5"), "challenge");
+			auth_hmac_md5(challenge, strlen(challenge), pw, strlen(pw), &digest);
+
+			/* Translate the digest to a hexadecimal notation */
+			p = h;
+			for(i=0; i<16; i++, p+=2)
+				snprintf(p, 3, "%02x", digest[i]);
+			xmlnode_insert_data(x, h, -1);
+
+			jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
+			jabber_iq_send(iq);
+
 		} else if(xmlnode_get_child(query, "password")) {
 			if(js->gsc == NULL && !purple_account_get_bool(js->gc->account,
 						"auth_plain_in_clear", FALSE)) {
--- a/libpurple/protocols/jabber/buddy.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Tue Dec 18 05:50:43 2007 +0000
@@ -1153,8 +1153,10 @@
 
 void jabber_vcard_fetch_mine(JabberStream *js)
 {
-	JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, "vcard-temp");
-
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
+	
+	xmlnode *vcard = xmlnode_new_child(iq->node, "vCard");
+	xmlnode_set_namespace(vcard, "vcard-temp");
 	jabber_iq_set_callback(iq, jabber_vcard_save_mine, NULL);
 
 	jabber_iq_send(iq);
--- a/libpurple/protocols/jabber/caps.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/jabber/caps.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Tue Dec 18 05:50:43 2007 +0000
@@ -536,12 +536,13 @@
 	purple_input_remove(js->gc->inpa);
 	js->gc->inpa = 0;
 	js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd,
-			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->serverFQDN, js->gc);
+			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->host, js->gc);
 }
 
 static void jabber_login_connect(JabberStream *js, const char *fqdn, const char *host, int port)
 {
 	js->serverFQDN = g_strdup(fqdn);
+	js->host = g_strdup(host);
 
 	if (purple_proxy_connect(js->gc, js->gc->account, host,
 			port, jabber_login_callback, js->gc) == NULL)
@@ -1279,6 +1280,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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Tue Dec 18 05:50:43 2007 +0000
@@ -191,6 +191,8 @@
 	char *old_uri;
 	int old_length;
 	char *old_track;
+	
+	char *host;
 };
 
 typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
--- a/libpurple/protocols/jabber/libxmpp.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Tue Dec 18 05:50:43 2007 +0000
@@ -53,7 +53,7 @@
 	OPT_PROTO_SLASH_COMMANDS_NATIVE,
 	NULL,							/* user_splits */
 	NULL,							/* protocol_options */
-	{"png", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
+	{"png", 32, 32, 96, 96, 0, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
 	jabber_list_icon,				/* list_icon */
 	jabber_list_emblem,			/* list_emblems */
 	jabber_status_text,				/* status_text */
--- a/libpurple/protocols/jabber/presence.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/jabber/presence.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msn/command.c	Tue Dec 18 05:50:43 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/contact.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msn/contact.c	Tue Dec 18 05:50:43 2007 +0000
@@ -1289,16 +1289,16 @@
 				purple_debug_info("MSNCL", "Adding group %s with guid = %s to the userlist\n", state->new_group_name, guid);
 				msn_group_new(session->userlist, guid, state->new_group_name);
 
-				g_free(guid);
-
 				if (state->action & MSN_ADD_BUDDY) {
 					msn_userlist_add_buddy(session->userlist,
 						state->who,
 						state->new_group_name);
 				} else if (state->action & MSN_MOVE_BUDDY) {
 					msn_add_contact_to_group(session->contact, state, state->who, guid); 
+					g_free(guid);
 					return;
 				}
+				g_free(guid);
 			} else {
 				purple_debug_info("MSNCL", "Adding group %s failed\n",
 					state->new_group_name);
--- a/libpurple/protocols/msn/notification.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msn/notification.c	Tue Dec 18 05:50:43 2007 +0000
@@ -1181,14 +1181,27 @@
 static void
 rea_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	/* TODO: This might be for us too */
-
 	MsnSession *session;
+	PurpleAccount *account;
 	PurpleConnection *gc;
 	const char *friendly;
+	char *username;
 
 	session = cmdproc->session;
-	gc = session->account->gc;
+	account = session->account;
+	username = g_strdup(purple_normalize(account,
+						purple_account_get_username(account)));
+
+	/* Only set display name if our *own* friendly name changed! */
+	if (strcmp(username, purple_normalize(account, cmd->params[2])))
+	{
+		g_free(username);
+		return;
+	}
+
+	g_free(username);
+
+	gc = account->gc;
 	friendly = purple_url_decode(cmd->params[3]);
 
 	purple_connection_set_display_name(gc, friendly);
@@ -1566,6 +1579,7 @@
 {
 	xmlnode * root;
 	gchar * buf;
+	int xmllen;
 
 	g_return_if_fail(cmd->payload != NULL);
 
@@ -1575,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);
@@ -1764,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);
 		}
 	}
@@ -1837,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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msn/oim.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msn/session.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msn/state.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msn/user.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msnp9/directconn.c	Tue Dec 18 05:50:43 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/msn.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msnp9/msn.c	Tue Dec 18 05:50:43 2007 +0000
@@ -1920,7 +1920,7 @@
 		purple_debug_warning("msn", "invalid connection. ignoring buddy photo info.\n");
 		g_free(stripped);
 		g_free(url_buffer);
-		g_free(user_info);
+		purple_notify_user_info_destroy(user_info);
 		g_free(info_data->name);
 		g_free(info_data);
 		g_free(photo_url_text);
--- a/libpurple/protocols/msnp9/nexus.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msnp9/nexus.c	Tue Dec 18 05:50:43 2007 +0000
@@ -338,7 +338,7 @@
 		g_strdup(purple_url_encode(purple_account_get_username(session->account)));
 
 	password =
-		g_strndup(purple_url_encode(purple_connection_get_password(session->account->gc)), 16);
+		g_strdup(purple_url_encode(purple_connection_get_password(session->account->gc)));
 
 	ctint = strtoul((char *)g_hash_table_lookup(nexus->challenge_data, "ct"), NULL, 10) + 200;
 
--- a/libpurple/protocols/msnp9/notification.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msnp9/notification.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msnp9/servconn.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msnp9/session.h	Tue Dec 18 05:50:43 2007 +0000
@@ -114,7 +114,7 @@
 		char *file;
 		char *client_ip;
 		int client_port;
-
+		int verified;
 	} passport_info;
 };
 
--- a/libpurple/protocols/msnp9/user.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/msnp9/user.c	Tue Dec 18 05:50:43 2007 +0000
@@ -126,16 +126,23 @@
 msn_user_set_friendly_name(MsnUser *user, const char *name)
 {
 	MsnCmdProc *cmdproc;
+	MsnSession *session;
+	const char *encoded;
 
 	g_return_if_fail(user != NULL);
 
-	if (user->friendly_name && strcmp(user->friendly_name, name)) {
+	encoded = purple_url_encode(name);
+	session = user->userlist->session;
+
+	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,
-						 purple_url_encode(name));
+						 encoded);
 	}
 
 	g_free(user->friendly_name);
--- a/libpurple/protocols/myspace/myspace.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/myspace/session.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/myspace/session.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/oscar/family_chatnav.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/oscar/family_feedbag.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/oscar/family_icbm.c	Tue Dec 18 05:50:43 2007 +0000
@@ -209,7 +209,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);
@@ -219,8 +218,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;
 }
@@ -288,7 +290,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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Tue Dec 18 05:50:43 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 *, ...);
@@ -1228,7 +1227,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);
@@ -1833,9 +1831,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 */
@@ -3437,6 +3432,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
@@ -3498,32 +3495,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;
@@ -5364,6 +5335,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;
 }
@@ -5380,26 +5352,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);
@@ -6725,7 +6700,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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/qq/header_info.h	Tue Dec 18 05:50:43 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/sametime/sametime.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Tue Dec 18 05:50:43 2007 +0000
@@ -3749,7 +3749,7 @@
 
     client = purple_account_get_int(account, MW_KEY_CLIENT, mwLogin_BINARY);
     major = purple_account_get_int(account, MW_KEY_MAJOR, 0x001e);
-    minor = purple_account_get_int(account, MW_KEY_MINOR, 0x001d);
+    minor = purple_account_get_int(account, MW_KEY_MINOR, 0x196f);
 
     DEBUG_INFO("client id: 0x%04x\n", client);
     DEBUG_INFO("client major: 0x%04x\n", major);
--- a/libpurple/protocols/simple/sipmsg.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/simple/sipmsg.c	Tue Dec 18 05:50:43 2007 +0000
@@ -45,7 +45,10 @@
 	line = g_strndup(msg, tmp - msg);
 
 	smsg = sipmsg_parse_header(line);
-	smsg->body = g_strdup(tmp + 4);
+	if(smsg != NULL)
+		smsg->body = g_strdup(tmp + 4);
+	else
+		purple_debug_error("SIMPLE", "No header parsed from line: %s\n", line);
 
 	g_free(line);
 	return smsg;
--- a/libpurple/protocols/yahoo/util.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/yahoo/util.c	Tue Dec 18 05:50:43 2007 +0000
@@ -715,12 +715,15 @@
 			} else if (((len - i) >= 4) && !strncmp(&src[i], "&gt;", 4)) {
 				g_string_append_c(dest, '>');
 				i += 3;
-			} else if (((len - i) >= 5) && !strncmp(&src[i], "&amp;", 4)) {
+			} else if (((len - i) >= 5) && !strncmp(&src[i], "&amp;", 5)) {
 				g_string_append_c(dest, '&');
 				i += 4;
-			} else if (((len - i) >= 6) && !strncmp(&src[i], "&quot;", 4)) {
+			} else if (((len - i) >= 6) && !strncmp(&src[i], "&quot;", 6)) {
 				g_string_append_c(dest, '"');
 				i += 5;
+			} else if (((len - i) >= 6) && !strncmp(&src[i], "&apos;", 6)) {
+				g_string_append_c(dest, '\'');
+				i += 5;
 			} else {
 				g_string_append_c(dest, src[i]);
 			}
--- a/libpurple/protocols/yahoo/yahoo.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/prpl.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/purple.h.in	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/savedstatuses.c	Tue Dec 18 05:50:43 2007 +0000
@@ -243,7 +243,9 @@
 
 	child = xmlnode_new_child(node, "account");
 	xmlnode_set_attrib(child, "protocol", purple_account_get_protocol_id(substatus->account));
-	xmlnode_insert_data(child, purple_account_get_username(substatus->account), -1);
+	xmlnode_insert_data(child,
+			purple_normalize(substatus->account,
+				purple_account_get_username(substatus->account)), -1);
 
 	child = xmlnode_new_child(node, "state");
 	xmlnode_insert_data(child, purple_status_type_get_id(substatus->type), -1);
--- a/libpurple/status.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/status.c	Tue Dec 18 05:50:43 2007 +0000
@@ -607,13 +607,10 @@
 
 		if (old_status != NULL)
 		{
-			tmp = g_strdup_printf(_("%s changed status from %s to %s"), buddy_alias,
+			tmp = g_strdup_printf(_("%s (%s) changed status from %s to %s"), buddy_alias, buddy->name,
 			                      purple_status_get_name(old_status),
 			                      purple_status_get_name(new_status));
-			logtmp = g_strdup_printf(_("%s (%s) changed status from %s to %s"), buddy_alias, buddy->name,
-			                      purple_status_get_name(old_status),
-			                      purple_status_get_name(new_status));
-
+			logtmp = g_markup_escape_text(tmp, -1);
 		}
 		else
 		{
@@ -621,18 +618,15 @@
 
 			if (purple_status_is_active(new_status))
 			{
-				tmp = g_strdup_printf(_("%s is now %s"), buddy_alias,
+				tmp = g_strdup_printf(_("%s (%s) is now %s"), buddy_alias, buddy->name,
 				                      purple_status_get_name(new_status));
-				logtmp = g_strdup_printf(_("%s (%s) is now %s"), buddy_alias, buddy->name,
-				                      purple_status_get_name(new_status));
-
+				logtmp = g_markup_escape_text(tmp, -1);
 			}
 			else
 			{
-				tmp = g_strdup_printf(_("%s is no longer %s"), buddy_alias,
+				tmp = g_strdup_printf(_("%s (%s) is no longer %s"), buddy_alias, buddy->name,
 				                      purple_status_get_name(new_status));
-				logtmp = g_strdup_printf(_("%s (%s) is no longer %s"), buddy_alias, buddy->name,
-				                      purple_status_get_name(new_status));
+				logtmp = g_markup_escape_text(tmp, -1);
 			}
 		}
 
@@ -1244,12 +1238,15 @@
 
 			if (log != NULL)
 			{
-				char *tmp = g_strdup_printf(_("%s became idle"),
+				char *tmp, *tmp2;
+				tmp = g_strdup_printf(_("%s became idle"),
 				purple_buddy_get_alias(buddy));
+				tmp2 = g_markup_escape_text(tmp, -1);
+				g_free(tmp);
 
 				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
-				purple_buddy_get_alias(buddy), current_time, tmp);
-				g_free(tmp);
+				purple_buddy_get_alias(buddy), current_time, tmp2);
+				g_free(tmp2);
 			}
 		}
 	}
@@ -1261,12 +1258,15 @@
 
 			if (log != NULL)
 			{
-				char *tmp = g_strdup_printf(_("%s became unidle"),
+				char *tmp, *tmp2;
+				tmp = g_strdup_printf(_("%s became unidle"),
 				purple_buddy_get_alias(buddy));
+				tmp2 = g_markup_escape_text(tmp, -1);
+				g_free(tmp);
 
 				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
-				purple_buddy_get_alias(buddy), current_time, tmp);
-				g_free(tmp);
+				purple_buddy_get_alias(buddy), current_time, tmp2);
+				g_free(tmp2);
 			}
 		}
 	}
@@ -1321,13 +1321,15 @@
 
 			if (log != NULL)
 			{
-				char *msg;
+				char *msg, *tmp;
 
 				if (idle)
-					msg = g_strdup_printf(_("+++ %s became idle"), purple_account_get_username(account));
+					tmp = g_strdup_printf(_("+++ %s became idle"), purple_account_get_username(account));
 				else
-					msg = g_strdup_printf(_("+++ %s became unidle"), purple_account_get_username(account));
+					tmp = g_strdup_printf(_("+++ %s became unidle"), purple_account_get_username(account));
 
+				msg = g_markup_escape_text(tmp, -1);
+				g_free(tmp);
 				purple_log_write(log, PURPLE_MESSAGE_SYSTEM,
 				                 purple_account_get_username(account),
 				                 (idle ? idle_time : current_time), msg);
--- a/libpurple/tests/check_libpurple.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/tests/check_libpurple.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/tests/tests.h	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/libpurple/util.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin.spec.in	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/gtkblist.c	Tue Dec 18 05:50:43 2007 +0000
@@ -753,6 +753,11 @@
 	{
 		case GTK_RESPONSE_OK:
 			do_join_chat(info);
+			break;
+
+		case 1:
+			pidgin_roomlist_dialog_show_with_account(info->account);
+			return;
 
 		break;
 	}
@@ -769,6 +774,8 @@
 static void
 joinchat_set_sensitive_if_input_cb(GtkWidget *entry, gpointer user_data)
 {
+	PurplePluginProtocolInfo *prpl_info;
+	PurpleConnection *gc;
 	PidginJoinChatData *data;
 	GList *tmp;
 	const char *text;
@@ -789,6 +796,12 @@
 	}
 
 	gtk_dialog_set_response_sensitive(GTK_DIALOG(data->window), GTK_RESPONSE_OK, sensitive);
+
+	gc = purple_account_get_connection(data->account);
+	prpl_info = (gc != NULL) ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL;
+	sensitive = (prpl_info != NULL && prpl_info->roomlist_get_list != NULL);
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(data->window), 1, sensitive);
 }
 
 static void
@@ -944,6 +957,7 @@
 
 	data->window = gtk_dialog_new_with_buttons(_("Join a Chat"),
 		NULL, GTK_DIALOG_NO_SEPARATOR,
+		_("Room _List"), 1,
 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 		PIDGIN_STOCK_CHAT, GTK_RESPONSE_OK, NULL);
 	gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
@@ -1384,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 && 
@@ -3156,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));
@@ -3168,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
@@ -3237,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)
 		{
@@ -3288,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
@@ -3357,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)
 {
@@ -3367,7 +3417,6 @@
 	PurplePluginProtocolInfo *prpl_info;
 	const char *name = NULL;
 	char *filename, *path;
-	GdkPixbuf *ret;
 	PurplePresence *p;
 
 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
@@ -3380,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) {
@@ -3396,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));
@@ -3432,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);
 }
 
 
@@ -4058,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;
@@ -4067,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);
@@ -4097,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);
@@ -4117,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;
 	}
@@ -4601,6 +4643,13 @@
 }
 
 static void
+clear_elsewhere_errors(PidginMiniDialog *mini_dialog,
+                       gpointer unused)
+{
+	elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error);
+}
+
+static void
 ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist)
 {
 	PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist);
@@ -4615,6 +4664,12 @@
 	pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"),
 		reconnect_elsewhere_accounts, NULL);
 
+	/* Make dismissing the dialog clear the errors.  The "destroy" signal
+	 * does not appear to fire at quit, which is fortunate!
+	 */
+	g_signal_connect(G_OBJECT(mini_dialog), "destroy",
+		(GCallback) clear_elsewhere_errors, NULL);
+
 	add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog));
 
 	/* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */
@@ -5677,7 +5732,6 @@
 static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded)
 {
 	PurpleGroup *group;
-	GdkColor textcolor;
 	gboolean selected;
 	char group_count[12] = "";
 	char *mark, *esc;
@@ -5685,7 +5739,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,
@@ -5700,12 +5753,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;
@@ -5935,7 +5983,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);
@@ -6392,6 +6441,10 @@
 	{
 		add_chat_cb(NULL, data);
 	}
+	else if (resp == 1)
+	{
+		pidgin_roomlist_dialog_show_with_account(data->account);
+	}
 	else
 	{
 		gtk_widget_destroy(data->window);
@@ -6408,6 +6461,8 @@
 static void
 addchat_set_sensitive_if_input_cb(GtkWidget *entry, gpointer user_data)
 {
+	PurplePluginProtocolInfo *prpl_info;
+	PurpleConnection *gc;
 	PidginAddChatData *data;
 	GList *tmp;
 	const char *text;
@@ -6428,6 +6483,12 @@
 	}
 
 	gtk_dialog_set_response_sensitive(GTK_DIALOG(data->window), GTK_RESPONSE_OK, sensitive);
+
+	gc = purple_account_get_connection(data->account);
+	prpl_info = (gc != NULL) ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL;
+	sensitive = (prpl_info != NULL && prpl_info->roomlist_get_list != NULL);
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(data->window), 1, sensitive);
 }
 
 static void
@@ -6593,6 +6654,7 @@
 
 	data->window = gtk_dialog_new_with_buttons(_("Add Chat"),
 		gtkblist ? GTK_WINDOW(gtkblist->window) : NULL, GTK_DIALOG_NO_SEPARATOR,
+		_("Room _List"), 1,
 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 		GTK_STOCK_ADD, GTK_RESPONSE_OK,
 		NULL);
@@ -6676,8 +6738,8 @@
 	pidgin_set_accessible_label (data->group_combo, label);
 	gtk_box_pack_end(GTK_BOX(rowbox), data->group_combo, TRUE, TRUE, 0);
 	
-	data->autojoin = gtk_check_button_new_with_mnemonic(_("Autojoin when account becomes online."));
-	data->persistent = gtk_check_button_new_with_mnemonic(_("Hide chat when the window is closed."));
+	data->autojoin = gtk_check_button_new_with_mnemonic(_("Auto_join when account becomes online."));
+	data->persistent = gtk_check_button_new_with_mnemonic(_("_Hide chat when the window is closed."));
 	gtk_box_pack_start(GTK_BOX(vbox), data->autojoin, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(vbox), data->persistent, FALSE, FALSE, 0);
 
@@ -6870,6 +6932,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);
@@ -6921,6 +6985,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/gtkconn.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/gtkconn.c	Tue Dec 18 05:50:43 2007 +0000
@@ -184,36 +184,39 @@
 
 static void pidgin_connection_network_connected ()
 {
-	GList *list = purple_accounts_get_all_active();
+	GList *list, *l;
 	PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist();
 
 	if(gtkblist)
 		pidgin_status_box_set_network_available(PIDGIN_STATUS_BOX(gtkblist->statusbox), TRUE);
 
-	while (list) {
-		PurpleAccount *account = (PurpleAccount*)list->data;
+	l = list = purple_accounts_get_all_active();
+	while (l) {
+		PurpleAccount *account = (PurpleAccount*)l->data;
 		g_hash_table_remove(auto_reconns, account);
 		if (purple_account_is_disconnected(account))
 			do_signon(account);
-		list = list->next;
+		l = l->next;
 	}
+	g_list_free(list);
 }
 
 static void pidgin_connection_network_disconnected ()
 {
-	GList *l = purple_accounts_get_all_active();
+	GList *list, *l;
 	PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist();
 	PurplePluginProtocolInfo *prpl_info = NULL;
 	PurpleConnection *gc = NULL;
-	
+
 	if(gtkblist)
 		pidgin_status_box_set_network_available(PIDGIN_STATUS_BOX(gtkblist->statusbox), FALSE);
 
+	l = list = purple_accounts_get_all_active();
 	while (l) {
 		PurpleAccount *a = (PurpleAccount*)l->data;
 		if (!purple_account_is_disconnected(a)) {
 			gc = purple_account_get_connection(a);
-			if (gc && gc->prpl) 
+			if (gc && gc->prpl)
 				prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
 			if (prpl_info) {
 				if (prpl_info->keepalive)
@@ -224,6 +227,7 @@
 		}
 		l = l->next;
 	}
+	g_list_free(list);
 }
 
 static void pidgin_connection_notice(PurpleConnection *gc, const char *text)
--- a/pidgin/gtkconv.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/gtkconv.c	Tue Dec 18 05:50:43 2007 +0000
@@ -95,9 +95,10 @@
 
 #define	PIDGIN_CONV_ALL	((1 << 7) - 1)
 
-#define SEND_COLOR "#204a87"
-#define RECV_COLOR "#cc0000"
-#define HIGHLIGHT_COLOR "#AF7F00"
+#define DEFAULT_SEND_COLOR "#204a87"
+#define DEFAULT_RECV_COLOR "#cc0000"
+#define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
+#define DEFAULT_ACTION_COLOR "#062585"
 
 /* Undef this to turn off "custom-smiley" debug messages */
 #define DEBUG_CUSTOM_SMILEY
@@ -125,6 +126,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 */
@@ -1324,7 +1326,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
 	}
 }
 
@@ -2765,6 +2773,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);
@@ -2797,20 +2808,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++;
-			}
 		}
 	}
 
@@ -2853,8 +2859,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);
@@ -3115,7 +3121,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);
@@ -3785,7 +3791,7 @@
 	if (is_me)
 	{
 		GdkColor send_color;
-		gdk_color_parse(SEND_COLOR, &send_color);
+		gdk_color_parse(DEFAULT_SEND_COLOR, &send_color);
 
 #if GTK_CHECK_VERSION(2,6,0)
 		gtk_list_store_insert_with_values(ls, &iter,
@@ -4338,14 +4344,15 @@
 
 	/* Show a maximum of 4 lines */
 	lines = MIN(lines, 4);
-	wrapped_lines = MIN(wrapped_lines, 4);
+	wrapped_lines = MIN(MAX(wrapped_lines, 2), 4);
 
 	pad_top = gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(gtkconv->entry));
 	pad_bottom = gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(gtkconv->entry));
 	pad_inside = gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(gtkconv->entry));
 
 	height = (oneline.height + pad_top + pad_bottom) * lines;
-	height += (oneline.height + pad_inside) * (wrapped_lines - lines);
+	if (wrapped_lines > lines)
+		height += (oneline.height + pad_inside) * (wrapped_lines - lines);
 
 	gtkconv->auto_resize = TRUE;
 	g_idle_add(reset_auto_resize_cb, gtkconv);
@@ -4370,7 +4377,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);
 
@@ -4519,16 +4526,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();
@@ -4947,9 +4963,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))
@@ -5049,7 +5062,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;
@@ -5057,13 +5073,11 @@
 	}
 }
 
-#if 0
 static void
 pidgin_conv_new_hidden(PurpleConversation *conv)
 {
 	private_gtkconv_new(conv, TRUE);
 }
-#endif
 
 void
 pidgin_conv_new(PurpleConversation *conv)
@@ -5079,24 +5093,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;
 	}
 }
 
@@ -5105,9 +5125,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) {
@@ -5586,6 +5603,7 @@
 		}
 		else {
 			if (purple_message_meify(new_message, -1)) {
+				GdkColor *col;
 				str = g_malloc(1024);
 
 				if (flags & PURPLE_MESSAGE_AUTO_RESP) {
@@ -5598,9 +5616,20 @@
 				}
 
 				if (flags & PURPLE_MESSAGE_NICK)
-					strcpy(color, HIGHLIGHT_COLOR);
+					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "highlight-name-color", &col, NULL);
 				else
-					strcpy(color, "#062585");
+					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "action-name-color", &col, NULL);
+
+				if(col) {
+					g_snprintf(color, sizeof(color), "#%02X%02X%02X",
+						col->red >> 8, col->green >> 8, col->blue >> 8);
+				}
+				else {
+					if (flags & PURPLE_MESSAGE_NICK)
+						strcpy(color, DEFAULT_HIGHLIGHT_COLOR);
+					else
+						strcpy(color, DEFAULT_ACTION_COLOR);
+				}
 			}
 			else {
 				str = g_malloc(1024);
@@ -5612,19 +5641,46 @@
 					g_snprintf(str, 1024, "%s:", alias_escaped);
 					tag_end_offset = 1;
 				}
-				if (flags & PURPLE_MESSAGE_NICK)
-					strcpy(color, HIGHLIGHT_COLOR);
+				if (flags & PURPLE_MESSAGE_NICK) {
+					GdkColor *col;
+					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "highlight-name-color", &col, NULL);
+					if(col) {
+						g_snprintf(color, sizeof(color), "#%02X%02X%02X",
+							col->red >> 8, col->green >> 8, col->blue >> 8);
+					}
+					else {
+						strcpy(color, DEFAULT_HIGHLIGHT_COLOR);
+					}
+				}
 				else if (flags & PURPLE_MESSAGE_RECV) {
 					if (type == PURPLE_CONV_TYPE_CHAT) {
 						GdkColor *col = get_nick_color(gtkconv, name);
 
 						g_snprintf(color, sizeof(color), "#%02X%02X%02X",
 							   col->red >> 8, col->green >> 8, col->blue >> 8);
-					} else
-						strcpy(color, RECV_COLOR);
+					} else {
+						GdkColor *col;
+						gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "receive-name-color", &col, NULL);
+						if(col) {
+							g_snprintf(color, sizeof(color), "#%02X%02X%02X",
+								col->red >> 8, col->green >> 8, col->blue >> 8);
+						}
+						else {
+							strcpy(color, DEFAULT_RECV_COLOR);
+						}
+					}
 				}
-				else if (flags & PURPLE_MESSAGE_SEND)
-					strcpy(color, SEND_COLOR);
+				else if (flags & PURPLE_MESSAGE_SEND) {
+					GdkColor *col;
+					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "send-name-color", &col, NULL);
+					if(col) {
+						g_snprintf(color, sizeof(color), "#%02X%02X%02X",
+							col->red >> 8, col->green >> 8, col->blue >> 8);
+					}
+					else {
+						strcpy(color, DEFAULT_SEND_COLOR);
+					}
+				}
 				else {
 					purple_debug_error("gtkconv", "message missing flags\n");
 					strcpy(color, "#000000");
@@ -6449,7 +6505,6 @@
 		AtkObject *accessibility_obj;
 		/* 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);
@@ -6471,8 +6526,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" : "",
@@ -6484,7 +6560,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);
 
@@ -6495,47 +6571,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, -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);
+				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);
@@ -6616,7 +6688,6 @@
 			unseen = PIDGIN_UNSEEN_TEXT;
 
 		conv_set_unseen(conv, unseen);
-		purple_conversation_update(conv, PURPLE_CONV_UPDATE_UNSEEN);
 	}
 }
 
@@ -7121,7 +7192,6 @@
 account_status_changed_cb(PurpleAccount *account, PurpleStatus *oldstatus,
                           PurpleStatus *newstatus)
 {
-#if 0
 	GList *l;
 	PurpleConversation *conv = NULL;
 	PidginConversation *gtkconv;
@@ -7131,7 +7201,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
@@ -7139,25 +7229,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);
 	}
 }
 
@@ -7427,10 +7527,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)
@@ -7438,8 +7541,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);
@@ -7657,17 +7774,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 *"));
 
@@ -7709,6 +7826,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);
 
@@ -7748,6 +7868,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
@@ -8582,7 +8737,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);
 	}
@@ -8593,7 +8748,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)) {
@@ -8609,9 +8764,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));
 	}
 
@@ -8630,10 +8797,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);
@@ -8923,8 +9089,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);
@@ -9050,6 +9214,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);
@@ -9209,7 +9374,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);
 }
 
@@ -9748,7 +9913,9 @@
 gboolean
 pidgin_conv_is_hidden(PidginConversation *gtkconv)
 {
-	return (gtkconv == NULL);
+	g_return_val_if_fail(gtkconv != NULL, FALSE);
+
+	return (gtkconv->win == hidden_convwin);
 }
 
 
@@ -9796,8 +9963,8 @@
 	GdkColor send_color;
 	time_t breakout_time;
 
-	gdk_color_parse(HIGHLIGHT_COLOR, &nick_highlight);
-	gdk_color_parse(SEND_COLOR, &send_color);
+	gdk_color_parse(DEFAULT_HIGHLIGHT_COLOR, &nick_highlight);
+	gdk_color_parse(DEFAULT_SEND_COLOR, &send_color);
 
 	srand(background.red + background.green + background.blue + 1);
 
@@ -9849,5 +10016,3 @@
 
 	return colors;
 }
-
-
--- a/pidgin/gtkdebug.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/gtkdebug.c	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/gtkimhtml.c	Tue Dec 18 05:50:43 2007 +0000
@@ -838,28 +838,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 *
@@ -1419,6 +1397,22 @@
 	                                        _("Hyperlink prelight color"),
 	                                        _("Color to draw hyperlinks when mouse is over them."),
 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("send-name-color",
+	                                        _("Sent Message Name Color"),
+	                                        _("Color to draw the name of a message you sent."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("receive-name-color",
+	                                        _("Received Message Name Color"),
+	                                        _("Color to draw the name of a message you received."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("highlight-name-color",
+	                                        _("\"Attention\" Name Color"),
+	                                        _("Color to draw the name of a message you received containing your name."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("action-name-color",
+	                                        _("Action Message Name Color"),
+	                                        _("Color to draw the name of an action message."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
 
 	binding_set = gtk_binding_set_by_class (parent_class);
 	gtk_binding_entry_add_signal (binding_set, GDK_b, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_BOLD);
@@ -4291,19 +4285,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	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Tue Dec 18 05:50:43 2007 +0000
@@ -1096,8 +1096,8 @@
 		{PIDGIN_STOCK_TOOLBAR_TEXT_SMALLER, do_small, &toolbar->smaller_size, _("Decrease Font Size")},
 		{"", NULL, NULL, NULL},
 		{PIDGIN_STOCK_TOOLBAR_FONT_FACE, toggle_font, &toolbar->font, _("Font Face")},
-		{PIDGIN_STOCK_TOOLBAR_FGCOLOR, toggle_bg_color, &toolbar->bgcolor, _("Background Color")},
-		{PIDGIN_STOCK_TOOLBAR_BGCOLOR, toggle_fg_color, &toolbar->fgcolor, _("Foreground Color")},
+		{PIDGIN_STOCK_TOOLBAR_BGCOLOR, toggle_bg_color, &toolbar->bgcolor, _("Background Color")},
+		{PIDGIN_STOCK_TOOLBAR_FGCOLOR, toggle_fg_color, &toolbar->fgcolor, _("Foreground Color")},
 		{"", NULL, NULL, NULL},
 		{PIDGIN_STOCK_CLEAR, clear_formatting_cb, &toolbar->clear, _("Reset Formatting")},
 		{"", NULL, NULL, NULL},
@@ -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	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/gtkmain.c	Tue Dec 18 05:50:43 2007 +0000
@@ -609,7 +609,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	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/gtkprefs.c	Tue Dec 18 05:50:43 2007 +0000
@@ -1211,6 +1211,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	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/gtkroomlist.c	Tue Dec 18 05:50:43 2007 +0000
@@ -953,8 +953,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	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/gtkutils.c	Tue Dec 18 05:50:43 2007 +0000
@@ -2500,7 +2500,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	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/pidginstock.c	Tue Dec 18 05:50:43 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/pidgin/plugins/convcolors.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/plugins/convcolors.c	Tue Dec 18 05:50:43 2007 +0000
@@ -48,22 +48,27 @@
 #define	PREF_SEND	PREF_PREFIX "/send"
 #define	PREF_SEND_C	PREF_SEND "/color"
 #define	PREF_SEND_F	PREF_SEND "/format"
+#define	PREF_SEND_E	PREF_SEND "/enabled"
 
 #define PREF_RECV	PREF_PREFIX "/recv"
 #define	PREF_RECV_C	PREF_RECV "/color"
 #define	PREF_RECV_F	PREF_RECV "/format"
+#define	PREF_RECV_E	PREF_RECV "/enabled"
 
 #define PREF_SYSTEM	PREF_PREFIX "/system"
 #define	PREF_SYSTEM_C	PREF_SYSTEM "/color"
 #define	PREF_SYSTEM_F	PREF_SYSTEM "/format"
+#define	PREF_SYSTEM_E	PREF_SYSTEM "/enabled"
 
 #define PREF_ERROR	PREF_PREFIX "/error"
 #define	PREF_ERROR_C	PREF_ERROR "/color"
 #define	PREF_ERROR_F	PREF_ERROR "/format"
+#define	PREF_ERROR_E	PREF_ERROR "/enabled"
 
 #define PREF_NICK	PREF_PREFIX "/nick"
 #define	PREF_NICK_C	PREF_NICK "/color"
 #define	PREF_NICK_F	PREF_NICK "/format"
+#define	PREF_NICK_E	PREF_NICK "/enabled"
 
 enum
 {
@@ -104,7 +109,10 @@
 	if (!formats[i].prefix)
 		return FALSE;
 
-	if ((purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM &&
+	g_snprintf(tmp, sizeof(tmp), "%s/enabled", formats[i].prefix);
+
+	if (!purple_prefs_get_bool(tmp) ||
+			(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM &&
 			!purple_prefs_get_bool(PREF_IMS)) ||
 			(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
 			!purple_prefs_get_bool(PREF_CHATS)))
@@ -223,17 +231,29 @@
 }
 
 static void
+toggle_enabled(GtkWidget *widget, gpointer data)
+{
+	const char *prefix = (char *)data;
+	gboolean e;
+	char tmp[128];
+
+	g_snprintf(tmp, sizeof(tmp), "%s/enabled", prefix);
+	e = purple_prefs_get_bool(tmp);
+	purple_prefs_set_bool(tmp, !e);
+}
+
+static void
 toggle_something(const char *prefix, int format)
 {
 	int f;
 	char tmp[128];
-	
+
 	g_snprintf(tmp, sizeof(tmp), "%s/format", prefix);
 	f = purple_prefs_get_int(tmp);
 	f ^= format;
 	purple_prefs_set_int(tmp, f);
 }
-		
+
 static void
 toggle_bold(GtkWidget *widget, gpointer data)
 {
@@ -252,6 +272,22 @@
 	toggle_something(data, FONT_UNDERLINE);
 }
 
+static void
+enable_toggled(const char *name, PurplePrefType type, gconstpointer val, gpointer data)
+{
+	GtkWidget *widget = (GtkWidget *)data;
+
+	gtk_widget_set_sensitive(widget, GPOINTER_TO_INT(val));
+}
+
+static void
+disconnect_prefs_callbacks(GtkObject *object, gpointer data)
+{
+	PurplePlugin *plugin = (PurplePlugin *)data;
+
+	purple_prefs_disconnect_by_handle(plugin);
+}
+
 static GtkWidget *
 get_config_frame(PurplePlugin *plugin)
 {
@@ -265,9 +301,14 @@
 	for (i = 0; formats[i].prefix; i++)
 	{
 		char tmp[128];
+		char tmp2[128];
 		int f;
+		gboolean e;
 		GtkWidget *vbox, *hbox, *button;
 
+		g_snprintf(tmp2, sizeof(tmp2), "%s/enabled", formats[i].prefix);
+		e = purple_prefs_get_bool(tmp2);
+
 		g_snprintf(tmp, sizeof(tmp), "%s/format", formats[i].prefix);
 		f = purple_prefs_get_int(tmp);
 
@@ -278,11 +319,20 @@
 		hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
 
+		button = gtk_check_button_new_with_label(_("Enabled"));
+		gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+		if (e)
+			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+		g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(toggle_enabled),
+				formats[i].prefix);
+
 		button = pidgin_pixbuf_button_from_stock(" Color", GTK_STOCK_SELECT_COLOR,
 				PIDGIN_BUTTON_HORIZONTAL);
 		gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
 		g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(set_color),
 				formats[i].prefix);
+		gtk_widget_set_sensitive(button, e);
+		purple_prefs_connect_callback(plugin, tmp2, enable_toggled, button);
 
 		button = gtk_check_button_new_with_label(_("Bold"));
 		gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
@@ -290,22 +340,29 @@
 			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
 		g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(toggle_bold),
 				formats[i].prefix);
-		
+		gtk_widget_set_sensitive(button, e);
+		purple_prefs_connect_callback(plugin, tmp2, enable_toggled, button);
+
 		button = gtk_check_button_new_with_label(_("Italic"));
 		gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
 		if (f & FONT_ITALIC)
 			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
 		g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(toggle_italic),
 				formats[i].prefix);
-		
+		gtk_widget_set_sensitive(button, e);
+		purple_prefs_connect_callback(plugin, tmp2, enable_toggled, button);
+
 		button = gtk_check_button_new_with_label(_("Underline"));
 		gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
 		if (f & FONT_UNDERLINE)
 			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
 		g_signal_connect(G_OBJECT(button), "clicked",
 				G_CALLBACK(toggle_underline), formats[i].prefix);
+		gtk_widget_set_sensitive(button, e);
+		purple_prefs_connect_callback(plugin, tmp2, enable_toggled, button);
 	}
 
+	g_signal_connect(GTK_OBJECT(ret), "destroy", G_CALLBACK(disconnect_prefs_callbacks), plugin);
 	frame = pidgin_make_frame(ret, _("General"));
 	pidgin_prefs_checkbox(_("Ignore incoming format"), PREF_IGNORE, frame);
 	pidgin_prefs_checkbox(_("Apply in Chats"), PREF_CHATS, frame);
@@ -388,6 +445,12 @@
 	purple_prefs_add_int(PREF_SYSTEM_F, FONT_ITALIC);
 	purple_prefs_add_int(PREF_ERROR_F, FONT_BOLD | FONT_UNDERLINE);
 	purple_prefs_add_int(PREF_NICK_F, FONT_BOLD);
+
+	purple_prefs_add_bool(PREF_SEND_E, TRUE);
+	purple_prefs_add_bool(PREF_RECV_E, TRUE);
+	purple_prefs_add_bool(PREF_SYSTEM_E, TRUE);
+	purple_prefs_add_bool(PREF_ERROR_E, TRUE);
+	purple_prefs_add_bool(PREF_NICK_E, TRUE);
 }
 
 PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
--- a/pidgin/plugins/pidginrc.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/plugins/pidginrc.c	Tue Dec 18 05:50:43 2007 +0000
@@ -30,17 +30,29 @@
 static const gchar *color_prefs[] = {
 	"/plugins/gtk/purplerc/color/GtkWidget::cursor-color",
 	"/plugins/gtk/purplerc/color/GtkWidget::secondary-cursor-color",
-	"/plugins/gtk/purplerc/color/GtkIMHtml::hyperlink-color"
+	"/plugins/gtk/purplerc/color/GtkIMHtml::hyperlink-color",
+	"/plugins/gtk/purplerc/color/GtkIMHtml::send-name-color",
+	"/plugins/gtk/purplerc/color/GtkIMHtml::receive-name-color",
+	"/plugins/gtk/purplerc/color/GtkIMHtml::highlight-name-color",
+	"/plugins/gtk/purplerc/color/GtkIMHtml::action-name-color"
 };
 static const gchar *color_prefs_set[] = {
 	"/plugins/gtk/purplerc/set/color/GtkWidget::cursor-color",
 	"/plugins/gtk/purplerc/set/color/GtkWidget::secondary-cursor-color",
-	"/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-color"
+	"/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-color",
+	"/plugins/gtk/purplerc/set/color/GtkIMHtml::send-name-color",
+	"/plugins/gtk/purplerc/set/color/GtkIMHtml::receive-name-color",
+	"/plugins/gtk/purplerc/set/color/GtkIMHtml::highlight-name-color",
+	"/plugins/gtk/purplerc/set/color/GtkIMHtml::action-name-color"
 };
 static const gchar *color_names[] = {
 	N_("Cursor Color"),
 	N_("Secondary Cursor Color"),
-	N_("Hyperlink Color")
+	N_("Hyperlink Color"),
+	N_("Sent Message Name Color"),
+	N_("Received Message Name Color"),
+	N_("Highlighted Message Name Color"),
+	N_("Action Message Name Color")
 };
 static GtkWidget *color_widgets[G_N_ELEMENTS(color_prefs)];
 
--- a/pidgin/plugins/timestamp.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/plugins/timestamp.c	Tue Dec 18 05:50:43 2007 +0000
@@ -49,11 +49,17 @@
 	const char *mdate;
 	int y, height;
 	GdkRectangle rect;
-	
+
 	/* display timestamp */
 	mdate = purple_utf8_strftime(then == 0 ? "%H:%M" : "\n%H:%M",
 		localtime(&now));
 	gtk_text_buffer_get_end_iter(buffer, &iter);
+
+	if (gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "TIMESTAMP") == NULL)
+		gtk_text_buffer_create_tag(buffer, "TIMESTAMP",
+			"foreground", "#888888", "justification", GTK_JUSTIFY_CENTER,
+			"weight", PANGO_WEIGHT_BOLD, NULL);
+
 	gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, mdate,
 		strlen(mdate), "TIMESTAMP", NULL);
 
@@ -95,17 +101,9 @@
 static void
 timestamp_new_convo(PurpleConversation *conv)
 {
-	PidginConversation *gtk_conv = PIDGIN_CONVERSATION(conv);
-	GtkTextBuffer *buffer;
-
 	if (!g_list_find(purple_get_conversations(), conv))
 		return;
 
-	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtk_conv->imhtml));
-	gtk_text_buffer_create_tag(buffer, "TIMESTAMP",
-		"foreground", "#888888", "justification", GTK_JUSTIFY_CENTER,
-		"weight", PANGO_WEIGHT_BOLD, NULL);
-
 	purple_conversation_set_data(conv, "timestamp-last", GINT_TO_POINTER(0));
 }
 
--- a/pidgin/win32/gtkwin32dep.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/win32/gtkwin32dep.c	Tue Dec 18 05:50:43 2007 +0000
@@ -40,6 +40,7 @@
 
 #include "debug.h"
 #include "notify.h"
+#include "network.h"
 
 #include "resource.h"
 #include "idletrack.h"
@@ -51,6 +52,7 @@
 #include "gtkwin32dep.h"
 #include "win32dep.h"
 #include "gtkconv.h"
+#include "gtkconn.h"
 #include "util.h"
 #include "wspell.h"
 
@@ -64,6 +66,7 @@
 
 typedef BOOL (CALLBACK* LPFNFLASHWINDOWEX)(PFLASHWINFO);
 static LPFNFLASHWINDOWEX MyFlashWindowEx = NULL;
+static gboolean pwm_handles_connections = TRUE;
 
 
 /*
@@ -202,6 +205,43 @@
 #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13)
 #define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14)
 
+static void*
+winpidgin_netconfig_changed_cb(void *data)
+{
+	pwm_handles_connections = FALSE;
+
+	return NULL;
+}
+
+static void*
+winpidgin_get_handle(void)
+{
+	static int handle;
+
+	return &handle;
+}
+
+static gboolean
+winpidgin_pwm_reconnect()
+{
+	purple_signal_disconnect(purple_network_get_handle(), "network-configuration-changed",
+		winpidgin_get_handle(), PURPLE_CALLBACK(winpidgin_netconfig_changed_cb));
+
+	if (pwm_handles_connections == TRUE) {
+		PurpleConnectionUiOps *ui_ops = pidgin_connections_get_ui_ops();
+
+		purple_debug_info("winpidgin", "Resumed from standby, reconnecting accounts.\n");
+
+		if (ui_ops != NULL && ui_ops->network_connected != NULL)
+			ui_ops->network_connected();
+	} else {
+		purple_debug_info("winpidgin", "Resumed from standby, gtkconn will handle reconnecting.\n");
+		pwm_handles_connections = TRUE;
+	}
+
+	return FALSE;
+}
+
 static LRESULT CALLBACK message_window_handler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
 
 	if (msg == PIDGIN_WM_FOCUS_REQUEST) {
@@ -213,6 +253,28 @@
 		purple_debug_info("winpidgin", "Got protocol handler request: %s\n", proto_msg ? proto_msg : "");
 		purple_got_protocol_handler_uri(proto_msg);
 		return TRUE;
+	} else if (msg == WM_POWERBROADCAST) {
+		if (wparam == PBT_APMQUERYSUSPEND) {
+			purple_debug_info("winpidgin", "Windows requesting permission to suspend.\n");
+			return TRUE;
+		} else if (wparam == PBT_APMSUSPEND) {
+			PurpleConnectionUiOps *ui_ops = pidgin_connections_get_ui_ops();
+
+			purple_debug_info("winpidgin", "Entering system standby, disconnecting accounts.\n");
+
+			if (ui_ops != NULL && ui_ops->network_disconnected != NULL)
+				ui_ops->network_disconnected();
+
+			purple_signal_connect(purple_network_get_handle(), "network-configuration-changed", winpidgin_get_handle(),
+				PURPLE_CALLBACK(winpidgin_netconfig_changed_cb), NULL);
+
+			return TRUE;
+		} else if (wparam == PBT_APMRESUMESUSPEND) {
+			purple_debug_info("winpidgin", "Resuming from system standby.\n");
+			/* TODO: It seems like it'd be wise to use the NLA message, if possible, instead of this. */
+			purple_timeout_add_seconds(1, winpidgin_pwm_reconnect, NULL);
+			return TRUE;
+		}
 	}
 
 	return DefWindowProc(hwnd, msg, wparam, lparam);
@@ -242,7 +304,7 @@
 
 	/* Create the window */
 	if(!(win_hwnd = CreateWindow(wname, TEXT("WinpidginMsgWin"), 0, 0, 0, 0, 0,
-			HWND_MESSAGE, NULL, winpidgin_exe_hinstance(), 0))) {
+			NULL, NULL, winpidgin_exe_hinstance(), 0))) {
 		purple_debug_error("winpidgin",
 			"Unable to create message window.\n");
 		return NULL;
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Tue Dec 18 05:50:43 2007 +0000
@@ -1154,11 +1154,21 @@
 !macro RunCheckMacro UN
 Function ${UN}RunCheck
   Push $R0
-  System::Call 'kernel32::OpenMutex(i 2031617, b 0, t "pidgin_is_running") i .R0'
-  IntCmp $R0 0 done
-    MessageBox MB_OK|MB_ICONEXCLAMATION $(PIDGIN_IS_RUNNING) /SD IDOK
+  Push $R1
+
+  IntOp $R1 0 + 0
+  retry_runcheck:
+  ; Close the Handle (needed if we're retrying)
+  IntCmp $R1 0 +2
+    System::Call 'kernel32::CloseHandle(i $R1) i .R1'
+  System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_is_running") i .R1 ?e'
+  Pop $R0
+  IntCmp $R0 0 +3 ;This could check for ERROR_ALREADY_EXISTS(183), but lets just assume
+    MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(PIDGIN_IS_RUNNING) /SD IDCANCEL IDRETRY retry_runcheck
     Abort
+
   done:
+  Pop $R1
   Pop $R0
 FunctionEnd
 !macroend
@@ -1169,10 +1179,16 @@
   Push $R0
   Push $R1
   Push $R2
-  System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_installer_running") i .r1 ?e'
+
+  IntOp $R1 0 + 0
+  retry_runcheck:
+  ; Close the Handle (needed if we're retrying)
+  IntCmp $R1 0 +2
+    System::Call 'kernel32::CloseHandle(i $R1) i .R1'
+  System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_installer_running") i .R1 ?e'
   Pop $R0
-  StrCmp $R0 0 +3
-    MessageBox MB_OK|MB_ICONEXCLAMATION $(INSTALLER_IS_RUNNING) /SD IDOK
+  IntCmp $R0 0 +3 ;This could check for ERROR_ALREADY_EXISTS(183), but lets just assume
+    MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(INSTALLER_IS_RUNNING) /SD IDCANCEL IDRETRY retry_runcheck
     Abort
   Call RunCheck
   StrCpy $name "Pidgin ${PIDGIN_VERSION}"
--- a/pidgin/win32/winpidgin.c	Wed Nov 28 01:54:34 2007 +0000
+++ b/pidgin/win32/winpidgin.c	Tue Dec 18 05:50:43 2007 +0000
@@ -445,11 +445,12 @@
 #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13)
 #define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14)
 
-static BOOL winpidgin_set_running() {
+static BOOL winpidgin_set_running(BOOL fail_if_running) {
 	HANDLE h;
 
 	if ((h = CreateMutex(NULL, FALSE, "pidgin_is_running"))) {
-		if (GetLastError() == ERROR_ALREADY_EXISTS) {
+		DWORD err = GetLastError();
+		if (err == ERROR_ALREADY_EXISTS && fail_if_running) {
 			HWND msg_win;
 
 			printf("An instance of Pidgin is already running.\n");
@@ -465,7 +466,8 @@
 				NULL, MB_OK | MB_TOPMOST);
 
 			return FALSE;
-		}
+		} else
+			printf("Error (%d) accessing \"pidgin_is_running\" mutex.\n", err);
 	}
 	return TRUE;
 }
@@ -628,8 +630,8 @@
 
 	winpidgin_set_locale();
 	/* If help, version or multiple flag used, do not check Mutex */
-	if (!strstr(lpszCmdLine, "-h") && !strstr(lpszCmdLine, "-v") && !strstr(lpszCmdLine, "-m"))
-		if (!getenv("PIDGIN_MULTI_INST") && !winpidgin_set_running())
+	if (!strstr(lpszCmdLine, "-h") && !strstr(lpszCmdLine, "-v"))
+		if (!winpidgin_set_running(getenv("PIDGIN_MULTI_INST") == NULL && strstr(lpszCmdLine, "-m") == NULL))
 			return 0;
 
 	/* Now we are ready for Pidgin .. */
--- a/po/ChangeLog	Wed Nov 28 01:54:34 2007 +0000
+++ b/po/ChangeLog	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/po/POTFILES.in	Tue Dec 18 05:50:43 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	Wed Nov 28 01:54:34 2007 +0000
+++ b/po/de.po	Tue Dec 18 05:50:43 2007 +0000
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-11-27 10:57+0100\n"
-"PO-Revision-Date: 2007-11-27 10:57+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"
@@ -21,6 +21,8 @@
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: KBabel 1.11.4\n"
 
+#. Translators may want to transliterate the name.
+#. It is not to be translated.
 msgid "Finch"
 msgstr "Finch"
 
@@ -2334,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
@@ -5758,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."
 
@@ -6561,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 "
@@ -6699,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."
@@ -9793,6 +9806,9 @@
 msgid "_Merge"
 msgstr "_Zusammenführen"
 
+msgid "Room _List"
+msgstr "Ra_umliste"
+
 msgid ""
 "Please enter the appropriate information about the chat you would like to "
 "join.\n"
@@ -9984,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"
 
@@ -10163,11 +10190,11 @@
 "Bitte geben Sie einen Alias und geeignete Informationen über den Chat ein, "
 "den Sie in Ihre Buddy-Liste aufnehmen wollen.\n"
 
-msgid "Autojoin when account becomes online."
-msgstr "Automatisch beitreten, wenn das Konto online geht."
-
-msgid "Hide chat when the window is closed."
-msgstr "Chat verstecken, wenn das Fenster geschlossen wird."
+msgid "Auto_join when account becomes online."
+msgstr "Automatisch _beitreten, wenn das Konto online geht."
+
+msgid "_Hide chat when the window is closed."
+msgstr "_Chat verstecken, wenn das Fenster geschlossen wird."
 
 msgid "Please enter the name of the group to be added."
 msgstr "Bitte geben Sie den Namen der Gruppe ein, die hinzugefügt werden soll."
@@ -11110,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"
 
@@ -11138,9 +11174,6 @@
 msgid "Insert Link"
 msgstr "Link einfügen"
 
-msgid "_Insert"
-msgstr "_Einfügen"
-
 #, c-format
 msgid "Failed to store image: %s\n"
 msgstr "Speichern des Bildes fehlgeschlagen: %s\n"
@@ -11155,9 +11188,6 @@
 msgid "Smile!"
 msgstr "Lächeln!"
 
-msgid "_Font"
-msgstr "_Schrift"
-
 msgid "Group Items"
 msgstr "Elemente gruppieren"
 
@@ -11624,9 +11654,6 @@
 msgid "Show _formatting on incoming messages"
 msgstr "Zeige _Formatierung bei ankommenden Nachrichten"
 
-msgid "Close IMs immediately when the tab is closed"
-msgstr "Schließe IMs sofort, wenn der Reiter geschlossen wird"
-
 msgid "Show _detailed information"
 msgstr "_Detaillierte Informationen anzeigen"
 
@@ -11691,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"