changeset 21354:fcb848b2d669

merge of '1037fab539d2a95db4fc657f42420566f7e53440' and 'd1b14e76c499bc5d99b77e71539d5ebb0e14b965'
author Richard Laager <rlaager@wiktel.com>
date Fri, 16 Nov 2007 23:32:17 +0000
parents 51cf02dbdb0e (current diff) 8ae227dca885 (diff)
children 665e04562de0
files finch/gntaccount.c finch/gntblist.c finch/gntcertmgr.c finch/gntconn.c finch/gntplugin.c finch/gntpounce.c finch/gntprefs.c finch/gntstatus.c libpurple/account.c libpurple/certificate.c libpurple/conversation.c libpurple/ft.c libpurple/plugins/autoaccept.c libpurple/plugins/buddynote.c libpurple/plugins/idle.c libpurple/plugins/offlinemsg.c libpurple/plugins/perl/common/Request.xs libpurple/protocols/gg/gg.c libpurple/protocols/jabber/auth.c libpurple/protocols/jabber/buddy.c libpurple/protocols/jabber/chat.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/presence.c libpurple/protocols/jabber/si.c libpurple/protocols/jabber/usermood.c libpurple/protocols/jabber/usernick.c libpurple/protocols/jabber/xdata.c libpurple/protocols/msn/dialog.c libpurple/protocols/msn/msn.c libpurple/protocols/novell/novell.c libpurple/protocols/oscar/oscar.c libpurple/protocols/oscar/peer.c libpurple/protocols/qq/buddy_info.c libpurple/protocols/qq/buddy_opt.c libpurple/protocols/qq/group.c libpurple/protocols/qq/group_im.c libpurple/protocols/qq/group_join.c libpurple/protocols/qq/group_opt.c libpurple/protocols/qq/sys_msg.c libpurple/protocols/sametime/sametime.c libpurple/protocols/silc/buddy.c libpurple/protocols/silc/chat.c libpurple/protocols/silc/ops.c libpurple/protocols/silc/pk.c libpurple/protocols/silc/silc.c libpurple/protocols/silc/util.c libpurple/protocols/silc/wb.c libpurple/protocols/silc10/buddy.c libpurple/protocols/silc10/chat.c libpurple/protocols/silc10/ops.c libpurple/protocols/silc10/pk.c libpurple/protocols/silc10/silc.c libpurple/protocols/silc10/util.c libpurple/protocols/silc10/wb.c libpurple/protocols/toc/toc.c libpurple/protocols/yahoo/yahoo.c libpurple/request.c libpurple/request.h pidgin/gtkaccount.c pidgin/gtkblist.c pidgin/gtkcertmgr.c pidgin/gtkconv.c pidgin/gtkdebug.c pidgin/gtkdialogs.c pidgin/gtkimhtmltoolbar.c pidgin/gtklog.c pidgin/gtkplugin.c pidgin/gtkpounce.c pidgin/gtkprefs.c pidgin/gtkprivacy.c pidgin/gtkrequest.c pidgin/gtksavedstatuses.c pidgin/gtkutils.c
diffstat 123 files changed, 2534 insertions(+), 693 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Fri Nov 16 23:30:03 2007 +0000
+++ b/COPYRIGHT	Fri Nov 16 23:32:17 2007 +0000
@@ -196,6 +196,7 @@
 Akuke Kok
 Konstantin Korikov
 Cole Kowalski
+Matt Kramer
 Gary Kramlich
 Jan Kratochvil
 Andrej Krivulčík
@@ -267,6 +268,7 @@
 Ruediger Oertel
 Gudmundur Bjarni Olafsson
 Bartosz Oler
+Stefan Ott
 Shawn Outman
 Nathan Owens (pianocomp81)
 John Oyler
--- a/ChangeLog	Fri Nov 16 23:30:03 2007 +0000
+++ b/ChangeLog	Fri Nov 16 23:32:17 2007 +0000
@@ -1,5 +1,24 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.3.0:
+	http://developer.pidgin.im/query?status=closed&milestone=2.3.0
+
+	libpurple:
+	* We now honor a PURPLE_DISABLE_DEPRECATED define to allow plugins to
+	  catch deprecated functions earlier rather than later.
+
+	Pidgin:
+	* If a plugin says it can't be unloaded, we now display an error and
+	  remove the plugin from the list of saved plugins so it won't load
+	  at the next startup.  Previously, we were ignoring this case, which
+	  could lead to crashes.
+
+	Finch:
+	* If a plugin says it can't be unloaded, we now display an error and
+	  remove the plugin from the list of saved plugins so it won't load
+	  at the next startup.  Previously, we were ignoring this case, which
+	  could lead to crashes.
+
 version 2.2.2:
 	http://developer.pidgin.im/query?status=closed&milestone=2.2.2
 		NOTE: Due to 2.2.1 being a security fix release, some bugs
@@ -31,6 +50,8 @@
 	* Pidgin's display is now saved with the command line for session
 	  restoration.  (David Mohr)
 	* ICQ Birthday notifications are shown as buddy list emblems.
+	* Plugin actions are now available from the docklet context menu
+	  in addition to the Tool menu of the buddy list.
 
 version 2.2.1 (09/29/2007):
 	http://developer.pidgin.im/query?status=closed&milestone=2.2.1
--- a/ChangeLog.API	Fri Nov 16 23:30:03 2007 +0000
+++ b/ChangeLog.API	Fri Nov 16 23:32:17 2007 +0000
@@ -1,6 +1,35 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-Version 2.2.0 (09/13/2007):
+version 2.3.0 (??/??/????):
+	libpurple:
+		Added:
+		* purple_request_field_blist_nodes_new and its accessory functions.
+		* a PurpleConversation field in PurpleConvMessage
+		* account-authorization signals (see account-signals.dox for
+		  details) (Stefan Ott)
+		* libpurple/purple.h, which includes #define's and #include's
+		  required to compile stand-alone plugins
+		* purple_plugin_disable(), which is intended to be called when
+		  a purple_plugin_unload()--which was called when a user tried
+		  to unload a plugin--fails.  This then prevents the plugin
+		  from being saved in the saved plugins list, so it'll won't
+		  be loaded at the next startup.
+
+		Changed:
+		* purple_plugin_unload() now honors the return value of a
+		  plugin's unload function and can actually return FALSE now.
+		* purple_plugin_unload() no longer does its own notifications
+		  when a dependent plugin fails to unload.  The UI should do
+		  something appropriate.
+
+version 2.2.2 (??/??/????):
+	libpurple:
+		Changed:
+		* The size parameter of purple_util_write_data_to_file_absolute
+		  has been changed to gssize instead of a size_t to correctly
+		  indicate that -1 can be used for a nul-delimited string.
+
+version 2.2.0 (09/13/2007):
 	libpurple:
 		Added:
 		* PURPLE_MESSAGE_INVISIBLE flag, which can be used by
@@ -57,7 +86,7 @@
 		* gnt_util_parse_xhtml_to_textview to parse XHTML strings in a
 		  GntTextView (this works only if libxml2 is available)
 
-Version 2.1.1 (08/20/2007):
+version 2.1.1 (08/20/2007):
 	libpurple:
 		Changed:
 		* PurpleAccountUiOps.request_authorize's authorize_cb and
@@ -89,9 +118,6 @@
 		* purple_timeout_add_seconds
 		    Callers should prefer this to purple_timeout_add for timers
 		    longer than 1 second away.  Be aware of the rounding, though.
-		* purple_timeout_add_seconds
-		    Callers should prefer this to purple_timeout_add for timers
-		    longer than 1 second away.  Be aware of the rounding, though.
 		* purple_xfer_get_remote_user
 		* purple_pounces_get_all_for_ui
 		* purple_prefs_get_children_names
--- a/Doxyfile.in	Fri Nov 16 23:30:03 2007 +0000
+++ b/Doxyfile.in	Fri Nov 16 23:32:17 2007 +0000
@@ -169,7 +169,8 @@
                          "endsignalproto=@endcode" \
                          "signaldesc=@par Description:" \
                          "signals=@b Signals:" \
-                         "endsignals="
+                         "endsignals=" \
+                         "constreturn=@note The return value of this function must not be modified or freed. @return"
 
 # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources 
 # only. Doxygen will then generate output that is more tailored for C. 
@@ -457,7 +458,8 @@
 # excluded from the INPUT source files. This way you can easily exclude a 
 # subdirectory from a directory tree whose root is specified with the INPUT tag.
 
-EXCLUDE                = 
+EXCLUDE                = libpurple/purple-client.h \
+                         libpurple/purple-client-bindings.h
 
 # The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories 
 # that are symbolic links (a Unix filesystem feature) are excluded from the input.
@@ -857,7 +859,7 @@
 # feature is still experimental and incomplete at the 
 # moment.
 
-GENERATE_XML           = NO
+GENERATE_XML           = YES
 
 # The XML_OUTPUT tag is used to specify where the XML pages will be put. 
 # If a relative path is entered the value of OUTPUT_DIRECTORY will be 
@@ -1160,7 +1162,7 @@
 # not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). 
 # If 0 is used for the depth value (the default), the graph is not depth-constrained.
 
-MAX_DOT_GRAPH_DEPTH    = 0
+MAX_DOT_GRAPH_DEPTH    = 2
 
 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
 # background. This is disabled by default, which results in a white background. 
--- a/Makefile.am	Fri Nov 16 23:30:03 2007 +0000
+++ b/Makefile.am	Fri Nov 16 23:32:17 2007 +0000
@@ -48,6 +48,13 @@
 if HAVE_DOXYGEN
 	@echo "Running doxygen..."
 	@doxygen
+if HAVE_XSLTPROC
+	@echo "Generating devhelp index..."
+	@xsltproc 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"
+endif
 else
 	@echo "doxygen was not found during configure.  Aborting."
 	@echo;
--- a/configure.ac	Fri Nov 16 23:30:03 2007 +0000
+++ b/configure.ac	Fri Nov 16 23:32:17 2007 +0000
@@ -43,10 +43,10 @@
 #
 # Make sure to update finch/libgnt/configure.ac with libgnt version changes.
 #
-m4_define([purple_lt_current], [2])
+m4_define([purple_lt_current], [3])
 m4_define([purple_major_version], [2])
-m4_define([purple_minor_version], [2])
-m4_define([purple_micro_version], [2])
+m4_define([purple_minor_version], [3])
+m4_define([purple_micro_version], [0])
 m4_define([purple_version_suffix], [])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
@@ -142,13 +142,21 @@
 dnl If we don't have msgfmt, then po/ is going to fail -- ensure that
 dnl AM_GLIB_GNU_GETTEXT found it.
 
-if test x$MSGFMT = xno
+if test x$MSGFMT = xno -o x$MSGFMT$GMSGFMT = x
 then
 	AC_ERROR([
 
 The msgfmt command is required to build libpurple.  If it is installed
 on your system, ensure that it is in your path.  If it is not, install
 GNU gettext to continue.
+
+If you have msgfmt installed, but for some reason this error message
+is still displayed, you have encountered what appears to be a bug in
+third-party configure macros.  Try setting the MSGFMT environment
+variable to the absolute path to your msgfmt binary and trying
+configure again, like this:
+
+MSGFMT=/path/to/msgfmt ./configure ...
 ])
 fi
 
@@ -2093,6 +2101,10 @@
 	[AC_HELP_STRING([--enable-dot],
 		[enable graphs in doxygen via 'dot'])],
 	enable_dot="$enableval", enable_dot="yes")
+AC_ARG_ENABLE(devhelp,
+	[AC_HELP_STRING([--enable-devhelp],
+		[enable building index for devhelp documentation browser])],
+	enable_devhelp="$enableval", enable_devhelp="yes")
 
 if test "x$enable_doxygen" = xyes; then
 	AC_CHECK_PROG(DOXYGEN, doxygen, true, false)
@@ -2112,14 +2124,28 @@
 				AC_DEFINE_UNQUOTED(HAVE_DOT, 1, [whether or not we have dot])
 			fi
 		fi
+
+		if test "x$enable_devhelp" = "xyes"; then
+			AC_CHECK_PROG(XSLTPROC, xsltproc, true, false)
+
+			if test $XSLTPROC = false; then
+				enable_devhelp="no";
+				AC_MSG_WARN([*** xsltproc not found; devhelp index will not be created])
+			else
+				AC_DEFINE_UNQUOTED(HAVE_XSLTPROC, 1, [whether or not we have xsltproc for devhelp index])
+			fi
+		fi
 	fi
 else
 	enable_dot="no"
+	enable_devhelp="no"
 fi
 
 AC_SUBST(enable_doxygen)
 AC_SUBST(enable_dot)
+AC_SUBST(enable_devhelp)
 AM_CONDITIONAL(HAVE_DOXYGEN, test "x$enable_doxygen" = "xyes")
+AM_CONDITIONAL(HAVE_XSLTPROC, test "x$enable_devhelp" = "xyes")
 
 AC_ARG_ENABLE(debug, [AC_HELP_STRING([--enable-debug],
 	[compile with debugging support])], , enable_debug=no)
--- a/doc/account-signals.dox	Fri Nov 16 23:30:03 2007 +0000
+++ b/doc/account-signals.dox	Fri Nov 16 23:32:17 2007 +0000
@@ -9,6 +9,9 @@
   @signal account-setting-info
   @signal account-set-info
   @signal account-status-changed
+  @signal account-authorization-requested
+  @signal account-authorization-denied
+  @signal account-authorization-granted
  @endsignals
 
  @see account.h
@@ -102,5 +105,41 @@
   @param old     The alias before change.
  @endsignaldef
 
+ @signaldef account-authorization-requested
+  @signalproto
+void (*account_authorization_requested)(PurpleAccount *account, const char *user);
+  @endsignalproto
+  @signaldesc
+   Emitted when a user requests authorization.
+  @param account The account.
+  @param user    The name of the user requesting authorization.
+  @return Less than zero to deny the request without prompting, greater
+          than zero if the request should be granted. If zero is returned,
+          then the user will be prompted with the request.
+  @since 2.3.0
+ @endsignaldef
+
+ @signaldef account-authorization-denied
+  @signalproto
+void (*account_authorization_denied)(PurpleAccount *account, const char *user);
+  @endsignalproto
+  @signaldesc
+   Emitted when the authorization request for a buddy is denied.
+  @param account The account.
+  @param user    The name of the user requesting authorization.
+  @since 2.3.0
+ @endsignaldef
+
+ @signaldef account-authorization-granted
+  @signalproto
+void (*account_authorization_granted)(PurpleAccount *account, const char *user);
+  @endsignalproto
+  @signaldesc
+   Emitted when the authorization request for a buddy is granted.
+  @param account The account.
+  @param user    The name of the user requesting authorization.
+  @since 2.3.0
+ @endsignaldef
+
  */
 // vim: syntax=c.doxygen tw=75 et
--- a/doc/conversation-signals.dox	Fri Nov 16 23:30:03 2007 +0000
+++ b/doc/conversation-signals.dox	Fri Nov 16 23:32:17 2007 +0000
@@ -429,6 +429,7 @@
    conversation.
   @param conv   The conversation.
   @param list   A pointer to the list of actions.
+  @since 2.1.0
  @endsignaldef
 */
 // vim: syntax=c.doxygen tw=75 et
--- a/doc/gtkconv-signals.dox	Fri Nov 16 23:30:03 2007 +0000
+++ b/doc/gtkconv-signals.dox	Fri Nov 16 23:32:17 2007 +0000
@@ -127,6 +127,7 @@
   @signaldesc
    Emitted immediately before an existing conversation is hidden.
   @param gtkconv  The PidginConversation
+  @since 2.2.0
  @endsignaldef
 
  @signaldef conversation-displayed
@@ -136,6 +137,7 @@
   @signaldesc
    Emitted right after the Pidgin UI is attached to a new or a hidden conversation.
   @param gtkconv  The PidginConversation
+  @since 2.2.0
  @endsignaldef
 
 */
--- a/doc/notify-signals.dox	Fri Nov 16 23:30:03 2007 +0000
+++ b/doc/notify-signals.dox	Fri Nov 16 23:32:17 2007 +0000
@@ -35,6 +35,7 @@
   @param from      Who the email is from.
   @param to        Who the email is to.
   @param url       A url to view the email.
+  @since 2.1.0
  @endsignaldef
 
  @signaldef displaying-emails-notification
@@ -52,6 +53,7 @@
   @param tos        Who the emails are to.
   @param urls       The urls to view the emails.
   @param count      Number of emails being notified of.
+  @since 2.1.0
  @endsignaldef
 
 */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doxy2devhelp.xsl	Fri Nov 16 23:32:17 2007 +0000
@@ -0,0 +1,98 @@
+<xsl:stylesheet
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+    xmlns:fo="http://www.w3.org/1999/XSL/Format"
+    version="1.0">
+
+<!-- Based on http://bur.st/~eleusis/devhelp/doxy2devhelp.xsl
+             (http://bur.st/~eleusis/devhelp/README)
+     which is based on http://bugzilla.gnome.org/show_bug.cgi?id=122450
+-->
+
+<xsl:output method="xml" version="1.0" indent="yes"/>
+
+<xsl:param name="reference_prefix"></xsl:param>
+
+<xsl:template match="/">
+  <book title="Pidgin Documentation"
+        name="pidgin"
+        link="{$reference_prefix}main.html">
+  <chapters>
+    <sub name="Modules" link="{$reference_prefix}modules.html">
+      <xsl:apply-templates select="doxygenindex/compound[@kind='group']">
+        <xsl:sort select="."/>
+      </xsl:apply-templates>
+    </sub>
+    <!-- annotated.html has the short descriptions beside each struct.  is
+         that more useful than being grouped alphabetically?
+      -->
+    <sub name="Structs" link="{$reference_prefix}classes.html">
+      <xsl:apply-templates select="doxygenindex/compound[@kind='struct']">
+        <xsl:sort select="."/>
+      </xsl:apply-templates>
+    </sub>
+    <!-- This is redundant given Modules -->
+    <!--
+    <sub name="Directories" link="{$reference_prefix}dirs.html">
+      <xsl:apply-templates select="doxygenindex/compound[@kind='dir']">
+        <xsl:sort select="."/>
+      </xsl:apply-templates>
+    </sub>
+    -->
+    <!-- FIXME: Some files show up here but are broken links; mostly
+                files that are under pages...
+      -->
+    <sub name="Files" link="{$reference_prefix}files.html">
+      <xsl:apply-templates select="doxygenindex/compound[@kind='file']">
+        <xsl:sort select="."/>
+      </xsl:apply-templates>
+    </sub>
+    <sub name="Signals, HOWTOs, Other" link="{$reference_prefix}pages.html">
+      <xsl:apply-templates select="doxygenindex/compound[@kind='page']">
+        <xsl:sort select="."/>
+      </xsl:apply-templates>
+    </sub>
+  </chapters>
+
+  <functions>
+    <!-- @todo: maybe select only the real functions, ie those with kind=="function"? -->
+    <xsl:apply-templates select="doxygenindex/compound/member" mode="as-function"/>
+  </functions>
+  </book>
+</xsl:template>
+
+<xsl:template match="compound">
+  <xsl:param name="name"><xsl:value-of select="name"/></xsl:param>
+  <xsl:param name="link"><xsl:value-of select="@refid"/>.html</xsl:param>
+  <sub name="{$name}" link="{$reference_prefix}{$link}">
+  <xsl:apply-templates select="member" mode="as-sub">
+    <xsl:sort select="."/>
+  </xsl:apply-templates>
+  </sub>
+</xsl:template>
+
+<xsl:template match="member" mode="as-function">
+  <!--
+  <function name="atk_set_value" link="atk-atkvalue.html#ATK-SET-VALUE"/>
+  -->
+  <xsl:param name="name"><xsl:value-of select="name"/></xsl:param>
+  <!-- Link is refid attribute of parent element + "#" + diff between refid of parent and own refid -->
+  <xsl:param name="refid_parent"><xsl:value-of select="parent::node()/@refid"/></xsl:param>
+  <xsl:param name="own_refid"><xsl:value-of select="@refid"/></xsl:param>
+  <xsl:param name="offset"><xsl:value-of select="string-length($refid_parent) + 3"/></xsl:param>
+  <xsl:param name="ref_diff"><xsl:value-of select="substring($own_refid, $offset, 33)"/></xsl:param>
+  <xsl:param name="link"><xsl:value-of select="$refid_parent"/>.html#<xsl:value-of select="$ref_diff"/></xsl:param>
+  <function name="{$name}" link="{$reference_prefix}{$link}"/>
+</xsl:template>
+
+<xsl:template match="member" mode="as-sub">
+  <xsl:param name="name"><xsl:value-of select="name"/></xsl:param>
+  <!-- Link is refid attribute of parent element + "#" + diff between refid of parent and own refid -->
+  <xsl:param name="refid_parent"><xsl:value-of select="parent::node()/@refid"/></xsl:param>
+  <xsl:param name="own_refid"><xsl:value-of select="@refid"/></xsl:param>
+  <xsl:param name="offset"><xsl:value-of select="string-length($refid_parent) + 3"/></xsl:param>
+  <xsl:param name="ref_diff"><xsl:value-of select="substring($own_refid, $offset, 33)"/></xsl:param>
+  <xsl:param name="link"><xsl:value-of select="$refid_parent"/>.html#<xsl:value-of select="$ref_diff"/></xsl:param>
+  <sub name="{$name}" link="{$reference_prefix}{$link}"/>
+</xsl:template>
+
+</xsl:stylesheet>
--- a/finch/gntaccount.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/gntaccount.h	Fri Nov 16 23:32:17 2007 +0000
@@ -59,6 +59,8 @@
  * Show the edit dialog for an account.
  *
  * @param account  The account to edit, or @c NULL to create a new account.
+ *
+ * @since 2.2.0
  */
 void finch_account_dialog_show(PurpleAccount *account);
 
--- a/finch/gntblist.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/gntblist.c	Fri Nov 16 23:32:17 2007 +0000
@@ -2328,10 +2328,12 @@
 	gnt_menuitem_set_submenu(item, GNT_MENU(sub));
 
 	item = gnt_menuitem_new(_("Send IM..."));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "send-im");
 	gnt_menu_add_item(GNT_MENU(sub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), send_im_select, NULL);
 
 	item = gnt_menuitem_new(_("Join Chat..."));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "join-chat");
 	gnt_menu_add_item(GNT_MENU(sub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), join_chat_select, NULL);
 
@@ -2341,12 +2343,14 @@
 	gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
 
 	item = gnt_menuitem_check_new(_("Empty groups"));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "show-empty-groups");
 	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
 				purple_prefs_get_bool(PREF_ROOT "/emptygroups"));
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), toggle_pref_cb, PREF_ROOT "/emptygroups");
 	
 	item = gnt_menuitem_check_new(_("Offline buddies"));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "show-offline-buddies");
 	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
 				purple_prefs_get_bool(PREF_ROOT "/showoffline"));
 	gnt_menu_add_item(GNT_MENU(subsub), item);
@@ -2358,14 +2362,17 @@
 	gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
 
 	item = gnt_menuitem_new(_("By Status"));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "sort-status");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), sort_blist_change_cb, "status");
 
 	item = gnt_menuitem_new(_("Alphabetically"));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "sort-alpha");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), sort_blist_change_cb, "text");
 
 	item = gnt_menuitem_new(_("By Log Size"));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "sort-log");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), sort_blist_change_cb, "log");
 
@@ -2376,14 +2383,17 @@
 	gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
 
 	item = gnt_menuitem_new("Buddy");
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-buddy");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_buddy_cb, NULL);
 
 	item = gnt_menuitem_new("Chat");
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-chat");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_chat_cb, NULL);
 
 	item = gnt_menuitem_new("Group");
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-group");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_group_cb, NULL);
 
--- a/finch/gntblist.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/gntblist.h	Fri Nov 16 23:32:17 2007 +0000
@@ -98,6 +98,8 @@
  * @param name   The user to get information about.
  *
  * @return  Returns the ui-handle for the userinfo notification.
+ *
+ * @since 2.1.0
  */
 gpointer finch_retrieve_user_info(PurpleConnection *conn, const char *name);
 
--- a/finch/gntnotify.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/gntnotify.c	Fri Nov 16 23:32:17 2007 +0000
@@ -194,6 +194,7 @@
 	PurpleAccount *account = purple_connection_get_account(gc);
 	GString *message = g_string_new(NULL);
 	void *ret;
+	static int key = 0;
 
 	if (!detailed)
 	{
@@ -212,7 +213,7 @@
 
 		to = g_strdup_printf("%s (%s)", tos ? *tos : purple_account_get_username(account),
 					purple_account_get_protocol_name(account));
-		gnt_tree_add_row_after(GNT_TREE(emaildialog.tree), GINT_TO_POINTER(time(NULL)),
+		gnt_tree_add_row_after(GNT_TREE(emaildialog.tree), GINT_TO_POINTER(++key),
 				gnt_tree_create_row(GNT_TREE(emaildialog.tree), to,
 					froms ? *froms : "[Unknown sender]",
 					*subjects),
@@ -360,7 +361,8 @@
 	i = 0;
 	for (iter = results->columns; iter; iter = iter->next)
 	{
-		gnt_tree_set_column_title(GNT_TREE(tree), i, iter->data);
+		PurpleNotifySearchColumn *column = iter->data;
+		gnt_tree_set_column_title(GNT_TREE(tree), i, column->title);
 		i++;
 	}
 
--- a/finch/gntplugin.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/gntplugin.c	Fri Nov 16 23:32:17 2007 +0000
@@ -83,6 +83,7 @@
 
 		if (!purple_plugin_unload(plugin)) {
 			purple_notify_error(NULL, _("ERROR"), _("unloading plugin failed"), NULL);
+			purple_plugin_disable(plugin);
 			gnt_tree_set_choice(GNT_TREE(tree), plugin, TRUE);
 		}
 
--- a/finch/gntsound.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/gntsound.h	Fri Nov 16 23:32:17 2007 +0000
@@ -37,6 +37,8 @@
  * Get the name of the active sound profile.
  *
  * @return The name of the profile
+ *
+ * @since 2.1.0
  */
 const char *finch_sound_get_active_profile(void);
 
@@ -44,6 +46,8 @@
  * Set the active profile.  If the profile doesn't exist, nothing is changed.
  * 
  * @param name  The name of the profile
+ *
+ * @since 2.1.0
  */
 void finch_sound_set_active_profile(const char *name);
 
@@ -52,6 +56,8 @@
  *
  * @return A list of strings denoting sound profile names.
  *         Caller must free the list (but not the data).
+ *
+ * @since 2.1.0
  */
 GList *finch_sound_get_profiles(void);
 
@@ -60,6 +66,8 @@
  *
  * @return Returns FALSE if preference is set to 'No sound', or if volume is
  *         set to zero.
+ *
+ * @since 2.2.0
  */
 gboolean finch_sound_is_enabled(void);
 
@@ -67,11 +75,15 @@
  * Gets GNT sound UI ops.
  *
  * @return The UI operations structure.
+ *
+ * @since 2.1.0
  */
 PurpleSoundUiOps *finch_sound_get_ui_ops(void);
 
 /**
  * Show the sound settings dialog.
+ *
+ * @since 2.1.0
  */
 void finch_sounds_show_all(void);
 
--- a/finch/libgnt/gnt.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gnt.h	Fri Nov 16 23:32:17 2007 +0000
@@ -62,6 +62,14 @@
  */
 gboolean gnt_ascii_only(void);
 
+/**
+ * Present a window. If the event was triggered because of user interaction,
+ * the window is moved to the foreground. Otherwise, the Urgent hint is set.
+ *
+ * @param window   The window the present.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 void gnt_window_present(GntWidget *window);
 /**
  * 
--- a/finch/libgnt/gntmenu.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntmenu.c	Fri Nov 16 23:32:17 2007 +0000
@@ -459,3 +459,34 @@
 	menu->list = g_list_append(menu->list, item);
 }
 
+GntMenuItem *gnt_menu_get_item(GntMenu *menu, const char *id)
+{
+	GntMenuItem *item = NULL;
+	GList *iter = menu->list;
+
+	if (!id || !*id)
+		return NULL;
+
+	for (; iter; iter = iter->next) {
+		GntMenu *sub;
+		item = iter->data;
+		sub = gnt_menuitem_get_submenu(item);
+		if (sub) {
+			item = gnt_menu_get_item(sub, id);
+			if (item)
+				break;
+		} else {
+			const char *itid = gnt_menuitem_get_id(item);
+			if (itid && strcmp(itid, id) == 0)
+				break;
+			/* XXX: Perhaps look at the menu-label as well? */
+		}
+		item = NULL;
+	}
+
+	if (item)
+		menuitem_activate(menu, item);
+
+	return item;
+}
+
--- a/finch/libgnt/gntmenu.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntmenu.h	Fri Nov 16 23:32:17 2007 +0000
@@ -86,27 +86,37 @@
 G_BEGIN_DECLS
 
 /**
- * 
- *
- * @return
+ * @return  The GType for GntMenu.
  */
 GType gnt_menu_get_gtype(void);
 
 /**
- * 
- * @param type
+ * Create a new menu.
  *
- * @return
+ * @param type  The type of the menu, whether it's a toplevel menu or a popup menu.
+ *
+ * @return  The newly created menu.
  */
 GntWidget * gnt_menu_new(GntMenuType type);
 
 /**
- * 
- * @param menu
- * @param item
+ * Add an item to the menu.
+ *
+ * @param menu   The menu.
+ * @param item   The item to add to the menu.
  */
 void gnt_menu_add_item(GntMenu *menu, GntMenuItem *item);
 
+/**
+ * Get the GntMenuItem with the given ID.
+ *
+ * @param menu   The menu.
+ * @param id     The ID for an item.
+ *
+ * @return  The menuitem with the given ID, or @c NULL.
+ */
+GntMenuItem *gnt_menu_get_item(GntMenu *menu, const char *id);
+
 G_END_DECLS
 
 #endif /* GNT_MENU_H */
--- a/finch/libgnt/gntmenuitem.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntmenuitem.c	Fri Nov 16 23:32:17 2007 +0000
@@ -33,6 +33,7 @@
 	item->text = NULL;
 	if (item->submenu)
 		gnt_widget_destroy(GNT_WIDGET(item->submenu));
+	g_free(item->priv.id);
 	parent_class->dispose(obj);
 }
 
@@ -104,6 +105,11 @@
 	item->submenu = menu;
 }
 
+GntMenu *gnt_menuitem_get_submenu(GntMenuItem *item)
+{
+	return item->submenu;
+}
+
 void gnt_menuitem_set_trigger(GntMenuItem *item, char trigger)
 {
 	item->priv.trigger = trigger;
@@ -114,3 +120,14 @@
 	return item->priv.trigger;
 }
 
+void gnt_menuitem_set_id(GntMenuItem *item, const char *id)
+{
+	g_free(item->priv.id);
+	item->priv.id = g_strdup(id);
+}
+
+const char * gnt_menuitem_get_id(GntMenuItem *item)
+{
+	return item->priv.id;
+}
+
--- a/finch/libgnt/gntmenuitem.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntmenuitem.h	Fri Nov 16 23:32:17 2007 +0000
@@ -53,6 +53,7 @@
 	int x;
 	int y;
 	char trigger;
+	char *id;
 };
 
 typedef void (*GntMenuItemCallback)(GntMenuItem *item, gpointer data);
@@ -86,36 +87,46 @@
 G_BEGIN_DECLS
 
 /**
- * 
- *
- * @return
+ * @return GType for GntMenuItem.
  */
 GType gnt_menuitem_get_gtype(void);
 
 /**
- * 
- * @param text
+ * Create a new menuitem.
  *
- * @return
+ * @param text   Label for the menuitem.
+ *
+ * @return  The newly created menuitem.
  */
 GntMenuItem * gnt_menuitem_new(const char *text);
 
 /**
- * 
- * @param item
- * @param callback
- * @param data
+ * Set a callback function for a menuitem.
+ *
+ * @param item       The menuitem.
+ * @param callback   The callback function.
+ * @param data       Data to send to the callback function.
  */
 void gnt_menuitem_set_callback(GntMenuItem *item, GntMenuItemCallback callback, gpointer data);
 
 /**
- * 
- * @param item
- * @param menu
+ * Set a submenu for a menuitem. A menuitem with a submenu cannot have a callback.
+ *
+ * @param item  The menuitem.
+ * @param menu  The submenu.
  */
 void gnt_menuitem_set_submenu(GntMenuItem *item, GntMenu *menu);
 
 /**
+ * Get the submenu for a menuitem.
+ *
+ * @param item   The menuitem.
+ *
+ * @return  The submenu, or @c NULL.
+ */
+GntMenu *gnt_menuitem_get_submenu(GntMenuItem *item);
+
+/**
  * Set a trigger key for the item.
  *
  * @param item     The menuitem
@@ -134,6 +145,23 @@
  */
 char gnt_menuitem_get_trigger(GntMenuItem *item);
 
+/**
+ * Set an ID for the menuitem.
+ *
+ * @param item   The menuitem.
+ * @param id     The ID for the menuitem.
+ */
+void gnt_menuitem_set_id(GntMenuItem *item, const char *id);
+
+/**
+ * Get the ID of the menuitem.
+ *
+ * @param item   The menuitem.
+ *
+ * @return  The ID for the menuitem.
+ */
+const char * gnt_menuitem_get_id(GntMenuItem *item);
+
 G_END_DECLS
 
 #endif /* GNT_MENUITEM_H */
--- a/finch/libgnt/gntslider.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntslider.h	Fri Nov 16 23:32:17 2007 +0000
@@ -75,6 +75,8 @@
 
 /**
  * @return The GType for GntSlider
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 GType gnt_slider_get_gtype(void);
 
@@ -89,6 +91,8 @@
  * @param min    The minimum value for the slider
  *
  * @return  The newly created slider
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 GntWidget * gnt_slider_new(gboolean orient, int max, int min);
 
@@ -98,6 +102,8 @@
  * @param slider  The slider
  * @param max     The maximum value
  * @param min     The minimum value
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_slider_set_range(GntSlider *slider, int max, int min);
 
@@ -106,6 +112,8 @@
  * 
  * @param slider  The slider
  * @param step    The amount for each step
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_slider_set_step(GntSlider *slider, int step);
 
@@ -114,6 +122,8 @@
  * 
  * @param slider  The slider
  * @param step    The amount for a small step (for the slider)
+ *
+ * @since 2.2.0
  */
 void gnt_slider_set_small_step(GntSlider *slider, int step);
 
@@ -122,6 +132,8 @@
  * 
  * @param slider  The slider
  * @param step    The amount for a large step (for the slider)
+ *
+ * @since 2.2.0
  */
 void gnt_slider_set_large_step(GntSlider *slider, int step);
 
@@ -133,6 +145,8 @@
  *                 forward, negative to change backward
  *
  * @return   The value of the slider after the change
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 int gnt_slider_advance_step(GntSlider *slider, int steps);
 
@@ -141,6 +155,8 @@
  *
  * @param slider  The slider
  * @param value   The current value
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_slider_set_value(GntSlider *slider, int value);
 
@@ -149,6 +165,8 @@
  *
  * @param slider The slider
  *
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 int gnt_slider_get_value(GntSlider *slider);
 
@@ -157,6 +175,8 @@
  *
  * @param slider   The slider
  * @param label    The label to update
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_slider_reflect_label(GntSlider *slider, GntLabel *label);
 
--- a/finch/libgnt/gntstyle.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntstyle.c	Fri Nov 16 23:32:17 2007 +0000
@@ -227,6 +227,65 @@
 #endif
 }
 
+gboolean gnt_style_read_menu_accels(const char *name, GHashTable *table)
+{
+#if GLIB_CHECK_VERSION(2,6,0)
+	char *kname;
+	GError *error = NULL;
+	gboolean ret = FALSE;
+
+	kname = g_strdup_printf("%s::menu", name);
+
+	if (g_key_file_has_group(gkfile, kname))
+	{
+		gsize len = 0;
+		char **keys;
+		
+		keys = g_key_file_get_keys(gkfile, kname, &len, &error);
+		if (error)
+		{
+			g_printerr("GntStyle: %s\n", error->message);
+			g_error_free(error);
+			g_free(kname);
+			return ret;
+		}
+
+		while (len--)
+		{
+			char *key, *menuid;
+
+			key = g_strdup(keys[len]);
+			menuid = g_key_file_get_string(gkfile, kname, keys[len], &error);
+
+			if (error)
+			{
+				g_printerr("GntStyle: %s\n", error->message);
+				g_error_free(error);
+				error = NULL;
+			}
+			else
+			{
+				const char *keycode = parse_key(key);
+				if (keycode == NULL) {
+					g_printerr("GntStyle: Invalid key-binding %s\n", key);
+				} else {
+					ret = TRUE;
+					g_hash_table_replace(table, g_strdup(keycode), menuid);
+					menuid = NULL;
+				}
+			}
+			g_free(key);
+			g_free(menuid);
+		}
+		g_strfreev(keys);
+	}
+
+	g_free(kname);
+	return ret;
+#endif
+	return FALSE;
+}
+
 void gnt_styles_get_keyremaps(GType type, GHashTable *hash)
 {
 #if GLIB_CHECK_VERSION(2,6,0)
--- a/finch/libgnt/gntstyle.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntstyle.h	Fri Nov 16 23:32:17 2007 +0000
@@ -53,6 +53,8 @@
  * @param key     The key
  *
  * @return  The value of the setting as a string, or @c NULL
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 char *gnt_style_get_from_name(const char *group, const char *key);
 
@@ -62,6 +64,8 @@
  *
  * @param value   The value of the boolean setting as a string
  * @return    The boolean value
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 gboolean gnt_style_parse_bool(const char *value);
 
@@ -89,6 +93,16 @@
  */
 void gnt_style_read_actions(GType type, GntBindableClass *klass);
 
+/*
+ * Read menu-accels from ~/.gntrc
+ *
+ * @param name  The name of the window.
+ * @param table The hastable to store the accel information.
+ *
+ * @return  @c TRUE if some accels were read, @c FALSE otherwise.
+ */
+gboolean gnt_style_read_menu_accels(const char *name, GHashTable *table);
+
 void gnt_style_read_workspaces(GntWM *wm);
 
 /**
--- a/finch/libgnt/gnttextview.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gnttextview.h	Fri Nov 16 23:32:17 2007 +0000
@@ -204,6 +204,8 @@
  *
  * @param view  The textview widget
  * @param flag  The flag to set
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_text_view_set_flag(GntTextView *view, GntTextViewFlag flag);
 
--- a/finch/libgnt/gnttree.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gnttree.h	Fri Nov 16 23:32:17 2007 +0000
@@ -383,6 +383,8 @@
  *
  * @see gnt_tree_set_column_titles
  * @see gnt_tree_set_show_title
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_tree_set_column_title(GntTree *tree, int index, const char *title);
 
@@ -486,6 +488,8 @@
  *
  * @see gnt_tree_set_col_width
  * @see gnt_tree_set_column_width_ratio
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_tree_set_column_resizable(GntTree *tree, int col, gboolean res);
 
@@ -505,6 +509,8 @@
  * @param tree  The tree
  * @param col   The index of the column
  * @param right @c TRUE if the text in the column should be right aligned
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_tree_set_column_is_right_aligned(GntTree *tree, int col, gboolean right);
 
@@ -519,6 +525,8 @@
  *
  * @see gnt_tree_set_col_width
  * @see gnt_tree_set_column_resizable
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_tree_set_column_width_ratio(GntTree *tree, int cols[]);
 
@@ -527,6 +535,8 @@
  *
  * @param tree   The tree
  * @param col    The index of the column
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_tree_set_search_column(GntTree *tree, int col);
 
@@ -535,6 +545,8 @@
  *
  * @param tree   The tree
  * @return  @c TRUE if the user is searching, @c FALSE otherwise.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 gboolean gnt_tree_is_searching(GntTree *tree);
 
@@ -547,6 +559,8 @@
  *              string and the content of row in the search column.
  *              If the function returns @c TRUE, the row is dislayed,
  *              otherwise it's not.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_tree_set_search_function(GntTree *tree,
 		gboolean (*func)(GntTree *tree, gpointer key, const char *search, const char *current));
--- a/finch/libgnt/gntutils.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntutils.h	Fri Nov 16 23:32:17 2007 +0000
@@ -139,6 +139,8 @@
  * @param string   The XHTML string
  * @param tv       The GntTextView
  * @return  @c TRUE if the string was added to the textview properly, @c FALSE otherwise.
+ *
+ * @since 2.2.0
  */
 gboolean gnt_util_parse_xhtml_to_textview(const char *string, GntTextView *tv);
 
@@ -148,6 +150,8 @@
  * @param widget  The widget
  * @param key     The key to trigger the button
  * @param button  The button to trigger
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
  */
 void gnt_util_set_trigger_widget(GntWidget *wid, const char *text, GntWidget *button);
 
--- a/finch/libgnt/gntwindow.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntwindow.c	Fri Nov 16 23:32:17 2007 +0000
@@ -25,6 +25,11 @@
 
 #include <string.h>
 
+struct _GntWindowPriv
+{
+	GHashTable *accels;   /* key => menuitem-id */
+};
+
 enum
 {
 	SIG_WORKSPACE_HIDE,
@@ -55,6 +60,10 @@
 	GntWindow *window = GNT_WINDOW(widget);
 	if (window->menu)
 		gnt_widget_destroy(GNT_WIDGET(window->menu));
+	if (window->priv) {
+		g_hash_table_destroy(window->priv->accels);
+		g_free(window->priv);
+	}
 	org_destroy(widget);
 }
 
@@ -98,8 +107,11 @@
 gnt_window_init(GTypeInstance *instance, gpointer class)
 {
 	GntWidget *widget = GNT_WIDGET(instance);
+	GntWindow *win = GNT_WINDOW(widget);
 	GNT_WIDGET_UNSET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW);
 	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_CAN_TAKE_FOCUS);
+	win->priv = g_new0(GntWindowPriv, 1);
+	win->priv->accels = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 	GNTDEBUG;
 }
 
@@ -170,8 +182,23 @@
 void gnt_window_set_menu(GntWindow *window, GntMenu *menu)
 {
 	/* If a menu already existed, then destroy that first. */
+	const char *name = gnt_widget_get_name(GNT_WIDGET(window));
 	if (window->menu)
 		gnt_widget_destroy(GNT_WIDGET(window->menu));
 	window->menu = menu;
+	if (name && window->priv) {
+		if (!gnt_style_read_menu_accels(name, window->priv->accels)) {
+			g_hash_table_destroy(window->priv->accels);
+			g_free(window->priv);
+			window->priv = NULL;
+		}
+	}
 }
 
+const char * gnt_window_get_accel_item(GntWindow *window, const char *key)
+{
+	if (window->priv)
+		return g_hash_table_lookup(window->priv->accels, key);
+	return NULL;
+}
+
--- a/finch/libgnt/gntwindow.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntwindow.h	Fri Nov 16 23:32:17 2007 +0000
@@ -52,6 +52,7 @@
 {
 	GntBox parent;
 	GntMenu *menu;
+	GntWindowPriv *priv;
 };
 
 struct _GntWindowClass
@@ -99,6 +100,8 @@
  */
 void gnt_window_set_menu(GntWindow *window, GntMenu *menu);
 
+const char * gnt_window_get_accel_item(GntWindow *window, const char *key);
+
 void gnt_window_workspace_hiding(GntWindow *);
 void gnt_window_workspace_showing(GntWindow *);
 
--- a/finch/libgnt/gntwm.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntwm.c	Fri Nov 16 23:32:17 2007 +0000
@@ -1717,12 +1717,11 @@
 void gnt_wm_window_close(GntWM *wm, GntWidget *widget)
 {
 	GntWS *s;
-	GntNode *node;
 	int pos;
 
 	s = gnt_wm_widget_find_workspace(wm, widget);
 
-	if ((node = g_hash_table_lookup(wm->nodes, widget)) == NULL)
+	if (g_hash_table_lookup(wm->nodes, widget) == NULL)
 		return;
 
 	g_signal_emit(wm, signals[SIG_CLOSE_WIN], 0, widget);
@@ -1853,8 +1852,19 @@
 		ret = gnt_widget_key_pressed(GNT_WIDGET(wm->menu), keys);
 	else if (wm->_list.window)
 		ret = gnt_widget_key_pressed(wm->_list.window, keys);
-	else if (wm->cws->ordered)
-		ret = gnt_widget_key_pressed(GNT_WIDGET(wm->cws->ordered->data), keys);
+	else if (wm->cws->ordered) {
+		GntWidget *win = wm->cws->ordered->data;
+		if (GNT_IS_WINDOW(win)) {
+			GntMenu *menu = GNT_WINDOW(win)->menu;
+			if (menu) {
+				const char *id = gnt_window_get_accel_item(GNT_WINDOW(win), keys);
+				if (id)
+					ret = (gnt_menu_get_item(menu, id) != NULL);
+			}
+		}
+		if (!ret)
+			ret = gnt_widget_key_pressed(win, keys);
+	}
 	return ret;
 }
 
--- a/finch/libgnt/gntws.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/gntws.h	Fri Nov 16 23:32:17 2007 +0000
@@ -69,18 +69,112 @@
 
 G_BEGIN_DECLS
 
+/**
+ * @return The GType for GntWS.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 GType gnt_ws_get_gtype(void);
 
+/**
+ * Create a new workspace with the specified name.
+ *
+ * @param name  The desired name of the workspace, or @c NULL.
+ *
+ * @return The newly created workspace.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 GntWS *gnt_ws_new(const char *name);
+
+/**
+ * Set the name of a workspace.
+ *
+ * @param ws    The workspace to rename.
+ * @param name  The new name of the workspace.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 void gnt_ws_set_name(GntWS *ws, const gchar *name);
+
+/**
+ * Add a widget to a workspace.
+ *
+ * @param ws     The workspace.
+ * @param widget The widget to add.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 void gnt_ws_add_widget(GntWS *ws, GntWidget *widget);
+
+/**
+ * Remove a widget from a workspace.
+ *
+ * @param ws      The workspace
+ * @param widget  The widget to remove from the workspace.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 void gnt_ws_remove_widget(GntWS *ws, GntWidget *widget);
+
+/**
+ * Hide a widget in a workspace.
+ *
+ * @param widget  The widget to hide.
+ * @param nodes   A hashtable containing information about the widgets.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 void gnt_ws_widget_hide(GntWidget *widget, GHashTable *nodes);
+
+/**
+ * Show a widget in a workspace.
+ *
+ * @param widget   The widget to show.
+ * @param nodes   A hashtable containing information about the widgets.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 void gnt_ws_widget_show(GntWidget *widget, GHashTable *nodes);
+
+/**
+ * Draw the taskbar in a workspace.
+ *
+ * @param ws         The workspace.
+ * @param reposition Whether the workspace should reposition the taskbar.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 void gnt_ws_draw_taskbar(GntWS *ws, gboolean reposition);
+
+/**
+ * Hide a workspace.
+ *
+ * @param ws      The workspace to hide.
+ * @param table   A hashtable containing information about the widgets.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 void gnt_ws_hide(GntWS *ws, GHashTable *table);
+
+/**
+ * Show a workspace.
+ *
+ * @param ws      The workspace to hide.
+ * @param table   A hashtable containing information about the widgets.
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 void gnt_ws_show(GntWS *ws, GHashTable *table);
 
+/**
+ * Get the name of a workspace.
+ *
+ * @param ws   The workspace.
+ * @return  The name of the workspace (can be @c NULL).
+ *
+ * @since 2.0.0 (gnt), 2.1.0 (pidgin)
+ */
 const char * gnt_ws_get_name(GntWS *ws);
 
 #endif
--- a/finch/libgnt/test/Makefile	Fri Nov 16 23:30:03 2007 +0000
+++ b/finch/libgnt/test/Makefile	Fri Nov 16 23:32:17 2007 +0000
@@ -1,5 +1,5 @@
 CC=gcc
-CFLAGS=`pkg-config --cflags gobject-2.0 gmodule-2.0` -g -I../ -DSTANDALONE
+CFLAGS=`pkg-config --cflags gobject-2.0 gmodule-2.0` -g -I../ -DSTANDALONE -I/usr/inclue/ncursesw/
 LDFLAGS=`pkg-config --libs gobject-2.0 gmodule-2.0 gnt` -pg
 
 EXAMPLES=combo focus tv multiwin keys menu parse
--- a/libpurple/Makefile.am	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/Makefile.am	Fri Nov 16 23:32:17 2007 +0000
@@ -114,6 +114,7 @@
 	privacy.h \
 	proxy.h \
 	prpl.h \
+	purple.h \
 	request.h \
 	roomlist.h \
 	savedstatuses.h \
--- a/libpurple/account.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/account.c	Fri Nov 16 23:32:17 2007 +0000
@@ -63,7 +63,10 @@
 	PurpleAccountRequestType type;
 	PurpleAccount *account;
 	void *ui_handle;
-
+	char *user;
+	gpointer userdata;
+	PurpleAccountRequestAuthorizationCb auth_cb;
+	PurpleAccountRequestAuthorizationCb deny_cb;
 } PurpleAccountRequestInfo;
 
 static PurpleAccountUiOps *account_ui_ops = NULL;
@@ -1157,6 +1160,28 @@
 	}
 }
 
+static void
+request_auth_cb(void *data)
+{
+	PurpleAccountRequestInfo *info = data;
+	info->auth_cb(info->userdata);
+	purple_signal_emit(purple_accounts_get_handle(),
+			"account-authorization-granted", info->account, info->user);
+	g_free(info->user);
+	g_free(info);
+}
+
+static void
+request_deny_cb(void *data)
+{
+	PurpleAccountRequestInfo *info = data;
+	info->deny_cb(info->userdata);
+	purple_signal_emit(purple_accounts_get_handle(),
+			"account-authorization-denied", info->account, info->user);
+	g_free(info->user);
+	g_free(info);
+}
+
 void *
 purple_account_request_authorization(PurpleAccount *account, const char *remote_user,
 				     const char *id, const char *alias, const char *message, gboolean on_list,
@@ -1164,18 +1189,35 @@
 {
 	PurpleAccountUiOps *ui_ops;
 	PurpleAccountRequestInfo *info;
+	int plugin_return;
 
 	g_return_val_if_fail(account     != NULL, NULL);
 	g_return_val_if_fail(remote_user != NULL, NULL);
 
 	ui_ops = purple_accounts_get_ui_ops();
 
+	plugin_return = GPOINTER_TO_INT(
+			purple_signal_emit_return_1(purple_accounts_get_handle(),
+				"account-authorization-requested", account, remote_user));
+
+	if (plugin_return > 0) {
+		auth_cb(user_data);
+		return NULL;
+	} else if (plugin_return < 0) {
+		deny_cb(user_data);
+		return NULL;
+	}
+
 	if (ui_ops != NULL && ui_ops->request_authorize != NULL) {
 		info            = g_new0(PurpleAccountRequestInfo, 1);
 		info->type      = PURPLE_ACCOUNT_REQUEST_AUTHORIZATION;
 		info->account   = account;
+		info->auth_cb   = auth_cb;
+		info->deny_cb   = deny_cb;
+		info->userdata  = user_data;
+		info->user      = g_strdup(remote_user);
 		info->ui_handle = ui_ops->request_authorize(account, remote_user, id, alias, message,
-							    on_list, auth_cb, deny_cb, user_data);
+							    on_list, request_auth_cb, request_deny_cb, info);
 
 		handles = g_list_append(handles, info);
 		return info->ui_handle;
@@ -2452,6 +2494,25 @@
 							 			PURPLE_SUBTYPE_ACCOUNT),
 						 purple_value_new(PURPLE_TYPE_STRING));
 
+	purple_signal_register(handle, "account-authorization-requested",
+						purple_marshal_INT__POINTER_POINTER,
+						purple_value_new(PURPLE_TYPE_INT), 2,
+						purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_ACCOUNT),
+						purple_value_new(PURPLE_TYPE_STRING));
+
+	purple_signal_register(handle, "account-authorization-denied",
+						purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+						purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_ACCOUNT),
+						purple_value_new(PURPLE_TYPE_STRING));
+
+	purple_signal_register(handle, "account-authorization-granted",
+						purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+						purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_ACCOUNT),
+						purple_value_new(PURPLE_TYPE_STRING));
+
 	load_accounts();
 
 }
--- a/libpurple/blist.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/blist.h	Fri Nov 16 23:32:17 2007 +0000
@@ -40,6 +40,8 @@
 typedef struct _PurpleContact PurpleContact;
 typedef struct _PurpleBuddy PurpleBuddy;
 
+typedef gboolean (*PurpleFilterBlistFunc)(PurpleBlistNode *node);
+
 /**************************************************************************/
 /* Enumerations                                                           */
 /**************************************************************************/
@@ -65,9 +67,12 @@
 typedef enum
 {
 	PURPLE_BLIST_NODE_FLAG_NO_SAVE      = 1 << 0, /**< node should not be saved with the buddy list */
+	PURPLE_BLIST_NODE_HAS_CONVERSATION  = 1 << 1, /**< node (buddy or chat) has an open conversation */
 
 } PurpleBlistNodeFlags;
 
+#define PURPLE_BLIST_NODE_SET_FLAG(node, f)    (((PurpleBlistNode *)node)->flags |= (f))
+#define PURPLE_BLIST_NODE_UNSET_FLAG(node, f)  (((PurpleBlistNode *)node)->flags &= ~(f))
 #define PURPLE_BLIST_NODE_HAS_FLAG(b, f) (((PurpleBlistNode*)(b))->flags & (f))
 #define PURPLE_BLIST_NODE_SHOULD_SAVE(b) (! PURPLE_BLIST_NODE_HAS_FLAG(b, PURPLE_BLIST_NODE_FLAG_NO_SAVE))
 
@@ -482,6 +487,7 @@
  */
 PurpleBuddy *purple_contact_get_priority_buddy(PurpleContact *contact);
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Sets the alias for a contact.
  *
@@ -491,6 +497,7 @@
  * @deprecated Use purple_blist_alias_contact() instead.
  */
 void purple_contact_set_alias(PurpleContact *contact, const char *alias);
+#endif
 
 /**
  * Gets the alias for a contact.
--- a/libpurple/certificate.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/certificate.h	Fri Nov 16 23:32:17 2007 +0000
@@ -2,6 +2,7 @@
  * @file certificate.h Public-Key Certificate API
  * @ingroup core
  * @see @ref certificate-signals
+ * @since 2.2.0
  */
 
 /*
@@ -434,7 +435,7 @@
  *
  * @return TRUE if 'crt' has a valid signature made by 'issuer',
  *         otherwise FALSE
- * @TODO Find a way to give the reason (bad signature, not the issuer, etc.) 
+ * @todo Find a way to give the reason (bad signature, not the issuer, etc.) 
  */
 gboolean
 purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer);
@@ -449,7 +450,7 @@
  * @param chain      List of PurpleCertificate instances comprising the chain,
  *                   in the order certificate, issuer, issuer's issuer, etc.
  * @return TRUE if the chain is valid. See description.
- * @TODO Specify which certificate in the chain caused a failure
+ * @todo Specify which certificate in the chain caused a failure
  */
 gboolean
 purple_certificate_check_signature_chain(GList *chain);
@@ -780,7 +781,7 @@
  * Displays a window showing X.509 certificate information
  *
  * @param crt    Certificate under an "x509" Scheme
- * @TODO Will break on CA certs, as they have no Common Name
+ * @todo Will break on CA certs, as they have no Common Name
  */
 void
 purple_certificate_display_x509(PurpleCertificate *crt);
--- a/libpurple/connection.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/connection.h	Fri Nov 16 23:32:17 2007 +0000
@@ -150,6 +150,7 @@
 /**************************************************************************/
 /*@{*/
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * This function should only be called by purple_account_connect()
  * in account.c.  If you're trying to sign on an account, use that
@@ -166,10 +167,14 @@
  * @param regist   Whether we are registering a new account or just
  *                 trying to do a normal signon.
  * @param password The password to use.
+ *
+ * @deprecated As this is internal, we should make it private in 3.0.0.
  */
 void purple_connection_new(PurpleAccount *account, gboolean regist,
 									const char *password);
+#endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * This function should only be called by purple_account_unregister()
  * in account.c.
@@ -179,9 +184,15 @@
  *
  * @param account  The account to unregister
  * @param password The password to use.
+ * @param cb Optional callback to be called when unregistration is complete
+ * @param user_data user data to pass to the callback
+ *
+ * @deprecated As this is internal, we should make it private in 3.0.0.
  */
 void purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data);
+#endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Disconnects and destroys a PurpleConnection.
  *
@@ -190,8 +201,11 @@
  * function instead.
  *
  * @param gc The purple connection to destroy.
+ *
+ * @deprecated As this is internal, we should make it private in 3.0.0.
  */
 void purple_connection_destroy(PurpleConnection *gc);
+#endif
 
 /**
  * Sets the connection state.  PRPLs should call this and pass in
--- a/libpurple/conversation.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/conversation.c	Fri Nov 16 23:32:17 2007 +0000
@@ -225,6 +225,7 @@
 	msg->flags = flags;
 	msg->what = g_strdup(message);
 	msg->when = when;
+	msg->conv = conv;
 
 	conv->message_history = g_list_prepend(conv->message_history, msg);
 }
--- a/libpurple/conversation.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/conversation.h	Fri Nov 16 23:32:17 2007 +0000
@@ -285,6 +285,8 @@
 
 /**
  * Description of a conversation message
+ *
+ * @since 2.2.0
  */
 struct _PurpleConvMessage
 {
@@ -292,6 +294,7 @@
 	char *what;
 	PurpleMessageFlags flags;
 	time_t when;
+	PurpleConversation *conv;
 };
 
 /**
@@ -670,6 +673,8 @@
  * @return  A GList of PurpleConvMessage's. The must not modify the list or the data within.
  *          The list contains the newest message at the beginning, and the oldest message at
  *          the end.
+ *
+ * @since 2.2.0
  */
 GList *purple_conversation_get_message_history(PurpleConversation *conv);
 
@@ -677,6 +682,8 @@
  * Clear the message history of a conversation.
  *
  * @param conv  The conversation
+ *
+ * @since 2.2.0
  */
 void purple_conversation_clear_message_history(PurpleConversation *conv);
 
@@ -686,6 +693,8 @@
  * @param msg   A PurpleConvMessage
  *
  * @return   The name of the sender of the message
+ *
+ * @since 2.2.0
  */
 const char *purple_conversation_message_get_sender(PurpleConvMessage *msg);
 
@@ -695,6 +704,8 @@
  * @param msg   A PurpleConvMessage
  *
  * @return   The name of the sender of the message
+ *
+ * @since 2.2.0
  */
 const char *purple_conversation_message_get_message(PurpleConvMessage *msg);
 
@@ -704,6 +715,8 @@
  * @param msg   A PurpleConvMessage
  *
  * @return   The name of the sender of the message
+ *
+ * @since 2.2.0
  */
 PurpleMessageFlags purple_conversation_message_get_flags(PurpleConvMessage *msg);
 
@@ -713,6 +726,8 @@
  * @param msg   A PurpleConvMessage
  *
  * @return   The name of the sender of the message
+ *
+ * @since 2.2.0
  */
 time_t purple_conversation_message_get_timestamp(PurpleConvMessage *msg);
 
@@ -1318,6 +1333,8 @@
  * @return  A list of PurpleMenuAction items, harvested by the
  *          chat-extended-menu signal. The list and the menuaction
  *          items should be freed by the caller.
+ *
+ * @since 2.1.0
  */
 GList * purple_conversation_get_extended_menu(PurpleConversation *conv);
 
@@ -1331,6 +1348,8 @@
  *                message, if not @c NULL. It must be freed by the caller with g_free().
  *
  * @return  @c TRUE if the command was executed successfully, @c FALSE otherwise.
+ *
+ * @since 2.1.0
  */
 gboolean purple_conversation_do_command(PurpleConversation *conv, const gchar *cmdline, const gchar *markup, gchar **error);
 
--- a/libpurple/dnsquery.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/dnsquery.h	Fri Nov 16 23:32:17 2007 +0000
@@ -59,7 +59,7 @@
 	                         PurpleDnsQueryFailedCallback failed_cb);
 
 	/** Called just before @a query_data is freed; this should cancel any
-	 *  further use of @q query_data the UI would make. Unneeded if
+	 *  further use of @a query_data the UI would make. Unneeded if
 	 *  #resolve_host is not implemented.
 	 */
 	void (*destroy)(PurpleDnsQueryData *query_data);
--- a/libpurple/eventloop.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/eventloop.h	Fri Nov 16 23:32:17 2007 +0000
@@ -138,6 +138,8 @@
  * @param data		data to pass to @a function.
  * @return A handle to the timer which can be passed to 
  *         purple_timeout_remove to remove the timer.
+ *
+ * @since 2.1.0
  */
 guint purple_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data);
 
--- a/libpurple/ft.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/ft.h	Fri Nov 16 23:32:17 2007 +0000
@@ -242,6 +242,8 @@
  * @param xfer The file transfer.
  *
  * @return The name of the remote user.
+ *
+ * @since 2.1.0
  */
 const char *purple_xfer_get_remote_user(const PurpleXfer *xfer);
 
--- a/libpurple/notify.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/notify.h	Fri Nov 16 23:32:17 2007 +0000
@@ -289,7 +289,7 @@
  */
 void purple_notify_searchresults_row_add(PurpleNotifySearchResults *results,
 									   GList *row);
-
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Returns a number of the rows in the search results object.
  *
@@ -308,7 +308,9 @@
  * @return Number of the result rows.
  */
 guint purple_notify_searchresults_get_rows_count(PurpleNotifySearchResults *results);
+#endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Returns a number of the columns in the search results object.
  *
@@ -327,7 +329,9 @@
  * @return Number of the columns.
  */
 guint purple_notify_searchresults_get_columns_count(PurpleNotifySearchResults *results);
+#endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Returns a row of the results from the search results object.
  *
@@ -348,7 +352,9 @@
  */
 GList *purple_notify_searchresults_row_get(PurpleNotifySearchResults *results,
 										 unsigned int row_id);
+#endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Returns a title of the search results object's column.
  *
@@ -367,6 +373,7 @@
  */
 char *purple_notify_searchresults_column_get_title(PurpleNotifySearchResults *results,
 												 unsigned int column_id);
+#endif
 
 /*@}*/
 
--- a/libpurple/plugin.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/plugin.c	Fri Nov 16 23:32:17 2007 +0000
@@ -58,13 +58,9 @@
 #ifdef PURPLE_PLUGINS
 static GList *load_queue       = NULL;
 static GList *plugin_loaders   = NULL;
+static GList *plugins_to_disable = NULL;
 #endif
 
-/*
- * TODO: I think the intention was to allow multiple load and unload
- *       callback functions.  Perhaps using a GList instead of a
- *       pointer to a single function.
- */
 static void (*probe_cb)(void *) = NULL;
 static void *probe_cb_data = NULL;
 static void (*load_cb)(PurplePlugin *, void *) = NULL;
@@ -254,7 +250,6 @@
 		 * plugins being added to the global name space.
 		 *
 		 * G_MODULE_BIND_LOCAL was added in glib 2.3.3.
-		 * TODO: I guess there's nothing we can do about that?
 		 */
 #if GLIB_CHECK_VERSION(2,3,3)
 		plugin->handle = g_module_open(filename, G_MODULE_BIND_LOCAL);
@@ -625,7 +620,6 @@
 
 	plugin->loaded = TRUE;
 
-	/* TODO */
 	if (load_cb != NULL)
 		load_cb(plugin, load_cb_data);
 
@@ -643,43 +637,37 @@
 {
 #ifdef PURPLE_PLUGINS
 	GList *l;
+	GList *ll;
 
 	g_return_val_if_fail(plugin != NULL, FALSE);
-
-	loaded_plugins = g_list_remove(loaded_plugins, plugin);
-	if ((plugin->info != NULL) && PURPLE_IS_PROTOCOL_PLUGIN(plugin))
-		protocol_plugins = g_list_remove(protocol_plugins, plugin);
-
 	g_return_val_if_fail(purple_plugin_is_loaded(plugin), FALSE);
 
 	purple_debug_info("plugins", "Unloading plugin %s\n", plugin->info->name);
 
-	/* cancel any pending dialogs the plugin has */
-	purple_request_close_with_handle(plugin);
-	purple_notify_close_with_handle(plugin);
-
-	plugin->loaded = FALSE;
-
 	/* Unload all plugins that depend on this plugin. */
-	while ((l = plugin->dependent_plugins) != NULL)
-	{
+	for (l = plugin->dependent_plugins; l != NULL; l = ll) {
 		const char * dep_name = (const char *)l->data;
 		PurplePlugin *dep_plugin;
 
+		/* Store a pointer to the next element in the list.
+		 * This is because we'll be modifying this list in the loop. */
+		ll = l->next;
+
 		dep_plugin = purple_plugins_find_with_id(dep_name);
 
 		if (dep_plugin != NULL && purple_plugin_is_loaded(dep_plugin))
 		{
 			if (!purple_plugin_unload(dep_plugin))
 			{
-				char *tmp;
-
-				tmp = g_strdup_printf(_("The dependent plugin %s failed to unload."),
-				                      _(dep_plugin->info->name));
-
-				purple_notify_error(NULL, NULL,
-				                  _("There were errors unloading the plugin."), tmp);
-				g_free(tmp);
+				g_free(plugin->error);
+				plugin->error = g_strdup_printf(_("%s requires %s, but it failed to unload."),
+				                                _(plugin->info->name),
+				                                _(dep_plugin->info->name));
+				return FALSE;
+			}
+			else
+			{
+				plugin->dependent_plugins = g_list_delete_link(plugin->dependent_plugins, l);
 			}
 		}
 	}
@@ -699,8 +687,8 @@
 	}
 
 	if (plugin->native_plugin) {
-		if (plugin->info->unload != NULL)
-			plugin->info->unload(plugin);
+		if (plugin->info->unload && !plugin->info->unload(plugin))
+			return FALSE;
 
 		if (plugin->info->type == PURPLE_PLUGIN_PROTOCOL) {
 			PurplePluginProtocolInfo *prpl_info;
@@ -724,8 +712,7 @@
 				prpl_info->protocol_options = NULL;
 			}
 		}
-	}
-	else {
+	} else {
 		PurplePlugin *loader;
 		PurplePluginLoaderInfo *loader_info;
 
@@ -736,14 +723,30 @@
 
 		loader_info = PURPLE_PLUGIN_LOADER_INFO(loader);
 
-		if (loader_info->unload != NULL)
-			loader_info->unload(plugin);
+		if (loader_info->unload && !loader_info->unload(plugin))
+			return FALSE;
 	}
 
+	/* cancel any pending dialogs the plugin has */
+	purple_request_close_with_handle(plugin);
+	purple_notify_close_with_handle(plugin);
+
 	purple_signals_disconnect_by_handle(plugin);
 	purple_plugin_ipc_unregister_all(plugin);
 
-	/* TODO */
+	loaded_plugins = g_list_remove(loaded_plugins, plugin);
+	if ((plugin->info != NULL) && PURPLE_IS_PROTOCOL_PLUGIN(plugin))
+		protocol_plugins = g_list_remove(protocol_plugins, plugin);
+	plugins_to_disable = g_list_remove(plugins_to_disable, plugin);
+	plugin->loaded = FALSE;
+
+	/* We wouldn't be anywhere near here if the plugin wasn't loaded, so
+	 * if plugin->error is set at all, it had to be from a previous
+	 * unload failure.  It's obviously okay now.
+	 */
+	g_free(plugin->error);
+	plugin->error = NULL;
+
 	if (unload_cb != NULL)
 		unload_cb(plugin, unload_cb_data);
 
@@ -757,6 +760,15 @@
 #endif /* PURPLE_PLUGINS */
 }
 
+void
+purple_plugin_disable(PurplePlugin *plugin)
+{
+	g_return_if_fail(plugin != NULL);
+
+	if (!g_list_find(plugins_to_disable, plugin))
+		plugins_to_disable = g_list_prepend(plugins_to_disable, plugin);
+}
+
 gboolean
 purple_plugin_reload(PurplePlugin *plugin)
 {
@@ -1222,14 +1234,14 @@
 #ifdef PURPLE_PLUGINS
 	GList *pl;
 	GList *files = NULL;
-	PurplePlugin *p;
 
 	for (pl = purple_plugins_get_loaded(); pl != NULL; pl = pl->next) {
-		p = pl->data;
+		PurplePlugin *plugin = pl->data;
 
-		if (p->info->type != PURPLE_PLUGIN_PROTOCOL &&
-			p->info->type != PURPLE_PLUGIN_LOADER) {
-				files = g_list_append(files, p->path);
+		if (plugin->info->type != PURPLE_PLUGIN_PROTOCOL &&
+		    plugin->info->type != PURPLE_PLUGIN_LOADER &&
+		    !g_list_find(plugins_to_disable, plugin)) {
+			files = g_list_append(files, plugin->path);
 		}
 	}
 
@@ -1391,6 +1403,7 @@
 
 	if (probe_cb != NULL)
 		probe_cb(probe_cb_data);
+
 #endif /* PURPLE_PLUGINS */
 }
 
@@ -1464,7 +1477,6 @@
 void
 purple_plugins_register_probe_notify_cb(void (*func)(void *), void *data)
 {
-	/* TODO */
 	probe_cb = func;
 	probe_cb_data = data;
 }
@@ -1472,7 +1484,6 @@
 void
 purple_plugins_unregister_probe_notify_cb(void (*func)(void *))
 {
-	/* TODO */
 	probe_cb = NULL;
 	probe_cb_data = NULL;
 }
@@ -1481,7 +1492,6 @@
 purple_plugins_register_load_notify_cb(void (*func)(PurplePlugin *, void *),
 									 void *data)
 {
-	/* TODO */
 	load_cb = func;
 	load_cb_data = data;
 }
@@ -1489,7 +1499,6 @@
 void
 purple_plugins_unregister_load_notify_cb(void (*func)(PurplePlugin *, void *))
 {
-	/* TODO */
 	load_cb = NULL;
 	load_cb_data = NULL;
 }
@@ -1498,7 +1507,6 @@
 purple_plugins_register_unload_notify_cb(void (*func)(PurplePlugin *, void *),
 									   void *data)
 {
-	/* TODO */
 	unload_cb = func;
 	unload_cb_data = data;
 }
@@ -1506,7 +1514,6 @@
 void
 purple_plugins_unregister_unload_notify_cb(void (*func)(PurplePlugin *, void *))
 {
-	/* TODO */
 	unload_cb = NULL;
 	unload_cb_data = NULL;
 }
--- a/libpurple/plugin.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/plugin.h	Fri Nov 16 23:32:17 2007 +0000
@@ -70,11 +70,6 @@
  *
  * This is used in the version 2.0 API and up.
  */
-/* TODO We need to figure out exactly what parts of this are required. The
- * dependent plugin unloading stuff was causing crashes with perl and tcl
- * plugins because they didn't set ids and the dependency code was requiring
- * them. Then we need to actually make sure that plugins have all the right
- * parts before loading them. */
 struct _PurplePluginInfo
 {
 	unsigned int magic;
@@ -296,6 +291,18 @@
 gboolean purple_plugin_unload(PurplePlugin *plugin);
 
 /**
+ * Disable a plugin.
+ *
+ * This function adds the plugin to a list of plugins to "disable at the next
+ * startup" by excluding said plugins from the list of plugins to save.  The
+ * UI needs to call purple_plugins_save_loaded() after calling this for it
+ * to have any effect.
+ *
+ * @since 2.3.0
+ */
+void purple_plugin_disable(PurplePlugin *plugin);
+
+/**
  * Reloads a plugin.
  *
  * @param plugin The old plugin handle.
@@ -525,53 +532,71 @@
  */
 gboolean purple_plugins_enabled(void);
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Registers a function that will be called when probing is finished.
  *
  * @param func The callback function.
  * @param data Data to pass to the callback.
+ * @deprecated If you need this, ask for a plugin-probe signal to be added.
  */
 void purple_plugins_register_probe_notify_cb(void (*func)(void *), void *data);
+#endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Unregisters a function that would be called when probing is finished.
  *
  * @param func The callback function.
+ * @deprecated If you need this, ask for a plugin-probe signal to be added.
  */
 void purple_plugins_unregister_probe_notify_cb(void (*func)(void *));
+#endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Registers a function that will be called when a plugin is loaded.
  *
  * @param func The callback function.
  * @param data Data to pass to the callback.
+ * @deprecated Use the plugin-load signal instead.
  */
 void purple_plugins_register_load_notify_cb(void (*func)(PurplePlugin *, void *),
 										  void *data);
+#endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Unregisters a function that would be called when a plugin is loaded.
  *
  * @param func The callback function.
+ * @deprecated Use the plugin-load signal instead.
  */
 void purple_plugins_unregister_load_notify_cb(void (*func)(PurplePlugin *, void *));
+#endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Registers a function that will be called when a plugin is unloaded.
  *
  * @param func The callback function.
  * @param data Data to pass to the callback.
+ * @deprecated Use the plugin-unload signal instead.
  */
 void purple_plugins_register_unload_notify_cb(void (*func)(PurplePlugin *, void *),
 											void *data);
+#endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Unregisters a function that would be called when a plugin is unloaded.
  *
  * @param func The callback function.
+ * @deprecated Use the plugin-unload signal instead.
  */
 void purple_plugins_unregister_unload_notify_cb(void (*func)(PurplePlugin *,
 														   void *));
+#endif
 
 /**
  * Finds a plugin with the specified name.
--- a/libpurple/pluginpref.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/pluginpref.h	Fri Nov 16 23:32:17 2007 +0000
@@ -35,16 +35,16 @@
  */
 typedef enum
 {
-	PURPLE_STRING_FORMAT_TYPE_NONE      = 0,
-	PURPLE_STRING_FORMAT_TYPE_MULTILINE = 1 << 0,
-	PURPLE_STRING_FORMAT_TYPE_HTML      = 1 << 1
+	PURPLE_STRING_FORMAT_TYPE_NONE      = 0,          /**< The string is plain text. */
+	PURPLE_STRING_FORMAT_TYPE_MULTILINE = 1 << 0,     /**< The string can have newlines. */
+	PURPLE_STRING_FORMAT_TYPE_HTML      = 1 << 1      /**< The string can be in HTML. */
 } PurpleStringFormatType;
 
 typedef enum {
 	PURPLE_PLUGIN_PREF_NONE,
 	PURPLE_PLUGIN_PREF_CHOICE,
-	PURPLE_PLUGIN_PREF_INFO,   /**< no-value label */
-	PURPLE_PLUGIN_PREF_STRING_FORMAT
+	PURPLE_PLUGIN_PREF_INFO,              /**< no-value label */
+	PURPLE_PLUGIN_PREF_STRING_FORMAT      /**< The preference has a string value. */
 } PurplePluginPrefType;
 
 #include <glib.h>
--- a/libpurple/plugins/log_reader.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/plugins/log_reader.c	Fri Nov 16 23:32:17 2007 +0000
@@ -28,6 +28,19 @@
 	NAME_GUESS_THEM
 };
 
+/* Some common functions. */
+static int get_month(const char *month)
+{
+	int iter;
+	const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
+	for (iter = 0; months[iter]; iter++) {
+		if (strcmp(month, months[iter]) == 0)
+			break;
+	}
+	return iter;
+}
+
 
 /*****************************************************************************
  * Adium Logger                                                              *
@@ -103,9 +116,10 @@
 				} else {
 					char *filename = g_build_filename(path, file, NULL);
 					FILE *handle = g_fopen(filename, "rb");
-					char *contents;
+					char contents[57];   /* XXX: This is really inflexible. */
 					char *contents2;
 					struct adium_logger_data *data;
+					size_t rd;
 					PurpleLog *log;
 
 					if (!handle) {
@@ -113,11 +127,9 @@
 						continue;
 					}
 
-					/* XXX: This is really inflexible. */
-					contents = g_malloc(57);
-					fread(contents, 56, 1, handle);
+					rd = fread(contents, 1, 56, handle) == 0;
 					fclose(handle);
-					contents[56] = '\0';
+					contents[rd] = '\0';
 
 					/* XXX: This is fairly inflexible. */
 					contents2 = contents;
@@ -135,11 +147,9 @@
 
 						purple_debug_error("Adium log parse",
 						                   "Contents timestamp parsing error\n");
-						g_free(contents);
 						g_free(filename);
 						continue;
 					}
-					g_free(contents);
 
 					data = g_new0(struct adium_logger_data, 1);
 					data->path = filename;
@@ -168,21 +178,20 @@
 				} else {
 					char *filename = g_build_filename(path, file, NULL);
 					FILE *handle = g_fopen(filename, "rb");
-					char *contents;
+					char contents[14];   /* XXX: This is really inflexible. */
 					char *contents2;
 					struct adium_logger_data *data;
 					PurpleLog *log;
+					size_t rd;
 
 					if (!handle) {
 						g_free(filename);
 						continue;
 					}
 
-					/* XXX: This is really inflexible. */
-					contents = g_malloc(14);
-					fread(contents, 13, 1, handle);
+					rd = fread(contents, 1, 13, handle);
 					fclose(handle);
-					contents[13] = '\0';
+					contents[rd] = '\0';
 
 					contents2 = contents;
 					while (*contents2 && *contents2 != '(')
@@ -195,13 +204,10 @@
 
 						purple_debug_error("Adium log parse",
 						                   "Contents timestamp parsing error\n");
-						g_free(contents);
 						g_free(filename);
 						continue;
 					}
 
-					g_free(contents);
-
 					tm.tm_year -= 1900;
 					tm.tm_mon  -= 1;
 
@@ -1355,36 +1361,7 @@
 						 * daylight savings time.
 						 */
 						tm.tm_isdst = -1;
-
-						/* Ugly hack, in case current locale
-						 * is not English. This code is taken
-						 * from log.c.
-						 */
-						if (strcmp(month, "Jan") == 0) {
-							tm.tm_mon= 0;
-						} else if (strcmp(month, "Feb") == 0) {
-							tm.tm_mon = 1;
-						} else if (strcmp(month, "Mar") == 0) {
-							tm.tm_mon = 2;
-						} else if (strcmp(month, "Apr") == 0) {
-							tm.tm_mon = 3;
-						} else if (strcmp(month, "May") == 0) {
-							tm.tm_mon = 4;
-						} else if (strcmp(month, "Jun") == 0) {
-							tm.tm_mon = 5;
-						} else if (strcmp(month, "Jul") == 0) {
-							tm.tm_mon = 6;
-						} else if (strcmp(month, "Aug") == 0) {
-							tm.tm_mon = 7;
-						} else if (strcmp(month, "Sep") == 0) {
-							tm.tm_mon = 8;
-						} else if (strcmp(month, "Oct") == 0) {
-							tm.tm_mon = 9;
-						} else if (strcmp(month, "Nov") == 0) {
-							tm.tm_mon = 10;
-						} else if (strcmp(month, "Dec") == 0) {
-							tm.tm_mon = 11;
-						}
+						tm.tm_mon = get_month(month);
 
 						data = g_new0(
 							struct trillian_logger_data, 1);
@@ -1446,7 +1423,7 @@
 
 	file = g_fopen(data->path, "rb");
 	fseek(file, data->offset, SEEK_SET);
-	fread(read, data->length, 1, file);
+	data->length = fread(read, 1, data->length, file);
 	fclose(file);
 
 	if (read[data->length-1] == '\n') {
@@ -1939,15 +1916,13 @@
 	g_return_val_if_fail(data->path != NULL, g_strdup(""));
 	g_return_val_if_fail(data->length > 0, g_strdup(""));
 
-	error = NULL;
-	
-	contents = g_malloc(data->length + 2);
-
 	file = g_fopen(data->path, "rb");
 	g_return_val_if_fail(file != NULL, g_strdup(""));
-	
+
+	contents = g_malloc(data->length + 2);
+
 	fseek(file, data->offset, SEEK_SET);
-	fread(contents, data->length, 1, file);
+	data->length = fread(contents, 1, data->length, file);
 	fclose(file);
 
 	contents[data->length] = '\n';
@@ -2026,7 +2001,7 @@
 					g_string_append(formatted, "</font> ");
 
 					if (is_in_message) {
-						if (buddy_name != NULL && buddy->alias) {
+						if (buddy_name != NULL && buddy != NULL && buddy->alias) {
 							g_string_append_printf(formatted,
 								"<span style=\"color: #A82F2F;\">"
 								"<b>%s</b></span>: ", buddy->alias);
@@ -2056,7 +2031,9 @@
 				g_string_append(formatted, line);
 				g_string_append(formatted, "<br>");
 			}
-			line = ++c;
+
+			if (c)
+				line = ++c;
 		}
 	}
 	g_free(contents);
@@ -2098,6 +2075,347 @@
 	g_free(data);
 }
 
+/*************************************************************************
+ * aMSN Logger                                                           *
+ *************************************************************************/
+
+/* The aMSN logger doesn't write logs, only reads them.  This is to include
+ * aMSN logs in the log viewer transparently.
+ */
+
+static PurpleLogLogger *amsn_logger;
+
+struct amsn_logger_data {
+	char *path;
+	int offset;
+	int length;
+};
+
+#define AMSN_LOG_CONV_START "|\"LRED[Conversation started on "
+#define AMSN_LOG_CONV_END "|\"LRED[You have closed the window on "
+#define AMSN_LOG_CONV_EXTRA "01 Aug 2001 00:00:00]"
+
+static GList *amsn_logger_parse_file(char *filename, const char *sn, PurpleAccount *account)
+{
+	GList *list = NULL;
+	GError *error;
+	char *contents;
+	struct amsn_logger_data *data;
+	PurpleLog *log;
+
+	purple_debug_info("aMSN logger", "Reading %s\n", filename);
+	error = NULL;
+	if (!g_file_get_contents(filename, &contents, NULL, &error)) {
+		purple_debug_error("aMSN logger",
+		                   "Couldn't read file %s: %s \n", filename,
+		                   (error && error->message) ?
+		                    error->message : "Unknown error");
+		if (error)
+			g_error_free(error);
+	} else {
+		char *c = contents;
+		gboolean found_start = FALSE;
+		char *start_log = c;
+		int offset = 0;
+		struct tm tm;
+		while (c && *c) {
+			if (purple_str_has_prefix(c, AMSN_LOG_CONV_START)) {
+				char month[4];
+				if (sscanf(c + strlen(AMSN_LOG_CONV_START),
+				           "%u %3s %u %u:%u:%u",
+				           &tm.tm_mday, (char*)&month, &tm.tm_year,
+				           &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
+					found_start = FALSE;
+					purple_debug_error("aMSN logger",
+					                   "Error parsing start date for %s\n",
+					                   filename);
+				} else {
+					tm.tm_year -= 1900;
+
+					/* Let the C library deal with
+					 * daylight savings time.
+					 */
+					tm.tm_isdst = -1;
+					tm.tm_mon = get_month(month);
+
+					found_start = TRUE;
+					offset = c - contents;
+					start_log = c;
+				}
+			} else if (purple_str_has_prefix(c, AMSN_LOG_CONV_END) && found_start) {
+				data = g_new0(struct amsn_logger_data, 1);
+				data->path = g_strdup(filename);
+				data->offset = offset;
+				data->length = c - start_log
+					             + strlen(AMSN_LOG_CONV_END)
+					             + strlen(AMSN_LOG_CONV_EXTRA);
+				log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, mktime(&tm), NULL);
+				log->logger = amsn_logger;
+				log->logger_data = data;
+				list = g_list_prepend(list, log);
+				found_start = FALSE;
+
+				purple_debug_info("aMSN logger",
+				                  "Found log for %s:"
+				                  " path = (%s),"
+				                  " offset = (%d),"
+				                  " length = (%d)\n",
+				                  sn, data->path, data->offset, data->length);
+			}
+			c = strstr(c, "\n");
+			c++;
+		}
+
+		/* I've seen the file end without the AMSN_LOG_CONV_END bit */
+		if (found_start) {
+			data = g_new0(struct amsn_logger_data, 1);
+			data->path = g_strdup(filename);
+			data->offset = offset;
+			data->length = c - start_log
+				             + strlen(AMSN_LOG_CONV_END)
+				             + strlen(AMSN_LOG_CONV_EXTRA);
+			log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, mktime(&tm), NULL);
+			log->logger = amsn_logger;
+			log->logger_data = data;
+			list = g_list_prepend(list, log);
+			found_start = FALSE;
+
+			purple_debug_info("aMSN logger",
+			                  "Found log for %s:"
+			                  " path = (%s),"
+			                  " offset = (%d),"
+			                  " length = (%d)\n",
+			                  sn, data->path, data->offset, data->length);
+		}
+		g_free(contents);
+	}
+
+	return list;
+}
+
+/* `log_dir`/username@hotmail.com/logs/buddyname@hotmail.com.log */
+/* `log_dir`/username@hotmail.com/logs/Month Year/buddyname@hotmail.com.log */
+static GList *amsn_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
+{
+	GList *list = NULL;
+	const char *logdir;
+	char *username;
+	char *log_path;
+	char *buddy_log;
+	char *filename;
+	GDir *dir;
+	const char *name;
+
+	logdir = purple_prefs_get_string("/plugins/core/log_reader/amsn/log_directory");
+
+	/* By clearing the log directory path, this logger can be (effectively) disabled. */
+	if (!logdir || !*logdir)
+		return NULL;
+
+	/* aMSN only works with MSN/WLM */
+	if (strcmp(account->protocol_id, "prpl-msn"))
+		return NULL;
+
+	username = g_strdup(purple_normalize(account, account->username));
+	buddy_log = g_strdup_printf("%s.log", purple_normalize(account, sn));
+	log_path = g_build_filename(logdir, username, "logs", NULL);
+
+	/* First check in the top-level */
+	filename = g_build_filename(log_path, buddy_log, NULL);
+	if (g_file_test(filename, G_FILE_TEST_EXISTS))
+		list = amsn_logger_parse_file(filename, sn, account);
+	else
+		g_free(filename);
+
+	/* Check in previous months */
+	dir = g_dir_open(log_path, 0, NULL);
+	if (dir) {
+		while ((name = g_dir_read_name(dir)) != NULL) {
+			filename = g_build_filename(log_path, name, buddy_log, NULL);
+			if (g_file_test(filename, G_FILE_TEST_EXISTS))
+				list = g_list_concat(list, amsn_logger_parse_file(filename, sn, account));
+			g_free(filename);
+		}
+		g_dir_close(dir);
+	}
+
+	g_free(log_path);
+
+	/* New versions use 'friendlier' directory names */
+	purple_util_chrreplace(username, '@', '_');
+	purple_util_chrreplace(username, '.', '_');
+
+	log_path = g_build_filename(logdir, username, "logs", NULL);
+
+	/* First check in the top-level */
+	filename = g_build_filename(log_path, buddy_log, NULL);
+	if (g_file_test(filename, G_FILE_TEST_EXISTS))
+		list = g_list_concat(list, amsn_logger_parse_file(filename, sn, account));
+	g_free(filename);
+
+	/* Check in previous months */
+	dir = g_dir_open(log_path, 0, NULL);
+	if (dir) {
+		while ((name = g_dir_read_name(dir)) != NULL) {
+			filename = g_build_filename(log_path, name, buddy_log, NULL);
+			if (g_file_test(filename, G_FILE_TEST_EXISTS))
+				list = g_list_concat(list, amsn_logger_parse_file(filename, sn, account));
+			g_free(filename);
+		}
+		g_dir_close(dir);
+	}
+
+	g_free(log_path);
+	g_free(username);
+	g_free(buddy_log);
+
+	return list;
+}
+
+/* Really it's |"L, but the string's been escaped */
+#define AMSN_LOG_FORMAT_TAG "|&quot;L"
+
+static char *amsn_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
+{
+	struct amsn_logger_data *data;
+	FILE *file;
+	char *contents;
+	char *escaped;
+	GString *formatted;
+	char *start;
+	gboolean in_span = FALSE;
+
+	if (flags != NULL)
+		*flags = PURPLE_LOG_READ_NO_NEWLINE;
+
+	g_return_val_if_fail(log != NULL, g_strdup(""));
+
+	data = log->logger_data;
+
+	g_return_val_if_fail(data->path != NULL, g_strdup(""));
+	g_return_val_if_fail(data->length > 0, g_strdup(""));
+
+	contents = g_malloc(data->length + 2);
+
+	file = g_fopen(data->path, "rb");
+	g_return_val_if_fail(file != NULL, g_strdup(""));
+	
+	fseek(file, data->offset, SEEK_SET);
+	data->length = fread(contents, 1, data->length, file);
+	fclose(file);
+
+	contents[data->length] = '\n';
+	contents[data->length + 1] = '\0';
+
+	escaped = g_markup_escape_text(contents, -1);
+	g_free(contents);
+	contents = escaped;
+
+	formatted = g_string_sized_new(data->length + 2);
+
+	start = contents;
+	while (start && *start) {
+		char *end;
+		char *old_tag;
+		char *tag;
+		end = strstr(start, "\n");
+		if (!end)
+			break;
+		*end = '\0';
+		if (purple_str_has_prefix(start, AMSN_LOG_FORMAT_TAG) && in_span) {
+			/* New format for this line */
+			g_string_append(formatted, "</span><br>");
+			in_span = FALSE;
+		} else if (start != contents) {
+			/* Continue format from previous line */
+			g_string_append(formatted, "<br>");
+		}
+		old_tag = start;
+		tag = strstr(start, AMSN_LOG_FORMAT_TAG);
+		while (tag) {
+			g_string_append_len(formatted, old_tag, tag - old_tag);
+			tag += strlen(AMSN_LOG_FORMAT_TAG);
+			if (in_span) {
+				g_string_append(formatted, "</span>");
+				in_span = FALSE;
+			}
+			if (*tag == 'C') {
+				/* |"LCxxxxxx is a hex colour */
+				char colour[7];
+				strncpy(colour, tag + 1, 6);
+				colour[6] = '\0';
+				g_string_append_printf(formatted, "<span style=\"color: #%s;\">", colour);
+				/* This doesn't appear to work? */
+				/* g_string_append_printf(formatted, "<span style=\"color: #%6s;\">", tag + 1); */
+				in_span = TRUE;
+				old_tag = tag + 7; /* C + xxxxxx */
+			} else {
+				/* |"Lxxx is a 3-digit colour code */
+				if (purple_str_has_prefix(tag, "RED")) {
+					g_string_append(formatted, "<span style=\"color: red;\">");
+					in_span = TRUE;
+				} else if (purple_str_has_prefix(tag, "GRA")) {
+					g_string_append(formatted, "<span style=\"color: gray;\">");
+					in_span = TRUE;
+				} else if (purple_str_has_prefix(tag, "NOR")) {
+					g_string_append(formatted, "<span style=\"color: black;\">");
+					in_span = TRUE;
+				} else if (purple_str_has_prefix(tag, "ITA")) {
+					g_string_append(formatted, "<span style=\"color: blue;\">");
+					in_span = TRUE;
+				} else if (purple_str_has_prefix(tag, "GRE")) {
+					g_string_append(formatted, "<span style=\"color: darkgreen;\">");
+					in_span = TRUE;
+				} else {
+					purple_debug_info("aMSN logger", "Unknown colour format: %3s\n", tag);
+				}
+				old_tag = tag + 3;
+			}
+			tag = strstr(tag, AMSN_LOG_FORMAT_TAG);
+		}
+		g_string_append(formatted, old_tag);
+		start = end + 1;
+	}
+	if (in_span)
+		g_string_append(formatted, "</span>");
+
+	g_free(contents);
+
+	return g_string_free(formatted, FALSE);
+}
+
+static int amsn_logger_size(PurpleLog *log)
+{
+	struct amsn_logger_data *data;
+	char *text;
+	int size;
+
+	g_return_val_if_fail(log != NULL, 0);
+
+	data = log->logger_data;
+	
+	if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
+		return data ? data->length : 0;
+	}
+
+	text = amsn_logger_read(log, NULL);
+	size = strlen(text);
+	g_free(text);
+
+	return size;
+}
+
+static void amsn_logger_finalize(PurpleLog *log)
+{
+	struct amsn_logger_data *data;
+
+	g_return_if_fail(log != NULL);
+
+	data = log->logger_data;
+	g_free(data->path);
+	g_free(data);
+}
+
 /*****************************************************************************
  * Plugin Code                                                               *
  *****************************************************************************/
@@ -2347,6 +2665,19 @@
 #endif
 	purple_prefs_add_string("/plugins/core/log_reader/qip/log_directory", path ? path : "");
 	g_free(path);
+
+	/* Add aMSN Messenger log directory preference. */
+	purple_prefs_add_none("/plugins/core/log_reader/amsn");
+
+	/* Calculate default aMSN log directory. */
+#ifdef _WIN32
+	folder = wpurple_get_special_folder(CSIDL_PROFILE); /* Silly aMSN, not using CSIDL_APPDATA */
+	path = g_build_filename(folder, "amsn", NULL);
+#else
+	path = g_build_filename(purple_home_dir(), ".amsn", NULL);
+#endif
+	purple_prefs_add_string("/plugins/core/log_reader/amsn/log_directory", path);
+	g_free(path);
 }
 
 static gboolean
@@ -2429,6 +2760,18 @@
 										  trillian_logger_size);
 	purple_log_logger_add(trillian_logger);
 
+	/* The names of IM clients are marked for translation at the request of
+	   translators who wanted to transliterate them.  Many translators
+	   choose to leave them alone.  Choose what's best for your language. */
+	amsn_logger = purple_log_logger_new("amsn", _("aMSN"), 6,
+									   NULL,
+									   NULL,
+									   amsn_logger_finalize,
+									   amsn_logger_list,
+									   amsn_logger_read,
+									   amsn_logger_size);
+	purple_log_logger_add(amsn_logger);
+
 	return TRUE;
 }
 
@@ -2445,6 +2788,7 @@
 	purple_log_logger_remove(msn_logger);
 	purple_log_logger_remove(trillian_logger);
 	purple_log_logger_remove(qip_logger);
+	purple_log_logger_remove(amsn_logger);
 
 	return TRUE;
 }
@@ -2505,6 +2849,10 @@
 		"/plugins/core/log_reader/trillian/log_directory", _("Trillian"));
 	purple_plugin_pref_frame_add(frame, ppref);
 
+	ppref = purple_plugin_pref_new_with_name_and_label(
+		"/plugins/core/log_reader/amsn/log_directory", _("aMSN"));
+	purple_plugin_pref_frame_add(frame, ppref);
+
 	return frame;
 }
 
--- a/libpurple/plugins/signals-test.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/plugins/signals-test.c	Fri Nov 16 23:32:17 2007 +0000
@@ -76,6 +76,28 @@
 					old, purple_account_get_alias(account));
 }
 
+static int
+account_authorization_requested_cb(PurpleAccount *account, const char *user, gpointer data)
+{
+	purple_debug_misc("signals test", "account-authorization-requested (%s, %s)\n",
+			purple_account_get_username(account), user);
+	return 0;
+}
+
+static void
+account_authorization_granted_cb(PurpleAccount *account, const char *user, gpointer data)
+{
+	purple_debug_misc("signals test", "account-authorization-granted (%s, %s)\n",
+			purple_account_get_username(account), user);
+}
+
+static void
+account_authorization_denied_cb(PurpleAccount *account, const char *user, gpointer data)
+{
+	purple_debug_misc("signals test", "account-authorization-denied (%s, %s)\n",
+			purple_account_get_username(account), user);
+}
+
 /**************************************************************************
  * Buddy Icons signal callbacks
  **************************************************************************/
@@ -568,6 +590,12 @@
 						plugin, PURPLE_CALLBACK(account_status_changed), NULL);
 	purple_signal_connect(accounts_handle, "account-alias-changed",
 						plugin, PURPLE_CALLBACK(account_alias_changed), NULL);
+	purple_signal_connect(accounts_handle, "account-authorization-requested",
+						plugin, PURPLE_CALLBACK(account_authorization_requested_cb), NULL);
+	purple_signal_connect(accounts_handle, "account-authorization-denied",
+						plugin, PURPLE_CALLBACK(account_authorization_denied_cb), NULL);
+	purple_signal_connect(accounts_handle, "account-authorization-granted",
+						plugin, PURPLE_CALLBACK(account_authorization_granted_cb), NULL);
 
 	/* Buddy List subsystem signals */
 	purple_signal_connect(blist_handle, "buddy-status-changed",
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Fri Nov 16 23:32:17 2007 +0000
@@ -884,7 +884,7 @@
 	gnutls_x509_crt crt_dat;
 	/* GnuTLS time functions return this on error */
 	const time_t errval = (time_t) (-1);
-
+	gboolean success = TRUE;
 
 	g_return_val_if_fail(crt, FALSE);
 	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
@@ -893,16 +893,16 @@
 
 	if (activation) {
 		*activation = gnutls_x509_crt_get_activation_time(crt_dat);
+		if (*activation == errval)
+			success = FALSE;
 	}
 	if (expiration) {
 		*expiration = gnutls_x509_crt_get_expiration_time(crt_dat);
+		if (*expiration == errval)
+			success = FALSE;
 	}
 
-	if (*activation == errval || *expiration == errval) {
-		return FALSE;
-	}
-
-	return TRUE;
+	return success;
 }
 
 /* X.509 certificate operations provided by this plugin */
--- a/libpurple/prefs.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/prefs.h	Fri Nov 16 23:32:17 2007 +0000
@@ -291,6 +291,8 @@
  * @return A list of newly allocated strings denoting the names of the children.
  *         Returns @c NULL if there are no children or if pref doesn't exist.
  *         The caller must free all the strings and the list.
+ *
+ * @since 2.1.0
  */
 GList *purple_prefs_get_children_names(const char *name);
 
--- a/libpurple/protocols/bonjour/Makefile.mingw	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/bonjour/Makefile.mingw	Fri Nov 16 23:32:17 2007 +0000
@@ -36,7 +36,7 @@
 			-I$(PIDGIN_TREE_TOP)
 
 LIB_PATHS +=		-L$(GTK_TOP)/lib \
-			-L$(BONJOUR_TOP)/lib \
+			-L$(BONJOUR_TOP)/lib/win32 \
 			-L$(LIBXML2_TOP)/lib \
 			-L$(PURPLE_TOP)
 
--- a/libpurple/protocols/bonjour/buddy.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Fri Nov 16 23:32:17 2007 +0000
@@ -62,9 +62,11 @@
 }
 
 void
-set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, uint32_t len){
+set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, guint32 len){
 	gchar **fld = NULL;
 
+	g_return_if_fail(record_key != NULL);
+
 	if (!strcmp(record_key, "1st"))
 		fld = &buddy->first;
 	else if(!strcmp(record_key, "email"))
--- a/libpurple/protocols/bonjour/buddy.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Fri Nov 16 23:32:17 2007 +0000
@@ -83,7 +83,7 @@
 /**
  * Sets a value in the BonjourBuddy struct, destroying the old value
  */
-void set_bonjour_buddy_value(BonjourBuddy *buddy, const char *record_key, const char *value, uint32_t len);
+void set_bonjour_buddy_value(BonjourBuddy *buddy, const char *record_key, const char *value, guint32 len);
 
 /**
  * Check if all the compulsory buddy data is present.
--- a/libpurple/protocols/bonjour/parser.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/bonjour/parser.c	Fri Nov 16 23:32:17 2007 +0000
@@ -64,7 +64,7 @@
 			char *attrib_ns = NULL;
 
 			if (attributes[i+2]) {
-				attrib_ns = g_strdup((char*)attributes[i+2]);;
+				attrib_ns = g_strdup((char*)attributes[i+2]);
 			}
 
 			memcpy(attrib, attributes[i+3], attrib_len);
--- a/libpurple/protocols/jabber/chat.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/chat.c	Fri Nov 16 23:32:17 2007 +0000
@@ -964,7 +964,7 @@
 static void jabber_chat_disco_traffic_cb(JabberStream *js, xmlnode *packet, gpointer data)
 {
 	JabberChat *chat;
-	xmlnode *query;
+	/*xmlnode *query;*/
 	int id = GPOINTER_TO_INT(data);
 
 	if(!(chat = jabber_chat_find_by_id(js, id)))
@@ -974,6 +974,8 @@
 	 * support this request */
 	chat->xhtml = TRUE;
 
+	/* disabling this until more MUC servers support
+	 * announcing this
 	if(xmlnode_get_child(packet, "error")) {
 		return;
 	}
@@ -981,8 +983,6 @@
 	if(!(query = xmlnode_get_child(packet, "query")))
 		return;
 
-	/* disabling this until more MUC servers support
-	 * announcing this
 	chat->xhtml = FALSE;
 
 	for(x = xmlnode_get_child(query, "feature"); x; x = xmlnode_get_next_twin(x)) {
--- a/libpurple/protocols/jabber/google.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/google.c	Fri Nov 16 23:32:17 2007 +0000
@@ -110,7 +110,7 @@
 		tos[i] = (to_name != NULL ?  to_name : "");
 		froms[i] = (from != NULL ?  from : "");
 		subjects[i] = (subject != NULL ? subject : g_strdup(""));
-		urls[i] = (url != NULL ? url : "");
+		urls[i] = url;
 
 		tid = xmlnode_get_attrib(message, "tid");
 		if (tid &&
@@ -516,3 +516,22 @@
 	}
 	return g_string_free(str, FALSE);
 }
+
+void jabber_google_presence_incoming(JabberStream *js, const char *user, JabberBuddyResource *jbr)
+{
+	if (!js->googletalk)
+		return;
+	if (jbr->status && !strncmp(jbr->status, "♫ ", strlen("♫ "))) {
+		purple_prpl_got_user_status(js->gc->account, user, "tune",
+					    PURPLE_TUNE_TITLE, jbr->status + strlen("♫ "), NULL);
+		jbr->status = NULL;
+	} else {
+		purple_prpl_got_user_status_deactive(js->gc->account, user, "tune");
+	}
+}
+
+char *jabber_google_presence_outgoing(PurpleStatus *tune)
+{
+	char *ret = g_strdup_printf("♫ %s", purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE));
+	return ret;
+}
--- a/libpurple/protocols/jabber/google.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/google.h	Fri Nov 16 23:32:17 2007 +0000
@@ -36,6 +36,10 @@
  * if this roster item should continue to be processed
  */
 gboolean jabber_google_roster_incoming(JabberStream *js, xmlnode *item);
+
+void jabber_google_presence_incoming(JabberStream *js, const char *who, JabberBuddyResource *jbr);
+char *jabber_google_presence_outgoing(PurpleStatus *tune);
+
 void jabber_google_roster_add_deny(PurpleConnection *gc, const char *who);
 void jabber_google_roster_rem_deny(PurpleConnection *gc, const char *who);
 
--- a/libpurple/protocols/jabber/iq.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/iq.c	Fri Nov 16 23:32:17 2007 +0000
@@ -248,7 +248,6 @@
 	JabberIq *iq;
 	const char *type, *from, *id;
 	xmlnode *query;
-	char *os = NULL;
 
 	type = xmlnode_get_attrib(packet, "type");
 
@@ -256,6 +255,7 @@
 		GHashTable *ui_info;
 		const char *ui_name = NULL, *ui_version = NULL;
 #if 0
+		char *os = NULL;
 		if(!purple_prefs_get_bool("/plugins/prpl/jabber/hide_os")) {
 			struct utsname osinfo;
 
@@ -290,10 +290,12 @@
 			xmlnode_insert_data(xmlnode_new_child(query, "version"), VERSION, -1);
 		}
 
+#if 0
 		if(os) {
 			xmlnode_insert_data(xmlnode_new_child(query, "os"), os, -1);
 			g_free(os);
 		}
+#endif
 
 		jabber_iq_send(iq);
 	}
--- a/libpurple/protocols/jabber/jabber.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Fri Nov 16 23:32:17 2007 +0000
@@ -1400,10 +1400,11 @@
 		char *stripped;
 
 		if(!(stripped = purple_markup_strip_html(jabber_buddy_get_status_msg(jb)))) {
-			PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(b));
-
-			if(!purple_status_is_available(status))
-				stripped = g_strdup(purple_status_get_name(status));
+			PurplePresence *presence = purple_buddy_get_presence(b);
+			if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
+				PurpleStatus *status = purple_presence_get_status(presence, "tune");
+				stripped = g_strdup(purple_status_get_attr_string(status, PURPLE_TUNE_TITLE));
+			}
 		}
 
 		if(stripped) {
@@ -1429,6 +1430,7 @@
 
 	if(jb) {
 		JabberBuddyResource *jbr = NULL;
+		PurplePresence *presence = purple_buddy_get_presence(b);
 		const char *sub;
 		GList *l;
 		const char *mood;
@@ -1455,7 +1457,7 @@
 			
 			purple_notify_user_info_add_pair(user_info, _("Subscription"), sub);
 			
-			status = purple_presence_get_active_status(purple_buddy_get_presence(b));
+			status = purple_presence_get_active_status(presence);
 			value = purple_status_get_attr_value(status, "mood");
 			if (value && purple_value_get_type(value) == PURPLE_TYPE_STRING && (mood = purple_value_get_string(value))) {
 				
@@ -1467,7 +1469,12 @@
 					g_free(moodplustext);
 				} else
 					purple_notify_user_info_add_pair(user_info, _("Mood"), mood);
-		}
+			}
+			if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {	
+				PurpleStatus *tune = purple_presence_get_status(presence, "tune");
+				const char *title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
+				purple_notify_user_info_add_pair(user_info, _("Current media"), title);
+			}
 		}
 
 		for(l=jb->resources; l; l = l->next) {
@@ -1532,15 +1539,6 @@
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
 			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
 			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
-			PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
-			PURPLE_TUNE_URL, _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
 			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
 			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
@@ -1555,15 +1553,6 @@
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
 			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
 			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
-			PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
-			PURPLE_TUNE_URL, _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
 			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
 			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
@@ -1578,15 +1567,6 @@
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
 			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
 			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
-			PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
-			PURPLE_TUNE_URL, _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
 			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
 			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
@@ -1601,15 +1581,6 @@
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
 			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
 			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
-			PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
-			PURPLE_TUNE_URL, _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
 			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
 			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
@@ -1624,15 +1595,6 @@
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
 			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
 			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
-			PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
-			PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
-			PURPLE_TUNE_URL, _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
 			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
 			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
@@ -1650,6 +1612,20 @@
 			NULL);
 	types = g_list_append(types, type);
 
+	type = purple_status_type_new_with_attrs(PURPLE_STATUS_TUNE,
+			"tune", NULL, TRUE, TRUE, TRUE,
+			PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			PURPLE_TUNE_URL, _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			NULL);
+	types = g_list_append(types, type);
+
 	return types;
 }
 
@@ -1914,7 +1890,7 @@
 			text = _("Authentication Failure");
 		}
 	} else if(!strcmp(packet->name, "stream:error") ||
-			 (!strcmp(packet->name, "error") &&
+			 (!strcmp(packet->name, "error") && xmlns &&
 				!strcmp(xmlns, "http://etherx.jabber.org/streams"))) {
 		if(xmlnode_get_child(packet, "bad-format")) {
 			text = _("Bad Format");
@@ -2189,55 +2165,67 @@
 	return PURPLE_CMD_RET_OK;
 }
 
+static gboolean _jabber_send_buzz(JabberStream *js, const char *username, char **error) {
+
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+	GList *iter;
+
+	if(!username)
+		return FALSE;
+
+	jb = jabber_buddy_find(js, username, FALSE);
+	if(!jb) {
+		*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), username);
+		return FALSE;
+	}
+
+	jbr = jabber_buddy_find_resource(jb, NULL);
+	if(!jbr) {
+		*error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), username);
+		return FALSE;
+	}
+
+	if(!jbr->caps) {
+		*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), username);
+		return FALSE;
+	}
+
+	for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
+		if(!strcmp(iter->data, "http://www.xmpp.org/extensions/xep-0224.html#ns")) {
+			xmlnode *buzz, *msg = xmlnode_new("message");
+			gchar *to;
+
+			to = g_strdup_printf("%s/%s", username, jbr->name);
+			xmlnode_set_attrib(msg, "to", to);
+			g_free(to);
+
+			/* avoid offline storage */
+			xmlnode_set_attrib(msg, "type", "headline");
+
+			buzz = xmlnode_new_child(msg, "attention");
+			xmlnode_set_namespace(buzz, "http://www.xmpp.org/extensions/xep-0224.html#ns");
+
+			jabber_send(js, msg);
+			xmlnode_free(msg);
+
+			return TRUE;
+		}
+	}
+
+	*error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), username);
+	return FALSE;
+}
+
 static PurpleCmdRet jabber_cmd_buzz(PurpleConversation *conv,
 		const char *cmd, char **args, char **error, void *data)
 {
 	JabberStream *js = conv->account->gc->proto_data;
-	xmlnode *msg, *buzz;
-	JabberBuddy *jb;
-	JabberBuddyResource *jbr;
-	char *to;
-	GList *iter;
 
 	if(!args || !args[0])
 		return PURPLE_CMD_RET_FAILED;
-	
-	jb = jabber_buddy_find(js, args[0], FALSE);
-	if(!jb) {
-		*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), args[0]);
-		return PURPLE_CMD_RET_FAILED;
-	}
-	
-	jbr = jabber_buddy_find_resource(jb, NULL);
-	if(!jbr) {
-		*error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), args[0]);
-		return PURPLE_CMD_RET_FAILED;
-	}
-	if(!jbr->caps) {
-		*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), args[0]);
-		return PURPLE_CMD_RET_FAILED;
-	}
-	for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
-		if(!strcmp(iter->data, "http://www.xmpp.org/extensions/xep-0224.html#ns")) {
-			msg = xmlnode_new("message");
-			to = g_strdup_printf("%s/%s", args[0], jbr->name);
-			xmlnode_set_attrib(msg,"to",to);
-			g_free(to);
-			
-			/* avoid offline storage */
-			xmlnode_set_attrib(msg,"type","headline");
-			
-			buzz = xmlnode_new_child(msg,"attention");
-			xmlnode_set_namespace(buzz,"http://www.xmpp.org/extensions/xep-0224.html#ns");
-			
-			jabber_send(js,msg);
-			xmlnode_free(msg);
-			
-			return PURPLE_CMD_RET_OK;
-		}
-	}
-	*error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), args[0]);
-	return PURPLE_CMD_RET_FAILED;
+
+	return _jabber_send_buzz(js, args[0], error)  ? PURPLE_CMD_RET_OK : PURPLE_CMD_RET_FAILED;
 }
 
 GList *jabber_attention_types(PurpleAccount *account)
@@ -2258,23 +2246,16 @@
 
 gboolean jabber_send_attention(PurpleConnection *gc, const char *username, guint code)
 {
-	PurpleConversation *conv;
-	char *error;
-	char *args[1];
-	PurpleCmdRet ret;
-
-	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, username, gc->account);
-
-	args[0] = (char *)username;
+	JabberStream *js = gc->proto_data;
+	gchar *error = NULL;
 
-	ret = jabber_cmd_buzz(conv, "buzz", args, &error, NULL);
-
-	if (ret == PURPLE_CMD_RET_FAILED) {
+	if (!_jabber_send_buzz(js, username, &error)) {
 		purple_debug_error("jabber", "jabber_send_attention: jabber_cmd_buzz failed with error: %s\n", error ? error : "(NULL)");
+		g_free(error);
 		return FALSE;
-	} else {
-		return TRUE;
 	}
+
+	return TRUE;
 }
 
 
--- a/libpurple/protocols/jabber/message.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/message.c	Fri Nov 16 23:32:17 2007 +0000
@@ -104,6 +104,7 @@
 
 					g_snprintf(buf, sizeof(buf),
 					           _("%s has left the conversation."), escaped);
+					g_free(escaped);
 
 					/* At some point when we restructure PurpleConversation,
 					 * this should be able to be implemented by removing the
--- a/libpurple/protocols/jabber/parser.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/parser.c	Fri Nov 16 23:32:17 2007 +0000
@@ -80,7 +80,7 @@
 			char *attrib_ns = NULL;
 
 			if (attributes[i+2]) {
-				attrib_ns = g_strdup((char*)attributes[i+2]);;
+				attrib_ns = g_strdup((char*)attributes[i+2]);
 			}
 
 			memcpy(attrib, attributes[i+3], attrib_len);
--- a/libpurple/protocols/jabber/presence.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/presence.c	Fri Nov 16 23:32:17 2007 +0000
@@ -33,6 +33,7 @@
 
 #include "buddy.h"
 #include "chat.h"
+#include "google.h"
 #include "presence.h"
 #include "iq.h"
 #include "jutil.h"
@@ -104,13 +105,14 @@
 	char *stripped = NULL;
 	JabberBuddyState state;
 	int priority;
-	const char *artist, *title, *source, *uri, *track;
-	int length;
+	const char *artist = NULL, *title = NULL, *source = NULL, *uri = NULL, *track = NULL;
+	int length = -1;
 	gboolean allowBuzz;
+	PurplePresence *p = purple_account_get_presence(account);
+	PurpleStatus *tune;
 
-	if(NULL == status) {
-		PurplePresence *gpresence = purple_account_get_presence(account);
-		status = purple_presence_get_active_status(gpresence);
+	if (NULL == status) {
+		status = purple_presence_get_active_status(p);
 	}
 
 	if(!purple_status_is_active(status))
@@ -144,6 +146,12 @@
 	if (allowBuzz != js->allowBuzz || js->old_state != state || CHANGED(js->old_msg, stripped) ||
 		js->old_priority != priority || CHANGED(js->old_avatarhash, js->avatar_hash)) {
 		js->allowBuzz = allowBuzz;
+
+		if (js->googletalk && stripped == NULL && purple_presence_is_status_primitive_active(p, PURPLE_STATUS_TUNE)) {
+			tune = purple_presence_get_status(p, "tune");
+			stripped = jabber_google_presence_outgoing(tune);
+		}
+
 		presence = jabber_presence_create_js(js, state, stripped, priority);
 
 		if(js->avatar_hash) {
@@ -172,12 +180,16 @@
 	}
 					  	
 	/* next, check if there are any changes to the tune values */
-	artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
-	title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
-	source = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM);
-	uri = purple_status_get_attr_string(status, PURPLE_TUNE_URL);
-	track = purple_status_get_attr_string(status, PURPLE_TUNE_TRACK);
-	length = (!purple_status_get_attr_value(status, PURPLE_TUNE_TIME))?-1:purple_status_get_attr_int(status, PURPLE_TUNE_TIME);
+	tune = purple_presence_get_status(p, "tune");
+	if (tune && purple_status_is_active(tune)) {
+		artist = purple_status_get_attr_string(tune, PURPLE_TUNE_ARTIST);
+		title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
+		source = purple_status_get_attr_string(tune, PURPLE_TUNE_ALBUM);
+		uri = purple_status_get_attr_string(tune, PURPLE_TUNE_URL);
+		track = purple_status_get_attr_string(tune, PURPLE_TUNE_TRACK);
+		length = (!purple_status_get_attr_value(tune, PURPLE_TUNE_TIME)) ? -1 :
+				purple_status_get_attr_int(tune, PURPLE_TUNE_TIME);
+	}
 	
 	if(CHANGED(artist, js->old_artist) || CHANGED(title, js->old_title) || CHANGED(source, js->old_source) ||
 	   CHANGED(uri, js->old_uri) || CHANGED(track, js->old_track) || (length != js->old_length)) {
@@ -731,7 +743,8 @@
 		}
 
 		if((found_jbr = jabber_buddy_find_resource(jb, NULL))) {
-			purple_prpl_got_user_status(js->gc->account, buddy_name, jabber_buddy_state_get_status_id(found_jbr->state), "priority", found_jbr->priority, found_jbr->status ? "message" : NULL, found_jbr->status, NULL);
+			jabber_google_presence_incoming(js, buddy_name, found_jbr);
+			purple_prpl_got_user_status(js->gc->account, buddy_name, jabber_buddy_state_get_status_id(found_jbr->state), "priority", found_jbr->priority, "message", found_jbr->status, NULL);
 		} else {
 			purple_prpl_got_user_status(js->gc->account, buddy_name, "offline", status ? "message" : NULL, status, NULL);
 		}
--- a/libpurple/protocols/jabber/roster.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/roster.c	Fri Nov 16 23:32:17 2007 +0000
@@ -403,12 +403,12 @@
 void jabber_roster_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
 		PurpleGroup *group) {
 	GSList *buddies = purple_find_buddies(gc->account, buddy->name);
-	GSList *groups = NULL;
 
 	buddies = g_slist_remove(buddies, buddy);
 	if(buddies != NULL) {
 		PurpleBuddy *tmpbuddy;
 		PurpleGroup *tmpgroup;
+		GSList *groups = NULL;
 
 		while(buddies) {
 			tmpbuddy = buddies->data;
@@ -418,6 +418,7 @@
 		}
 
 		jabber_roster_update(gc->proto_data, buddy->name, groups);
+		g_slist_free(groups);
 	} else {
 		JabberIq *iq = jabber_iq_new_query(gc->proto_data, JABBER_IQ_SET,
 				"jabber:iq:roster");
@@ -429,9 +430,4 @@
 
 		jabber_iq_send(iq);
 	}
-
-	if(buddies)
-		g_slist_free(buddies);
-	if(groups)
-		g_slist_free(groups);
 }
--- a/libpurple/protocols/jabber/usermood.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/usermood.c	Fri Nov 16 23:32:17 2007 +0000
@@ -26,6 +26,7 @@
 #include <string.h>
 #include "internal.h"
 #include "request.h"
+#include "debug.h"
 
 static const char *moodstrings[] = {
 	"afraid",
@@ -145,9 +146,26 @@
 }
 
 static void do_mood_set_from_fields(PurpleConnection *gc, PurpleRequestFields *fields) {
-	JabberStream *js = gc->proto_data;
-	
-	jabber_mood_set(js, moodstrings[purple_request_fields_get_choice(fields, "mood")], purple_request_fields_get_string(fields, "text"));
+	JabberStream *js;
+	int max_mood_idx;
+	int selected_mood = purple_request_fields_get_choice(fields, "mood");
+
+	if (!PURPLE_CONNECTION_IS_VALID(gc)) {
+		purple_debug_error("jabber", "Unable to set mood; account offline.\n");
+		return;
+	}
+
+	js = gc->proto_data;
+
+	/* This is ugly, but protects us from unexpected values. */
+	for (max_mood_idx = 0; moodstrings[max_mood_idx]; max_mood_idx++);
+
+	if (selected_mood < 0 || selected_mood >= max_mood_idx) {
+		purple_debug_error("jabber", "Invalid mood index (%d) selected.\n", selected_mood);
+		return;
+	}
+
+	jabber_mood_set(js, moodstrings[selected_mood], purple_request_fields_get_string(fields, "text"));
 }
 
 static void do_mood_set_mood(PurplePluginAction *action) {
--- a/libpurple/protocols/jabber/usertune.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/jabber/usertune.c	Fri Nov 16 23:32:17 2007 +0000
@@ -35,7 +35,6 @@
 	xmlnode *tuneinfo, *tune;
 	PurpleJabberTuneInfo tuneinfodata;
 	JabberBuddyResource *resource;
-	const char *status_id;
 	
 	/* ignore the tune of people not on our buddy list */
 	if (!buddy || !item)
@@ -81,9 +80,8 @@
 			}
 		}
 	}
-	status_id = jabber_buddy_state_get_status_id(resource->state);
 
-	purple_prpl_got_user_status(js->gc->account, from, status_id,
+	purple_prpl_got_user_status(js->gc->account, from, "tune",
 			PURPLE_TUNE_ARTIST, tuneinfodata.artist,
 			PURPLE_TUNE_TITLE, tuneinfodata.title,
 			PURPLE_TUNE_ALBUM, tuneinfodata.album,
--- a/libpurple/protocols/msn/contact.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/msn/contact.c	Fri Nov 16 23:32:17 2007 +0000
@@ -1177,7 +1177,7 @@
 		purple_debug_warning("MSN CL", "Unable to retrieve user %s from the userlist!\n", passport);
 	}
 
-	if (user->uid != NULL) {
+	if (user != NULL && user->uid != NULL) {
 		contact_xml = g_strdup_printf(MSN_CONTACT_ID_XML, user->uid);
 	} else {
 		contact_xml = g_strdup_printf(MSN_CONTACT_XML, passport);
--- a/libpurple/protocols/msn/msn.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/msn/msn.c	Fri Nov 16 23:32:17 2007 +0000
@@ -539,25 +539,36 @@
 
 /*
  * Set the User status text
- * Add the PSM String Using "Name - PSM String" format
  */
 static char *
 msn_status_text(PurpleBuddy *buddy)
 {
 	PurplePresence *presence;
 	PurpleStatus *status;
-	const char *msg, *cmedia;
+	const char *msg;
 
 	presence = purple_buddy_get_presence(buddy);
 	status = purple_presence_get_active_status(presence);
 
+	/* I think status message should take precedence over media */
 	msg = purple_status_get_attr_string(status, "message");
-	cmedia = purple_status_get_attr_string(status, "currentmedia");
-
-	if (cmedia)
-		return g_markup_escape_text(cmedia, -1);
-	else if (msg)
+	if (msg && *msg)
 		return g_markup_escape_text(msg, -1);
+
+	if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
+		const char *title, *artist;
+		char *media, *esc;
+		status = purple_presence_get_status(presence, "tune");
+		title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
+		artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
+
+		media = g_strdup_printf("%s%s%s", title, artist ? " - " : "",
+				artist ? artist : "");
+		esc = g_markup_escape_text(media, -1);
+		g_free(media);
+		return esc;
+	}
+
 	return NULL;
 }
 
@@ -570,14 +581,21 @@
 
 	user = buddy->proto_data;
 
-
 	if (purple_presence_is_online(presence))
 	{
-		const char *psm, *currentmedia, *name;
+		const char *psm, *name;
+		char *currentmedia = NULL;
 		char *tmp;
 
 		psm = purple_status_get_attr_string(status, "message");
-		currentmedia = purple_status_get_attr_string(status, "currentmedia");
+		if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
+			PurpleStatus *tune = purple_presence_get_status(presence, "tune");
+			const char *title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
+			const char *artist = purple_status_get_attr_string(tune, PURPLE_TUNE_ARTIST);
+			currentmedia = g_strdup_printf("%s%s%s", title, artist ? " - " : "",
+					artist ? artist : "");
+			/* We could probably just use user->media.title etc. here */
+		}
 
 		if (!purple_presence_is_available(presence)) {
 			name = purple_status_get_name(status);
@@ -609,6 +627,7 @@
 			tmp = g_markup_escape_text(currentmedia, -1);
 			purple_notify_user_info_add_pair(user_info, _("Current media"), tmp);
 			g_free(tmp);
+			g_free(currentmedia);
 		}
 	}
 
@@ -632,40 +651,34 @@
 	status = purple_status_type_new_with_attrs(
 				PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE,
 				"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
-				"currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING),
 				NULL);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_with_attrs(
 			PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
-			"currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING),
 			NULL);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_with_attrs(
 			PURPLE_STATUS_AWAY, "brb", _("Be Right Back"), TRUE, TRUE, FALSE,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
-			"currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING),
 			NULL);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_with_attrs(
 			PURPLE_STATUS_UNAVAILABLE, "busy", _("Busy"), TRUE, TRUE, FALSE,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
-			"currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING),
 			NULL);
 	types = g_list_append(types, status);
 	status = purple_status_type_new_with_attrs(
 			PURPLE_STATUS_UNAVAILABLE, "phone", _("On the Phone"), TRUE, TRUE, FALSE,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
-			"currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING),
 			NULL);
 	types = g_list_append(types, status);
 	status = purple_status_type_new_with_attrs(
 			PURPLE_STATUS_AWAY, "lunch", _("Out to Lunch"), TRUE, TRUE, FALSE,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
-			"currentmedia", _("Current media"), purple_value_new(PURPLE_TYPE_STRING),
 			NULL);
 	types = g_list_append(types, status);
 
@@ -681,6 +694,14 @@
 			"mobile", NULL, FALSE, FALSE, TRUE);
 	types = g_list_append(types, status);
 
+	status = purple_status_type_new_with_attrs(PURPLE_STATUS_TUNE,
+			"tune", NULL, TRUE, TRUE, TRUE,
+			PURPLE_TUNE_ARTIST, _("Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_ALBUM, _("Album"), purple_value_new(PURPLE_TYPE_STRING),
+			PURPLE_TUNE_TITLE, _("Title"), purple_value_new(PURPLE_TYPE_STRING),
+			NULL);
+	types = g_list_append(types, status);
+
 	return types;
 }
 
@@ -943,7 +964,7 @@
 			imdata->msg = body_str;
 			imdata->flags = flags;
 			imdata->when = time(NULL);
-			g_idle_add(msn_send_me_im, imdata);
+			purple_timeout_add(0, msn_send_me_im, imdata);
 		}
 
 		msn_message_destroy(msg);
@@ -1103,7 +1124,7 @@
 	userlist = session->userlist;
 	who = msn_normalize(gc->account, buddy->name);
 
-	purple_debug_info("MSN","Add user:%s to group:%s\n", who, group->name);
+	purple_debug_info("MSN","Add user:%s to group:%s\n", who, (group && group->name) ? group->name : "(null)");
 	if (!session->logged_in)
 	{
 #if 0
--- a/libpurple/protocols/msn/notification.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/msn/notification.c	Fri Nov 16 23:32:17 2007 +0000
@@ -230,11 +230,9 @@
 {
 	MsnSession *session;
 	PurpleAccount *account;
-	PurpleConnection *gc;
 
 	session = cmdproc->session;
 	account = session->account;
-	gc = purple_account_get_connection(account);
 
 	if (!g_ascii_strcasecmp(cmd->params[1], "OK"))
 	{
@@ -1616,32 +1614,29 @@
 {
 	MsnSession *session;
 	PurpleAccount *account;
-	PurpleConnection *gc;
 	MsnUser *user;
 	const char *passport;
-	char *psm_str, *currentmedia_str, *str;
-
-	/*get the payload content*/
-//	purple_debug_info("MSNP14","UBX {%s} payload{%s}\n",cmd->params[0], cmd->payload);
+	char *psm_str, *str;
+	CurrentMedia media = {NULL, NULL, NULL};
 
 	session = cmdproc->session;
 	account = session->account;
-	gc = purple_account_get_connection(account);
 
 	passport = cmd->params[0];
 	user = msn_userlist_find_user(session->userlist, passport);
 	
 	psm_str = msn_get_psm(cmd->payload,len);
-	currentmedia_str = msn_parse_currentmedia(
-	                                 str = msn_get_currentmedia(cmd->payload, len));
+	msn_user_set_statusline(user, psm_str);
+	g_free(psm_str);
+
+	str = msn_get_currentmedia(cmd->payload, len);
+	if (msn_parse_currentmedia(str, &media))
+		msn_user_set_currentmedia(user, &media);
+	else
+		msn_user_set_currentmedia(user, NULL);
 	g_free(str);
 
-	msn_user_set_statusline(user, psm_str);
-	msn_user_set_currentmedia(user, currentmedia_str);
 	msn_user_update(user);
-
-	g_free(psm_str);
-	g_free(currentmedia_str);
 }
 
 static void
--- a/libpurple/protocols/msn/oim.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/msn/oim.c	Fri Nov 16 23:32:17 2007 +0000
@@ -608,8 +608,9 @@
 	purple_debug_info("MSN OIM:OIM", "%s", xmlmsg);
 
 	node = xmlnode_from_str(xmlmsg, strlen(xmlmsg));
-	if (strcmp(node->name, "MD") != 0) {
-		xmlnode_free(node);
+	if (!node || !node->name || strcmp(node->name, "MD") != 0) {
+		if (node)
+			xmlnode_free(node);
 		return;
 	}
 
--- a/libpurple/protocols/msn/state.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/msn/state.c	Fri Nov 16 23:32:17 2007 +0000
@@ -83,60 +83,56 @@
 }
 
 /* parse CurrentMedia string */
-char *
-msn_parse_currentmedia(const char *cmedia)
+gboolean
+msn_parse_currentmedia(const char *cmedia, CurrentMedia *media)
 {
 	char **cmedia_array;
-	GString *buffer = NULL;
 	int strings;
 
 	if ((cmedia == NULL) || (*cmedia == '\0')) {
 		purple_debug_info("msn", "No currentmedia string\n");
-		return NULL;
+		return FALSE;
 	}
 
 	purple_debug_info("msn", "Parsing currentmedia string: \"%s\"\n", cmedia);
 
 	cmedia_array = g_strsplit(cmedia, "\\0", 0);
 
+	/*
+	 * 0: Media Player
+	 * 1: 'Music'
+	 * 2: '1' if enabled, '0' if not
+	 * 3: Format (eg. {0} by {1})
+	 * 4: Title
+	 * 5: Artist
+	 * 6: Album
+	 * 7: ?
+	 */
 	strings = 0;
-	/* Yes, we want to skip the first element here, as it is empty due to
-	 * the cmedia string starting with \0 -- see the examples below. */
 	while (cmedia_array[++strings] != NULL);
 
-	/* The cmedia_array[2] field contains a 1 if enabled. */
-	if ((strings > 3) && (!strcmp(cmedia_array[2], "1"))) {
-		char *inptr = cmedia_array[3];
-
-		buffer = g_string_new(NULL);
-
-		while (*inptr != '\0') {
-			if ((*inptr == '{') && ((*(inptr + 1) != '\0') && (*(inptr+2) == '}'))) {
-				char *tmpptr;
-				int tmp;
-
-				errno = 0;
-				tmp = strtol(inptr + 1, &tmpptr, 10);
+	if (strings < 4)
+		return FALSE;
+	if (strcmp(cmedia_array[2], "1"))
+		return FALSE;
 
-				if (errno == 0 && tmpptr != inptr + 1 &&
-				    tmp + 4 < strings) {
-					/* Replace {?} tag with appropriate text only when successful.
-					 * Skip otherwise. */
-					buffer = g_string_append(buffer, cmedia_array[tmp + 4]);
-				}
-				inptr += 3; /* Skip to the next char after '}' */
-			} else {
-				buffer = g_string_append_c(buffer, *inptr++);
-			}
-		}
-		purple_debug_info("msn", "Parsed currentmedia string, result: \"%s\"\n",
-		                  buffer->str);
+	if (strings == 4) {
+		media->title = g_strdup(cmedia_array[3]);
 	} else {
-		purple_debug_info("msn", "Current media marked disabled, not parsing.\n");
+		media->title = g_strdup(cmedia_array[4]);
 	}
 
-	g_strfreev(cmedia_array);
-	return buffer ? g_string_free(buffer, FALSE) : NULL;
+	if (strings > 5)
+		media->artist = g_strdup(cmedia_array[5]);
+	else
+		media->artist = NULL;
+
+	if (strings > 6)
+		media->album = g_strdup(cmedia_array[6]);
+	else
+		media->album = NULL;
+
+	return TRUE;
 }
 
 /* get the CurrentMedia info from the XML string */
@@ -191,6 +187,27 @@
 	return psm;
 }
 
+static char *
+create_media_string(PurplePresence *presence)
+{
+	const char *artist, *title, *album;
+	char *ret;
+	PurpleStatus *status = purple_presence_get_status(presence, "tune");
+	if (!status || !purple_status_is_active(status))
+		return g_strdup_printf("WMP\\0Music\\00\\0{0} - {1}\\0\\0\\0\\0\\0");
+
+	artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
+	title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
+	album = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM);
+
+	ret = g_strdup_printf("WMP\\0Music\\0%c\\0{0} - {1}\\0%s\\0%s\\0%s\\0\\0",
+			(title && *title) ? '1' : '0',
+			title ? title : "",
+			artist ? artist : "",
+			album ? album : "");
+	return ret;
+}
+
 /* set the MSN's PSM info,Currently Read from the status Line 
  * Thanks for Cris Code
  */
@@ -204,7 +221,7 @@
 	MsnTransaction *trans;
 	char *payload;
 	const char *statusline;
-	gchar *unescapedstatusline;
+	gchar *unescapedstatusline, *media = NULL;
 
 	g_return_if_fail(session != NULL);
 	g_return_if_fail(session->notification != NULL);
@@ -219,8 +236,10 @@
 	status = purple_presence_get_active_status(presence);
 	statusline = purple_status_get_attr_string(status, "message");
 	unescapedstatusline = purple_unescape_html(statusline);
-	session->psm = msn_build_psm(unescapedstatusline, NULL, NULL);
+	media = create_media_string(presence);
+	session->psm = msn_build_psm(unescapedstatusline, media, NULL);
 	g_free(unescapedstatusline);
+	g_free(media);
 	payload = session->psm;
 
 	purple_debug_misc("MSNP14","Sending UUX command with payload: %s\n",payload);
--- a/libpurple/protocols/msn/state.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/msn/state.h	Fri Nov 16 23:32:17 2007 +0000
@@ -62,7 +62,7 @@
 void msn_set_psm(MsnSession *session);
 
 /* Parse CurrentMedia string */
-char * msn_parse_currentmedia(const char *cmedia);
+gboolean msn_parse_currentmedia(const char *cmedia, CurrentMedia *media);
 
 /* Get the CurrentMedia info from the XML string */
 char * msn_get_currentmedia(char *xml_str,gsize len);
--- a/libpurple/protocols/msn/user.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/msn/user.c	Fri Nov 16 23:32:17 2007 +0000
@@ -80,6 +80,9 @@
 	g_free(user->phone.home);
 	g_free(user->phone.work);
 	g_free(user->phone.mobile);
+	g_free(user->media.artist);
+	g_free(user->media.title);
+	g_free(user->media.album);
 
 	g_free(user);
 }
@@ -91,23 +94,24 @@
 
 	account = user->userlist->session->account;
 
-	if (user->statusline != NULL && user->currentmedia != NULL) {
+	if (user->status != NULL) {
+		gboolean offline = (strcmp(user->status, "offline") == 0);
 		purple_prpl_got_user_status(account, user->passport, user->status,
-		                          "message", user->statusline,
-		                          "currentmedia", user->currentmedia, NULL);
-	} else if (user->currentmedia != NULL) {
-		purple_prpl_got_user_status(account, user->passport, user->status, "currentmedia",
-		                          user->currentmedia, NULL);
-	} else if (user->statusline != NULL) {
-		//char *status = g_strdup_printf("%s - %s", user->status, user->statusline);
-		purple_prpl_got_user_status(account, user->passport, user->status,
-		                          "message", user->statusline, NULL);
-	} else if (user->status != NULL) {
-		if (!strcmp(user->status, "offline") && user->mobile) {
-			purple_prpl_got_user_status(account, user->passport, "offline", NULL);
+				"message", user->statusline, NULL);
+
+		if (!offline && user->media.title) {
+			purple_prpl_got_user_status(account, user->passport, "tune",
+					PURPLE_TUNE_ARTIST, user->media.artist,
+					PURPLE_TUNE_ALBUM, user->media.album,
+					PURPLE_TUNE_TITLE, user->media.title,
+					NULL);
+		} else {
+			purple_prpl_got_user_status_deactive(account, user->passport, "tune");
+		}
+
+		if (!offline && user->mobile) {
 			purple_prpl_got_user_status(account, user->passport, "mobile", NULL);
 		} else {
-			purple_prpl_got_user_status(account, user->passport, user->status, NULL);
 			purple_prpl_got_user_status_deactive(account, user->passport, "mobile");
 		}
 	}
@@ -172,12 +176,17 @@
 }
 
 void
-msn_user_set_currentmedia(MsnUser *user, const char *currentmedia)
+msn_user_set_currentmedia(MsnUser *user, const CurrentMedia *media)
 {
 	g_return_if_fail(user != NULL);
 
-	g_free(user->currentmedia);
-	user->currentmedia = g_strdup(currentmedia);
+	g_free(user->media.title);
+	g_free(user->media.album);
+	g_free(user->media.artist);
+
+	user->media.title  = media ? g_strdup(media->title) : NULL;
+	user->media.artist = media ? g_strdup(media->artist) : NULL;
+	user->media.album  = media ? g_strdup(media->album) : NULL;
 }
 
 void
--- a/libpurple/protocols/msn/user.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/msn/user.h	Fri Nov 16 23:32:17 2007 +0000
@@ -43,6 +43,16 @@
 } MsnUserType;
 
 /**
+ * Current media.
+ */
+typedef struct _CurrentMedia
+{
+	char *artist;   /**< Artist. */
+	char *album;    /**< Album.  */
+	char *title;    /**< Title.  */
+} CurrentMedia;
+
+/**
  * A user.
  */
 struct _MsnUser
@@ -60,7 +70,7 @@
 
 	const char *status;     /**< The state of the user.         */
 	char *statusline;       /**< The state of the user.         */	
-	char *currentmedia;     /**< The current media of the user. */
+	CurrentMedia media;     /**< Current media of the user.     */
 
 	gboolean idle;          /**< The idle state of the user.    */
 
@@ -134,10 +144,10 @@
  /**
   *  Sets the current media of user.
   * 
-  *  @param user The user.
-  *  @param state The statusline string.
+  *  @param user   The user.
+  *  @param cmedia Current media.
   */
-void msn_user_set_currentmedia(MsnUser *user, const char *currentmedia);
+void msn_user_set_currentmedia(MsnUser *user, const CurrentMedia *cmedia);
 
 /**
  * Sets the new state of user.
--- a/libpurple/protocols/msn/userlist.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/msn/userlist.c	Fri Nov 16 23:32:17 2007 +0000
@@ -227,11 +227,8 @@
 	}
 	else if (list_id == MSN_LIST_RL)
 	{
-		PurpleConnection *gc;
 		PurpleConversation *convo;
 
-		gc = purple_account_get_connection(account);
-
 		purple_debug_info("msn",
 						"%s has added you to his or her buddy list.\n",
 						passport);
--- a/libpurple/protocols/myspace/markup.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/myspace/markup.c	Fri Nov 16 23:32:17 2007 +0000
@@ -400,6 +400,8 @@
 msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, 
 		gchar **end)
 {
+	g_return_if_fail(root != NULL);
+
 	if (g_str_equal(root->name, "f")) {
 		msim_markup_f_to_html(session, root, begin, end);
 	} else if (g_str_equal(root->name, "a")) {
@@ -415,7 +417,7 @@
 	} else {
 		purple_debug_info("msim", "msim_markup_tag_to_html: "
 				"unknown tag name=%s, ignoring", 
-				(root && root->name) ? root->name : "(NULL)");
+				root->name ? root->name : "(NULL)");
 		*begin = g_strdup("");
 		*end = g_strdup("");
 	}
--- a/libpurple/protocols/myspace/message.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/myspace/message.c	Fri Nov 16 23:32:17 2007 +0000
@@ -314,7 +314,7 @@
 	MsimMessageElement *elem;
 	MsimMessage **new;
 	gpointer new_data;
-				
+
 	GString *gs;
 	MsimMessage *dict;
 
@@ -349,7 +349,7 @@
 
 		default:
 			purple_debug_info("msim", "msim_msg_clone_element: unknown type %d\n", elem->type);
-			g_return_if_fail(NULL);
+			g_return_if_reached();
 	}
 
 	/* Append cloned data. Note that the 'name' field is a static string, so it
@@ -905,7 +905,7 @@
 
 		default:
 			g_free(data_string);
-			g_return_if_fail(FALSE);
+			g_return_if_reached();
 			break;
 	}
 
--- a/libpurple/protocols/myspace/myspace.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Fri Nov 16 23:32:17 2007 +0000
@@ -2035,18 +2035,14 @@
 msim_do_postprocessing(MsimMessage *msg, const gchar *uid_before, 
 		const gchar *uid_field_name, guint uid)
 {
+	MsimMessageElement *elem;
 	msim_msg_dump("msim_do_postprocessing msg: %s\n", msg);
 
 	/* First, check - if the field already exists, replace <uid> within it */
-	if (msim_msg_get(msg, uid_field_name)) {
-		MsimMessageElement *elem;
+	if ((elem = msim_msg_get(msg, uid_field_name)) != NULL) {
 		gchar *fmt_string;
 		gchar *uid_str, *new_str;
 
-		/* Warning: this is a delicate, but safe, operation */
-
-		elem = msim_msg_get(msg, uid_field_name);
-
 		/* Get the packed element, flattening it. This allows <uid> to be
 		 * replaced within nested data structures, since the replacement is done
 		 * on the linear, packed data, not on a complicated data structure.
--- a/libpurple/protocols/oscar/oscar.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Fri Nov 16 23:32:17 2007 +0000
@@ -1128,8 +1128,10 @@
 	aim_clientready(od, conn);
 
 	chatcon = find_oscar_chat_by_conn(gc, conn);
-	chatcon->id = id;
-	chatcon->conv = serv_got_joined_chat(gc, id++, chatcon->show);
+	if (chatcon) {
+		chatcon->id = id;
+		chatcon->conv = serv_got_joined_chat(gc, id++, chatcon->show);
+	}
 }
 
 static void
--- a/libpurple/protocols/qq/buddy_info.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/qq/buddy_info.c	Fri Nov 16 23:32:17 2007 +0000
@@ -306,12 +306,10 @@
 	g_free(mid);
 }
 
-static gchar *parse_field(GList **list, gboolean choice)
+static gchar *parse_field(PurpleRequestField *field, gboolean choice)
 {
 	gchar *value;
-	PurpleRequestField *field;
 
-	field = (PurpleRequestField *) (*list)->data;
 	if (choice) {
 		value = g_strdup_printf("%d", purple_request_field_choice_get_value(field));
 	} else {
@@ -321,7 +319,6 @@
 		else
 			value = utf8_to_qq(value, QQ_CHARSET_DEFAULT);
 	}
-	*list = g_list_remove_link(*list, *list);
 
 	return value;
 }
@@ -331,7 +328,7 @@
 {
 	PurpleConnection *gc;
 	qq_data *qd;
-	GList *list,  *groups;
+	GList *groups;
 	contact_info *info;
 
 	gc = mid->gc;
@@ -341,33 +338,76 @@
 	info = mid->info;
 
 	groups = purple_request_fields_get_groups(fields);
-	list = purple_request_field_group_get_fields(groups->data);
-	info->uid = parse_field(&list, FALSE);
-	info->nick = parse_field(&list, FALSE);
-	info->name = parse_field(&list, FALSE);
-	info->age = parse_field(&list, FALSE);
-	info->gender = parse_field(&list, TRUE);
-	info->country = parse_field(&list, FALSE);
-	info->province = parse_field(&list, FALSE);
-	info->city = parse_field(&list, FALSE);
-	groups = g_list_remove_link(groups, groups);
-	list = purple_request_field_group_get_fields(groups->data);
-	info->horoscope = parse_field(&list, TRUE);
-	info->occupation = parse_field(&list, FALSE);
-	info->zodiac = parse_field(&list, TRUE);
-	info->blood = parse_field(&list, TRUE);
-	info->college = parse_field(&list, FALSE);
-	info->email = parse_field(&list, FALSE);
-	info->address = parse_field(&list, FALSE);
-	info->zipcode = parse_field(&list, FALSE);
-	info->hp_num = parse_field(&list, FALSE);
-	info->tel = parse_field(&list, FALSE);
-	info->homepage = parse_field(&list, FALSE);
-	groups = g_list_remove_link(groups, groups);
-	list = purple_request_field_group_get_fields(groups->data);
-	info->intro = parse_field(&list, FALSE);
-	groups = g_list_remove_link(groups, groups);
+	while (groups != NULL) {
+		PurpleRequestFieldGroup *group = groups->data;
+		const char *g_name = purple_request_field_group_get_title(group);
+		GList *fields = purple_request_field_group_get_fields(group);
+
+		if (g_name == NULL)
+			continue;
+
+		while (fields != NULL) {
+			PurpleRequestField *field = fields->data;
+			const char *f_id = purple_request_field_get_id(field);
+
+			if (!strcmp(QQ_PRIMARY_INFORMATION, g_name)) {
+
+				if (!strcmp(f_id, "uid"))
+					info->uid = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "nick"))
+					info->nick = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "name"))
+					info->name = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "age"))
+					info->age = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "gender"))
+					info->gender = parse_field(field, TRUE);
+				else if (!strcmp(f_id, "country"))
+					info->country = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "province"))
+					info->province = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "city"))
+					info->city = parse_field(field, FALSE);
+
+			} else if (!strcmp(QQ_ADDITIONAL_INFORMATION, g_name)) {
 
+				if (!strcmp(f_id, "horoscope"))
+					info->horoscope = parse_field(field, TRUE);
+				else if (!strcmp(f_id, "occupation"))
+					info->occupation = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "zodiac"))
+					info->zodiac = parse_field(field, TRUE);
+				else if (!strcmp(f_id, "blood"))
+					info->blood = parse_field(field, TRUE);
+				else if (!strcmp(f_id, "college"))
+					info->college = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "email"))
+					info->email = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "address"))
+					info->address = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "zipcode"))
+					info->zipcode = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "hp_num"))
+					info->hp_num = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "tel"))
+					info->tel = parse_field(field, FALSE);
+				else if (!strcmp(f_id, "homepage"))
+					info->homepage = parse_field(field, FALSE);
+
+			} else if (!strcmp(QQ_INTRO, g_name)) {
+
+				if (!strcmp(f_id, "intro"))
+					info->intro = parse_field(field, FALSE);
+
+			}
+
+			fields = fields->next;
+		}
+
+		groups = groups->next;
+	}
+
+	/* This casting looks like a horrible idea to me -DAA */
 	qq_send_packet_modify_info(gc, (gchar **) info);
 
 	g_strfreev((gchar **) mid->info);
@@ -437,6 +477,7 @@
 		add_string_field_to_group(group, "country", QQ_COUNTRY, info->country);
 		add_string_field_to_group(group, "province", QQ_PROVINCE, info->province);
 		add_string_field_to_group(group, "city", QQ_CITY, info->city);
+
 		group = setup_field_group(fields, QQ_ADDITIONAL_INFORMATION);
 		add_choice_field_to_group(group, "horoscope", QQ_HOROSCOPE, info->horoscope, horoscope_names, QQ_HOROSCOPE_SIZE);
 		add_string_field_to_group(group, "occupation", QQ_OCCUPATION, info->occupation);
--- a/libpurple/protocols/sametime/sametime.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Fri Nov 16 23:32:17 2007 +0000
@@ -5563,7 +5563,7 @@
     msgA = _("No matches");
     msgB = _("The identifier '%s' did not match any users in your"
 	     " Sametime community.");
-    msg = g_strdup_printf(msgB, NSTR(res->name));
+    msg = g_strdup_printf(msgB, (res && res->name) ? NSTR(res->name) : "");
 
     purple_notify_error(gc, _("No Matches"), msgA, msg);
 
--- a/libpurple/protocols/simple/simple.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/simple/simple.c	Fri Nov 16 23:32:17 2007 +0000
@@ -80,14 +80,15 @@
 static gboolean process_register_response(struct simple_account_data *sip, struct sipmsg *msg, struct transaction *tc);
 static void send_notify(struct simple_account_data *sip, struct simple_watcher *);
 
-static void send_publish(struct simple_account_data *sip);
+static void send_open_publish(struct simple_account_data *sip);
+static void send_closed_publish(struct simple_account_data *sip);
 
 static void do_notifies(struct simple_account_data *sip) {
 	GSList *tmp = sip->watcher;
 	purple_debug_info("simple", "do_notifies()\n");
 	if((sip->republish != -1) || sip->republish < time(NULL)) {
 		if(purple_account_get_bool(sip->account, "dopublish", TRUE)) {
-			send_publish(sip);
+			send_open_publish(sip);
 		}
 	}
 
@@ -1020,7 +1021,7 @@
 		case 200:
 			if(sip->registerstatus < SIMPLE_REGISTER_COMPLETE) { /* registered */
 				if(purple_account_get_bool(sip->account, "dopublish", TRUE)) {
-					send_publish(sip);
+					send_open_publish(sip);
 				}
 			}
 			sip->registerstatus = SIMPLE_REGISTER_COMPLETE;
@@ -1072,7 +1073,7 @@
 static void process_incoming_notify(struct simple_account_data *sip, struct sipmsg *msg) {
 	gchar *from;
 	gchar *fromhdr;
-	gchar *tmp2;
+	gchar *basicstatus_data;
 	xmlnode *pidf;
 	xmlnode *basicstatus = NULL, *tuple, *status;
 	gboolean isonline = FALSE;
@@ -1085,8 +1086,9 @@
 
 	if(!pidf) {
 		purple_debug_info("simple", "process_incoming_notify: no parseable pidf\n");
+		purple_prpl_got_user_status(sip->account, from, "offline", NULL);
+		send_sip_response(sip->gc, msg, 200, "OK", NULL);
 		g_free(from);
-		send_sip_response(sip->gc, msg, 200, "OK", NULL);
 		return;
 	}
 
@@ -1101,27 +1103,28 @@
 		return;
 	}
 
-	tmp2 = xmlnode_get_data(basicstatus);
+	basicstatus_data = xmlnode_get_data(basicstatus);
 
-	if(!tmp2) {
+	if(!basicstatus_data) {
 		purple_debug_info("simple", "process_incoming_notify: no basic data found\n");
 		xmlnode_free(pidf);
 		g_free(from);
 		return;
 	}
 
-	if(strstr(tmp2, "open")) {
+	if(strstr(basicstatus_data, "open"))
 		isonline = TRUE;
-	}
+
 
-	g_free(tmp2);
-
-	if(isonline) purple_prpl_got_user_status(sip->account, from, "available", NULL);
-	else purple_prpl_got_user_status(sip->account, from, "offline", NULL);
+	if(isonline) 
+		purple_prpl_got_user_status(sip->account, from, "available", NULL);
+	else 
+		purple_prpl_got_user_status(sip->account, from, "offline", NULL);
 
 	xmlnode_free(pidf);
+	g_free(from);
+	g_free(basicstatus_data);
 
-	g_free(from);
 	send_sip_response(sip->gc, msg, 200, "OK", NULL);
 }
 
@@ -1188,28 +1191,27 @@
 	return doc;
 }
 
-
-
-static gchar* gen_pidf(struct simple_account_data *sip) {
+static gchar* gen_pidf(struct simple_account_data *sip, gboolean open) {
 	gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 			"<presence xmlns=\"urn:ietf:params:xml:ns:pidf\"\n"
 			"xmlns:im=\"urn:ietf:params:xml:ns:pidf:im\"\n"
 			"entity=\"sip:%s@%s\">\n"
 			"<tuple id=\"bs35r9f\">\n"
 			"<status>\n"
-			"<basic>open</basic>\n"
+			"<basic>%s</basic>\n"
 			"</status>\n"
 			"<note>%s</note>\n"
 			"</tuple>\n"
 			"</presence>",
 			sip->username,
 			sip->servername,
-			sip->status);
+			(open == TRUE) ? "open" : "closed",
+			(open == TRUE) ? sip->status : "");
 	return doc;
 }
 
 static void send_notify(struct simple_account_data *sip, struct simple_watcher *watcher) {
-	gchar *doc = watcher->needsxpidf ? gen_xpidf(sip) : gen_pidf(sip);
+	gchar *doc = watcher->needsxpidf ? gen_xpidf(sip) : gen_pidf(sip, TRUE);
 	gchar *hdr = watcher->needsxpidf ? "Event: presence\r\nContent-Type: application/xpidf+xml\r\n" : "Event: presence\r\nContent-Type: application/pidf+xml\r\n";
 	send_sip_request(sip->gc, "NOTIFY", watcher->name, watcher->name, hdr, doc, &watcher->dialog, NULL);
 	g_free(doc);
@@ -1223,9 +1225,9 @@
 	return TRUE;
 }
 
-static void send_publish(struct simple_account_data *sip) {
+static void send_open_publish(struct simple_account_data *sip) {
 	gchar *uri = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
-	gchar *doc = gen_pidf(sip);
+	gchar *doc = gen_pidf(sip, TRUE);
 	send_sip_request(sip->gc, "PUBLISH", uri, uri,
 		"Expires: 600\r\nEvent: presence\r\n"
 		"Content-Type: application/pidf+xml\r\n",
@@ -1235,6 +1237,18 @@
 	g_free(doc);
 }
 
+static void send_closed_publish(struct simple_account_data *sip) {
+	gchar *uri = g_strdup_printf("sip:%s@%s", sip->username, sip->servername);
+	gchar *doc = gen_pidf(sip, FALSE);
+	send_sip_request(sip->gc, "PUBLISH", uri, uri,
+		"Expires: 600\r\nEvent: presence\r\n"
+		"Content-Type: application/pidf+xml\r\n",
+		doc, NULL, process_publish_response);
+	/*sip->republish = time(NULL) + 500;*/
+	g_free(uri);
+	g_free(doc);
+}
+
 static void process_incoming_subscribe(struct simple_account_data *sip, struct sipmsg *msg) {
 	const char *from_hdr = sipmsg_find_header(msg, "From");
 	gchar *from = parse_from(from_hdr);
@@ -1738,7 +1752,14 @@
 	if(sip) {
 		/* unregister */
 		if (sip->registerstatus == SIMPLE_REGISTER_COMPLETE)
+		{
+			if(purple_account_get_bool(sip->account, 
+				"dopublish", 
+				TRUE))
+				send_closed_publish(sip);
+			
 			do_register_exp(sip, 0);
+		}
 		connection_free_all(sip);
 
 		if (sip->query_data != NULL)
--- a/libpurple/protocols/yahoo/yahoo_aliases.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_aliases.c	Fri Nov 16 23:32:17 2007 +0000
@@ -117,7 +117,7 @@
 					if (alias != NULL) {
 						serv_got_alias(cb->gc, yid, alias);
 						purple_debug_info("yahoo","Fetched alias '%s' (%s)\n",alias,id);
-					} else if (g_strcasecmp((alias!=NULL?alias:""),(b->alias!=NULL?b->alias:"")) != 0) {
+					} else if (b->alias != alias && strcmp(b->alias, "") != 0) {
 					/* Or if we have an alias that Yahoo doesn't, send it up */
 						yahoo_update_alias(cb->gc, yid, b->alias);
 						purple_debug_info("yahoo","Sent alias '%s'\n", b->alias);
@@ -216,7 +216,7 @@
 	struct callback_data *cb;
 	PurpleBuddy *buddy;
 	PurpleUtilFetchUrlData *url_data;
-   
+
 	g_return_if_fail(alias!= NULL);
 	g_return_if_fail(who!=NULL);
 	g_return_if_fail(gc!=NULL);
@@ -224,7 +224,7 @@
 	purple_debug_info("yahoo", "Sending '%s' as new alias for user '%s'.\n",alias, who);
 
 	buddy = purple_find_buddy(gc->account, who);
-	if (buddy->proto_data == NULL) {
+	if (buddy == NULL || buddy->proto_data == NULL) {
 		purple_debug_info("yahoo", "Missing proto_data (get_yahoo_aliases must have failed), bailing out\n");
 		return;
 	}
--- a/libpurple/prpl.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/prpl.h	Fri Nov 16 23:32:17 2007 +0000
@@ -226,11 +226,17 @@
 	void (*tooltip_text)(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full);
 
 	/**
-	 * This must be implemented, and must add at least the offline
-	 * and online states.
+	 * Returns a list of #PurpleStatusType which exist for this account;
+	 * this must be implemented, and must add at least the offline and
+	 * online states.
 	 */
 	GList *(*status_types)(PurpleAccount *account);
 
+	/**
+	 * Returns a list of #PurpleMenuAction structs, which represent extra
+	 * actions to be shown in (for example) the right-click menu for @a
+	 * node.
+	 */
 	GList *(*blist_node_menu)(PurpleBlistNode *node);
 	GList *(*chat_info)(PurpleConnection *);
 	GHashTable *(*chat_info_defaults)(PurpleConnection *, const char *chat_name);
@@ -258,6 +264,10 @@
 
 	void (*set_info)(PurpleConnection *, const char *info);
 	unsigned int (*send_typing)(PurpleConnection *, const char *name, PurpleTypingState state);
+	/**
+	 * Should arrange for purple_notify_userinfo() to be called with
+	 * @a who's user info.
+	 */
 	void (*get_info)(PurpleConnection *, const char *who);
 	void (*set_status)(PurpleAccount *account, PurpleStatus *status);
 
@@ -287,8 +297,14 @@
 	/** new user registration */
 	void (*register_user)(PurpleAccount *);
 
-	/* get "chat buddy" info and away message */
+	/**
+	 * @deprecated Use #PurplePluginProtocolInfo.get_info instead.
+	 */
 	void (*get_cb_info)(PurpleConnection *, int, const char *who);
+	/**
+	 * @deprecated Use #PurplePluginProtocolInfo.get_cb_real_name and
+	 *             #PurplePluginProtocolInfo.status_text instead.
+	 */
 	void (*get_cb_away)(PurpleConnection *, int, const char *who);
 
 	/** save/store buddy's alias on server list/roster */
@@ -348,9 +364,12 @@
 	/* room list serialize */
 	char *(*roomlist_room_serialize)(PurpleRoomlistRoom *room);
 
-	/* Remove the user from the server. (This is only at the bottom to keep binary compatibility.)
-	 * The account can either be connected or disconnected. After the removal is finished,
-	 * the connection will stay open and has to be closed!
+	/** Remove the user from the server.  The account can either be
+	 * connected or disconnected. After the removal is finished, the
+	 * connection will stay open and has to be closed!
+	 */
+	/* This is here rather than next to register_user for API compatibility
+	 * reasons.
 	 */
 	void (*unregister_user)(PurpleAccount *, PurpleAccountUnregistrationCb cb, void *user_data);
 	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purple.h	Fri Nov 16 23:32:17 2007 +0000
@@ -0,0 +1,92 @@
+/**
+ * @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
+ * include any other libpurple files.  It will still be necessary for plugins
+ * to define @c PURPLE_PLUGINS before including this header.
+ *
+ * @ingroup core libpurple
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _PURPLE_H
+#define _PURPLE_H
+
+#ifndef G_GNUC_NULL_TERMINATED
+#	if     __GNUC__ >= 4
+#		define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__))
+#	else
+#		define G_GNUC_NULL_TERMINATED
+#	endif
+#endif
+
+#include <account.h>
+#include <accountopt.h>
+#include <blist.h>
+#include <buddyicon.h>
+#include <certificate.h>
+#include <cipher.h>
+#include <circbuffer.h>
+#include <cmds.h>
+#include <connection.h>
+#include <conversation.h>
+#include <core.h>
+#include <debug.h>
+#include <desktopitem.h>
+#include <dnsquery.h>
+#include <dnssrv.h>
+#include <eventloop.h>
+#include <ft.h>
+#include <idle.h>
+#include <imgstore.h>
+#include <log.h>
+#include <mime.h>
+#include <nat-pmp.h>
+#include <network.h>
+#include <ntlm.h>
+#include <notify.h>
+#include <plugin.h>
+#include <pluginpref.h>
+#include <pounce.h>
+#include <prefs.h>
+#include <privacy.h>
+#include <proxy.h>
+#include <prpl.h>
+#include <request.h>
+#include <roomlist.h>
+#include <savedstatuses.h>
+#include <server.h>
+#include <signals.h>
+#include <status.h>
+#include <stringref.h>
+#include <stun.h>
+#include <sound.h>
+#include <sslconn.h>
+#include <upnp.h>
+#include <util.h>
+#include <value.h>
+#include <version.h>
+#include <xmlnode.h>
+#include <whiteboard.h>
+
+#endif
--- a/libpurple/request.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/request.c	Fri Nov 16 23:32:17 2007 +0000
@@ -377,6 +377,13 @@
 		g_hash_table_destroy(field->u.list.item_data);
 		g_hash_table_destroy(field->u.list.selected_table);
 	}
+	else if (field->type == PURPLE_REQUEST_FIELD_BLIST)
+	{
+		if (field->u.blist.default_nodes)
+			g_list_free(field->u.blist.default_nodes);
+		if (field->u.blist.selecteds)
+			g_list_free(field->u.blist.selecteds);
+	}
 
 	g_free(field);
 }
@@ -1133,6 +1140,85 @@
 
 /* -- */
 
+PurpleRequestField *purple_request_field_blist_nodes_new(const char *id,
+		const char *text, PurpleRequestBlistFlags flags, GList *selected)
+{
+	PurpleRequestField *field;
+
+	g_return_val_if_fail(id   != NULL, NULL);
+	g_return_val_if_fail(text != NULL, NULL);
+
+	field = purple_request_field_new(id, text, PURPLE_REQUEST_FIELD_BLIST);
+
+	field->u.blist.flags = flags;
+	field->u.blist.default_nodes = selected;
+	purple_request_field_blist_set_selection_list(field, selected);
+
+	return field;
+}
+
+PurpleFilterBlistFunc
+purple_request_field_blist_set_filter(PurpleRequestField *field, PurpleFilterBlistFunc filter)
+{
+	PurpleFilterBlistFunc old;
+	g_return_val_if_fail(field != NULL, NULL);
+	g_return_val_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST, NULL);
+	old = field->u.blist.filter;
+	field->u.blist.filter = filter;
+	return old;
+}
+
+PurpleFilterBlistFunc
+purple_request_field_blist_get_filter(const PurpleRequestField *field)
+{
+	g_return_val_if_fail(field != NULL, NULL);
+	g_return_val_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST, NULL);
+	return field->u.blist.filter;
+}
+
+GList *purple_request_field_blist_get_selection_list(const PurpleRequestField *field)
+{
+	g_return_val_if_fail(field != NULL, NULL);
+	g_return_val_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST, NULL);
+	return field->u.blist.selecteds;
+}
+
+gboolean purple_request_field_blist_add(PurpleRequestField *field, PurpleBlistNode *node)
+{
+	g_return_val_if_fail(field != NULL, FALSE);
+	g_return_val_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST, FALSE);
+	if (!g_list_find(field->u.blist.selecteds, node)) {
+		field->u.blist.selecteds = g_list_append(field->u.blist.selecteds, node);
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+}
+
+gboolean purple_request_field_blist_remove(PurpleRequestField *field, PurpleBlistNode *node)
+{
+	GList *search;
+	g_return_val_if_fail(field != NULL, FALSE);
+	g_return_val_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST, FALSE);
+	if ((search = g_list_find(field->u.blist.selecteds, node)) != NULL) {
+		field->u.blist.selecteds = g_list_delete_link(field->u.blist.selecteds, search);
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+}
+
+void purple_request_field_blist_set_selection_list(PurpleRequestField *field, GList *selecteds)
+{
+	g_return_if_fail(field != NULL);
+	g_return_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST);
+	if (field->u.blist.selecteds)
+		g_list_free(field->u.blist.selecteds);
+	field->u.blist.selecteds = selecteds;
+}
+
+/* -- */
+
 void *
 purple_request_input(void *handle, const char *title, const char *primary,
 				   const char *secondary, const char *default_value,
--- a/libpurple/request.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/request.h	Fri Nov 16 23:32:17 2007 +0000
@@ -61,7 +61,8 @@
 	PURPLE_REQUEST_FIELD_LIST,
 	PURPLE_REQUEST_FIELD_LABEL,
 	PURPLE_REQUEST_FIELD_IMAGE,
-	PURPLE_REQUEST_FIELD_ACCOUNT
+	PURPLE_REQUEST_FIELD_ACCOUNT,
+	PURPLE_REQUEST_FIELD_BLIST,
 
 } PurpleRequestFieldType;
 
@@ -94,6 +95,17 @@
 } PurpleRequestFieldGroup;
 
 /**
+ * Flags that can be used for Buddylist Fields.
+ */
+typedef enum
+{
+	PURPLE_REQUEST_BLIST_FLAG_BUDDY         = 0x01,  /**< Include buddies in the list. */
+	PURPLE_REQUEST_BLIST_FLAG_CHAT          = 0x02,  /**< Include chats in the list. */
+	PURPLE_REQUEST_BLIST_FLAG_GROUP         = 0x04,  /**< Include groups in the list. */
+	PURPLE_REQUEST_BLIST_FLAG_ALLOW_OFFLINE = 0x08,  /**< Include offline buddies in the list. */
+} PurpleRequestBlistFlags;
+
+/**
  * A request field.
  */
 typedef struct
@@ -172,6 +184,14 @@
 			gsize size;
 		} image;
 
+		struct
+		{
+			GList *default_nodes;
+			PurpleRequestBlistFlags flags;
+			GList *selecteds;
+			PurpleFilterBlistFunc filter;
+		} blist;
+
 	} u;
 
 	void *ui_data;
@@ -1151,6 +1171,98 @@
 /*@}*/
 
 /**************************************************************************/
+/** @name Buddylist Field API                                             */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a buddylist field.
+ *
+ * @param id       The field ID.
+ * @param text     The label for the field.
+ * @param flag     Flags dictating what kind of blist nodes should be
+ *                 included in the request field.
+ * @param selected A list of PurpleBlistNode's to select by default, or @c NULL.
+ *
+ * @return  The new field.
+ *
+ * @since 2.3.0
+ */
+PurpleRequestField *purple_request_field_blist_nodes_new(const char *id, const char *text,
+		PurpleRequestBlistFlags flag, GList *selected);
+
+/**
+ * Set a filter for the request field.
+ *
+ * @param field   The request field.
+ * @param filter  The filter function.
+ *
+ * @return  The old filter function, or @c NULL if there was none.
+ *
+ * @since 2.3.0
+ */
+PurpleFilterBlistFunc purple_request_field_blist_set_filter(PurpleRequestField *field, PurpleFilterBlistFunc filter);
+
+/**
+ * Get the filter function for the request field.
+ *
+ * @param field  The request field.
+ *
+ * @return  The filter function, or @c NULL if there isn't any.
+ *
+ * @since 2.3.0
+ */
+PurpleFilterBlistFunc purple_request_field_blist_get_filter(const PurpleRequestField *field);
+
+/**
+ * Add a PurpleBlistNode to the selected list.
+ *
+ * @param field  The request field.
+ * @param node   The buddylist node to add to the list.
+ *
+ * @return  @c TRUE if the node is added to the list, @c FALSE if it was already in the list.
+ *
+ * @since 2.3.0
+ */
+gboolean purple_request_field_blist_add(PurpleRequestField *field, PurpleBlistNode *node);
+
+/**
+ * Remove a PurpleBlistNode from the selected list.
+ *
+ * @param field   The request field.
+ * @param node    The buddylist node to remove from the list.
+ *
+ * @return @c TRUE if the node is removed from the list, @c FALSE if the node is not in the list.
+ *
+ * @since 2.3.0
+ */
+gboolean purple_request_field_blist_remove(PurpleRequestField *field, PurpleBlistNode *node);
+
+/**
+ * Set the list of selected nodes in the request field.
+ *
+ * @param field      The request field.
+ * @param selecteds  The list of selected PurpleBlistNode's. Note that the request field
+ *                   becomes the owner of the list, and so the caller should not modify it.
+ *
+ * @since 2.3.0
+ */
+void purple_request_field_blist_set_selection_list(PurpleRequestField *field, GList *selecteds);
+
+/**
+ * Get a list of the selected buddylist nodes.
+ *
+ * @param field  The request field.
+ *
+ * @return A GList of PurpleBlistNode's.
+ *
+ * @since 2.3.0
+ */
+GList *purple_request_field_blist_get_selection_list(const PurpleRequestField *field);
+
+/*@}*/
+
+/**************************************************************************/
 /** @name Request API                                                     */
 /**************************************************************************/
 /*@{*/
--- a/libpurple/signals.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/signals.c	Fri Nov 16 23:32:17 2007 +0000
@@ -794,6 +794,19 @@
 		*return_val = GINT_TO_POINTER(ret_val);
 }
 
+void
+purple_marshal_INT__POINTER_POINTER(PurpleCallback cb, va_list args, void *data,
+                                      void **return_val)
+{
+	gint ret_val;
+	void *arg1 = va_arg(args, void *);
+	void *arg2 = va_arg(args, void *);
+
+	ret_val = ((gint (*)(void *, void *, void *))cb)(arg1, arg2, data);
+
+	if (return_val != NULL)
+		*return_val = GINT_TO_POINTER(ret_val);
+}
 
 void
 purple_marshal_INT__POINTER_POINTER_POINTER_POINTER_POINTER(
--- a/libpurple/signals.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/signals.h	Fri Nov 16 23:32:17 2007 +0000
@@ -307,6 +307,8 @@
 		PurpleCallback cb, va_list args, void *data, void **return_val);
 void purple_marshal_INT__INT_INT(
 		PurpleCallback cb, va_list args, void *data, void **return_val);
+void purple_marshal_INT__POINTER_POINTER(
+		PurpleCallback cb, va_list args, void *data, void **return_val);
 void purple_marshal_INT__POINTER_POINTER_POINTER_POINTER_POINTER(
 		PurpleCallback cb, va_list args, void *data, void **return_val);
 
--- a/libpurple/sslconn.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/sslconn.h	Fri Nov 16 23:32:17 2007 +0000
@@ -185,6 +185,7 @@
 									PurpleSslErrorFunction error_func,
 									void *data);
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Makes a SSL connection using an already open file descriptor.
  *
@@ -202,19 +203,22 @@
 									   PurpleSslInputFunction func,
 									   PurpleSslErrorFunction error_func,
  									   void *data);
+#endif
 
 /**
-  * Makes a SSL connection using an already open file descriptor.
-  *
-  * @param account    The account making the connection.
-  * @param fd         The file descriptor.
-  * @param func       The SSL input handler function.
-  * @param error_func The SSL error handler function.
-  * @param host       The hostname of the other peer (to verify the CN)
-  * @param data       User-defined data.
-  *
-  * @return The SSL connection handle.
-  */
+ * Makes a SSL connection using an already open file descriptor.
+ *
+ * @param account    The account making the connection.
+ * @param fd         The file descriptor.
+ * @param func       The SSL input handler function.
+ * @param error_func The SSL error handler function.
+ * @param host       The hostname of the other peer (to verify the CN)
+ * @param data       User-defined data.
+ *
+ * @return The SSL connection handle.
+ *
+ * @since 2.2.0
+ */
 PurpleSslConnection *purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd,
                                            PurpleSslInputFunction func,
                                            PurpleSslErrorFunction error_func,
@@ -268,6 +272,8 @@
  *
  * @return The peer certificate chain, in the order of certificate, issuer,
  *         issuer's issuer, etc. @a NULL if no certificates have been provided,
+ *
+ * @since 2.2.0
  */
 GList * purple_ssl_get_peer_certificates(PurpleSslConnection *gsc);
 
--- a/libpurple/status.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/status.c	Fri Nov 16 23:32:17 2007 +0000
@@ -157,7 +157,8 @@
 	{ PURPLE_STATUS_INVISIBLE,       "invisible",       N_("Invisible")       },
 	{ PURPLE_STATUS_AWAY,            "away",            N_("Away")            },
 	{ PURPLE_STATUS_EXTENDED_AWAY,   "extended_away",   N_("Extended away")   },
-	{ PURPLE_STATUS_MOBILE,          "mobile",          N_("Mobile")          }
+	{ PURPLE_STATUS_MOBILE,          "mobile",          N_("Mobile")          },
+	{ PURPLE_STATUS_TUNE,            "tune",            N_("Listening to music") }
 };
 
 const char *
@@ -903,6 +904,8 @@
 	}
 	g_return_if_fail(purple_value_get_type(attr_value) == PURPLE_TYPE_STRING);
 
+	/* XXX: Check if the value has actually changed. If it has, and the status
+	 * is active, should this trigger 'status_has_changed'? */
 	purple_value_set_string(attr_value, value);
 }
 
--- a/libpurple/status.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/status.h	Fri Nov 16 23:32:17 2007 +0000
@@ -94,6 +94,10 @@
 /**
  * A primitive defining the basic structure of a status type.
  */
+/*
+ * If you add a value to this enum, make sure you update
+ * the status_primitive_map array in status.c.
+ */
 typedef enum
 {
 	PURPLE_STATUS_UNSET = 0,
@@ -104,6 +108,7 @@
 	PURPLE_STATUS_AWAY,
 	PURPLE_STATUS_EXTENDED_AWAY,
 	PURPLE_STATUS_MOBILE,
+	PURPLE_STATUS_TUNE,
 	PURPLE_STATUS_NUM_PRIMITIVES
 
 } PurpleStatusPrimitive;
--- a/libpurple/util.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/util.c	Fri Nov 16 23:32:17 2007 +0000
@@ -2548,15 +2548,14 @@
 
 	filename_full = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", user_dir, filename);
 
-	ret = purple_util_write_data_to_file_absolute(filename_full,
-						      data,size);
+	ret = purple_util_write_data_to_file_absolute(filename_full, data, size);
 
 	g_free(filename_full);
 	return ret;
 }
 
 gboolean
-purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, size_t size)
+purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, gssize size)
 {
 	gchar *filename_temp;
 	FILE *file;
@@ -2566,6 +2565,8 @@
 	purple_debug_info("util", "Writing file %s\n",
 					filename_full);
 
+	g_return_val_if_fail((size >= -1), FALSE);
+
 	filename_temp = g_strdup_printf("%s.save", filename_full);
 
 	/* Remove an old temporary file, if one exists */
@@ -2591,7 +2592,7 @@
 	}
 
 	/* Write to file */
-	real_size = (size == -1) ? strlen(data) : size;
+	real_size = (size == -1) ? strlen(data) : (size_t) size;
 	byteswritten = fwrite(data, 1, real_size, file);
 
 	/* Close file */
--- a/libpurple/util.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/util.h	Fri Nov 16 23:32:17 2007 +0000
@@ -607,7 +607,7 @@
  *
  */
 gboolean
-purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, size_t size);
+purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, gssize size);
 
 /**
  * Read the contents of a given file and parse the results into an
--- a/libpurple/xmlnode.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/libpurple/xmlnode.c	Fri Nov 16 23:32:17 2007 +0000
@@ -131,7 +131,7 @@
 		if(attr_node->type == XMLNODE_TYPE_ATTRIB &&
 				!strcmp(attr_node->name, attr))
 		{
-			if(node->child == attr_node) {
+			if(sibling == NULL) {
 				node->child = attr_node->next;
 			} else {
 				sibling->next = attr_node->next;
@@ -146,6 +146,19 @@
 	}
 }
 
+/* Compare two nullable xmlns strings.
+ * They are considered equal if they're both NULL or the strings are equal
+ */
+static gboolean _xmlnode_compare_xmlns(const char *xmlns1, const char *xmlns2) {
+	gboolean equal = FALSE;
+
+	if (xmlns1 == NULL && xmlns2 == NULL)
+		equal = TRUE;
+	else if (xmlns1 != NULL && xmlns2 != NULL && !strcmp(xmlns1, xmlns2))
+		equal = TRUE;
+
+	return equal;
+}
 
 void
 xmlnode_remove_attrib_with_namespace(xmlnode *node, const char *attr, const char *xmlns)
@@ -159,9 +172,9 @@
 	{
 		if(attr_node->type == XMLNODE_TYPE_ATTRIB &&
 		   !strcmp(attr_node->name, attr) &&
-		   !strcmp(attr_node->xmlns, xmlns))
+		   _xmlnode_compare_xmlns(xmlns, attr_node->xmlns))
 		{
-			if(node->child == attr_node) {
+			if(sibling == NULL) {
 				node->child = attr_node->next;
 			} else {
 				sibling->next = attr_node->next;
@@ -238,7 +251,8 @@
 
 	for(x = node->child; x; x = x->next) {
 		if(x->type == XMLNODE_TYPE_ATTRIB &&
-		   !strcmp(attr, x->name) && !strcmp(x->xmlns, xmlns)) {
+		   !strcmp(attr, x->name) &&
+		   _xmlnode_compare_xmlns(xmlns, x->xmlns)) {
 			return x->data;
 		}
 	}
@@ -326,6 +340,7 @@
 	child_name = names[1];
 
 	for(x = parent->child; x; x = x->next) {
+		/* XXX: Is it correct to ignore the namespace for the match if none was specified? */
 		const char *xmlns = NULL;
 		if(ns)
 			xmlns = xmlnode_get_namespace(x);
@@ -673,6 +688,7 @@
 	g_return_val_if_fail(node->type == XMLNODE_TYPE_TAG, NULL);
 
 	for(sibling = node->next; sibling; sibling = sibling->next) {
+		/* XXX: Is it correct to ignore the namespace for the match if none was specified? */
 		const char *xmlns = NULL;
 		if(ns)
 			xmlns = xmlnode_get_namespace(sibling);
--- a/pidgin/gtkaccount.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkaccount.c	Fri Nov 16 23:32:17 2007 +0000
@@ -1328,8 +1328,9 @@
 					break;
 
 				case PURPLE_PREF_STRING_LIST:
-					gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter);
-					gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)), &iter, 1, &value2, -1);
+					value2 = NULL;
+					if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter))
+						gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)), &iter, 1, &value2, -1);
 					purple_account_set_string(account, setting, value2);
 					break;
 
--- a/pidgin/gtkblist.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkblist.c	Fri Nov 16 23:32:17 2007 +0000
@@ -617,7 +617,7 @@
 
 static void gtk_blist_menu_bp_cb(GtkWidget *w, PurpleBuddy *b)
 {
-	pidgin_pounce_editor_show_with_parent(GTK_WINDOW(gtkblist->window), b->account, b->name, NULL);
+	pidgin_pounce_editor_show(b->account, b->name, NULL);
 }
 
 static void gtk_blist_menu_showlog_cb(GtkWidget *w, PurpleBlistNode *node)
@@ -643,7 +643,7 @@
 			name = prpl_info->get_chat_name(c->components);
 		}
 	} else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
-		pidgin_log_show_contact_with_parent(GTK_WINDOW(gtkblist->window), (PurpleContact *)node);
+		pidgin_log_show_contact((PurpleContact *)node);
 		pidgin_clear_cursor(gtkblist->window);
 		return;
 	} else {
@@ -655,7 +655,7 @@
 	}
 
 	if (name && account) {
-		pidgin_log_show_with_parent(GTK_WINDOW(gtkblist->window), type, name, account);
+		pidgin_log_show(type, name, account);
 		g_free(name);
 
 		pidgin_clear_cursor(gtkblist->window);
@@ -682,6 +682,11 @@
 	pidgin_blist_update(purple_get_blist(), node);
 }
 
+static void gtk_blist_show_systemlog_cb()
+{
+	pidgin_syslog_show();
+}
+
 static void gtk_blist_show_onlinehelp_cb()
 {
 	purple_notify_uri(NULL, PURPLE_WEBSITE "documentation");
@@ -3052,11 +3057,6 @@
 			!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"));
 }
 
-static void
-pidgin_blist_show_with_parent(gpointer data1, void (*callback)(GtkWindow *parent), gpointer data3)
-{
-	callback(GTK_WINDOW(gtkblist->window));
-}
 
 /***************************************************
  *            Crap                                 *
@@ -3090,15 +3090,15 @@
 
 	/* Tools */
 	{ N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
-	{ N_("/Tools/Buddy _Pounces"), NULL, pidgin_blist_show_with_parent, (int)pidgin_pounces_manager_show_with_parent, "<Item>", NULL },
+	{ N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 0, "<Item>", NULL },
 	{ N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "<Item>", NULL },
-	{ N_("/Tools/Plu_gins"), "<CTL>U", pidgin_blist_show_with_parent, (int)pidgin_plugin_dialog_show_with_parent, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
+	{ N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
 	{ N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
 	{ N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
 	{ "/Tools/sep2", NULL, NULL, 0, "<Separator>", NULL },
 	{ N_("/Tools/_File Transfers"), "<CTL>T", pidgin_xfer_dialog_show, 0, "<Item>", NULL },
 	{ N_("/Tools/R_oom List"), NULL, pidgin_roomlist_dialog_show, 0, "<Item>", NULL },
-	{ N_("/Tools/System _Log"), NULL, pidgin_blist_show_with_parent, (int)pidgin_syslog_show_with_parent, "<Item>", NULL },
+	{ N_("/Tools/System _Log"), NULL, gtk_blist_show_systemlog_cb, 0, "<Item>", NULL },
 	{ "/Tools/sep3", NULL, NULL, 0, "<Separator>", NULL },
 	{ N_("/Tools/Mute _Sounds"), "<CTL>S", pidgin_blist_mute_sounds_cb, 0, "<CheckItem>", NULL },
 	/* Help */
@@ -3106,9 +3106,9 @@
 	{ N_("/Help/Online _Help"), "F1", gtk_blist_show_onlinehelp_cb, 0, "<StockItem>", GTK_STOCK_HELP },
 	{ N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL },
 #if GTK_CHECK_VERSION(2,6,0)
-	{ N_("/Help/_About"), NULL, pidgin_blist_show_with_parent, (int)pidgin_dialogs_about_with_parent, "<StockItem>", GTK_STOCK_ABOUT },
+	{ N_("/Help/_About"), NULL, pidgin_dialogs_about, 0,  "<StockItem>", GTK_STOCK_ABOUT },
 #else
-	{ N_("/Help/_About"), NULL, pidgin_blist_show_with_parent, (int)pidgin_dialogs_about_with_parent, "<Item>", NULL },
+	{ N_("/Help/_About"), NULL, pidgin_dialogs_about, 0,  "<Item>", NULL },
 #endif
 };
 
@@ -3384,7 +3384,7 @@
 		return ret;
 	}
 
-	if (purple_status_get_attr_string(purple_presence_get_active_status(p), PURPLE_TUNE_TITLE)) {
+	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);
@@ -5300,7 +5300,7 @@
 static void buddy_node(PurpleBuddy *buddy, GtkTreeIter *iter, PurpleBlistNode *node)
 {
 	PurplePresence *presence;
-	GdkPixbuf *status, *avatar, *emblem;
+	GdkPixbuf *status, *avatar, *emblem, *prpl_icon;
 	char *mark;
 	char *idle = NULL;
 	gboolean expanded = ((struct _pidgin_blist_node *)(node->parent->ui_data))->contact_expanded;
@@ -5310,7 +5310,7 @@
 
 	if (editing_blist)
 		return;
-	
+
 	status = pidgin_blist_get_status_icon((PurpleBlistNode*)buddy,
 						PIDGIN_STATUS_ICON_SMALL);
 
@@ -5357,6 +5357,8 @@
 		}
 	}
 
+	prpl_icon = pidgin_create_prpl_icon(buddy->account, PIDGIN_PRPL_ICON_SMALL);
+
 	gtk_tree_store_set(gtkblist->treemodel, iter,
 			   STATUS_ICON_COLUMN, status,
 			   STATUS_ICON_VISIBLE_COLUMN, TRUE,
@@ -5367,7 +5369,7 @@
 			   BUDDY_ICON_VISIBLE_COLUMN, biglist,
 			   EMBLEM_COLUMN, emblem,
 			   EMBLEM_VISIBLE_COLUMN, (emblem != NULL),
-			   PROTOCOL_ICON_COLUMN, pidgin_create_prpl_icon(buddy->account, PIDGIN_PRPL_ICON_SMALL),
+			   PROTOCOL_ICON_COLUMN, prpl_icon,
 			   PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
 			   BGCOLOR_COLUMN, NULL,
 			   CONTACT_EXPANDER_COLUMN, NULL,
@@ -5383,6 +5385,8 @@
 		g_object_unref(status);
 	if(avatar)
 		g_object_unref(avatar);
+	if(prpl_icon)
+		g_object_unref(prpl_icon);
 }
 
 /* This is a variation on the original gtk_blist_update_contact. Here we
@@ -5503,9 +5507,7 @@
 
 	if(purple_account_is_connected(chat->account)) {
 		GtkTreeIter iter;
-		GdkPixbuf *status;
-		GdkPixbuf *avatar;
-		GdkPixbuf *emblem;
+		GdkPixbuf *status, *avatar, *emblem, *prpl_icon;
 		char *mark;
 		gboolean showicons = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
 		PidginBlistNode *ui;
@@ -5536,6 +5538,8 @@
 			mark = bold;
 		}
 
+		prpl_icon = pidgin_create_prpl_icon(chat->account, PIDGIN_PRPL_ICON_SMALL);
+
 		gtk_tree_store_set(gtkblist->treemodel, &iter,
 				STATUS_ICON_COLUMN, status,
 				STATUS_ICON_VISIBLE_COLUMN, TRUE,
@@ -5543,7 +5547,7 @@
 				BUDDY_ICON_VISIBLE_COLUMN,  purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"),
 				EMBLEM_COLUMN, emblem,
 				EMBLEM_VISIBLE_COLUMN, emblem != NULL,
-				PROTOCOL_ICON_COLUMN, pidgin_create_prpl_icon(chat->account, PIDGIN_PRPL_ICON_SMALL),
+				PROTOCOL_ICON_COLUMN, prpl_icon,
 				PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
 				NAME_COLUMN, mark,
 				GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
@@ -5556,6 +5560,8 @@
 			g_object_unref(status);
 		if(avatar)
 			g_object_unref(avatar);
+		if(prpl_icon)
+			g_object_unref(prpl_icon);
 	} else {
 		pidgin_blist_hide_node(list, node, TRUE);
 	}
@@ -5781,11 +5787,13 @@
 	gtkblist = PIDGIN_BLIST(purple_get_blist());
 
 	data->window = gtk_dialog_new_with_buttons(_("Add Buddy"),
-			NULL, GTK_DIALOG_NO_SEPARATOR,
+			gtkblist ? gtkblist->window : NULL, GTK_DIALOG_NO_SEPARATOR,
 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 			GTK_STOCK_ADD, GTK_RESPONSE_OK,
 			NULL);
 
+	if (gtkblist)
+		gtk_window_set_transient_for(GTK_WINDOW(data->window), GTK_WINDOW(gtkblist->window));
 	gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
 	gtk_container_set_border_width(GTK_CONTAINER(data->window), PIDGIN_HIG_BOX_SPACE);
 	gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
@@ -6163,11 +6171,13 @@
 	data->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
 	data->window = gtk_dialog_new_with_buttons(_("Add Chat"),
-		NULL, GTK_DIALOG_NO_SEPARATOR,
+		gtkblist ? gtkblist->window : NULL, GTK_DIALOG_NO_SEPARATOR,
 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 		GTK_STOCK_ADD, GTK_RESPONSE_OK,
 		NULL);
 
+	if (gtkblist)
+		gtk_window_set_transient_for(GTK_WINDOW(data->window), GTK_WINDOW(gtkblist->window));
 	gtk_dialog_set_default_response(GTK_DIALOG(data->window), GTK_RESPONSE_OK);
 	gtk_container_set_border_width(GTK_CONTAINER(data->window), PIDGIN_HIG_BOX_SPACE);
 	gtk_window_set_resizable(GTK_WINDOW(data->window), FALSE);
--- a/pidgin/gtkblist.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkblist.h	Fri Nov 16 23:32:17 2007 +0000
@@ -371,6 +371,8 @@
  * @param selected  Whether this buddy is selected. If TRUE, the markup will not change the color.
  * @param aliased  TRUE to return the appropriate alias of this buddy, FALSE to return its screenname and status information
  * @return The markup for this buddy
+ *
+ * @since 2.1.0
  */
 gchar *pidgin_blist_get_name_markup(PurpleBuddy *buddy, gboolean selected, gboolean aliased);
 
@@ -382,11 +384,15 @@
  *
  * @param node The buddy list node to show a tooltip for
  * @param widget The widget to draw the tooltip on
+ *
+ * @since 2.1.0
  */
 void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget);
 
 /**
  * Destroys the current (if any) Buddy List tooltip
+ *
+ * @since 2.1.0
  */
 void pidgin_blist_tooltip_destroy(void);
 
--- a/pidgin/gtkconv.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkconv.c	Fri Nov 16 23:32:17 2007 +0000
@@ -6519,6 +6519,7 @@
 		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);
@@ -6552,7 +6553,7 @@
 		gtk_list_store_set(gtkconv->infopane_model, &(gtkconv->infopane_iter),
 				CONV_TEXT_COLUMN, markup, -1);
 	        /* XXX seanegan Why do I have to do this? */
-        	gtk_widget_queue_draw(gtkconv->infopane);
+		gtk_widget_queue_draw(gtkconv->infopane);
 	
 		if (title != markup)
 			g_free(markup);
@@ -6571,31 +6572,41 @@
 			style = "color=\"#c4a000\"";
 		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK)	{
 			atk_object_set_description(accessibility_obj, _("Nick Said"));
-			style = "color=\"#204a87\" weight=\"bold\"";
+			style = "color=\"#cc0000\"";
 		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT)	{
 			atk_object_set_description(accessibility_obj, _("Unread Messages"));
-			style = "color=\"#cc0000\" weight=\"bold\"";
+			if (gtkconv->active_conv->type == PURPLE_CONV_TYPE_CHAT)
+				style = "color=\"#204a87\" weight=\"bold\"";
+			else
+				style = "color=\"#cc0000\" weight=\"bold\"";
 		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT) {
 			atk_object_set_description(accessibility_obj, _("New Event"));
-			style = "color=\"#888a85\" weight=\"bold\"";
+			style = "color=\"#888a85\"";
 		} else {
-			style = "";
-		}
+			style = NULL;
+		}
+
+		if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT ||
+				gtkconv->unseen_state == PIDGIN_UNSEEN_NICK ||
+				gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT)
+			bold = TRUE;
 		
-		if (*style != '\0')
+		if (style || bold)
 		{
 			char *html_title,*label;
 
 			html_title = g_markup_escape_text(title, -1);
-			label = g_strdup_printf("<span %s>%s</span>",
-			                        style, html_title);
+			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);
-		
+
 		if (pidgin_conv_window_is_active_conversation(conv))
 			update_typing_icon(gtkconv);
 
@@ -7397,6 +7408,17 @@
 	pidgin_conv_update_fields(conv, PIDGIN_CONV_TOPIC);
 }
 
+/* Message history stuff */
+
+/* Compare two PurpleConvMessage's, according to time in ascending order. */
+static int
+message_compare(gconstpointer p1, gconstpointer p2)
+{
+	const PurpleConvMessage *m1 = p1, *m2 = p2;
+	return (m1->when > m2->when);
+}
+
+/* Adds some message history to the gtkconv. This happens in a idle-callback. */
 static gboolean
 add_message_history_to_gtkconv(gpointer data)
 {
@@ -7404,49 +7426,106 @@
 	int count = 0;
 	int timer = gtkconv->attach.timer;
 	time_t when = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtkconv->entry), "attach-start-time"));
+	gboolean im = (gtkconv->active_conv->type == PURPLE_CONV_TYPE_IM);
 
 	gtkconv->attach.timer = 0;
 	while (gtkconv->attach.current && count < 100) {  /* XXX: 100 is a random value here */
 		PurpleConvMessage *msg = gtkconv->attach.current->data;
-		if (when && when < msg->when) {
+		if (!im && when && when < msg->when) {
 			gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR><HR>", 0);
 			g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL);
 		}
-		pidgin_conv_write_conv(gtkconv->active_conv, msg->who, msg->who, msg->what, msg->flags, msg->when);
-		gtkconv->attach.current = gtkconv->attach.current->prev;
+		pidgin_conv_write_conv(msg->conv, msg->who, msg->who, msg->what, msg->flags, msg->when);
+		if (im) {
+			gtkconv->attach.current = g_list_delete_link(gtkconv->attach.current, gtkconv->attach.current);
+		} else {
+			gtkconv->attach.current = gtkconv->attach.current->prev;
+		}
 		count++;
 	}
 	gtkconv->attach.timer = timer;
 	if (gtkconv->attach.current)
 		return TRUE;
 
+	g_source_remove(gtkconv->attach.timer);
+	gtkconv->attach.timer = 0;
+	if (im) {
+		/* Print any message that was sent while the old history was being added back. */
+		GList *msgs = NULL;
+		GList *iter = gtkconv->convs;
+		for (; iter; iter = iter->next) {
+			PurpleConversation *conv = iter->data;
+			GList *history = purple_conversation_get_message_history(conv);
+			for (; history; history = history->next) {
+				PurpleConvMessage *msg = history->data;
+				if (msg->when > when)
+					msgs = g_list_prepend(msgs, msg);
+			}
+		}
+		msgs = g_list_sort(msgs, message_compare);
+		for (; msgs; msgs = g_list_delete_link(msgs, msgs)) {
+			PurpleConvMessage *msg = msgs->data;
+			pidgin_conv_write_conv(msg->conv, msg->who, msg->who, msg->what, msg->flags, msg->when);
+		}
+		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR><HR>", 0);
+		g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL);
+	}
+
 	g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL);
 	purple_signal_emit(pidgin_conversations_get_handle(),
 			"conversation-displayed", gtkconv);
-	g_source_remove(gtkconv->attach.timer);
-	gtkconv->attach.timer = 0;
 	return FALSE;
 }
 
+static void
+pidgin_conv_attach(PurpleConversation *conv)
+{
+	int timer;
+	purple_conversation_set_data(conv, "unseen-count", NULL);
+	purple_conversation_set_ui_ops(conv, pidgin_conversations_get_conv_ui_ops());
+	private_gtkconv_new(conv, FALSE);
+	timer = GPOINTER_TO_INT(purple_conversation_get_data(conv, "close-timer"));
+	if (timer)
+		purple_timeout_remove(timer);
+}
+
 gboolean pidgin_conv_attach_to_conversation(PurpleConversation *conv)
 {
 	GList *list;
 	PidginConversation *gtkconv;
-	int timer;
 
 	if (PIDGIN_IS_PIDGIN_CONVERSATION(conv))
 		return FALSE;
 
-	purple_conversation_set_data(conv, "unseen-count", NULL);
-	purple_conversation_set_ui_ops(conv, pidgin_conversations_get_conv_ui_ops());
-	private_gtkconv_new(conv, FALSE);
+	pidgin_conv_attach(conv);
 	gtkconv = PIDGIN_CONVERSATION(conv);
 
 	list = purple_conversation_get_message_history(conv);
 	if (list) {
+		switch (purple_conversation_get_type(conv)) {
+			case PURPLE_CONV_TYPE_IM: 
+			{
+				GList *convs;
+				list = g_list_copy(list);
+				for (convs = purple_get_ims(); convs; convs = convs->next)
+					if (convs->data != conv &&
+							pidgin_conv_find_gtkconv(convs->data) == gtkconv) {
+						pidgin_conv_attach(convs->data);
+						list = g_list_concat(list, g_list_copy(purple_conversation_get_message_history(convs->data)));
+					}
+				list = g_list_sort(list, message_compare);
+				gtkconv->attach.current = list;
+				list = g_list_last(list);
+				break;
+			}
+			case PURPLE_CONV_TYPE_CHAT:
+				gtkconv->attach.current = g_list_last(list);
+				break;
+			default:
+				g_return_val_if_reached(TRUE);
+		}
 		g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time",
 				GINT_TO_POINTER(((PurpleConvMessage*)(list->data))->when));
-		gtkconv->attach.current = g_list_last(list);
 		gtkconv->attach.timer = g_idle_add(add_message_history_to_gtkconv, gtkconv);
 	} else {
 		purple_signal_emit(pidgin_conversations_get_handle(),
@@ -7458,9 +7537,6 @@
 		pidgin_conv_chat_add_users(conv, PURPLE_CONV_CHAT(conv)->in_room, TRUE);
 	}
 
-	timer = GPOINTER_TO_INT(purple_conversation_get_data(conv, "close-timer"));
-	if (timer)
-		purple_timeout_remove(timer);
 	return TRUE;
 }
 
@@ -9791,3 +9867,4 @@
 	return colors;
 }
 
+
--- a/pidgin/gtkconv.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkconv.h	Fri Nov 16 23:32:17 2007 +0000
@@ -253,6 +253,8 @@
  * @param conv  The conversation.
  *
  * @return  Wheter Pidgin UI was successfully attached.
+ *
+ * @since 2.2.0
  */
 gboolean pidgin_conv_attach_to_conversation(PurpleConversation *conv);
 
--- a/pidgin/gtkdebug.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkdebug.c	Fri Nov 16 23:32:17 2007 +0000
@@ -36,7 +36,6 @@
 #include "gtkimhtml.h"
 #include "gtkutils.h"
 #include "pidginstock.h"
-#include "gtkblist.h"
 
 #ifdef HAVE_REGEX_H
 # include <regex.h>
@@ -674,7 +673,6 @@
 static DebugWindow *
 debug_window_new(void)
 {
-	PidginBuddyList *blist;
 	DebugWindow *win;
 	GtkWidget *vbox;
 	GtkWidget *toolbar;
@@ -689,9 +687,6 @@
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/height");
 
 	PIDGIN_DIALOG(win->window);
-	if ((blist = pidgin_blist_get_default_gtk_blist()) != NULL)
-		if (blist->window)
-			gtk_window_set_transient_for(GTK_WINDOW(win->window), GTK_WINDOW(blist->window));
 	purple_debug_info("gtkdebug", "Setting dimensions to %d, %d\n",
 					width, height);
 
--- a/pidgin/gtkdialogs.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkdialogs.c	Fri Nov 16 23:32:17 2007 +0000
@@ -334,13 +334,6 @@
 
 void pidgin_dialogs_about()
 {
-	PidginBuddyList *blist = pidgin_blist_get_default_gtk_blist();
-
-	pidgin_dialogs_about_with_parent(blist ? GTK_WINDOW(blist->window) : NULL);
-}
-
-void pidgin_dialogs_about_with_parent(GtkWindow *parent)
-{
 	GtkWidget *hbox;
 	GtkWidget *vbox;
 	GtkWidget *logo;
@@ -356,15 +349,11 @@
 	GdkPixbuf *pixbuf;
 
 	if (about != NULL) {
-		if (parent)
-			gtk_window_set_transient_for(GTK_WINDOW(about), parent);
 		gtk_window_present(GTK_WINDOW(about));
 		return;
 	}
 
 	PIDGIN_DIALOG(about);
-	if (parent)
-		gtk_window_set_transient_for(GTK_WINDOW(about), parent);
 	tmp = g_strdup_printf(_("About %s"), PIDGIN_NAME);
 	gtk_window_set_title(GTK_WINDOW(about), tmp);
 	g_free(tmp);
@@ -767,6 +756,10 @@
 	purple_request_field_set_required(field, TRUE);
 	purple_request_field_group_add_field(group, field);
 
+	field = purple_request_field_blist_nodes_new("blistnodes", _("Buddy"),
+			PURPLE_REQUEST_BLIST_FLAG_BUDDY, NULL);
+	purple_request_field_group_add_field(group, field);
+
 	field = purple_request_field_account_new("account", _("_Account"), NULL);
 	purple_request_field_set_type_hint(field, "account");
 	purple_request_field_set_visible(field,
--- a/pidgin/gtkdialogs.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkdialogs.h	Fri Nov 16 23:32:17 2007 +0000
@@ -33,7 +33,6 @@
 /* Functions in gtkdialogs.c (these should actually stay in this file) */
 void pidgin_dialogs_destroy_all(void);
 void pidgin_dialogs_about(void);
-void pidgin_dialogs_about_with_parent(GtkWindow *parent);
 void pidgin_dialogs_im(void);
 void pidgin_dialogs_im_with_user(PurpleAccount *, const char *);
 void pidgin_dialogs_info(void);
--- a/pidgin/gtkdocklet.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkdocklet.c	Fri Nov 16 23:32:17 2007 +0000
@@ -407,7 +407,7 @@
 	GdkPixbuf *pixbuf;
 	GtkWidget *image;
 
-	menuitem = gtk_image_menu_item_new_with_mnemonic(str);
+	menuitem = gtk_image_menu_item_new_with_label(str);
 
 	if (menu)
 		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
@@ -473,6 +473,87 @@
 	return menuitem;
 }
 
+
+
+static void
+plugin_act(GtkObject *obj, PurplePluginAction *pam)
+{
+	if (pam && pam->callback)
+		pam->callback(pam);
+}
+
+static void
+build_plugin_actions(GtkWidget *menu, PurplePlugin *plugin,
+		gpointer context)
+{
+	GtkWidget *menuitem;
+	PurplePluginAction *action = NULL;
+	GList *actions, *l;
+
+	actions = PURPLE_PLUGIN_ACTIONS(plugin, context);
+
+	for (l = actions; l != NULL; l = l->next)
+	{
+		if (l->data)
+		{
+			action = (PurplePluginAction *) l->data;
+			action->plugin = plugin;
+			action->context = context;
+
+			menuitem = gtk_menu_item_new_with_label(action->label);
+			gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+			g_signal_connect(G_OBJECT(menuitem), "activate",
+					G_CALLBACK(plugin_act), action);
+			g_object_set_data_full(G_OBJECT(menuitem), "plugin_action",
+								   action,
+								   (GDestroyNotify)purple_plugin_action_free);
+			gtk_widget_show(menuitem);
+		}
+		else
+			pidgin_separator(menu);
+	}
+
+	g_list_free(actions);
+}
+
+
+static void
+docklet_plugin_actions(GtkWidget *menu)
+{
+	GtkWidget *menuitem, *submenu;
+	PurplePlugin *plugin = NULL;
+	GList *l;
+	int c = 0;
+
+	g_return_if_fail(menu != NULL);
+
+	/* Add a submenu for each plugin with custom actions */
+	for (l = purple_plugins_get_loaded(); l; l = l->next) {
+		plugin = (PurplePlugin *) l->data;
+
+		if (PURPLE_IS_PROTOCOL_PLUGIN(plugin))
+			continue;
+
+		if (!PURPLE_PLUGIN_HAS_ACTIONS(plugin))
+			continue;
+
+		menuitem = gtk_image_menu_item_new_with_label(_(plugin->info->name));
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+		gtk_widget_show(menuitem);
+
+		submenu = gtk_menu_new();
+		gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
+		gtk_widget_show(submenu);
+
+		build_plugin_actions(submenu, plugin, NULL);
+
+		c++;
+	}
+	if(c>0)
+		pidgin_separator(menu);
+}
+
 static void
 docklet_menu() {
 	static GtkWidget *menu = NULL;
@@ -539,6 +620,9 @@
 
 	pidgin_separator(menu);
 
+	/* add plugin actions */
+	docklet_plugin_actions(menu);
+
 	pidgin_new_item_from_stock(menu, _("Quit"), GTK_STOCK_QUIT, G_CALLBACK(purple_core_quit), NULL, 0, 0, NULL);
 
 #ifdef _WIN32
--- a/pidgin/gtkimhtml.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkimhtml.h	Fri Nov 16 23:32:17 2007 +0000
@@ -441,6 +441,8 @@
  * @param id       The id to associate with the image.
  *
  * @return A new IM/HTML Scalable object with an image.
+ *
+ * @since 2.1.0
  */
 /*
  * TODO: All this animation code could be combined much better with
@@ -837,6 +839,8 @@
  *
  * @param imhtml  The GTK+ IM/HTML.
  * @param flags   The connection flag which describes the allowed types of formatting.
+ *
+ * @since 2.1.0
  */
 void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags);
 
--- a/pidgin/gtkimhtmltoolbar.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Fri Nov 16 23:32:17 2007 +0000
@@ -184,8 +184,6 @@
 			g_signal_connect_after(G_OBJECT(toolbar->font_dialog), "realize",
 							 G_CALLBACK(realize_toolbar_font), toolbar);
 		}
-		gtk_window_set_transient_for(GTK_WINDOW(toolbar->font_dialog),
-		    GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar))));
 		gtk_window_present(GTK_WINDOW(toolbar->font_dialog));
 	} else {
 		cancel_toolbar_font(font, toolbar);
@@ -711,8 +709,6 @@
 	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
 	gtk_window_set_role(GTK_WINDOW(dialog), "smiley_dialog");
 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
-	gtk_window_set_transient_for(GTK_WINDOW(dialog),
-	    GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar))));
 
 	if (unique_smileys != NULL) {
 		struct smiley_button_list *ls, *it, *it_tmp;
--- a/pidgin/gtklog.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtklog.c	Fri Nov 16 23:32:17 2007 +0000
@@ -523,7 +523,7 @@
 	}
 }
 
-static PidginLogViewer *display_log_viewer(GtkWindow *parent, struct log_viewer_hash_t *ht, GList *logs,
+static PidginLogViewer *display_log_viewer(struct log_viewer_hash_t *ht, GList *logs,
 						const char *title, GtkWidget *icon, int log_size)
 {
 	PidginLogViewer *lv;
@@ -569,7 +569,7 @@
 		g_hash_table_insert(log_viewers, ht, lv);
 
 	/* Window ***********/
-	lv->window = gtk_dialog_new_with_buttons(title, parent, 0,
+	lv->window = gtk_dialog_new_with_buttons(title, NULL, 0,
 					     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
 #ifdef _WIN32
 	/* Steal the "HELP" response and use it to trigger browsing to the logs folder */
@@ -676,14 +676,11 @@
 }
 
 void pidgin_log_show(PurpleLogType type, const char *screenname, PurpleAccount *account) {
-	pidgin_log_show_with_parent(NULL, type, screenname, account);
-}
-
-void pidgin_log_show_with_parent(GtkWindow *parent, PurpleLogType type, const char *screenname, PurpleAccount *account) {
 	struct log_viewer_hash_t *ht;
 	PidginLogViewer *lv = NULL;
 	const char *name = screenname;
 	char *title;
+	GdkPixbuf *prpl_icon;
 
 	g_return_if_fail(account != NULL);
 	g_return_if_fail(screenname != NULL);
@@ -721,17 +718,18 @@
 		title = g_strdup_printf(_("Conversations with %s"), name);
 	}
 
-	display_log_viewer(parent, ht, purple_log_get_logs(type, screenname, account),
-			title, gtk_image_new_from_pixbuf(pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM)), 
+	prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM);
+
+	display_log_viewer(ht, purple_log_get_logs(type, screenname, account),
+			title, gtk_image_new_from_pixbuf(prpl_icon),
 			purple_log_get_total_size(type, screenname, account));
+
+	if (prpl_icon)
+		g_object_unref(prpl_icon);
 	g_free(title);
 }
 
 void pidgin_log_show_contact(PurpleContact *contact) {
-	pidgin_log_show_contact_with_parent(NULL, contact);
-}
-
-void pidgin_log_show_contact_with_parent(GtkWindow *parent, PurpleContact *contact) {
 	struct log_viewer_hash_t *ht = g_new0(struct log_viewer_hash_t, 1);
 	PurpleBlistNode *child;
 	PidginLogViewer *lv = NULL;
@@ -785,16 +783,11 @@
 	}
 
 	title = g_strdup_printf(_("Conversations with %s"), name);
-	display_log_viewer(parent, ht, logs, title, image, total_log_size);
+	display_log_viewer(ht, logs, title, image, total_log_size);
 	g_free(title);
 }
 
-void pidgin_syslog_show(void)
-{
-	pidgin_syslog_show_with_parent(NULL);
-}
-
-void pidgin_syslog_show_with_parent(GtkWindow *parent)
+void pidgin_syslog_show()
 {
 	GList *accounts = NULL;
 	GList *logs = NULL;
@@ -814,7 +807,7 @@
 	}
 	logs = g_list_sort(logs, purple_log_compare);
 
-	syslog_viewer = display_log_viewer(parent, NULL, logs, _("System Log"), NULL, 0);
+	syslog_viewer = display_log_viewer(NULL, logs, _("System Log"), NULL, 0);
 }
 
 /****************************************************************************
--- a/pidgin/gtklog.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtklog.h	Fri Nov 16 23:32:17 2007 +0000
@@ -54,12 +54,9 @@
 
 
 void pidgin_log_show(PurpleLogType type, const char *screenname, PurpleAccount *account);
-void pidgin_log_show_with_parent(GtkWindow *parent, PurpleLogType type, const char *screenname, PurpleAccount *account);
 void pidgin_log_show_contact(PurpleContact *contact);
-void pidgin_log_show_contact_with_parent(GtkWindow *parent, PurpleContact *contact);
 
 void pidgin_syslog_show(void);
-void pidgin_syslog_show_with_parent(GtkWindow *parent);
 
 /**************************************************************************/
 /** @name GTK+ Log Subsystem                                              */
--- a/pidgin/gtknotify.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtknotify.c	Fri Nov 16 23:32:17 2007 +0000
@@ -270,8 +270,9 @@
 	primary_esc = g_markup_escape_text(primary, -1);
 	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
 	g_snprintf(label_text, sizeof(label_text),
-			   "<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s",
-			   primary_esc, (secondary ? secondary_esc : ""));
+			   "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
+			   primary_esc, (secondary ? "\n\n" : ""),
+			   (secondary ? secondary_esc : ""));
 	g_free(primary_esc);
 	g_free(secondary_esc);
 
@@ -740,7 +741,7 @@
 	GtkListStore *model;
 	GtkCellRenderer *renderer;
 	guint col_num;
-	GList *column;
+	GList *columniter;
 	guint i;
 
 	GtkWidget *vbox;
@@ -824,11 +825,12 @@
 					-1, "", renderer, "pixbuf", 0, NULL);
 
 	i = 1;
-	for (column = results->columns; column != NULL; column = column->next) {
+	for (columniter = results->columns; columniter != NULL; columniter = columniter->next) {
+		PurpleNotifySearchColumn *column = columniter->data;
 		renderer = gtk_cell_renderer_text_new();
 
 		gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1,
-				column->data, renderer, "text", i, NULL);
+				column->title, renderer, "text", i, NULL);
 		i++;
 	}
 
--- a/pidgin/gtkplugin.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkplugin.c	Fri Nov 16 23:32:17 2007 +0000
@@ -303,7 +303,24 @@
 	{
 		pidgin_set_cursor(plugin_dialog, GDK_WATCH);
 
-		purple_plugin_unload(plug);
+		if (!purple_plugin_unload(plug))
+		{
+			const char *primary = _("Could not unload plugin");
+			const char *reload = _("The plugin could not be unloaded now, but will be disabled at the next startup.");
+
+			if (!plug->error)
+			{
+				purple_notify_warning(NULL, NULL, primary, reload);
+			}
+			else
+			{
+				char *tmp = g_strdup_printf("%s\n\n%s", reload, plug->error);
+				purple_notify_warning(NULL, NULL, primary, tmp);
+				g_free(tmp);
+			}
+
+			purple_plugin_disable(plug);
+		}
 
 		pidgin_clear_cursor(plugin_dialog);
 	}
@@ -516,11 +533,6 @@
 
 void pidgin_plugin_dialog_show()
 {
-	pidgin_plugin_dialog_show_with_parent(NULL);
-}
-
-void pidgin_plugin_dialog_show_with_parent(GtkWindow *parent)
-{
 	GtkWidget *sw;
 	GtkWidget *event_view;
 	GtkListStore *ls;
@@ -529,8 +541,6 @@
 	GtkTreeSelection *sel;
 
 	if (plugin_dialog != NULL) {
-		if (parent)
-			gtk_window_set_transient_for(GTK_WINDOW(plugin_dialog), parent);
 		gtk_window_present(GTK_WINDOW(plugin_dialog));
 		return;
 	}
@@ -539,8 +549,6 @@
 						    NULL,
 						    GTK_DIALOG_NO_SEPARATOR,
 						    NULL);
-	if (parent)
-		gtk_window_set_transient_for(GTK_WINDOW(plugin_dialog), parent);
 	pref_button = gtk_dialog_add_button(GTK_DIALOG(plugin_dialog),
 						_("Configure Pl_ugin"), PIDGIN_RESPONSE_CONFIGURE);
 	gtk_dialog_add_button(GTK_DIALOG(plugin_dialog),
--- a/pidgin/gtkplugin.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkplugin.h	Fri Nov 16 23:32:17 2007 +0000
@@ -77,9 +77,4 @@
  */
 void pidgin_plugin_dialog_show(void);
 
-/**
- * Shows the Plugins dialog, transient to a parent window
- */
-void pidgin_plugin_dialog_show_with_parent(GtkWindow *parent);
-
 #endif /* _PIDGINPLUGIN_H_ */
--- a/pidgin/gtkpounce.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkpounce.c	Fri Nov 16 23:32:17 2007 +0000
@@ -472,13 +472,6 @@
 pidgin_pounce_editor_show(PurpleAccount *account, const char *name,
 							PurplePounce *cur_pounce)
 {
-	pidgin_pounce_editor_show_with_parent(NULL, account, name, cur_pounce);
-}
-
-void
-pidgin_pounce_editor_show_with_parent(GtkWindow *parent, PurpleAccount *account, const char *name,
-							PurplePounce *cur_pounce)
-{
 	PidginPounceDialog *dialog;
 	GtkWidget *window;
 	GtkWidget *label;
@@ -1057,7 +1050,7 @@
 static void
 pounces_manager_add_cb(GtkButton *button, gpointer user_data)
 {
-	pidgin_pounce_editor_show_with_parent(GTK_WINDOW(pounces_manager->window), NULL, NULL, NULL);
+	pidgin_pounce_editor_show(NULL, NULL, NULL);
 }
 
 static void
@@ -1067,7 +1060,7 @@
 	PurplePounce *pounce;
 
 	gtk_tree_model_get(model, iter, POUNCES_MANAGER_COLUMN_POUNCE, &pounce, -1);
-	pidgin_pounce_editor_show_with_parent(GTK_WINDOW(pounces_manager->window), NULL, NULL, pounce);
+	pidgin_pounce_editor_show(NULL, NULL, pounce);
 }
 
 static void
@@ -1167,7 +1160,7 @@
 	if ((pounce != NULL) && (event->button == 1) &&
 		(event->type == GDK_2BUTTON_PRESS))
 	{
-		pidgin_pounce_editor_show_with_parent(GTK_WINDOW(pounces_manager->window), NULL, NULL, pounce);
+		pidgin_pounce_editor_show(NULL, NULL, pounce);
 		return TRUE;
 	}
 
@@ -1318,12 +1311,6 @@
 void
 pidgin_pounces_manager_show(void)
 {
-	pidgin_pounces_manager_show_with_parent(NULL);
-}
-
-void
-pidgin_pounces_manager_show_with_parent(GtkWindow *parent)
-{
 	PouncesManager *dialog;
 	GtkWidget *bbox;
 	GtkWidget *button;
@@ -1334,7 +1321,6 @@
 
 	if (pounces_manager != NULL) {
 		gtk_window_present(GTK_WINDOW(pounces_manager->window));
-		gtk_window_set_transient_for(GTK_WINDOW(pounces_manager->window), parent);
 		return;
 	}
 
@@ -1345,7 +1331,6 @@
 
 	dialog->window = win = pidgin_create_window(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
-	gtk_window_set_transient_for(GTK_WINDOW(win), parent);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(pounces_manager_destroy_cb), dialog);
--- a/pidgin/gtkpounce.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkpounce.h	Fri Nov 16 23:32:17 2007 +0000
@@ -31,28 +31,16 @@
 /**
  * Displays a New Buddy Pounce or Edit Buddy Pounce dialog.
  *
- * @param parent     The parent window.
  * @param account    The optional account to use.
  * @param name       The optional name to pounce on.
  * @param cur_pounce The current buddy pounce, if editing an existing one.
  */
-void pidgin_pounce_editor_show_with_parent(GtkWindow *parent, PurpleAccount *account, const char *name,
-								PurplePounce *cur_pounce);
-
-/**
- * @deprecated Please use pidgin_pounce_editor_show_with_parent
- */
 void pidgin_pounce_editor_show(PurpleAccount *account, const char *name,
 								PurplePounce *cur_pounce);
 
 /**
  * Shows the pounces manager window.
  */
-void pidgin_pounces_manager_show_with_parent(GtkWindow *parent);
-
-/**
- * @deprecated Please use pidgin_pounces_manager_show_with_parent
- */
 void pidgin_pounces_manager_show(void);
 
 /**
--- a/pidgin/gtkrequest.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkrequest.c	Fri Nov 16 23:32:17 2007 +0000
@@ -29,6 +29,7 @@
 #include "prefs.h"
 #include "util.h"
 
+#include "gtkblist.h"
 #include "gtkimhtml.h"
 #include "gtkimhtmltoolbar.h"
 #include "gtkrequest.h"
@@ -952,6 +953,92 @@
 	return widget;
 }
 
+static GtkWidget *
+create_blist_field(PurpleRequestField *field)
+{
+	GtkTreeStore *model;
+	GtkWidget *tree, *sw;
+	PurpleBlistNode *node;
+	GtkCellRenderer *rend;
+	GtkTreeViewColumn *column;
+	GtkTreeIter parent = {0, NULL, NULL, NULL}, iter;
+	PurpleRequestBlistFlags flags = field->u.blist.flags;
+	gboolean offline = !!(field->u.blist.flags & PURPLE_REQUEST_BLIST_FLAG_ALLOW_OFFLINE);
+
+	/* Create the treeview. Populate the blistnodes.
+	 * Hook to signed-on, signed-off signals to update the list when account goes online/offline.
+	 * Hook to buddy-signed-on/off, -status-changed signals to update the status pixbuf.
+	 */
+
+	model = gtk_tree_store_new(3, GDK_TYPE_PIXBUF, G_TYPE_STRING, GDK_TYPE_PIXBUF);
+	node = purple_blist_get_root();
+	while (node) {
+		GdkPixbuf *status = NULL, *prpl = NULL;
+		const char *name = NULL;
+		if ((PURPLE_BLIST_NODE_IS_BUDDY(node) || PURPLE_BLIST_NODE_IS_CONTACT(node))
+				&& (flags & PURPLE_REQUEST_BLIST_FLAG_BUDDY)) {
+			PurpleBuddy *buddy; 
+			GtkTreeIter *p = NULL;
+			if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+				buddy = (PurpleBuddy*)node;
+				p = &parent;
+			} else {
+				buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
+				parent = iter;
+			}
+			if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
+				gtk_tree_store_append(model, &iter, p);
+				name = purple_buddy_get_name(buddy);
+				status = pidgin_blist_get_status_icon(node, PIDGIN_STATUS_ICON_SMALL);
+			}
+		} else if (PURPLE_BLIST_NODE_IS_CHAT(node) && (flags & PURPLE_REQUEST_BLIST_FLAG_CHAT)) {
+			gtk_tree_store_append(model, &iter, NULL);
+			name = purple_chat_get_name((PurpleChat*)node);
+			status = pidgin_blist_get_status_icon(node, PIDGIN_STATUS_ICON_SMALL);
+		}
+		if (name)
+			gtk_tree_store_set(model, &iter,
+					0, status,
+					1, name,
+					2, prpl,
+					-1);
+		if (prpl)
+			gdk_pixbuf_unref(prpl);
+		if (status)
+			gdk_pixbuf_unref(status);
+		node = purple_blist_node_next(node, offline);
+	}
+
+	tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
+	gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), 1);
+	gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(tree), pidgin_tree_view_search_equal_func, NULL, NULL);
+	gtk_widget_show(tree);
+
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	rend = gtk_cell_renderer_pixbuf_new();
+	gtk_tree_view_column_pack_start(column, rend, FALSE);
+	gtk_tree_view_column_set_attributes(column, rend, "pixbuf", 0, NULL);
+
+	rend = gtk_cell_renderer_text_new();
+	gtk_tree_view_column_pack_start(column, rend, TRUE);
+	gtk_tree_view_column_set_attributes(column, rend, "markup", 1, NULL);
+
+	rend = gtk_cell_renderer_pixbuf_new();
+	gtk_tree_view_column_pack_start(column, rend, FALSE);
+	gtk_tree_view_column_set_attributes(column, rend, "pixbuf", 2, NULL);
+
+	sw = gtk_scrolled_window_new(NULL,NULL);
+	gtk_widget_show(sw);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_NONE);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_container_add(GTK_CONTAINER(sw), tree);
+
+	return sw;
+}
+
 static void
 select_field_list_item(GtkTreeModel *model, GtkTreePath *path,
 					   GtkTreeIter *iter, gpointer data)
@@ -1328,6 +1415,8 @@
 					widget = create_image_field(field);
 				else if (type == PURPLE_REQUEST_FIELD_ACCOUNT)
 					widget = create_account_field(field);
+				else if (type == PURPLE_REQUEST_FIELD_BLIST)
+					widget = create_blist_field(field);
 				else
 					continue;
 
--- a/pidgin/gtksavedstatuses.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtksavedstatuses.c	Fri Nov 16 23:32:17 2007 +0000
@@ -63,7 +63,7 @@
 };
 
 /**
- * These is used for the GtkTreeView containing the list of accounts
+ * These are used for the GtkTreeView containing the list of accounts
  * at the bottom of the window when you're editing a particular
  * saved status.
  */
@@ -898,6 +898,12 @@
 
 	for (i = PURPLE_STATUS_UNSET + 1; i < PURPLE_STATUS_NUM_PRIMITIVES; i++)
 	{
+		if (i == PURPLE_STATUS_MOBILE || i == PURPLE_STATUS_TUNE)
+			/*
+			 * Special-case these.  They're intended to be independent
+			 * status types, so don't show them in the list.
+			 */
+			continue;
 		item = gtk_menu_item_new_with_label(purple_primitive_get_name_from_type(i));
 		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
 	}
@@ -1588,8 +1594,12 @@
 
 		status_type = list->data;
 
-		/* Only allow users to select statuses that are flagged as "user settable" */
-		if (!purple_status_type_is_user_settable(status_type))
+		/*
+		 * Only allow users to select statuses that are flagged as
+		 * "user settable" and that aren't independent.
+		 */
+		if (!purple_status_type_is_user_settable(status_type) ||
+				purple_status_type_is_independent(status_type))
 			continue;
 
 		id = purple_status_type_get_id(status_type);
--- a/pidgin/gtksound.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtksound.c	Fri Nov 16 23:32:17 2007 +0000
@@ -118,12 +118,9 @@
 	if (conv != NULL && PIDGIN_IS_PIDGIN_CONVERSATION(conv))
 	{
 		PidginConversation *gtkconv;
-		PidginWindow *win;
 		gboolean has_focus;
 
 		gtkconv = PIDGIN_CONVERSATION(conv);
-		win = gtkconv->win;
-
 		has_focus = purple_conversation_has_focus(conv);
 
 		if (!gtkconv->make_sound ||
--- a/pidgin/gtkutils.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkutils.c	Fri Nov 16 23:32:17 2007 +0000
@@ -111,19 +111,20 @@
 		desc = pango_font_description_from_string(font);
 	} else if (purple_running_gnome()) {
 		/* Use the GNOME "document" font, if applicable */
-		char *path, *font;
+		char *path;
 
 		if ((path = g_find_program_in_path("gconftool-2"))) {
+			char *font = NULL;
 			g_free(path);
-			if (!g_spawn_command_line_sync(
+			if (g_spawn_command_line_sync(
 					"gconftool-2 -g /desktop/gnome/interface/document_font_name",
-					&font, NULL, NULL, NULL))
-				return;
+					&font, NULL, NULL, NULL)) {
+				desc = pango_font_description_from_string(font);
+			}
+			g_free(font);
 		}
-		desc = pango_font_description_from_string(font);
-		g_free(font);
 	}
-	
+
 	if (desc) {
 		gtk_widget_modify_font(imhtml, desc);
 		pango_font_description_free(desc);
@@ -1525,6 +1526,8 @@
 
 			if (prpl_info && prpl_info->can_receive_file)
 				ft = prpl_info->can_receive_file(gc, who);
+			else if (prpl_info && prpl_info->send_file)
+				ft = TRUE;
 
 			if (im && ft)
 				purple_request_choice(NULL, NULL,
@@ -1558,6 +1561,7 @@
 						    _("Set as buddy icon"), DND_BUDDY_ICON,
 						    (ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
 							NULL);
+			gdk_pixbuf_unref(pb);
 			return;
 		}
 
--- a/pidgin/gtkutils.h	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/gtkutils.h	Fri Nov 16 23:32:17 2007 +0000
@@ -376,6 +376,8 @@
  *
  * @param conn   The connection to get information from.
  * @param name   The user to get information about.
+ *
+ * @since 2.1.0
  */
 void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name);
 
@@ -385,6 +387,8 @@
  * @param conn   The connection to get information from.
  * @param name   The user to get information about.
  * @param chatid The chat id.
+ *
+ * @since 2.1.0
  */
 void pidgin_retrieve_user_info_in_chat(PurpleConnection *conn, const char *name, int chatid);
 
@@ -423,6 +427,8 @@
  *
  * @param w The widget that we want to label.
  * @param l A GtkLabel that we want to use as the label for the widget.
+ *
+ * @since 2.2.0
  */
 void pidgin_set_accessible_relations(GtkWidget *w, GtkWidget *l);
 
@@ -437,6 +443,8 @@
  *        where the menu shall be drawn. This is an output parameter.
  * @param push_in This is an output parameter?
  * @param data Not used by this particular position function.
+ *
+ * @since 2.1.0
  */
 void pidgin_menu_position_func_helper(GtkMenu *menu, gint *x, gint *y,
 										gboolean *push_in, gpointer data);
@@ -671,6 +679,8 @@
  *
  * @return               A newly created text GtkComboBox containing a GtkEntry
  *                       child.
+ *
+ * @since 2.2.0
  */
 GtkWidget *pidgin_text_combo_box_entry_new(const char *default_item, GList *items);
 
@@ -680,6 +690,8 @@
  * @param widget         The simple text GtkComboBoxEntry equivalent widget
  *
  * @return               The text in the widget's entry. It must not be freed
+ *
+ * @since 2.2.0
  */
 const char *pidgin_text_combo_box_entry_get_text(GtkWidget *widget);
 
@@ -688,6 +700,8 @@
  *
  * @param widget         The simple text GtkComboBoxEntry equivalent widget
  * @param text           The text to set
+ *
+ * @since 2.2.0
  */
 void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text);
 
--- a/pidgin/plugins/history.c	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/plugins/history.c	Fri Nov 16 23:32:17 2007 +0000
@@ -42,6 +42,7 @@
 	GtkIMHtmlOptions options = GTK_IMHTML_NO_COLOURS;
 	char *header;
 	char *protocol;
+	char *escaped_alias;
 
 	convtype = purple_conversation_get_type(c);
 	gtkconv = PIDGIN_CONVERSATION(c);
@@ -120,10 +121,12 @@
 	if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml))))
 		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", options);
 
-	header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), alias,
+	escaped_alias = g_markup_escape_text(alias, -1);
+	header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), escaped_alias,
 							 purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time)));
 	gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), header, options);
 	g_free(header);
+	g_free(escaped_alias);
 
 	g_strchomp(history);
 	gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), history, options);
--- a/pidgin/plugins/perl/common/GtkDialogs.xs	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/plugins/perl/common/GtkDialogs.xs	Fri Nov 16 23:32:17 2007 +0000
@@ -10,10 +10,6 @@
 pidgin_dialogs_about()
 
 void
-pidgin_dialogs_about_with_parent(parent)
-	void * parent
-
-void
 pidgin_dialogs_im()
 
 void
--- a/pidgin/plugins/perl/common/GtkLog.xs	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/plugins/perl/common/GtkLog.xs	Fri Nov 16 23:32:17 2007 +0000
@@ -13,27 +13,11 @@
 	Purple::Account account
 
 void
-pidgin_log_show_with_parent(parent, type, screenname, account)
-	void * parent
-	Purple::LogType type
-	const char * screenname
-	Purple::Account account
-
-void
 pidgin_log_show_contact(contact)
 	Purple::BuddyList::Contact contact
 
-void
-pidgin_log_show_contact_with_parent(parent, contact)
-	void * parent
-	Purple::BuddyList::Contact contact
-
 MODULE = Pidgin::Log  PACKAGE = Pidgin::SysLog  PREFIX = pidgin_syslog_
 PROTOTYPES: ENABLE
 
 void
 pidgin_syslog_show()
-
-void
-pidgin_syslog_show_with_parent(parent)
-	void * parent
--- a/pidgin/plugins/perl/common/GtkPlugin.xs	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/plugins/perl/common/GtkPlugin.xs	Fri Nov 16 23:32:17 2007 +0000
@@ -11,7 +11,3 @@
 
 void
 pidgin_plugin_dialog_show()
-
-void
-pidgin_plugin_dialog_show_with_parent(parent)
-	void * parent
--- a/pidgin/plugins/perl/common/GtkPounce.xs	Fri Nov 16 23:30:03 2007 +0000
+++ b/pidgin/plugins/perl/common/GtkPounce.xs	Fri Nov 16 23:32:17 2007 +0000
@@ -9,13 +9,6 @@
 	const char * name
 	Purple::Pounce cur_pounce
 
-void
-pidgin_pounce_editor_show_with_parent(parent, account, name, cur_pounce)
-	void * parent
-	Purple::Account account
-	const char * name
-	Purple::Pounce cur_pounce
-
 MODULE = Pidgin::Pounce  PACKAGE = Pidgin::Pounces  PREFIX = pidgin_pounces_
 PROTOTYPES: ENABLE
 
@@ -29,8 +22,4 @@
 pidgin_pounces_manager_show()
 
 void
-pidgin_pounces_manager_show_with_parent(parent)
-	void * parent
-
-void
 pidgin_pounces_manager_hide()