changeset 18924:3afcfbed2ced

propagate from branch 'im.pidgin.pidgin' (head 5775dc23bad7ecf62c8f951574460d2f075f9e72) to branch 'im.pidgin.soc.2007.xmpp' (head c9129cb5ffdd67387b55d418b79fd8c2b6665f55)
author Andreas Monitzer <pidgin@monitzer.com>
date Wed, 08 Aug 2007 21:05:47 +0000
parents 03a0054954bb (diff) 5d0141588299 (current diff)
children c9de28101e6d
files libpurple/account.h libpurple/connection.h libpurple/protocols/bonjour/mdns_howl.h libpurple/protocols/bonjour/mdns_win32.h libpurple/protocols/jabber/xdata.c pidgin/pixmaps/status/16/message-pending.png
diffstat 108 files changed, 3830 insertions(+), 1205 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Wed Aug 08 21:01:42 2007 +0000
+++ b/COPYRIGHT	Wed Aug 08 21:05:47 2007 +0000
@@ -144,6 +144,7 @@
 Gustavo Giráldez
 Richard Gobeille
 Ian Goldberg
+Matthew Goldstein
 Michael Golden
 Charlie Gordon
 Ryan C. Gordon
@@ -245,6 +246,7 @@
 Andrew Molloy
 Michael Monreal
 Benjamin Moody
+John Moody
 Tim Mooney
 Sergio Moretto
 Christian Muise
--- a/ChangeLog	Wed Aug 08 21:01:42 2007 +0000
+++ b/ChangeLog	Wed Aug 08 21:05:47 2007 +0000
@@ -1,5 +1,19 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.1.1 (??/??/????):
+	libpurple:
+	* Added an account action to open your inbox in the yahoo prpl.
+	* Added support for Unicode status messages in Yahoo.
+	* Server-stored aliases for Yahoo. (John Moody)
+	* Fixed support for Yahoo! doodling.
+	* Bonjour plugin uses native Avahi instead of Howl
+
+	Pidgin:
+	* Show current outgoing conversation formatting on the font label on
+	  the toolbar
+	* Slim new redesign of conversation tabs to maximize number of
+	  conversations that can fit in a window
+
 version 2.1.0 (07/28/2007):
 	libpurple:
 	* Core changes to allow UIs to use second-granularity for scheduling.
--- a/ChangeLog.API	Wed Aug 08 21:01:42 2007 +0000
+++ b/ChangeLog.API	Wed Aug 08 21:05:47 2007 +0000
@@ -19,7 +19,6 @@
 		* purple_core_ensure_single_instance
 		    This is for UIs to use to ensure only one copy is running.
 		* purple_dbus_is_owner
-		* purple_image_data_calculate_filename
 		* 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.
--- a/Doxyfile.in	Wed Aug 08 21:01:42 2007 +0000
+++ b/Doxyfile.in	Wed Aug 08 21:05:47 2007 +0000
@@ -300,7 +300,7 @@
 # by member name. If set to NO (the default) the members will appear in 
 # declaration order.
 
-SORT_BRIEF_DOCS        = YES
+SORT_BRIEF_DOCS        = NO
 
 # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
 # sorted by fully-qualified names, including namespaces. If set to 
--- a/configure.ac	Wed Aug 08 21:01:42 2007 +0000
+++ b/configure.ac	Wed Aug 08 21:05:47 2007 +0000
@@ -46,8 +46,8 @@
 m4_define([purple_lt_current], [1])
 m4_define([purple_major_version], [2])
 m4_define([purple_minor_version], [1])
-m4_define([purple_micro_version], [0])
-m4_define([purple_version_suffix], [])
+m4_define([purple_micro_version], [1])
+m4_define([purple_version_suffix], [devel])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
 m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix]))
@@ -593,6 +593,46 @@
 AC_SUBST(MEANWHILE_LIBS)
 
 dnl #######################################################################
+dnl # Check for Native Avahi headers (for Bonjour)
+dnl #######################################################################
+AC_ARG_WITH(avahi-client-includes, [AC_HELP_STRING([--with-avahi-client-includes=DIR], [compile the Bonjour plugin against the Avahi Client includes in DIR])], [ac_avahi_client_includes="$withval"], [ac_avahi_client_includes="no"])
+AC_ARG_WITH(avahi-client-libs, [AC_HELP_STRING([--with-avahi-client-libs=DIR], [compile the Bonjour plugin against the Avahi Client libs in DIR])], [ac_avahi_client_libs="$withval"], [ac_avahi_client_libs="no"])
+AVAHI_CFLAGS=""
+AVAHI_LIBS=""
+
+dnl Attempt to autodetect Avahi
+PKG_CHECK_MODULES(AVAHI, [avahi-client avahi-glib], [
+	avahiincludes="yes"
+	avahilibs="yes"
+], [
+	AC_MSG_RESULT(no)
+	avahiincludes="no"
+	avahilibs="no"
+])
+
+dnl Override AVAHI_CFLAGS if the user specified an include dir
+if test "$ac_avahi_client_includes" != "no"; then
+	AVAHI_CFLAGS="-I$ac_avahi_client_includes"
+fi
+CPPFLAGS_save="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $AVAHI_CFLAGS"
+AC_CHECK_HEADER(avahi-client/client.h, [avahiincludes=yes], [avahiincludes=no])
+CPPFLAGS="$CPPFLAGS $AVAHI_CFLAGS $GLIB_CFLAGS"
+AC_CHECK_HEADER(avahi-glib/glib-malloc.h, [avahiincludes=yes], [avahiincludes=no])
+CPPFLAGS="$CPPFLAGS_save"
+
+dnl Override AVAHI_LIBS if the user specified a libs dir
+if test "$ac_avahi_client_libs" != "no"; then
+	AVAHI_LIBS="-L$ac_avahi_client_libs -lavahi-common -lavahi-client -lavahi-glib "
+fi
+AC_CHECK_LIB(avahi-client, avahi_client_new, [avahilibs=yes], [avahilibs=no], $AVAHI_LIBS)
+
+AC_SUBST(AVAHI_CFLAGS)
+AC_SUBST(AVAHI_LIBS)
+
+AM_CONDITIONAL(MDNS_AVAHI, test "x$avahiincludes" = "xyes" -a "x$avahilibs" = "xyes")
+
+dnl #######################################################################
 dnl # Check for Howl headers (for Bonjour)
 dnl #######################################################################
 AC_ARG_WITH(howl-includes, [AC_HELP_STRING([--with-howl-includes=DIR], [compile the Bonjour plugin against the Howl includes in DIR])], [ac_howl_includes="$withval"], [ac_howl_includes="no"])
@@ -601,6 +641,7 @@
 HOWL_LIBS=""
 
 dnl Attempt to autodetect avahi-compat-howl
+dnl TODO: (This should be removed when the native avahi stuff is stable)
 PKG_CHECK_MODULES(HOWL, avahi-compat-howl, [
 	howlincludes="yes"
 	howllibs="yes"
@@ -640,6 +681,9 @@
 AC_SUBST(HOWL_CFLAGS)
 AC_SUBST(HOWL_LIBS)
 
+AM_CONDITIONAL(MDNS_HOWL, test "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes")
+
+
 dnl #######################################################################
 dnl # Check for SILC client includes and libraries
 dnl #######################################################################
@@ -819,8 +863,10 @@
 if test "x$have_meanwhile" != "xyes" ; then
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'`
 fi
-if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then
-	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'`
+if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then
+	if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then
+		STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'`
+	fi
 fi
 if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/silc/silc10/'`
@@ -873,7 +919,7 @@
 		*)			echo "Invalid static protocol $i!!" ; exit ;;
 	esac
 done
-AM_CONDITIONAL(STATIC_BONJOUR, test "x$static_bonjour" = "xyes" -a "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes")
+AM_CONDITIONAL(STATIC_BONJOUR, test "x$static_bonjour" = "xyes")
 AM_CONDITIONAL(STATIC_GG, test "x$static_gg" = "xyes")
 AM_CONDITIONAL(STATIC_IRC, test "x$static_irc" = "xyes")
 AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes")
@@ -898,8 +944,10 @@
 if test "x$have_meanwhile" != "xyes"; then
 	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'`
 fi
-if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then
-	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'`
+if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then
+	if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then
+		DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'`
+	fi
 fi
 if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then
 	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/silc/silc10/'`
@@ -930,7 +978,7 @@
 		*)			echo "Invalid dynamic protocol $i!!" ; exit ;;
 	esac
 done
-AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes" -a "x$bonjourincludes" = "xyes" -a "x$bonjourclient" = "xyes")
+AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes"  -a [ [ "x$avahiincludes" = "xyes" -a "x$avahilibs " = "xyes" ] -o [ "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes" ] ] )
 AM_CONDITIONAL(DYNAMIC_GG, test "x$dynamic_gg" = "xyes")
 AM_CONDITIONAL(DYNAMIC_IRC, test "x$dynamic_irc" = "xyes")
 AM_CONDITIONAL(DYNAMIC_JABBER, test "x$dynamic_jabber" = "xyes")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/ui-ops.dox	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,24 @@
+/** @page ui-ops UiOps structures
+
+  When implementing a UI for libpurple, you need to fill in various UiOps
+  structures:
+
+   - #PurpleAccountUiOps
+   - #PurpleBlistUiOps
+   - #PurpleConnectionUiOps
+   - #PurpleConversationUiOps
+   - #PurpleCoreUiOps
+   - #PurpleDebugUiOps
+   - #PurpleDnsQueryUiOps
+   - #PurpleEventLoopUiOps
+   - #PurpleIdleUiOps
+   - #PurpleNotifyUiOps
+   - #PurplePrivacyUiOps
+   - #PurpleRequestUiOps
+   - #PurpleRoomlistUiOps
+   - #PurpleSoundUiOps
+   - #PurpleWhiteboardUiOps
+   - #PurpleXferUiOps
+
+ */
+// vim: ft=c.doxygen
--- a/finch/finch.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/finch.c	Wed Aug 08 21:05:47 2007 +0000
@@ -401,7 +401,9 @@
 	signal(SIGPIPE, SIG_IGN);
 
 	g_set_prgname("Finch");
+#if GLIB_CHECK_VERSION(2,2,0)
 	g_set_application_name(_("Finch"));
+#endif
 
 	/* Initialize the libpurple stuff */
 	if (!init_libpurple(argc, argv))
--- a/finch/gntsound.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/gntsound.c	Wed Aug 08 21:05:47 2007 +0000
@@ -22,6 +22,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
+#include "internal.h"
 #include "gntsound.h"
 
 const char *finch_sound_get_active_profile(void)
--- a/finch/libgnt/gntbindable.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/gntbindable.c	Wed Aug 08 21:05:47 2007 +0000
@@ -184,7 +184,7 @@
 
 	action = g_hash_table_lookup(klass->actions, name);
 	if (!action) {
-		g_printerr("GntWidget: Invalid action name %s for %s\n",
+		g_printerr("GntBindable: Invalid action name %s for %s\n",
 				name, g_type_name(G_OBJECT_CLASS_TYPE(klass)));
 		if (list)
 			g_list_free(list);
--- a/finch/libgnt/gntmain.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/gntmain.c	Wed Aug 08 21:05:47 2007 +0000
@@ -517,7 +517,8 @@
 
 void gnt_screen_release(GntWidget *widget)
 {
-	gnt_wm_window_close(wm, widget);
+	if (wm)
+		gnt_wm_window_close(wm, widget);
 }
 
 void gnt_screen_update(GntWidget *widget)
@@ -564,7 +565,9 @@
 
 void gnt_quit()
 {
-	g_hash_table_destroy(wm->nodes); /* XXX: */
+	g_object_unref(G_OBJECT(wm));
+	wm = NULL;
+
 	update_panels();
 	doupdate();
 	gnt_uninit_colors();
--- a/finch/libgnt/gnttextview.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/gnttextview.c	Wed Aug 08 21:05:47 2007 +0000
@@ -68,17 +68,31 @@
 	int i = 0;
 	GList *lines;
 	int rows, scrcol;
+	int comp = 0;          /* Used for top-aligned text */
 	gboolean has_scroll = !(view->flags & GNT_TEXT_VIEW_NO_SCROLL);
 
 	wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL));
 	werase(widget->window);
 
+	if ((view->flags & GNT_TEXT_VIEW_TOP_ALIGN) &&
+			g_list_length(view->list) < widget->priv.height) {
+		GList *now = view->list;
+		comp = widget->priv.height - g_list_length(view->list);
+		view->list = g_list_nth_prev(view->list, comp);
+		if (!view->list) {
+			view->list = g_list_first(now);
+			comp = widget->priv.height - g_list_length(view->list);
+		} else {
+			comp = 0;
+		}
+	}
+
 	for (i = 0, lines = view->list; i < widget->priv.height && lines; i++, lines = lines->next)
 	{
 		GList *iter;
 		GntTextLine *line = lines->data;
 
-		wmove(widget->window, widget->priv.height - 1 - i, 0);
+		wmove(widget->window, widget->priv.height - 1 - i - comp, 0);
 
 		for (iter = line->segments; iter; iter = iter->next)
 		{
@@ -398,7 +412,7 @@
 static void
 gnt_text_view_size_changed(GntWidget *widget, int w, int h)
 {
-	if (w != widget->priv.width) {
+	if (w != widget->priv.width && GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_MAPPED)) {
 		gnt_text_view_reflow(GNT_TEXT_VIEW(widget));
 	}
 }
@@ -422,11 +436,16 @@
 gnt_text_view_init(GTypeInstance *instance, gpointer class)
 {
 	GntWidget *widget = GNT_WIDGET(instance);
-	
-	GNT_WIDGET_SET_FLAGS(GNT_WIDGET(instance), GNT_WIDGET_GROW_Y | GNT_WIDGET_GROW_X);
+	GntTextView *view = GNT_TEXT_VIEW(widget);
+	GntTextLine *line = g_new0(GntTextLine, 1);
 
+	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW | 
+            GNT_WIDGET_GROW_Y | GNT_WIDGET_GROW_X);
 	widget->priv.minw = 5;
 	widget->priv.minh = 2;
+	view->string = g_string_new(NULL);
+	view->list = g_list_append(view->list, line);
+
 	GNTDEBUG;
 }
 
@@ -464,13 +483,6 @@
 GntWidget *gnt_text_view_new()
 {
 	GntWidget *widget = g_object_new(GNT_TYPE_TEXT_VIEW, NULL);
-	GntTextView *view = GNT_TEXT_VIEW(widget);
-	GntTextLine *line = g_new0(GntTextLine, 1);
-
-	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW);
-
-	view->string = g_string_new(NULL);
-	view->list = g_list_append(view->list, line);
 
 	return widget;
 }
--- a/finch/libgnt/gnttextview.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/gnttextview.h	Wed Aug 08 21:05:47 2007 +0000
@@ -47,9 +47,11 @@
 typedef struct _GntTextViewPriv		GntTextViewPriv;
 typedef struct _GntTextViewClass		GntTextViewClass;
 
-typedef enum _GntTextViewFlag {
+typedef enum
+{
 	GNT_TEXT_VIEW_NO_SCROLL     = 1 << 0,
 	GNT_TEXT_VIEW_WRAP_CHAR     = 1 << 1,
+	GNT_TEXT_VIEW_TOP_ALIGN     = 1 << 2,
 } GntTextViewFlag;
 
 struct _GntTextView
--- a/finch/libgnt/gnttree.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/gnttree.c	Wed Aug 08 21:05:47 2007 +0000
@@ -450,7 +450,7 @@
 			if (COLUMN_INVISIBLE(tree, i)) {
 				continue;
 			}
-			mvwaddstr(widget->window, pos, x + 1, tree->columns[i].title);
+			mvwaddnstr(widget->window, pos, x + (x != pos), tree->columns[i].title, tree->columns[i].width);
 			NEXT_X;
 		}
 		if (pos)
@@ -768,6 +768,7 @@
 		g_string_free(tree->priv->search, TRUE);
 		tree->priv->search = NULL;
 		tree->priv->search_timeout = 0;
+		GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS);
 	}
 }
 
@@ -984,7 +985,11 @@
 			g_param_spec_int("columns", "Columns",
 				"Number of columns in the tree.",
 				1, G_MAXINT, 1,
+#if GLIB_CHECK_VERSION(2,8,0)
 				G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+#else
+				G_PARAM_READWRITE|G_PARAM_PRIVATE
+#endif
 			)
 		);
 
@@ -1049,7 +1054,9 @@
 	GntTree *tree = GNT_TREE(widget);
 	tree->show_separator = TRUE;
 	tree->priv = g_new0(GntTreePriv, 1);
-	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y | GNT_WIDGET_CAN_TAKE_FOCUS);
+	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y |
+			GNT_WIDGET_CAN_TAKE_FOCUS | GNT_WIDGET_NO_SHADOW);
+	gnt_widget_set_take_focus(widget, TRUE);
 	widget->priv.minw = 4;
 	widget->priv.minh = 1;
 	GNTDEBUG;
@@ -1090,7 +1097,7 @@
 free_tree_col(gpointer data)
 {
 	GntTreeCol *col = data;
-	if (col->isbinary)
+	if (!col->isbinary)
 		g_free(col->text);
 	g_free(col);
 }
@@ -1601,9 +1608,6 @@
 			"columns", col,
 			NULL);
 
-	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_SHADOW);
-	gnt_widget_set_take_focus(widget, TRUE);
-
 	return widget;
 }
 
--- a/finch/libgnt/gntwm.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/gntwm.c	Wed Aug 08 21:05:47 2007 +0000
@@ -684,6 +684,8 @@
 		{'j', "&#x2518;"},
 		{'a', "&#x2592;"},
 		{'n', "&#x253c;"},
+		{'w', "&#x252c;"},
+		{'v', "&#x2534;"},
 		{'\0', NULL}
 	};
 
@@ -1137,9 +1139,37 @@
 }
 
 static void
+accumulate_windows(gpointer window, gpointer node, gpointer p)
+{
+	GList *list = *(GList**)p;
+	list = g_list_prepend(list, window);
+	*(GList**)p = list;
+}
+
+static void
+gnt_wm_destroy(GObject *obj)
+{
+	GntWM *wm = GNT_WM(obj);
+	GList *list = NULL;
+	g_hash_table_foreach(wm->nodes, accumulate_windows, &list);
+	g_list_foreach(list, (GFunc)gnt_widget_destroy, NULL);
+	g_list_free(list);
+	g_hash_table_destroy(wm->nodes);
+	wm->nodes = NULL;
+
+	while (wm->workspaces) {
+		g_object_unref(wm->workspaces->data);
+		wm->workspaces = g_list_delete_link(wm->workspaces, wm->workspaces);
+	}
+}
+
+static void
 gnt_wm_class_init(GntWMClass *klass)
 {
 	int i;
+	GObjectClass *gclass = G_OBJECT_CLASS(klass);
+
+	gclass->dispose = gnt_wm_destroy;
 
 	klass->new_window = gnt_wm_new_window_real;
 	klass->decorate_window = NULL;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/Makefile.am	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,40 @@
+EXTRA_DIST = gendef.sh
+
+pg_LTLIBRARIES = gnt.la
+
+pgdir = $(libdir)
+
+sources = \
+	gnt.def \
+	gnt.override \
+	gntbox.override \
+	gntfilesel.override \
+	gnttree.override \
+	gntwidget.override
+
+gnt_la_SOURCES = gnt.c common.c common.h gntmodule.c
+
+gnt_la_LDFLAGS = -module -avoid-version \
+	`pkg-config --libs pygobject-2.0`
+
+gnt_la_LIBADD = \
+	$(GLIB_LIBS) \
+	../libgnt.la
+
+AM_CPPFLAGS = \
+	-I../ \
+	$(GLIB_CFLAGS) \
+	$(GNT_CFLAGS)  \
+	-I/usr/include/python2.4 \
+	`pkg-config --cflags pygobject-2.0`
+
+CLEANFILES = gnt.def gnt.c gnt.defe
+
+gnt.def: $(srcdir)/../*.h
+	$(srcdir)/gendef.sh
+
+gnt.c: $(sources)
+	pygtk-codegen-2.0 --prefix gnt \
+	--override gnt.override \
+	gnt.def > $@
+
--- a/finch/libgnt/pygnt/Makefile.make	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/pygnt/Makefile.make	Wed Aug 08 21:05:47 2007 +0000
@@ -10,5 +10,7 @@
 	--override gnt.override \
 	gnt.def > $@
 
+#python codegen/codegen.py --prefix gnt \
+
 clean:
 	@rm *.so *.o gnt.c
--- a/finch/libgnt/pygnt/dbus-gnt	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/pygnt/dbus-gnt	Wed Aug 08 21:05:47 2007 +0000
@@ -17,13 +17,14 @@
 
 convwins = {}
 
-def buddysignedon():
+def buddysignedon(buddy):
     pass
 
 def conv_closed(conv):
     key = get_dict_key(conv)
     stuff = convwins[key]
     stuff[0].destroy()
+    # if a conv window is closed, then reopened, this thing crashes
     convwins[key] = None
 
 def wrote_msg(account, who, msg, conv, flags):
@@ -31,10 +32,13 @@
     tv = stuff[1]
     tv.append_text_with_flags("\n", 0)
     tv.append_text_with_flags(strftime("(%X) "), 8)
-    tv.append_text_with_flags(who + ": ", 1)
-    tv.append_text_with_flags(msg, 0)
+    if flags & 3:
+        tv.append_text_with_flags(who + ": ", 1)
+        tv.append_text_with_flags(msg, 0)
+        stuff[0].set_urgent()
+    else:
+        tv.append_text_with_flags(msg, 8)
     tv.scroll(0)
-    stuff[0].set_urgent()
 
 bus = dbus.SessionBus()
 obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
@@ -72,6 +76,9 @@
             purple.PurpleConvChatSend(chatdata, entry.get_text())
         entry.clear()
 
+def conv_window_destroyed(win, key):
+    del convwins[key]
+
 def show_conversation(conv):
     key = get_dict_key(conv)
     if key in convwins:
@@ -91,27 +98,28 @@
     tv.clear()
     win.show()
     convwins[key] = [win, tv, entry]
+    win.connect("destroy", conv_window_destroyed, key)
     return convwins[key]
 
 def show_buddylist():
-	win = gnt.Window()
-	tree = gnt.Tree()
-	tree.set_property("columns", 1)
-	win.add_widget(tree)
-	node = purple.PurpleBlistGetRoot()
-	while node:
-		if purple.PurpleBlistNodeIsGroup(node):
-			sys.stderr.write(str(node) + "\n")
-			tree.add_row_after(str(node), ["asd", ""], None, None)
-			#tree.add_row_after(node, [str(purple.PurpleGroupGetName(node)), ""], None, None)
-			#tree.add_row_after(node, ["aasd", ""], None, None)
-		elif purple.PurpleBlistNodeIsContact(node):
-			buddy = purple.PurpleContactGetPriorityBuddy(node)
-			group = purple.PurpleBuddyGetGroup(buddy)
-			#tree.add_row_after(node, [str(purple.PurpleBuddyGetName(buddy)), ""], group, None)
+    win = gnt.Window()
+    tree = gnt.Tree()
+    tree.set_property("columns", 1)
+    win.add_widget(tree)
+    node = purple.PurpleBlistGetRoot()
+    while node:
+        if purple.PurpleBlistNodeIsGroup(node):
+            sys.stderr.write(str(node) + "\n")
+            tree.add_row_after(str(node), ["asd", ""], None, None)
+            #tree.add_row_after(node, [str(purple.PurpleGroupGetName(node)), ""], None, None)
+            #tree.add_row_after(node, ["aasd", ""], None, None)
+        elif purple.PurpleBlistNodeIsContact(node):
+            buddy = purple.PurpleContactGetPriorityBuddy(node)
+            group = purple.PurpleBuddyGetGroup(buddy)
+            #tree.add_row_after(node, [str(purple.PurpleBuddyGetName(buddy)), ""], group, None)
 
-		node = purple.PurpleBlistNodeNext(node, False)
-	win.show()
+        node = purple.PurpleBlistNodeNext(node, False)
+    win.show()
 
 gnt.gnt_init()
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/example/rss/gnthtml.py	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+
+"""
+gr - An RSS-reader built using libgnt and feedparser.
+
+Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+
+This application is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This application 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
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this application; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+"""
+
+"""
+This file defines GParser, which is a simple HTML parser to display HTML
+in a GntTextView nicely.
+"""
+
+import sgmllib
+import gnt
+
+class GParser(sgmllib.SGMLParser):
+    def __init__(self, view):
+        sgmllib.SGMLParser.__init__(self, False)
+        self.link = None
+        self.view = view
+        self.flag = gnt.TEXT_FLAG_NORMAL
+
+    def parse(self, s):
+        self.feed(s)
+        self.close()
+
+    def unknown_starttag(self, tag, attrs):
+        if tag in ["b", "i", "blockquote", "strong"]:
+            self.flag = self.flag | gnt.TEXT_FLAG_BOLD
+        elif tag in ["p", "hr", "br"]:
+            self.view.append_text_with_flags("\n", self.flag)
+        else:
+            print tag
+
+    def unknown_endtag(self, tag):
+        if tag in ["b", "i", "blockquote", "strong"]:
+            self.flag = self.flag & ~gnt.TEXT_FLAG_BOLD
+        elif tag in ["p", "hr", "br"]:
+            self.view.append_text_with_flags("\n", self.flag)
+        else:
+            print tag
+
+    def start_u(self, attrs):
+        self.flag = self.flag | gnt.TEXT_FLAG_UNDERLINE
+
+    def end_u(self):
+        self.flag = self.flag & ~gnt.TEXT_FLAG_UNDERLINE
+
+    def start_a(self, attributes):
+        for name, value in attributes:
+            if name == "href":
+                self.link = value
+
+    def do_img(self, attrs):
+        for name, value in attrs:
+            if name == 'src':
+                self.view.append_text_with_flags("[img:" + value + "]", self.flag)
+
+    def end_a(self):
+        if not self.link:
+            return
+        self.view.append_text_with_flags(" (", self.flag)
+        self.view.append_text_with_flags(self.link, self.flag | gnt.TEXT_FLAG_UNDERLINE)
+        self.view.append_text_with_flags(")", self.flag)
+        self.link = None
+
+    def handle_data(self, data):
+        if len(data.strip()) == 0:
+            return
+        self.view.append_text_with_flags(data, self.flag)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/example/rss/gntrss-ui.py	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,399 @@
+#!/usr/bin/env python
+
+"""
+gr - An RSS-reader built using libgnt and feedparser.
+
+Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+
+This application is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This application 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
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this application; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+"""
+
+"""
+This file deals with the UI part (gnt) of the application
+
+TODO:
+    - Allow showing feeds of only selected 'category' and/or 'priority'. A different
+      window should be used to change such filtering.
+    - Display details of each item in its own window.
+    - Add search capability, and allow searching only in title/body. Also allow
+      filtering in the search results.
+    - Show the data and time for feed items (probably in a separate column .. perhaps not)
+    - Have a simple way to add a feed.
+    - Allow renaming a feed.
+"""
+
+import gntrss
+import gnthtml
+import gnt
+import gobject
+import sys
+
+__version__ = "0.0.1alpha"
+__author__ = "Sadrul Habib Chowdhury (sadrul@pidgin.im)"
+__copyright__ = "Copyright 2007, Sadrul Habib Chowdhury"
+__license__ = "GPL" # see full license statement above
+
+gnt.gnt_init()
+
+class RssTree(gnt.Tree):
+    __gsignals__ = {
+        'active_changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
+    }
+
+    __gntbindings__ = {
+        'jump-next-unread' : ('jump_next_unread', 'J')
+    }
+
+    def jump_next_unread(self, null):
+        first = None
+        next = None
+        all = self.get_rows()
+        for item in all:
+            if item.unread:
+                if next:
+                    first = item
+                    break
+                elif not first and self.active != item:
+                    first = item
+            if self.active == item:
+                next = item
+        if first:
+            self.set_active(first)
+            self.set_selected(first)
+
+    def __init__(self):
+        self.active = None
+        gnt.Tree.__init__(self)
+        gnt.set_flag(self, 8)    # remove borders
+        self.connect('key_pressed', self.do_key_pressed)
+
+    def set_active(self, active):
+        if self.active == active:
+            return
+        if self.active:
+            flag = gnt.TEXT_FLAG_NORMAL
+            if self.active.unread:
+                flag = flag | gnt.TEXT_FLAG_BOLD
+            self.set_row_flags(self.active, flag)
+        old = self.active
+        self.active = active
+        flag = gnt.TEXT_FLAG_UNDERLINE
+        if self.active.unread:
+            flag = flag | gnt.TEXT_FLAG_BOLD
+        self.set_row_flags(self.active, flag)
+        self.emit('active_changed', old)
+
+    def do_key_pressed(self, null, text):
+        if text == '\r':
+            now = self.get_selection_data()
+            self.set_active(now)
+            return True
+        return False
+
+gobject.type_register(RssTree)
+gnt.register_bindings(RssTree)
+
+win = gnt.Box(homo = False, vert = True)
+win.set_toplevel(True)
+win.set_title("GntRss")
+win.set_pad(0)
+
+#
+# [[[ Generic feed/item callbacks
+#
+def feed_item_added(feed, item):
+    add_feed_item(item)
+
+def add_feed(feed):
+    if not feed.get_data('gntrss-connected'):
+        feed.connect('added', feed_item_added)
+        feed.connect('notify', update_feed_title)
+        feed.set_data('gntrss-connected', True)
+    feeds.add_row_after(feed, [feed.title, str(feed.unread)], None, None)
+
+def remove_item(item, feed):
+    items.remove(item)
+
+def update_feed_item(item, property):
+    if property.name == 'unread':
+        if feeds.active == item.parent:
+            flag = 0
+            if item == items.active:
+                flag = gnt.TEXT_FLAG_UNDERLINE
+            if item.unread:
+                flag = flag | gnt.TEXT_FLAG_BOLD
+            else:
+                flag = flag | gnt.TEXT_FLAG_NORMAL
+            items.set_row_flags(item, flag)
+
+        unread = item.parent.unread
+        if item.unread:
+            unread = unread + 1
+        else:
+            unread = unread - 1
+        item.parent.set_property('unread', unread)
+
+def add_feed_item(item):
+    currentfeed = feeds.active
+    if item.parent != currentfeed:
+        return
+    months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+    dt = str(item.date_parsed[2]) + "." + months[item.date_parsed[1]] + "." + str(item.date_parsed[0])
+    items.add_row_after(item, [str(item.title), dt], None, None)
+    if item.unread:
+        items.set_row_flags(item, gnt.TEXT_FLAG_BOLD)
+    if not item.get_data('gntrss-connected'):
+        item.set_data('gntrss-connected', True)
+        # this needs to happen *without* having to add the item in the tree
+        item.connect('notify', update_feed_item)
+        item.connect('delete', remove_item)
+
+#
+# ]]] Generic feed/item callbacks
+#
+
+
+####
+# [[[ The list of feeds
+###
+
+# 'Add Feed' dialog
+add_feed_win = None
+def add_feed_win_closed(win):
+    global add_feed_win
+    add_feed_win = None
+
+def add_new_feed():
+    global add_feed_win
+
+    if add_feed_win:
+        gnt.gnt_window_present(add_feed_win)
+        return
+    win = gnt.Window()
+    win.set_title("New Feed")
+
+    box = gnt.Box(False, False)
+    label = gnt.Label("Link")
+    box.add_widget(label)
+    entry = gnt.Entry("")
+    entry.set_size(40, 1)
+    box.add_widget(entry)
+
+    win.add_widget(box)
+    win.show()
+    add_feed_win = win
+    add_feed_win.connect("destroy", add_feed_win_closed)
+
+#
+# The active row in the feed-list has changed. Update the feed-item table.
+def feed_active_changed(tree, old):
+    items.remove_all()
+    if not tree.active:
+        return
+    update_items_title()
+    for item in tree.active.items:
+        add_feed_item(item)
+    win.give_focus_to_child(items)
+
+#
+# Check for the action keys and decide how to deal with them.
+def feed_key_pressed(tree, text):
+    if tree.is_searching():
+        return
+    if text == 'r':
+        feed = tree.get_selection_data()
+        tree.perform_action_key('j')
+        #tree.perform_action('move-down')
+        feed.refresh()
+    elif text == 'R':
+        feeds = tree.get_rows()
+        for feed in feeds:
+            feed.refresh()
+    elif text == 'm':
+        feed = tree.get_selection_data()
+        if feed:
+            feed.mark_read()
+            feed.set_property('unread', 0)
+    elif text == 'a':
+        add_new_feed()
+    else:
+        return False
+    return True
+
+feeds = RssTree()
+feeds.set_property('columns', 2)
+feeds.set_col_width(0, 20)
+feeds.set_col_width(1, 6)
+feeds.set_column_resizable(0, False)
+feeds.set_column_resizable(1, False)
+feeds.set_column_is_right_aligned(1, True)
+feeds.set_show_separator(False)
+feeds.set_column_title(0, "Feeds")
+feeds.set_show_title(True)
+
+feeds.connect('active_changed', feed_active_changed)
+feeds.connect('key_pressed', feed_key_pressed)
+gnt.unset_flag(feeds, 256)   # Fix the width
+
+####
+# ]]] The list of feeds
+###
+
+####
+# [[[ The list of items in the feed
+####
+
+#
+# The active item in the feed-item list has changed. Update the
+# summary content.
+def item_active_changed(tree, old):
+    details.clear()
+    if not tree.active:
+        return
+    item = tree.active
+    details.append_text_with_flags(str(item.title) + "\n", gnt.TEXT_FLAG_BOLD)
+    details.append_text_with_flags("Link: ", gnt.TEXT_FLAG_BOLD)
+    details.append_text_with_flags(str(item.link) + "\n", gnt.TEXT_FLAG_UNDERLINE)
+    details.append_text_with_flags("Date: ", gnt.TEXT_FLAG_BOLD)
+    details.append_text_with_flags(str(item.date) + "\n", gnt.TEXT_FLAG_NORMAL)
+    details.append_text_with_flags("\n", gnt.TEXT_FLAG_NORMAL)
+    parser = gnthtml.GParser(details)
+    parser.parse(str(item.summary))
+    item.mark_unread(False)
+
+    if old and old.unread:   # If the last selected item is marked 'unread', then make sure it's bold
+        items.set_row_flags(old, gnt.TEXT_FLAG_BOLD)
+
+#
+# Look for action keys in the feed-item list.
+def item_key_pressed(tree, text):
+    if tree.is_searching():
+        return
+    current = tree.get_selection_data()
+    if text == 'M':     # Mark all of the items 'read'
+        feed = feeds.active
+        if feed:
+            feed.mark_read()
+    elif text == 'm':     # Mark the current item 'read'
+        current.mark_unread(False)
+        tree.perform_action_key('j')
+    elif text == 'U':     # Mark the current item 'unread'
+        current.mark_unread(True)
+    elif text == 'd':
+        current.remove()
+        tree.perform_action_key('j')
+    else:
+        return False
+    return True
+
+items = RssTree()
+items.set_property('columns', 2)
+items.set_col_width(0, 40)
+items.set_col_width(1, 11)
+items.set_column_resizable(1, False)
+items.set_column_title(0, "Items")
+items.set_column_title(1, "Date")
+items.set_show_title(True)
+items.connect('key_pressed', item_key_pressed)
+items.connect('active_changed', item_active_changed)
+
+####
+# ]]] The list of items in the feed
+####
+
+#
+# Update the title of the items list depending on the selection in the feed list
+def update_items_title():
+    feed = feeds.active
+    if feed:
+        items.set_column_title(0, str(feed.title) + ": " + str(feed.unread) + "(" + str(len(feed.items)) + ")")
+    else:
+        items.set_column_title(0, "Items")
+    items.draw()
+
+# The container on the top
+line = gnt.Line(vertical = False)
+
+# The textview to show the details of a feed
+details = gnt.TextView()
+details.set_take_focus(True)
+details.set_flag(gnt.TEXT_VIEW_TOP_ALIGN)
+details.attach_scroll_widget(details)
+
+# Make it look nice
+s = feeds.get_size()
+size = gnt.screen_size()
+size[0] = size[0] - s[0]
+items.set_size(size[0], size[1] / 2)
+details.set_size(size[0], size[1] / 2)
+
+# Category tree
+cat = gnt.Tree()
+cat.set_property('columns', 1)
+cat.set_column_title(0, 'Category')
+cat.set_show_title(True)
+gnt.set_flag(cat, 8)    # remove borders
+
+box = gnt.Box(homo = False, vert = False)
+box.set_pad(0)
+
+vbox = gnt.Box(homo = False, vert = True)
+vbox.set_pad(0)
+vbox.add_widget(feeds)
+vbox.add_widget(gnt.Line(False))
+vbox.add_widget(cat)
+box.add_widget(vbox)
+
+box.add_widget(gnt.Line(True))
+
+vbox = gnt.Box(homo = False, vert = True)
+vbox.set_pad(0)
+vbox.add_widget(items)
+vbox.add_widget(gnt.Line(False))
+vbox.add_widget(details)
+box.add_widget(vbox)
+
+win.add_widget(box)
+win.show()
+
+def update_feed_title(feed, property):
+    if property.name == 'title':
+        if feed.customtitle:
+            title = feed.customtitle
+        else:
+            title = feed.title
+        feeds.change_text(feed, 0, title)
+    elif property.name == 'unread':
+        feeds.change_text(feed, 1, str(feed.unread) + "(" + str(len(feed.items)) + ")")
+        flag = 0
+        if feeds.active == feed:
+            flag = gnt.TEXT_FLAG_UNDERLINE
+            update_items_title()
+        if feed.unread > 0:
+            flag = flag | gnt.TEXT_FLAG_BOLD
+        feeds.set_row_flags(feed, flag)
+
+# populate everything
+for feed in gntrss.feeds:
+    feed.refresh()
+    feed.set_auto_refresh(True)
+    add_feed(feed)
+
+gnt.gnt_register_action("Stuff", add_new_feed)
+gnt.gnt_main()
+
+gnt.gnt_quit()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/example/rss/gntrss.py	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,250 @@
+#!/usr/bin/env python
+
+"""
+gr - An RSS-reader built using libgnt and feedparser.
+
+Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+
+This application is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This application 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
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this application; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+"""
+
+"""
+This file deals with the rss parsing part (feedparser) of the application
+"""
+
+import os
+import tempfile, urllib2
+import feedparser
+import gobject
+import sys
+import time
+
+##
+# The FeedItem class. It will update emit 'delete' signal when it's
+# destroyed.
+##
+class FeedItem(gobject.GObject):
+    __gproperties__ = {
+        'unread' : (gobject.TYPE_BOOLEAN, 'read',
+            'The unread state of the item.',
+            False, gobject.PARAM_READWRITE)
+    }
+    __gsignals__ = {
+        'delete' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
+    }
+    def __init__(self, item, parent):
+        self.__gobject_init__()
+        try:
+            "Apparently some feed items don't have any dates in them"
+            self.date = item['date']
+            self.date_parsed = item['date_parsed']
+        except:
+            item['date'] = self.date = time.ctime()
+            self.date_parsed = feedparser._parse_date(self.date)
+
+        self.title = item['title']
+        sum = item['summary']
+        self.summary = item['summary'].encode('utf8')
+        self.link = item['link']
+        self.parent = parent
+        self.unread = True
+
+    def remove(self):
+        self.emit('delete', self.parent)
+        if self.unread:
+            self.parent.set_property('unread', self.parent.unread - 1)
+
+    def do_set_property(self, property, value):
+        if property.name == 'unread':
+            self.unread = value
+
+    def mark_unread(self, unread):
+        if self.unread == unread:
+            return
+        self.set_property('unread', unread)
+
+gobject.type_register(FeedItem)
+
+def item_hash(item):
+    return str(item['date'] + item['title'])
+
+"""
+The Feed class. It will update the 'link', 'title', 'desc' and 'items'
+attributes if/when they are updated (triggering 'notify::<attr>' signal)
+
+TODO:
+    - Add a 'count' attribute
+    - Each feed will have a 'uidata', which will be its display window
+    - Look into 'category'. Is it something that feed defines, or the user?
+    - Have separate refresh times for each feed.
+    - Have 'priority' for each feed. (somewhat like category, perhaps?)
+"""
+class Feed(gobject.GObject):
+    __gproperties__ = {
+        'link' : (gobject.TYPE_STRING, 'link',
+            'The web page this feed is associated with.',
+            '...', gobject.PARAM_READWRITE),
+        'title' : (gobject.TYPE_STRING, 'title',
+            'The title of the feed.',
+            '...', gobject.PARAM_READWRITE),
+        'desc' : (gobject.TYPE_STRING, 'description',
+            'The description for the feed.',
+            '...', gobject.PARAM_READWRITE),
+        'items' : (gobject.TYPE_POINTER, 'items',
+            'The items in the feed.', gobject.PARAM_READWRITE),
+        'unread' : (gobject.TYPE_INT, 'unread',
+            'Number of unread items in the feed.', 0, 10000, 0, gobject.PARAM_READWRITE)
+    }
+    __gsignals__ = {
+        'added' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))
+    }
+
+    def __init__(self, feed):
+        self.__gobject_init__()
+        url = feed['link']
+        name = feed['name']
+        self.url = url           # The url of the feed itself
+        self.link = url          # The web page associated with the feed
+        self.desc = url
+        self.title = (name, url)[not name]
+        self.customtitle = name
+        self.unread = 0
+        self.items = []
+        self.hash = {}
+        self.pending = False
+        self._refresh = {'time' : 30, 'id' : 0}
+
+    def do_set_property(self, property, value):
+        if property.name == 'link':
+            self.link = value
+        elif property.name == 'desc':
+            self.desc = value
+        elif property.name == 'title':
+            self.title = value
+        elif property.name == 'unread':
+            self.unread = value
+        pass
+
+    def set_result(self, result):
+        # XXX Look at result['bozo'] first, and emit some signal that the UI can use
+        # to indicate (dim the row?) that the feed has invalid XML format or something
+
+        try:
+            channel = result['channel']
+            self.set_property('link', channel['link'])
+            self.set_property('desc', channel['description'])
+            self.set_property('title', channel['title'])
+            items = result['items']
+        except:
+            items = ()
+
+        tmp = {}
+        for item in self.items:
+            tmp[hash(item)] = item
+
+        unread = self.unread
+        for item in items:
+            try:
+                exist = self.hash[item_hash(item)]
+                del tmp[hash(exist)]
+            except:
+                itm = FeedItem(item, self)
+                self.items.append(itm)
+                self.emit('added', itm)
+                self.hash[item_hash(item)] = itm
+                unread = unread + 1
+
+        if unread != self.unread:
+            self.set_property('unread', unread)
+
+        for hv in tmp:
+            tmp[hv].remove()
+            "Also notify the UI about the count change"
+
+        self.pending = False
+        return False
+
+    def refresh(self):
+        if self.pending:
+            return
+        self.pending = True
+        FeedReader(self).run()
+        return True
+
+    def mark_read(self):
+        for item in self.items:
+            item.mark_unread(False)
+
+    def set_auto_refresh(self, auto):
+        if auto:
+            if self._refresh['id']:
+                return
+            if self._refresh['time'] < 1:
+                self._refresh['time'] = 1
+            self.id = gobject.timeout_add(self._refresh['time'] * 1000 * 60, self.refresh)
+        else:
+            if not self._refresh['id']:
+                return
+            gobject.source_remove(self._refresh['id'])
+            self._refresh['id'] = 0
+
+gobject.type_register(Feed)
+
+"""
+The FeedReader updates a Feed. It fork()s off a child to avoid blocking.
+"""
+class FeedReader:
+    def __init__(self, feed):
+        self.feed = feed
+
+    def reap_child(self, pid, status):
+        result = feedparser.parse(self.tmpfile.name)
+        self.tmpfile.close()
+        self.feed.set_result(result)
+
+    def run(self):
+        self.tmpfile = tempfile.NamedTemporaryFile()
+        self.pid = os.fork()
+        if self.pid == 0:
+            tmp = urllib2.urlopen(self.feed.url)
+            content = tmp.read()
+            tmp.close()
+            self.tmpfile.write(content)
+            self.tmpfile.flush()
+            # Do NOT close tmpfile here
+            os._exit(os.EX_OK)
+        gobject.child_watch_add(self.pid, self.reap_child)
+
+feeds = []
+urls = (
+    {'name': '/.',
+     'link': "http://rss.slashdot.org/Slashdot/slashdot"},
+    {'name': 'KernelTrap',
+     'link': "http://kerneltrap.org/node/feed"},
+    {'name': None,
+     'link': "http://pidgin.im/rss.php"},
+    {'name': "F1",
+     'link': "http://www.formula1.com/rss/news/latest.rss"},
+    {'name': "Freshmeat",
+     'link': "http://www.pheedo.com/f/freshmeatnet_announcements_unix"},
+    {'name': "Cricinfo",
+     'link': "http://www.cricinfo.com/rss/livescores.xml"}
+)
+
+for url in urls:
+    feed = Feed(url)
+    feeds.append(feed)
+
--- a/finch/libgnt/pygnt/gendef.sh	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/pygnt/gendef.sh	Wed Aug 08 21:05:47 2007 +0000
@@ -28,7 +28,7 @@
 	gnt.h"
 
 # Generate the def file
-rm gnt.def
+rm -f gnt.def
 for file in $FILES
 do
 	python /usr/share/pygtk/2.0/codegen/h2def.py ../$file >> gnt.def
@@ -43,6 +43,7 @@
 GNT_TYPE_KEY_PRESS_MODE
 GNT_TYPE_ENTRY_FLAG
 GNT_TYPE_TEXT_FORMAT_FLAGS
+GNT_TYPE_TEXT_VIEW_FLAG
 "
 
 for enum in $ENUMS
--- a/finch/libgnt/pygnt/gnt.override	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/pygnt/gnt.override	Wed Aug 08 21:05:47 2007 +0000
@@ -29,8 +29,10 @@
 #include "common.h"
 %%
 include
+ gntbox.override
  gntfilesel.override
  gnttree.override
+ gntwidget.override
 %%
 modulename gnt
 %%
@@ -39,3 +41,154 @@
 ignore-glob
 	*_get_gtype
 %%
+define set_flag
+static PyObject *
+_wrap_set_flag(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = {"flags", NULL};
+	PyGObject *widget;
+	int flags;
+
+	if (!PyArg_ParseTuple(args, "O!i:gnt.set_flag", &PyGntWidget_Type, &widget,
+				&flags)) {
+		return NULL;
+	}
+
+	GNT_WIDGET_SET_FLAGS(widget->obj, flags);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+define unset_flag
+static PyObject *
+_wrap_unset_flag(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = {"flags", NULL};
+	PyGObject *widget;
+	int flags;
+
+	if (!PyArg_ParseTuple(args, "O!i:gnt.unset_flag", &PyGntWidget_Type, &widget,
+				&flags)) {
+		return NULL;
+	}
+
+	GNT_WIDGET_UNSET_FLAGS(widget->obj, flags);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+define screen_size noargs
+static PyObject *
+_wrap_screen_size(PyObject *self)
+{
+	PyObject *list = PyList_New(0);
+
+	if (list == NULL)
+		return NULL;
+
+	PyList_Append(list, PyInt_FromLong((long)getmaxx(stdscr)));
+	PyList_Append(list, PyInt_FromLong((long)getmaxy(stdscr)));
+
+	return list;
+}
+%%
+override gnt_register_action
+static GHashTable *actions;
+
+
+
+static PyObject *
+_wrap_gnt_register_action(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = {"name", "callback", NULL};
+	PyGObject *callback;
+	GClosure *closure;
+	char *name;
+
+	if (!PyArg_ParseTuple(args, "sO:gnt.gnt_register_action", &name, &callback)) {
+		return NULL;
+	}
+
+	if (!PyCallable_Check(callback)) {
+		PyErr_SetString(PyExc_TypeError, "the callback must be callable ... doh!");
+		return NULL;
+	}
+
+	gnt_register_action(name, callback->obj);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+define register_bindings
+
+static gboolean
+pygnt_binding_callback(GntBindable *bindable, GList *list)
+{
+	PyObject *wrapper = pygobject_new(G_OBJECT(bindable));
+	PyObject_CallMethod(wrapper, list->data, "O", Py_None);
+	Py_DECREF(wrapper);
+	return TRUE;
+}
+
+static PyObject *
+_wrap_register_bindings(PyObject *self, PyObject *args)
+{
+	PyTypeObject *class;
+	int pos = 0;
+	PyObject *key, *value, *gbindings;
+	GntBindableClass *bindable;
+
+	if (!PyArg_ParseTuple(args, "O!:gnt.register_bindings",
+				&PyType_Type, &class)) {
+		/* Make sure it's a GntBindableClass subclass */
+		PyErr_SetString(PyExc_TypeError,
+				"argument must be a GntBindable subclass");
+		return NULL;
+	}
+
+	gbindings = PyDict_GetItemString(class->tp_dict, "__gntbindings__");
+	if (!gbindings)
+		goto end;
+
+	if (!PyDict_Check(gbindings)) {
+		PyErr_SetString(PyExc_TypeError,
+				"__gntbindings__ attribute not a dict!");
+		return NULL;
+	}
+
+	bindable = g_type_class_ref(pyg_type_from_object((PyObject *)class));
+	while (PyDict_Next(gbindings, &pos, &key, &value)) {
+		const char *trigger, *callback, *name;
+		GList *list = NULL;
+
+		if (!PyString_Check(key)) {
+			PyErr_SetString(PyExc_TypeError,
+					"__gntbindings__ keys must be strings");
+			g_type_class_unref(bindable);
+			return NULL;
+		}
+		name = PyString_AsString(key);
+
+		if (!PyTuple_Check(value) ||
+				!PyArg_ParseTuple(value, "ss", &callback, &trigger)) {
+			PyErr_SetString(PyExc_TypeError,
+					"__gntbindings__ values must be (callback, trigger) tupples");
+			g_type_class_unref(bindable);
+			return NULL;
+		}
+
+		gnt_bindable_class_register_action(bindable, name, pygnt_binding_callback,
+				trigger, g_strdup(callback), NULL);
+	}
+	if (gbindings)
+		PyDict_DelItemString(class->tp_dict, "__gntbindings__");
+	g_type_class_unref(bindable);
+
+end:
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/gntbox.override	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,38 @@
+/**
+ * pygnt- Python bindings for the GNT toolkit.
+ * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+ *
+ *   gntbox.override: overrides for the box widget.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+%%
+override gnt_box_add_widget kwargs
+static PyObject *
+_wrap_gnt_box_add_widget(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "widget", NULL };
+    PyGObject *widget;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:GntBox.add_widget", kwlist, &PyGntWidget_Type, &widget))
+        return NULL;
+    
+    gnt_box_add_widget(GNT_BOX(self->obj), GNT_WIDGET(widget->obj));
+	Py_INCREF(widget);
+    
+    Py_INCREF(Py_None);
+    return Py_None;
+}
--- a/finch/libgnt/pygnt/gntmodule.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/pygnt/gntmodule.c	Wed Aug 08 21:05:47 2007 +0000
@@ -9,11 +9,12 @@
     PyObject *m, *d;
  
     init_pygobject ();
- 
+
     m = Py_InitModule ("gnt", gnt_functions);
     d = PyModule_GetDict (m);
  
     gnt_register_classes (d);
+    gnt_add_constants(m, "GNT_");
  
     if (PyErr_Occurred ()) {
         Py_FatalError ("can't initialise module sad");
--- a/finch/libgnt/pygnt/gnttree.override	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/pygnt/gnttree.override	Wed Aug 08 21:05:47 2007 +0000
@@ -20,7 +20,7 @@
  * USA
  */
 %%
-headrs
+headers
 #include "common.h"
 %%
 ignore 
@@ -49,7 +49,7 @@
 		return NULL;
 	}
 	while (list) {
-		PyObject *obj = pyg_pointer_new(G_TYPE_POINTER, list->data);
+		PyObject *obj = list->data;
 		PyList_Append(py_list, obj);
 		Py_DECREF(obj);
 		list = list->next;
@@ -100,4 +100,79 @@
 	Py_INCREF(Py_None);
 	return Py_None;
 }
+%%
+override gnt_tree_get_selection_data noargs
+static PyObject *
+_wrap_gnt_tree_get_selection_data(PyGObject *self)
+{
+	PyObject *ret = gnt_tree_get_selection_data(GNT_TREE(self->obj));
+	if (!ret)
+		ret = Py_None;
+	Py_INCREF(ret);
+	return ret;
+}
+%%
+override gnt_tree_change_text
+static PyObject *
+_wrap_gnt_tree_change_text(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = { "key", "colno", "text", NULL };
+	char *text;
+	int colno;
+	gpointer key;
 
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs,"Ois:GntTree.change_text", kwlist, &key, &colno, &text))
+		return NULL;
+
+	gnt_tree_change_text(GNT_TREE(self->obj), key, colno, text);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+override gnt_tree_set_row_flags
+static PyObject *
+_wrap_gnt_tree_set_row_flags(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = { "key", "flag", NULL };
+	int flag;
+	gpointer key;
+
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs,"Oi:GntTree.set_row_flags", kwlist, &key, &flag))
+		return NULL;
+
+	gnt_tree_set_row_flags(GNT_TREE(self->obj), key, flag);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+override gnt_tree_remove
+static PyObject *
+_wrap_gnt_tree_remove(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+	static char *kwlist[] = { "key", NULL };
+	gpointer key;
+
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs,"O:GntTree.remove", kwlist, &key))
+		return NULL;
+
+	gnt_tree_remove(GNT_TREE(self->obj), key);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+%%
+override gnt_tree_set_selected
+static PyObject *
+_wrap_gnt_tree_set_selected(PyGObject *self, PyObject *args)
+{
+	gpointer key;
+	if (!PyArg_ParseTuple(args, "O:GntTree.set_selected", &key)) {
+		return NULL;
+	}
+	gnt_tree_set_selected(GNT_TREE(self->obj), key);
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/pygnt/gntwidget.override	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,36 @@
+/**
+ * pygnt- Python bindings for the GNT toolkit.
+ * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im>
+ *
+ *   gntwidget.override: overrides for generic widgets.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+%%
+override gnt_widget_get_size args
+static PyObject *
+_wrap_gnt_widget_get_size(PyGObject *self)
+{
+    PyObject *list = PyList_New(0);
+    int x = 0, y = 0;
+
+    gnt_widget_get_size(GNT_WIDGET(self->obj), &x, &y);
+    PyList_Append(list, PyInt_FromLong((long)x));
+    PyList_Append(list, PyInt_FromLong((long)y));
+
+    return list;
+}
+
--- a/finch/libgnt/pygnt/test.py	Wed Aug 08 21:01:42 2007 +0000
+++ b/finch/libgnt/pygnt/test.py	Wed Aug 08 21:05:47 2007 +0000
@@ -1,18 +1,59 @@
 #!/usr/bin/python
+import gobject
 import gnt
 
+class MyObject(gobject.GObject):
+    __gproperties__ = {
+        'mytype': (gobject.TYPE_INT, 'mytype', 'the type of the object',
+                0, 10000, 0, gobject.PARAM_READWRITE),
+        'string': (gobject.TYPE_STRING, 'string property', 'the string',
+                None, gobject.PARAM_READWRITE),
+        'gobject': (gobject.TYPE_OBJECT, 'object property', 'the object',
+                gobject.PARAM_READWRITE),
+    }
+
+    def __init__(self, type = 'string', value = None):
+        self.__gobject_init__()
+        self.set_property(type, value)
+
+    def do_set_property(self, pspec, value):
+        if pspec.name == 'string':
+            self.string = value
+            self.type = gobject.TYPE_STRING
+        elif pspec.name == 'gobject':
+            self.gobject = value
+            self.type = gobject.TYPE_OBJECT
+        else:
+            raise AttributeError, 'unknown property %s' % pspec.name
+    def do_get_property(self, pspec):
+        if pspec.name == 'string':
+            return self.string
+        elif pspec.name == 'gobject':
+            return self.gobject
+        elif pspec.name == 'mytype':
+            return self.type
+        else:
+            raise AttributeError, 'unknown property %s' % pspec.name
+gobject.type_register(MyObject)
+
 def button_activate(button, tree):
-	list = tree.get_selection_text_list()
-	str = ""
-	for i in list:
-		str = str + i
-	entry.set_text("clicked!!!" + str)
+    list = tree.get_selection_text_list()
+    ent = tree.get_selection_data()
+    if ent.type == gobject.TYPE_STRING:
+        str = ""
+        for i in list:
+            str = str + i
+        entry.set_text("clicked!!!" + str)
+    elif ent.type == gobject.TYPE_OBJECT:
+        ent.gobject.set_text("mwhahaha!!!")
 
 gnt.gnt_init()
 
 win = gnt.Window()
 
 entry = gnt.Entry("")
+obj = MyObject()
+obj.set_property('gobject', entry)
 
 win.add_widget(entry)
 win.set_title("Entry")
@@ -27,12 +68,20 @@
 # so random non-string values can be used as the key for a row in a GntTree!
 last = None
 for i in range(1, 100):
-	tree.add_row_after(i, [str(i), ""], None, i-1)
-tree.add_row_after(entry, ["asd"], None, None)
-tree.add_row_after("b", ["123", ""], entry, None)
+    key = MyObject('string', str(i))
+    tree.add_row_after(key, [str(i)], None, last)
+    last = key
+
+tree.add_row_after(MyObject('gobject', entry), ["asd"], None, None)
+tree.add_row_after(MyObject('string', "b"), ["123"], MyObject('gobject', entry), None)
 
 button.connect("activate", button_activate, tree)
 
+tv = gnt.TextView()
+
+win.add_widget(tv)
+tv.append_text_with_flags("What up!!", gnt.TEXT_FLAG_BOLD)
+
 win.show()
 
 gnt.gnt_main()
--- a/libpurple/Makefile.am	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/Makefile.am	Wed Aug 08 21:05:47 2007 +0000
@@ -148,7 +148,7 @@
 # purple dbus server
 
 dbus_sources  = dbus-server.c dbus-useful.c
-dbus_headers  = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h
+dbus_headers  = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h dbus-types.h
 
 dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h \
                 connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \
--- a/libpurple/account.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/account.h	Wed Aug 08 21:05:47 2007 +0000
@@ -53,20 +53,50 @@
 	PURPLE_ACCOUNT_REQUEST_AUTHORIZATION = 0 /* Account authorization request */
 } PurpleAccountRequestType;
 
+
+/**  Account UI operations, used to notify the user of status changes and when
+ *   buddies add this account to their buddy lists.
+ */
 struct _PurpleAccountUiOps
 {
-	/* A buddy we already have added us to their buddy list. */
-	void (*notify_added)(PurpleAccount *account, const char *remote_user,
-	                    const char *id, const char *alias,
+	/** A buddy who is already on this account's buddy list added this account
+	 *  to their buddy list.
+	 */
+	void (*notify_added)(PurpleAccount *account,
+	                     const char *remote_user,
+	                     const char *id,
+	                     const char *alias,
 	                     const char *message);
-	void (*status_changed)(PurpleAccount *account, PurpleStatus *status);
-	/* Someone we don't have on our list added us. Will prompt to add them. */
-	void (*request_add)(PurpleAccount *account, const char *remote_user,
-	                    const char *id, const char *alias,
+
+	/** This account's status changed. */
+	void (*status_changed)(PurpleAccount *account,
+	                       PurpleStatus *status);
+
+	/** Someone we don't have on our list added us; prompt to add them. */
+	void (*request_add)(PurpleAccount *account,
+	                    const char *remote_user,
+	                    const char *id,
+	                    const char *alias,
 	                    const char *message);
-	void *(*request_authorize)(PurpleAccount *account, const char *remote_user, const char *id,
-				 const char *alias, const char *message, gboolean on_list, 
-				 GCallback authorize_cb, GCallback deny_cb, void *user_data);
+
+	/** Prompt for authorization when someone adds this account to their buddy
+	 * list.  To authorize them to see this account's presence, call \a
+	 * authorize_cb (\a user_data); otherwise call \a deny_cb (\a user_data);
+	 * @return a UI-specific handle, as passed to #close_account_request.
+	 */
+	void *(*request_authorize)(PurpleAccount *account,
+	                           const char *remote_user,
+	                           const char *id,
+	                           const char *alias,
+	                           const char *message,
+	                           gboolean on_list,
+	                           GCallback authorize_cb,
+	                           GCallback deny_cb,
+	                           void *user_data);
+
+	/** Close a pending request for authorization.  \a ui_handle is a handle
+	 *  as returned by #request_authorize.
+	 */
 	void (*close_account_request)(void *ui_handle);
 
 	void (*_purple_reserved1)(void);
@@ -215,7 +245,7 @@
 
 /**
  * Notifies the user that a remote user has wants to add the local user
- * to his or her buddy list and requires authorization to d oso.
+ * to his or her buddy list and requires authorization to do so.
  *
  * This will present a dialog informing the user of this and ask if the 
  * user authorizes or denies the remote user from adding him.
--- a/libpurple/connection.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/connection.h	Wed Aug 08 21:05:47 2007 +0000
@@ -60,15 +60,52 @@
 #include "plugin.h"
 #include "status.h"
 
+/** Connection UI operations.  Used to notify the user of changes to
+ *  connections, such as being disconnected, and to respond to the
+ *  underlying network connection appearing and disappearing.  UIs should
+ *  call #purple_connections_set_ui_ops() with an instance of this struct.
+ *
+ *  @see @ref ui-ops
+ */
 typedef struct
 {
-	void (*connect_progress)(PurpleConnection *gc, const char *text,
-							 size_t step, size_t step_count);
+	/** When an account is connecting, this operation is called to notify
+	 *  the UI of what is happening, as well as which @a step out of @a
+	 *  step_count has been reached (which might be displayed as a progress
+	 *  bar).
+	 */
+	void (*connect_progress)(PurpleConnection *gc,
+	                         const char *text,
+	                         size_t step,
+	                         size_t step_count);
+	/** Called when a connection is established (just before the
+	 *  @ref signed-on signal).
+	 */
 	void (*connected)(PurpleConnection *gc);
+	/** Called when a connection is ended (between the @ref signing-off
+	 *  and @ref signed-off signals).
+	 */
 	void (*disconnected)(PurpleConnection *gc);
+	/** Used to display connection-specific notices.  (Pidgin's Gtk user
+	 *  interface implements this as a no-op; #purple_connection_notice(),
+	 *  which uses this operation, is not used by any of the protocols
+	 *  shipped with libpurple.)
+	 */
 	void (*notice)(PurpleConnection *gc, const char *text);
+	/** Called when an error causes a connection to be disconnected.
+	 *  Called before #disconnected.
+	 *  @param text  a localized error message.
+	 */
 	void (*report_disconnect)(PurpleConnection *gc, const char *text);
+	/** Called when libpurple discovers that the computer's network
+	 *  connection is active.  On Linux, this uses Network Manager if
+	 *  available; on Windows, it uses Win32's network change notification
+	 *  infrastructure.
+	 */
 	void (*network_connected)();
+	/** Called when libpurple discovers that the computer's network
+	 *  connection has gone away.
+	 */
 	void (*network_disconnected)();
 
 	void (*_purple_reserved1)(void);
--- a/libpurple/conversation.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/conversation.c	Wed Aug 08 21:05:47 2007 +0000
@@ -1106,7 +1106,7 @@
 
 	c = purple_conv_im_get_conversation(im);
 
-	/* Raise the window, if specified in prefs. */
+	/* Pass this on to either the ops structure or the default write func. */
 	if (c->ui_ops != NULL && c->ui_ops->write_im != NULL)
 		c->ui_ops->write_im(c, who, message, flags, mtime);
 	else
--- a/libpurple/conversation.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/conversation.h	Wed Aug 08 21:05:47 2007 +0000
@@ -149,27 +149,74 @@
  */
 struct _PurpleConversationUiOps
 {
+	/** Called when @a conv is created (but before the @ref
+	 *  conversation-created signal is emitted).
+	 */
 	void (*create_conversation)(PurpleConversation *conv);
+
+	/** Called just before @a conv is freed. */
 	void (*destroy_conversation)(PurpleConversation *conv);
+	/** Write a message to a chat.  If this field is @c NULL, libpurple will
+	 *  fall back to using #write_conv.
+	 *  @see purple_conv_chat_write()
+	 */
 	void (*write_chat)(PurpleConversation *conv, const char *who,
 	                   const char *message, PurpleMessageFlags flags,
 	                   time_t mtime);
+	/** Write a message to an IM conversation.  If this field is @c NULL,
+	 *  libpurple will fall back to using #write_conv.
+	 *  @see purple_conv_im_write()
+	 */
 	void (*write_im)(PurpleConversation *conv, const char *who,
 	                 const char *message, PurpleMessageFlags flags,
 	                 time_t mtime);
-	void (*write_conv)(PurpleConversation *conv, const char *name, const char *alias,
-	                   const char *message, PurpleMessageFlags flags,
+	/** Write a message to a conversation.  This is used rather than
+	 *  the chat- or im-specific ops for generic messages, such as system
+	 *  messages like "x is now know as y".
+	 *  @see purple_conversation_write()
+	 */
+	void (*write_conv)(PurpleConversation *conv,
+	                   const char *name,
+	                   const char *alias,
+	                   const char *message,
+	                   PurpleMessageFlags flags,
 	                   time_t mtime);
 
-	void (*chat_add_users)(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals);	
-	
+	/** Add @a cbuddies to a chat.
+	 *  @param cbuddies      A @C GList of #PurpleConvChatBuddy structs.
+	 *  @param new_arrivals  Whether join notices should be shown.
+	 *                       (Join notices are actually written to the
+	 *                       conversation by #purple_conv_chat_add_users().)
+	 */
+	void (*chat_add_users)(PurpleConversation *conv,
+	                       GList *cbuddies,
+	                       gboolean new_arrivals);
+	/** Rename the user in this chat named @a old_name to @a new_name.  (The
+	 *  rename message is written to the conversation by libpurple.)
+	 *  @param new_alias  @a new_name's new alias, if they have one.
+	 *  @see purple_conv_chat_add_users()
+	 */
 	void (*chat_rename_user)(PurpleConversation *conv, const char *old_name,
 	                         const char *new_name, const char *new_alias);
+	/** Remove @a users from a chat.
+	 *  @param users    A @C GList of <tt>const char *</tt>s.
+	 *  @see purple_conv_chat_rename_user()
+	 */
 	void (*chat_remove_users)(PurpleConversation *conv, GList *users);
+	/** Called when a user's flags are changed.
+	 *  @see purple_conv_chat_user_set_flags()
+	 */
 	void (*chat_update_user)(PurpleConversation *conv, const char *user);
 
+	/** Present this conversation to the user; for example, by displaying
+	 *  the IM dialog.
+	 */
 	void (*present)(PurpleConversation *conv);
 
+	/** If this UI has a concept of focus (as in a windowing system) and
+	 *  this conversation has the focus, return @c TRUE; otherwise, return
+	 *  @c FALSE.
+	 */
 	gboolean (*has_focus)(PurpleConversation *conv);
 
 	/* Custom Smileys */
@@ -178,6 +225,11 @@
 	                            const guchar *data, gsize size);
 	void (*custom_smiley_close)(PurpleConversation *conv, const char *smile);
 
+	/** Prompt the user for confirmation to send @a message.  This function
+	 *  should arrange for the message to be sent if the user accepts.  If
+	 *  this field is @c NULL, libpurple will fall back to using
+	 *  #purple_request_action().
+	 */
 	void (*send_confirm)(PurpleConversation *conv, const char *message);
 
 	void (*_purple_reserved1)(void);
--- a/libpurple/core.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/core.c	Wed Aug 08 21:05:47 2007 +0000
@@ -159,8 +159,9 @@
 	/*
 	 * Call this early on to try to auto-detect our IP address and
 	 * hopefully save some time later.
+	 * TODO: do this here after purple_prefs_load() has been moved into purple_prefs_init()
 	 */
-	purple_network_get_my_ip(-1);
+	/*purple_network_get_my_ip(-1);*/
 
 	if (ops != NULL && ops->ui_init != NULL)
 		ops->ui_init();
--- a/libpurple/plugins/log_reader.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/plugins/log_reader.c	Wed Aug 08 21:05:47 2007 +0000
@@ -60,14 +60,14 @@
 	char *path;
 	GDir *dir;
 
-	g_return_val_if_fail(sn != NULL, list);
-	g_return_val_if_fail(account != NULL, list);
+	g_return_val_if_fail(sn != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
 
 	logdir = purple_prefs_get_string("/plugins/core/log_reader/adium/log_directory");
 
 	/* By clearing the log directory path, this logger can be (effectively) disabled. */
-	if (!*logdir)
-		return list;
+	if (!logdir || !*logdir)
+		return NULL;
 
 	plugin = purple_find_prpl(purple_account_get_protocol_id(account));
 	if (!plugin)
@@ -236,7 +236,8 @@
 
 	/* XXX: TODO: We probably want to set PURPLE_LOG_READ_NO_NEWLINE
 	 * XXX: TODO: for HTML logs. */
-	*flags = 0;
+	if (flags != NULL)
+		*flags = 0;
 
 	g_return_val_if_fail(log != NULL, g_strdup(""));
 
@@ -625,17 +626,17 @@
 	const char *old_session_id = "";
 	struct msn_logger_data *data = NULL;
 
-	g_return_val_if_fail(sn != NULL, list);
-	g_return_val_if_fail(account != NULL, list);
+	g_return_val_if_fail(sn != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
 
 	if (strcmp(account->protocol_id, "prpl-msn"))
-		return list;
+		return NULL;
 
 	logdir = purple_prefs_get_string("/plugins/core/log_reader/msn/log_directory");
 
 	/* By clearing the log directory path, this logger can be (effectively) disabled. */
-	if (!*logdir)
-		return list;
+	if (!logdir || !*logdir)
+		return NULL;
 
 	buddy = purple_find_buddy(account, sn);
 
@@ -874,7 +875,8 @@
 	GString *text = NULL;
 	xmlnode *message;
 
-	*flags = PURPLE_LOG_READ_NO_NEWLINE;
+	if (flags != NULL)
+		*flags = PURPLE_LOG_READ_NO_NEWLINE;
 	g_return_val_if_fail(log != NULL, g_strdup(""));
 
 	data = log->logger_data;
@@ -1119,7 +1121,7 @@
 		if (name_guessed != NAME_GUESS_UNKNOWN)
 			text = g_string_append(text, "</span>");
 
-		style     = xmlnode_get_attrib(text_node, "Style");
+		style = xmlnode_get_attrib(text_node, "Style");
 
 		tmp = xmlnode_get_data(text_node);
 		if (style && *style) {
@@ -1209,14 +1211,14 @@
 	gchar *line;
 	gchar *c;
 
-	g_return_val_if_fail(sn != NULL, list);
-	g_return_val_if_fail(account != NULL, list);
+	g_return_val_if_fail(sn != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
 
 	logdir = purple_prefs_get_string("/plugins/core/log_reader/trillian/log_directory");
 
 	/* By clearing the log directory path, this logger can be (effectively) disabled. */
-	if (!*logdir)
-		return list;
+	if (!logdir || !*logdir)
+		return NULL;
 
 	plugin = purple_find_prpl(purple_account_get_protocol_id(account));
 	if (!plugin)
@@ -1427,7 +1429,9 @@
 	char *c;
 	const char *line;
 
-	*flags = PURPLE_LOG_READ_NO_NEWLINE;
+	if (flags != NULL)
+		*flags = PURPLE_LOG_READ_NO_NEWLINE;
+
 	g_return_val_if_fail(log != NULL, g_strdup(""));
 
 	data = log->logger_data;
@@ -1772,18 +1776,18 @@
 	int offset = 0;
 	GError *error;
 
-	g_return_val_if_fail(sn != NULL, list);
-	g_return_val_if_fail(account != NULL, list);
+	g_return_val_if_fail(sn != NULL, NULL);
+	g_return_val_if_fail(account != NULL, NULL);
 
 	/* QIP only supports ICQ. */
 	if (strcmp(account->protocol_id, "prpl-icq"))
-		return list;
+		return NULL;
 
 	logdir = purple_prefs_get_string("/plugins/core/log_reader/qip/log_directory");
 
 	/* By clearing the log directory path, this logger can be (effectively) disabled. */
-	if (!*logdir)
-		return list;
+	if (!logdir || !*logdir)
+		return NULL;
 
 	plugin = purple_find_prpl(purple_account_get_protocol_id(account));
 	if (!plugin)
@@ -1923,7 +1927,9 @@
 	char *utf8_string;
 	FILE *file;
 
-	*flags = PURPLE_LOG_READ_NO_NEWLINE;
+	if (flags != NULL)
+		*flags = PURPLE_LOG_READ_NO_NEWLINE;
+
 	g_return_val_if_fail(log != NULL, g_strdup(""));
 
 	data = log->logger_data;
--- a/libpurple/protocols/bonjour/Makefile.am	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/Makefile.am	Wed Aug 08 21:05:47 2007 +0000
@@ -1,7 +1,7 @@
 EXTRA_DIST = \
-		mdns_win32.c \
-		mdns_win32.h \
-		Makefile.mingw
+	mdns_win32.c \
+	dns_sd_proxy.h \
+	Makefile.mingw
 
 pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
 
@@ -10,16 +10,24 @@
 	bonjour.h \
 	buddy.c \
 	buddy.h \
-	dns_sd_proxy.h \
 	jabber.c \
 	jabber.h \
 	mdns_common.c \
 	mdns_common.h \
-	mdns_howl.c \
-	mdns_howl.h \
-	mdns_types.h
+	mdns_interface.h \
+	mdns_types.h \
+	parser.c \
+	parser.h
 
-AM_CFLAGS = $(st) -DUSE_BONJOUR_HOWL
+if MDNS_AVAHI
+  BONJOURSOURCES += mdns_avahi.c
+else
+if MDNS_HOWL
+    BONJOURSOURCES += mdns_howl.c
+endif
+endif
+
+AM_CFLAGS = $(st)
 
 libbonjour_la_LDFLAGS = -module -avoid-version
 
@@ -29,14 +37,29 @@
 noinst_LIBRARIES     = libbonjour.a
 libbonjour_a_SOURCES = $(BONJOURSOURCES)
 libbonjour_a_CFLAGS  = $(AM_CFLAGS)
-libbonjour_a_LIBADD  = $(HOWL_LIBS)
+libbonjour_a_LIBADD  =
+
+if MDNS_AVAHI
+  libbonjour_a_LIBADD  += $(AVAHI_LIBS)
+else
+if MDNS_HOWL
+    libbonjour_a_LIBADD  += $(HOWL_LIBS)
+endif
+endif
 
 else
 
 st =
 pkg_LTLIBRARIES       = libbonjour.la
 libbonjour_la_SOURCES = $(BONJOURSOURCES)
-libbonjour_la_LIBADD   = $(GLIB_LIBS) $(HOWL_LIBS)
+libbonjour_la_LIBADD  = $(GLIB_LIBS) $(LIBXML_LIBS)
+if MDNS_AVAHI
+  libbonjour_la_LIBADD  += $(AVAHI_LIBS)
+else
+if MDNS_HOWL
+    libbonjour_la_LIBADD  += $(HOWL_LIBS)
+endif
+endif
 
 endif
 
@@ -46,4 +69,13 @@
 	-I$(top_builddir)/libpurple \
 	$(GLIB_CFLAGS) \
 	$(DEBUG_CFLAGS) \
-	$(HOWL_CFLAGS)
+	$(LIBXML_CFLAGS)
+
+if MDNS_AVAHI
+  AM_CPPFLAGS += $(AVAHI_CFLAGS)
+else
+if MDNS_HOWL
+    AM_CPPFLAGS += $(HOWL_CFLAGS)
+endif
+endif
+
--- a/libpurple/protocols/bonjour/Makefile.mingw	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/Makefile.mingw	Wed Aug 08 21:05:47 2007 +0000
@@ -30,12 +30,14 @@
 			-I$(GTK_TOP)/include/glib-2.0 \
 			-I$(GTK_TOP)/lib/glib-2.0/include \
 			-I$(BONJOUR_TOP)/include \
+			-I$(LIBXML2_TOP)/include \
 			-I$(PURPLE_TOP) \
 			-I$(PURPLE_TOP)/win32 \
 			-I$(PIDGIN_TREE_TOP)
 
 LIB_PATHS +=		-L$(GTK_TOP)/lib \
 			-L$(BONJOUR_TOP)/lib \
+			-L$(LIBXML2_TOP)/lib \
 			-L$(PURPLE_TOP)
 
 ##
@@ -45,6 +47,7 @@
 			buddy.c \
 			mdns_common.c \
 			mdns_win32.c \
+			parser.c \
 			jabber.c
 
 OBJECTS = $(C_SRC:%.c=%.o)
@@ -58,6 +61,7 @@
 			-lintl \
 			-ldnssd \
 			-lnetapi32 \
+			-lxml2 \
 			-lpurple
 
 include $(PIDGIN_COMMON_RULES)
--- a/libpurple/protocols/bonjour/buddy.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Wed Aug 08 21:05:47 2007 +0000
@@ -22,6 +22,7 @@
 #include "account.h"
 #include "blist.h"
 #include "bonjour.h"
+#include "mdns_interface.h"
 #include "debug.h"
 
 /**
@@ -35,6 +36,8 @@
 	buddy->account = account;
 	buddy->name = g_strdup(name);
 
+	_mdns_init_buddy(buddy);
+
 	return buddy;
 }
 
@@ -102,8 +105,9 @@
 {
 	PurpleBuddy *buddy;
 	PurpleGroup *group;
-	const char *status_id, *first, *last;
-	gchar *alias;
+	PurpleAccount *account = bonjour_buddy->account;
+	const char *status_id, *first, *last, *old_hash, *new_hash;
+	gchar *alias = NULL;
 
 	/* Translate between the Bonjour status and the Purple status */
 	if (g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0)
@@ -116,44 +120,55 @@
 	 * field from the DNS SD.
 	 */
 
-	/* Create the alias for the buddy using the first and the last name */
-	first = bonjour_buddy->first;
-	last = bonjour_buddy->last;
-	alias = g_strdup_printf("%s%s%s",
-							(first && *first ? first : ""),
-							(first && *first && last && *last ? " " : ""),
-							(last && *last ? last : ""));
-
 	/* Make sure the Bonjour group exists in our buddy list */
 	group = purple_find_group(BONJOUR_GROUP_NAME); /* Use the buddy's domain, instead? */
-	if (group == NULL)
-	{
+	if (group == NULL) {
 		group = purple_group_new(BONJOUR_GROUP_NAME);
 		purple_blist_add_group(group, NULL);
 	}
 
 	/* Make sure the buddy exists in our buddy list */
-	buddy = purple_find_buddy(bonjour_buddy->account, bonjour_buddy->name);
+	buddy = purple_find_buddy(account, bonjour_buddy->name);
 
-	if (buddy == NULL)
-	{
-		buddy = purple_buddy_new(bonjour_buddy->account, bonjour_buddy->name, alias);
+	if (buddy == NULL) {
+		buddy = purple_buddy_new(account, bonjour_buddy->name, NULL);
 		buddy->proto_data = bonjour_buddy;
 		purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE);
 		purple_blist_add_buddy(buddy, NULL, group, NULL);
 	}
 
+	/* Create the alias for the buddy using the first and the last name */
+	first = bonjour_buddy->first;
+	last = bonjour_buddy->last;
+	if ((first && *first) || (last && *last))
+		alias = g_strdup_printf("%s%s%s",
+					(first && *first ? first : ""),
+					(first && *first && last && *last ? " " : ""),
+					(last && *last ? last : ""));
+	serv_got_alias(purple_account_get_connection(account), buddy->name, alias);
+	g_free(alias);
+
 	/* Set the user's status */
 	if (bonjour_buddy->msg != NULL)
-		purple_prpl_got_user_status(bonjour_buddy->account, buddy->name, status_id,
-								  "message", bonjour_buddy->msg,
-								  NULL);
+		purple_prpl_got_user_status(account, buddy->name, status_id,
+					    "message", bonjour_buddy->msg, NULL);
 	else
-		purple_prpl_got_user_status(bonjour_buddy->account, buddy->name, status_id,
-								  NULL);
-	purple_prpl_got_user_idle(bonjour_buddy->account, buddy->name, FALSE, 0);
+		purple_prpl_got_user_status(account, buddy->name, status_id, NULL);
+
+	purple_prpl_got_user_idle(account, buddy->name, FALSE, 0);
+
+	/* TODO: Because we don't save Bonjour buddies in blist.xml,
+	 * we will always have to look up the buddy icon at login time.
+	 * I think we should figure out a way to do something about this. */
 
-	g_free(alias);
+	/* Deal with the buddy icon */
+	old_hash = purple_buddy_icons_get_checksum_for_user(buddy);
+	new_hash = (bonjour_buddy->phsh && *(bonjour_buddy->phsh)) ? bonjour_buddy->phsh : NULL;
+	if (new_hash && (!old_hash || strcmp(old_hash, new_hash) != 0)) {
+		/* Look up the new icon data */
+		bonjour_dns_sd_retrieve_buddy_icon(bonjour_buddy);
+	} else
+		purple_buddy_icons_set_for_user(account, buddy->name, NULL, 0, NULL);
 }
 
 /**
@@ -164,6 +179,7 @@
 {
 	g_free(buddy->name);
 	g_free(buddy->ip);
+	g_free(buddy->full_service_name);
 
 	g_free(buddy->first);
 	g_free(buddy->phsh);
@@ -182,13 +198,8 @@
 	bonjour_jabber_close_conversation(buddy->conversation);
 	buddy->conversation = NULL;
 
-#ifdef USE_BONJOUR_APPLE
-	if (buddy->txt_query != NULL)
-	{
-		purple_input_remove(buddy->txt_query_fd);
-		DNSServiceRefDeallocate(buddy->txt_query);
-	}
-#endif
+	/* Clean up any mdns implementation data */
+	_mdns_delete_buddy(buddy);
 
 	g_free(buddy);
 }
--- a/libpurple/protocols/bonjour/buddy.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Wed Aug 08 21:05:47 2007 +0000
@@ -19,22 +19,17 @@
 
 #include <glib.h>
 
-#include "config.h"
 #include "account.h"
 #include "jabber.h"
 
-#ifdef USE_BONJOUR_APPLE 
-#include "dns_sd_proxy.h"
-#else /* USE_BONJOUR_HOWL */
-#include <howl.h>
-#endif
-
 typedef struct _BonjourBuddy
 {
 	PurpleAccount *account;
 
 	gchar *name;
+	/* TODO: Remove and just use the hostname */
 	gchar *ip;
+	gchar *full_service_name;
 	gint port_p2pj;
 
 	gchar *first;
@@ -53,11 +48,7 @@
 
 	BonjourJabberConversation *conversation;
 
-#ifdef USE_BONJOUR_APPLE
-	DNSServiceRef txt_query;
-	int txt_query_fd;
-#endif
-
+	gpointer mdns_impl_data;
 } BonjourBuddy;
 
 static const char *const buddy_TXT_records[] = {
--- a/libpurple/protocols/bonjour/jabber.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Wed Aug 08 21:05:47 2007 +0000
@@ -42,6 +42,7 @@
 #include "util.h"
 
 #include "jabber.h"
+#include "parser.h"
 #include "bonjour.h"
 #include "buddy.h"
 
@@ -109,9 +110,10 @@
 }
 
 static void
-_jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleConnection *connection, PurpleBuddy *pb)
+_jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleBuddy *pb)
 {
 	xmlnode *body_node, *html_node, *events_node;
+	PurpleConnection *gc = pb->account->gc;
 	char *body, *html_body = NULL;
 	const char *ichat_balloon_color = NULL;
 	const char *ichat_text_color = NULL;
@@ -186,7 +188,7 @@
 	/* TODO: Should we do something with "composing_event" here? */
 
 	/* Send the message to the UI */
-	serv_got_im(connection, pb->name, body, 0, time(NULL));
+	serv_got_im(gc, pb->name, body, 0, time(NULL));
 
 	g_free(body);
 	g_free(html_body);
@@ -218,37 +220,6 @@
 	}
 }
 
-static gint
-_read_data(gint socket, char **message)
-{
-	GString *data = g_string_new("");
-	char partial_data[512];
-	gint total_message_length = 0;
-	gint partial_message_length = 0;
-
-	/* Read chunks of 512 bytes till the end of the data */
-	while ((partial_message_length = recv(socket, partial_data, 512, 0)) > 0)
-	{
-		g_string_append_len(data, partial_data, partial_message_length);
-		total_message_length += partial_message_length;
-	}
-
-	if (partial_message_length == -1)
-	{
-		if (errno != EAGAIN)
-			purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno));
-		if (total_message_length == 0) {
-			return -1;
-		}
-	}
-
-	*message = g_string_free(data, FALSE);
-	if (total_message_length != 0)
-		purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", *message, total_message_length);
-
-	return total_message_length;
-}
-
 static void
 _send_data_write_cb(gpointer data, gint source, PurpleInputCondition cond)
 {
@@ -303,7 +274,8 @@
 	/* If we're not ready to actually send, append it to the buffer */
 	if (bconv->tx_handler != -1
 			|| bconv->connect_data != NULL
-			|| !bconv->stream_started
+			|| !bconv->sent_stream_start
+			|| !bconv->recv_stream_start
 			|| purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
 		ret = -1;
 		errno = EAGAIN;
@@ -341,20 +313,32 @@
 	return ret;
 }
 
+void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet) {
+	if (!strcmp(packet->name, "message"))
+		_jabber_parse_and_write_message_to_ui(packet, pb);
+	else
+		purple_debug_warning("bonjour", "Unknown packet: %s\n",
+				packet->name);
+}
+
+
 static void
 _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition)
 {
-	char *message = NULL;
-	gint message_length;
 	PurpleBuddy *pb = data;
-	PurpleAccount *account = pb->account;
-	BonjourBuddy *bb = pb->proto_data;
-	gboolean closed_conversation = FALSE;
+	gint len, message_length;
+	static char message[4096];
+
+	/*TODO: use a static buffer */
 
 	/* Read the data from the socket */
-	if ((message_length = _read_data(socket, &message)) == -1) {
+	if ((len = recv(socket, message, sizeof(message) - 1, 0)) == -1) {
 		/* There have been an error reading from the socket */
 		if (errno != EAGAIN) {
+			BonjourBuddy *bb = pb->proto_data;
+
+			purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno));
+
 			bonjour_jabber_close_conversation(bb->conversation);
 			bb->conversation = NULL;
 
@@ -362,65 +346,71 @@
 			 * If they try to send another message it'll reconnect */
 		}
 		return;
-	} else if (message_length == 0) { /* The other end has closed the socket */
-		closed_conversation = TRUE;
+	} else if (len == 0) { /* The other end has closed the socket */
+		purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name);
+		bonjour_jabber_stream_ended(pb);
+		return;
 	} else {
+		message_length = len;
 		message[message_length] = '\0';
 
-		while (g_ascii_iscntrl(message[message_length - 1])) {
+		while (message_length > 0 && g_ascii_iscntrl(message[message_length - 1])) {
 			message[message_length - 1] = '\0';
 			message_length--;
 		}
 	}
 
-	/*
-	 * Check that this is not the end of the conversation.  This is
-	 * using a magic string, but xmlnode won't play nice when just
-	 * parsing an end tag
-	 */
-	if (closed_conversation || purple_str_has_prefix(message, STREAM_END)) {
-		PurpleConversation *conv;
+	purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", message, len);
+
+	bonjour_parser_process(pb, message, message_length);
+}
 
-		/* Close the socket, clear the watcher and free memory */
-		bonjour_jabber_close_conversation(bb->conversation);
-		bb->conversation = NULL;
+void bonjour_jabber_stream_ended(PurpleBuddy *pb) {
+	BonjourBuddy *bb = pb->proto_data;
+	PurpleConversation *conv;
+
+	purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", pb->name);
+
+	/* Close the socket, clear the watcher and free memory */
+	bonjour_jabber_close_conversation(bb->conversation);
+	bb->conversation = NULL;
 
-		/* Inform the user that the conversation has been closed */
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, account);
-		if (conv != NULL) {
-			char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
-			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
-			g_free(tmp);
-		}
-	} else {
-		xmlnode *message_node;
+	/* Inform the user that the conversation has been closed */
+	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account);
+	if (conv != NULL) {
+		char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
+		purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+		g_free(tmp);
+	}
+}
 
-		/* Parse the message into an XMLnode for analysis */
-		message_node = xmlnode_from_str(message, strlen(message));
+void bonjour_jabber_stream_started(PurpleBuddy *pb) {
+	BonjourBuddy *bb = pb->proto_data;
+	BonjourJabberConversation *bconv = bb->conversation;
 
-		if (message_node != NULL) {
-			/* Parse the message to get the data and send to the ui */
-			_jabber_parse_and_write_message_to_ui(message_node, account->gc, pb);
-			xmlnode_free(message_node);
-		} else {
-			/* TODO: Deal with receiving only a partial message */
-		}
+	/* If the stream has been completely started, we can start doing stuff */
+	if (bconv->sent_stream_start && bconv->recv_stream_start && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
+		/* Watch for when we can write the buffered messages */
+		bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
+			_send_data_write_cb, pb);
+		/* We can probably write the data right now. */
+		_send_data_write_cb(pb, bconv->socket, PURPLE_INPUT_WRITE);
 	}
 
-	g_free(message);
 }
 
 struct _stream_start_data {
 	char *msg;
-	PurpleInputFunction tx_handler_cb;
 };
 
+
 static void
 _start_stream(gpointer data, gint source, PurpleInputCondition condition)
 {
 	PurpleBuddy *pb = data;
 	BonjourBuddy *bb = pb->proto_data;
-	struct _stream_start_data *ss = bb->conversation->stream_data;
+	BonjourJabberConversation *bconv = bb->conversation;
+	struct _stream_start_data *ss = bconv->stream_data;
 	int len, ret;
 
 	len = strlen(ss->msg);
@@ -443,7 +433,7 @@
 				  _("Unable to send the message, the conversation couldn't be started."),
 				  PURPLE_MESSAGE_SYSTEM, time(NULL));
 
-		bonjour_jabber_close_conversation(bb->conversation);
+		bonjour_jabber_close_conversation(bconv);
 		bb->conversation = NULL;
 
 		return;
@@ -457,22 +447,67 @@
 		return;
 	}
 
-	/* Stream started; process the send buffer if there is one*/
-	purple_input_remove(bb->conversation->tx_handler);
-	bb->conversation->tx_handler= -1;
-
-	bb->conversation->stream_started = TRUE;
-
 	g_free(ss->msg);
 	g_free(ss);
-	bb->conversation->stream_data = NULL;
+	bconv->stream_data = NULL;
+
+	/* Stream started; process the send buffer if there is one */
+	purple_input_remove(bconv->tx_handler);
+	bconv->tx_handler= -1;
+	bconv->sent_stream_start = TRUE;
+
+	bonjour_jabber_stream_started(pb);
+
+}
+
+static gboolean bonjour_jabber_stream_init(PurpleBuddy *pb, int client_socket)
+{
+	int ret, len;
+	char *stream_start;
+	BonjourBuddy *bb = pb->proto_data;
+
+	stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
+								   purple_buddy_get_name(pb));
+	len = strlen(stream_start);
+
+	/* Start the stream */
+	ret = send(client_socket, stream_start, len, 0);
+
+	if (ret == -1 && errno == EAGAIN)
+		ret = 0;
+	else if (ret <= 0) {
+		const char *err = strerror(errno);
 
-	if (ss->tx_handler_cb) {
-		bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
-			ss->tx_handler_cb, pb);
-		/* We can probably write the data now. */
-		(ss->tx_handler_cb)(pb, source, PURPLE_INPUT_WRITE);
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
+				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+
+		close(client_socket);
+		g_free(stream_start);
+
+		return FALSE;
 	}
+
+	/* This is unlikely to happen */
+	if (ret < len) {
+		struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
+		ss->msg = g_strdup(stream_start + ret);
+		bb->conversation->stream_data = ss;
+		/* Finish sending the stream start */
+		bb->conversation->tx_handler = purple_input_add(client_socket,
+			PURPLE_INPUT_WRITE, _start_stream, pb);
+	} else
+		bb->conversation->sent_stream_start = TRUE;
+
+	g_free(stream_start);
+
+	/* setup the parser fresh for each stream */
+	bonjour_parser_setup(bb->conversation);
+
+	bb->conversation->socket = client_socket;
+	bb->conversation->rx_handler = purple_input_add(client_socket,
+		PURPLE_INPUT_READ, _client_socket_handler, pb);
+
+	return TRUE;
 }
 
 static void
@@ -498,6 +533,7 @@
 
 	/* Look for the buddy that has opened the conversation and fill information */
 	address_text = inet_ntoa(their_addr.sin_addr);
+	purple_debug_info("bonjour", "Received incoming connection from %s\n.", address_text);
 	cbba = g_new0(struct _check_buddy_by_address_t, 1);
 	cbba->address = address_text;
 	cbba->pb = &pb;
@@ -515,49 +551,15 @@
 	/* Check if the conversation has been previously started */
 	if (bb->conversation == NULL)
 	{
-		int ret, len;
-		char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
-			purple_buddy_get_name(pb));
-
-		len = strlen(stream_start);
-
-		/* Start the stream */
-		ret = send(client_socket, stream_start, len, 0);
+		bb->conversation = bonjour_jabber_conv_new();
 
-		if (ret == -1 && errno == EAGAIN)
-			ret = 0;
-		else if (ret <= 0) {
-			const char *err = strerror(errno);
-
-			purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
-					   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
-
+		if (!bonjour_jabber_stream_init(pb, client_socket)) {
 			close(client_socket);
-			g_free(stream_start);
-
 			return;
 		}
 
-		bb->conversation = bonjour_jabber_conv_new();
-		bb->conversation->socket = client_socket;
-		bb->conversation->rx_handler = purple_input_add(client_socket,
-			PURPLE_INPUT_READ, _client_socket_handler, pb);
-
-		/* This is unlikely to happen */
-		if (ret < len) {
-			struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
-			ss->msg = g_strdup(stream_start + ret);
-			ss->tx_handler_cb = NULL; /* We have nothing to write yet */
-			bb->conversation->stream_data = ss;
-			/* Finish sending the stream start */
-			bb->conversation->tx_handler = purple_input_add(client_socket,
-				PURPLE_INPUT_WRITE, _start_stream, pb);
-		} else {
-			bb->conversation->stream_started = TRUE;
-		}
-
-		g_free(stream_start);
 	} else {
+		purple_debug_warning("bonjour", "Ignoring incoming connection because an existing connection exists.\n");
 		close(client_socket);
 	}
 }
@@ -639,8 +641,6 @@
 {
 	PurpleBuddy *pb = data;
 	BonjourBuddy *bb = pb->proto_data;
-	int len, ret;
-	char *stream_start;
 
 	bb->conversation->connect_data = NULL;
 
@@ -661,15 +661,7 @@
 		return;
 	}
 
-	stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), purple_buddy_get_name(pb));
-	len = strlen(stream_start);
-
-	/* Start the stream and send queued messages */
-	ret = send(source, stream_start, len, 0);
-
-	if (ret == -1 && errno == EAGAIN)
-		ret = 0;
-	else if (ret <= 0) {
+	if (!bonjour_jabber_stream_init(pb, source)) {
 		const char *err = strerror(errno);
 		PurpleConversation *conv;
 
@@ -685,37 +677,8 @@
 		close(source);
 		bonjour_jabber_close_conversation(bb->conversation);
 		bb->conversation = NULL;
-
-		g_free(stream_start);
-
 		return;
 	}
-
-	bb->conversation->socket = source;
-	bb->conversation->rx_handler = purple_input_add(source,
-		PURPLE_INPUT_READ, _client_socket_handler, pb);
-
-	/* This is unlikely to happen */
-	if (ret < len) {
-		struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
-		ss->msg = g_strdup(stream_start + ret);
-		ss->tx_handler_cb = _send_data_write_cb;
-		bb->conversation->stream_data = ss;
-		/* Finish sending the stream start */
-		bb->conversation->tx_handler = purple_input_add(source,
-			PURPLE_INPUT_WRITE, _start_stream, pb);
-	}
-	/* Process the send buffer */
-	else {
-		bb->conversation->stream_started = TRUE;
-		/* Watch for when we can write the buffered messages */
-		bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
-			_send_data_write_cb, pb);
-		/* We can probably write the data now. */
-		_send_data_write_cb(pb, source, PURPLE_INPUT_WRITE);
-	}
-
-	g_free(stream_start);
 }
 
 int
@@ -809,7 +772,7 @@
 		/* Close the socket and remove the watcher */
 		if (bconv->socket >= 0) {
 			/* Send the end of the stream to the other end of the conversation */
-			if (bconv->stream_started)
+			if (bconv->sent_stream_start)
 				send(bconv->socket, STREAM_END, strlen(STREAM_END), 0);
 			/* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
 			close(bconv->socket);
@@ -828,6 +791,10 @@
 			g_free(ss->msg);
 			g_free(ss);
 		}
+
+		if (bconv->context != NULL)
+			bonjour_parser_setup(bconv);
+
 		g_free(bconv);
 	}
 }
--- a/libpurple/protocols/bonjour/jabber.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.h	Wed Aug 08 21:05:47 2007 +0000
@@ -26,6 +26,10 @@
 #ifndef _BONJOUR_JABBER_H_
 #define _BONJOUR_JABBER_H_
 
+#include <libxml/parser.h>
+
+#include "xmlnode.h"
+
 #include "account.h"
 #include "circbuffer.h"
 
@@ -43,9 +47,12 @@
 	guint rx_handler;
 	guint tx_handler;
 	PurpleCircBuffer *tx_buf;
-	gboolean stream_started;
+	gboolean sent_stream_start;
+	gboolean recv_stream_start;
 	PurpleProxyConnectData *connect_data;
 	gpointer stream_data;
+	xmlParserCtxt *context;
+	xmlnode *current;
 } BonjourJabberConversation;
 
 /**
@@ -60,6 +67,12 @@
 
 void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv);
 
+void bonjour_jabber_stream_started(PurpleBuddy *pb);
+
+void bonjour_jabber_stream_ended(PurpleBuddy *pb);
+
+void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet);
+
 void bonjour_jabber_stop(BonjourJabber *data);
 
 #endif /* _BONJOUR_JABBER_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,349 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "internal.h"
+
+#include "mdns_interface.h"
+#include "debug.h"
+#include "buddy.h"
+
+#include <avahi-client/client.h>
+#include <avahi-client/lookup.h>
+#include <avahi-client/publish.h>
+
+#include <avahi-common/address.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/error.h>
+#include <avahi-common/strlst.h>
+
+#include <avahi-glib/glib-malloc.h>
+#include <avahi-glib/glib-watch.h>
+
+/* data used by avahi bonjour implementation */
+typedef struct _avahi_session_impl_data {
+	AvahiClient *client;
+	AvahiGLibPoll *glib_poll;
+	AvahiServiceBrowser *sb;
+	AvahiEntryGroup *group;
+} AvahiSessionImplData;
+
+static void
+_resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol,
+		  AvahiResolverEvent event, const char *name, const char *type, const char *domain,
+		  const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt,
+		  AvahiLookupResultFlags flags, void *userdata) {
+
+	BonjourBuddy *buddy;
+	PurpleAccount *account = userdata;
+	AvahiStringList *l;
+	size_t size;
+	char *key, *value;
+	int ret;
+
+	g_return_if_fail(r != NULL);
+
+	switch (event) {
+		case AVAHI_RESOLVER_FAILURE:
+			purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n",
+				avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
+			break;
+		case AVAHI_RESOLVER_FOUND:
+			/* create a buddy record */
+			buddy = bonjour_buddy_new(name, account);
+
+			/* Get the ip as a string */
+			buddy->ip = g_malloc(AVAHI_ADDRESS_STR_MAX);
+			avahi_address_snprint(buddy->ip, AVAHI_ADDRESS_STR_MAX, a);
+
+			buddy->port_p2pj = port;
+
+			/* Obtain the parameters from the text_record */
+			l = txt;
+			while (l != NULL) {
+				ret = avahi_string_list_get_pair(l, &key, &value, &size);
+				l = l->next;
+				if (ret < 0)
+					continue;
+				set_bonjour_buddy_value(buddy, key, value, size);
+				/* TODO: Since we're using the glib allocator, I think we
+				 * can use the values instead of re-copying them */
+				avahi_free(key);
+				avahi_free(value);
+			}
+
+			if (!bonjour_buddy_check(buddy))
+				bonjour_buddy_delete(buddy);
+			else
+				/* Add or update the buddy in our buddy list */
+				bonjour_buddy_add_to_purple(buddy);
+
+			break;
+		default:
+			purple_debug_info("bonjour", "Unrecognized Service Resolver event: %d.\n", event);
+	}
+
+	avahi_service_resolver_free(r);
+}
+
+static void
+_browser_callback(AvahiServiceBrowser *b, AvahiIfIndex interface,
+		  AvahiProtocol protocol, AvahiBrowserEvent event,
+		  const char *name, const char *type, const char *domain,
+		  AvahiLookupResultFlags flags, void *userdata) {
+
+	PurpleAccount *account = userdata;
+	PurpleBuddy *gb = NULL;
+
+	switch (event) {
+		case AVAHI_BROWSER_FAILURE:
+			purple_debug_error("bonjour", "_browser_callback - Failure: %s\n",
+				avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
+			/* TODO: This is an error that should be handled. */
+			break;
+		case AVAHI_BROWSER_NEW:
+			/* A new peer has joined the network and uses iChat bonjour */
+			purple_debug_info("bonjour", "_browser_callback - new service\n");
+			/* Make sure it isn't us */
+			if (g_ascii_strcasecmp(name, account->username) != 0) {
+				if (!avahi_service_resolver_new(avahi_service_browser_get_client(b),
+						interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC,
+						0, _resolver_callback, account)) {
+					purple_debug_warning("bonjour", "_browser_callback -- Error initiating resolver: %s\n",
+						avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
+				}
+			}
+			break;
+		case AVAHI_BROWSER_REMOVE:
+			purple_debug_info("bonjour", "_browser_callback - Remove service\n");
+			gb = purple_find_buddy(account, name);
+			if (gb != NULL) {
+				bonjour_buddy_delete(gb->proto_data);
+				purple_blist_remove_buddy(gb);
+			}
+			break;
+		case AVAHI_BROWSER_ALL_FOR_NOW:
+		case AVAHI_BROWSER_CACHE_EXHAUSTED:
+			purple_debug_warning("bonjour", "(Browser) %s\n",
+				event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
+			break;
+		default:
+			purple_debug_info("bonjour", "Unrecognized Service browser event: %d.\n", event);
+	}
+}
+
+static void
+_entry_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
+	AvahiSessionImplData *idata = userdata;
+
+	g_return_if_fail(g == idata->group || idata->group == NULL);
+
+	switch(state) {
+		case AVAHI_ENTRY_GROUP_ESTABLISHED:
+			purple_debug_info("bonjour", "Successfully registered service.\n");
+			break;
+		case AVAHI_ENTRY_GROUP_COLLISION:
+			purple_debug_error("bonjour", "Collision registering entry group.\n");
+			/* TODO: Handle error - this should log out the account. (Possibly with "wants to die")*/
+			break;
+		case AVAHI_ENTRY_GROUP_FAILURE:
+			purple_debug_error("bonjour", "Error registering entry group: %s\n.",
+				avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+			/* TODO: Handle error - this should log out the account.*/
+			break;
+		case AVAHI_ENTRY_GROUP_UNCOMMITED:
+		case AVAHI_ENTRY_GROUP_REGISTERING:
+			break;
+	}
+
+}
+
+/****************************
+ * mdns_interface functions *
+ ****************************/
+
+gboolean _mdns_init_session(BonjourDnsSd *data) {
+	AvahiSessionImplData *idata = g_new0(AvahiSessionImplData, 1);
+	const AvahiPoll *poll_api;
+	int error;
+
+	/* Tell avahi to use g_malloc and g_free */
+	avahi_set_allocator (avahi_glib_allocator ());
+
+	/* This currently depends on the glib mainloop,
+	 * we should make it use the libpurple abstraction */
+
+	idata->glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT);
+
+	poll_api = avahi_glib_poll_get(idata->glib_poll);
+
+	idata->client = avahi_client_new(poll_api, 0, NULL, data, &error);
+
+	if (idata->client == NULL) {
+		purple_debug_error("bonjour", "Error initializing Avahi: %s", avahi_strerror(error));
+		avahi_glib_poll_free(idata->glib_poll);
+		g_free(idata);
+		return FALSE;
+	}
+
+	data->mdns_impl_data = idata;
+
+	return TRUE;
+}
+
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) {
+	int publish_result = 0;
+	char portstring[6];
+	const char *jid, *aim, *email;
+	AvahiSessionImplData *idata = data->mdns_impl_data;
+	AvahiStringList *lst = NULL;
+
+	g_return_val_if_fail(idata != NULL, FALSE);
+
+	if (!idata->group) {
+		idata->group = avahi_entry_group_new(idata->client,
+						     _entry_group_cb, idata);
+		if (!idata->group) {
+			purple_debug_error("bonjour",
+				"Unable to initialize the data for the mDNS (%s).\n",
+				avahi_strerror(avahi_client_errno(idata->client)));
+			return FALSE;
+		}
+	}
+
+	/* Convert the port to a string */
+	snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
+
+	jid = purple_account_get_string(data->account, "jid", NULL);
+	aim = purple_account_get_string(data->account, "AIM", NULL);
+	email = purple_account_get_string(data->account, "email", NULL);
+
+	/* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
+	 * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
+	 */
+
+	/* Needed by iChat */
+	lst = avahi_string_list_add_pair(lst,"txtvers", "1");
+	/* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
+	lst = avahi_string_list_add_pair(lst, "1st", data->first);
+	/* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
+	lst = avahi_string_list_add_pair(lst, "last", data->last);
+	/* Needed by Adium */
+	lst = avahi_string_list_add_pair(lst, "port.p2pj", portstring);
+	/* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
+	lst = avahi_string_list_add_pair(lst, "status", data->status);
+	/* Currently always set to "!" since we don't support AV and wont ever be in a conference */
+	lst = avahi_string_list_add_pair(lst, "vc", data->vc);
+	lst = avahi_string_list_add_pair(lst, "ver", VERSION);
+	if (email != NULL && *email != '\0')
+		lst = avahi_string_list_add_pair(lst, "email", email);
+	if (jid != NULL && *jid != '\0')
+		lst = avahi_string_list_add_pair(lst, "jid", jid);
+	/* Nonstandard, but used by iChat */
+	if (aim != NULL && *aim != '\0')
+		lst = avahi_string_list_add_pair(lst, "AIM", aim);
+	if (data->msg != NULL && *data->msg != '\0')
+		lst = avahi_string_list_add_pair(lst, "msg", data->msg);
+	if (data->phsh != NULL && *data->phsh != '\0')
+		lst = avahi_string_list_add_pair(lst, "phsh", data->phsh);
+
+	/* TODO: ext, nick, node */
+
+
+	/* Publish the service */
+	switch (type) {
+		case PUBLISH_START:
+			publish_result = avahi_entry_group_add_service_strlst(
+				idata->group, AVAHI_IF_UNSPEC,
+				AVAHI_PROTO_UNSPEC, 0,
+				purple_account_get_username(data->account),
+				ICHAT_SERVICE, NULL, NULL, data->port_p2pj, lst);
+			break;
+		case PUBLISH_UPDATE:
+			publish_result = avahi_entry_group_update_service_txt_strlst(
+				idata->group, AVAHI_IF_UNSPEC,
+				AVAHI_PROTO_UNSPEC, 0,
+				purple_account_get_username(data->account),
+				ICHAT_SERVICE, NULL, lst);
+			break;
+	}
+
+	/* Free the memory used by temp data */
+	avahi_string_list_free(lst);
+
+	if (publish_result < 0) {
+		purple_debug_error("bonjour",
+			"Failed to add the " ICHAT_SERVICE " service. Error: %s\n",
+			avahi_strerror(publish_result));
+		return FALSE;
+	}
+
+	if ((publish_result = avahi_entry_group_commit(idata->group)) < 0) {
+		purple_debug_error("bonjour",
+			"Failed to commit " ICHAT_SERVICE " service. Error: %s\n",
+			avahi_strerror(publish_result));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean _mdns_browse(BonjourDnsSd *data) {
+	AvahiSessionImplData *idata = data->mdns_impl_data;
+
+	g_return_val_if_fail(idata != NULL, FALSE);
+
+	idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, ICHAT_SERVICE, NULL, 0, _browser_callback, data->account);
+	if (!idata->sb) {
+
+		purple_debug_error("bonjour",
+			"Unable to initialize service browser.  Error: %s\n.",
+			avahi_strerror(avahi_client_errno(idata->client)));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/* This is done differently than with Howl/Apple Bonjour */
+guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
+	return 0;
+}
+
+void _mdns_stop(BonjourDnsSd *data) {
+	AvahiSessionImplData *idata = data->mdns_impl_data;
+
+	if (idata == NULL || idata->client == NULL)
+		return;
+
+	if (idata->sb != NULL)
+		avahi_service_browser_free(idata->sb);
+
+	avahi_client_free(idata->client);
+	avahi_glib_poll_free(idata->glib_poll);
+
+	g_free(idata);
+
+	data->mdns_impl_data = NULL;
+}
+
+void _mdns_init_buddy(BonjourBuddy *buddy) {
+}
+
+void _mdns_delete_buddy(BonjourBuddy *buddy) {
+}
+
+void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+}
--- a/libpurple/protocols/bonjour/mdns_common.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.c	Wed Aug 08 21:05:47 2007 +0000
@@ -17,8 +17,8 @@
 #include <string.h>
 
 #include "internal.h"
-#include "config.h"
 #include "mdns_common.h"
+#include "mdns_interface.h"
 #include "bonjour.h"
 #include "buddy.h"
 #include "debug.h"
@@ -73,65 +73,28 @@
 gboolean
 bonjour_dns_sd_start(BonjourDnsSd *data)
 {
-	PurpleAccount *account;
 	PurpleConnection *gc;
-	gint dns_sd_socket;
-	gpointer opaque_data;
 
-#ifdef USE_BONJOUR_HOWL
-	sw_discovery_oid session_id;
-#endif
-
-	account = data->account;
-	gc = purple_account_get_connection(account);
+	gc = purple_account_get_connection(data->account);
 
 	/* Initialize the dns-sd data and session */
-#ifndef USE_BONJOUR_APPLE
-	if (sw_discovery_init(&data->session) != SW_OKAY)
-	{
-		purple_debug_error("bonjour", "Unable to initialize an mDNS session.\n");
-
-		/* In Avahi, sw_discovery_init frees data->session but doesn't clear it */
-		data->session = NULL;
-
+	if (!_mdns_init_session(data))
 		return FALSE;
-	}
-#endif
 
 	/* Publish our bonjour IM client at the mDNS daemon */
-
-	if (0 != _mdns_publish(data, PUBLISH_START))
-	{
+	if (!_mdns_publish(data, PUBLISH_START))
 		return FALSE;
-	}
 
 	/* Advise the daemon that we are waiting for connections */
-	
-#ifdef USE_BONJOUR_APPLE
-	if (DNSServiceBrowse(&data->browser, 0, 0, ICHAT_SERVICE, NULL, _mdns_service_browse_callback, account) 
-			!= kDNSServiceErr_NoError)
-#else /* USE_BONJOUR_HOWL */
-	if (sw_discovery_browse(data->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
-			account, &session_id) != SW_OKAY)
-#endif
-	{
+	if (!_mdns_browse(data)) {
 		purple_debug_error("bonjour", "Unable to get service.");
 		return FALSE;
 	}
 
+
 	/* Get the socket that communicates with the mDNS daemon and bind it to a */
 	/* callback that will handle the dns_sd packets */
-
-#ifdef USE_BONJOUR_APPLE
-	dns_sd_socket = DNSServiceRefSockFD(data->browser);
-	opaque_data = data->browser;
-#else /* USE_BONJOUR_HOWL */
-	dns_sd_socket = sw_discovery_socket(data->session);
-	opaque_data = data->session;
-#endif
-
-	gc->inpa = purple_input_add(dns_sd_socket, PURPLE_INPUT_READ,
-				    _mdns_handle_event, opaque_data);
+	gc->inpa = _mdns_register_to_mainloop(data);
 
 	return TRUE;
 }
@@ -143,34 +106,11 @@
 void
 bonjour_dns_sd_stop(BonjourDnsSd *data)
 {
-	PurpleAccount *account;
 	PurpleConnection *gc;
 
-#ifdef USE_BONJOUR_APPLE
-	if (data->advertisement == NULL || data->browser == NULL)
-#else /* USE_BONJOUR_HOWL */
-	if (data->session == NULL)
-#endif
-		return;
-
-#ifdef USE_BONJOUR_HOWL
-	sw_discovery_cancel(data->session, data->session_id);
-#endif
+	_mdns_stop(data);
 
-	account = data->account;
-	gc = purple_account_get_connection(account);
-	purple_input_remove(gc->inpa);
-
-#ifdef USE_BONJOUR_APPLE
-	/* hack: for win32, we need to stop listening to the advertisement pipe too */
-	purple_input_remove(data->advertisement_handler);
-
-	DNSServiceRefDeallocate(data->advertisement);
-	DNSServiceRefDeallocate(data->browser);
-	data->advertisement = NULL;
-	data->browser = NULL;
-#else /* USE_BONJOUR_HOWL */
-	g_free(data->session);
-	data->session = NULL;
-#endif
+	gc = purple_account_get_connection(data->account);
+	if (gc->inpa > 0)
+		purple_input_remove(gc->inpa);
 }
--- a/libpurple/protocols/bonjour/mdns_common.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.h	Wed Aug 08 21:05:47 2007 +0000
@@ -19,11 +19,7 @@
 
 #include "mdns_types.h"
 
-#ifdef USE_BONJOUR_APPLE
-#include "mdns_win32.h"
-#elif defined USE_BONJOUR_HOWL
-#include "mdns_howl.h"
-#endif
+#include "buddy.h"
 
 /**
  * Allocate space for the dns-sd data.
@@ -41,6 +37,11 @@
 void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message);
 
 /**
+ * Retrieve the buddy icon blob
+ */
+void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy);
+
+/**
  * Advertise our presence within the dns-sd daemon and start
  * browsing for other bonjour peers.
  */
--- a/libpurple/protocols/bonjour/mdns_howl.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_howl.c	Wed Aug 08 21:05:47 2007 +0000
@@ -14,12 +14,21 @@
  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-#include "mdns_howl.h"
+#include "internal.h"
 
+#include "mdns_interface.h"
 #include "debug.h"
 #include "buddy.h"
 
-sw_result HOWL_API
+#include <howl.h>
+
+/* data used by howl bonjour implementation */
+typedef struct _howl_impl_data {
+	sw_discovery session;
+	sw_discovery_oid session_id;
+} HowlSessionImplData;
+
+static sw_result HOWL_API
 _publish_reply(sw_discovery discovery, sw_discovery_oid oid,
 			   sw_discovery_publish_status status, sw_opaque extra)
 {
@@ -45,7 +54,7 @@
 	return SW_OKAY;
 }
 
-sw_result HOWL_API
+static sw_result HOWL_API
 _resolve_reply(sw_discovery discovery, sw_discovery_oid oid,
 			   sw_uint32 interface_index, sw_const_string name,
 			   sw_const_string type, sw_const_string domain,
@@ -94,7 +103,7 @@
 	return SW_OKAY;
 }
 
-sw_result HOWL_API
+static sw_result HOWL_API
 _browser_reply(sw_discovery discovery, sw_discovery_oid oid,
 			   sw_discovery_browse_status status,
 			   sw_uint32 interface_index, sw_const_string name,
@@ -136,7 +145,7 @@
 			break;
 		case SW_DISCOVERY_BROWSE_REMOVE_SERVICE:
 			purple_debug_info("bonjour", "_browser_reply --> Remove service\n");
-			gb = purple_find_buddy((PurpleAccount*)extra, name);
+			gb = purple_find_buddy(account, name);
 			if (gb != NULL)
 			{
 				bonjour_buddy_delete(gb->proto_data);
@@ -153,19 +162,49 @@
 	return SW_OKAY;
 }
 
-int
-_mdns_publish(BonjourDnsSd *data, PublishType type)
+static void
+_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition)
 {
+	sw_discovery_read_socket((sw_discovery)data);
+}
+
+/****************************
+ * mdns_interface functions *
+ ****************************/
+
+gboolean _mdns_init_session(BonjourDnsSd *data) {
+	HowlSessionImplData *idata = g_new0(HowlSessionImplData, 1);
+
+	if (sw_discovery_init(&idata->session) != SW_OKAY) {
+		purple_debug_error("bonjour", "Unable to initialize an mDNS session.\n");
+
+		/* In Avahi, sw_discovery_init frees data->session but doesn't clear it */
+		idata->session = NULL;
+
+		g_free(idata);
+
+		return FALSE;
+	}
+
+	data->mdns_impl_data = idata;
+
+	return TRUE;
+}
+
+
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) {
 	sw_text_record dns_data;
 	sw_result publish_result = SW_OKAY;
 	char portstring[6];
 	const char *jid, *aim, *email;
+	HowlSessionImplData *idata = data->mdns_impl_data;
+
+	g_return_val_if_fail(idata != NULL, FALSE);
 
 	/* Fill the data for the service */
-	if (sw_text_record_init(&dns_data) != SW_OKAY)
-	{
+	if (sw_text_record_init(&dns_data) != SW_OKAY) {
 		purple_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n");
-		return -1;
+		return FALSE;
 	}
 
 	/* Convert the port to a string */
@@ -207,32 +246,70 @@
 	/* TODO: ext, nick, node */
 
 	/* Publish the service */
-	switch (type)
-	{
+	switch (type) {
 		case PUBLISH_START:
-			publish_result = sw_discovery_publish(data->session, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL,
+			publish_result = sw_discovery_publish(idata->session, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL,
 								NULL, data->port_p2pj, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data),
-								_publish_reply, NULL, &data->session_id);
+								_publish_reply, NULL, &idata->session_id);
 			break;
 		case PUBLISH_UPDATE:
-			publish_result = sw_discovery_publish_update(data->session, data->session_id,
+			publish_result = sw_discovery_publish_update(idata->session, idata->session_id,
 								sw_text_record_bytes(dns_data), sw_text_record_len(dns_data));
 			break;
 	}
-	if (publish_result != SW_OKAY)
-	{
-		purple_debug_error("bonjour", "Unable to publish or change the status of the _presence._tcp service.\n");
-		return -1;
-	}
 
 	/* Free the memory used by temp data */
 	sw_text_record_fina(dns_data);
 
-	return 0;
+	if (publish_result != SW_OKAY) {
+		purple_debug_error("bonjour", "Unable to publish or change the status of the " ICHAT_SERVICE " service.\n");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean _mdns_browse(BonjourDnsSd *data) {
+	HowlSessionImplData *idata = data->mdns_impl_data;
+	/* TODO: don't we need to hang onto this to cancel later? */
+	sw_discovery_oid session_id;
+
+	g_return_val_if_fail(idata != NULL, FALSE);
+
+	return (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
+				    data->account, &session_id) == SW_OKAY);
 }
 
-void
-_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition)
-{
-	sw_discovery_read_socket((sw_discovery)data);
+guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
+	HowlSessionImplData *idata = data->mdns_impl_data;
+
+	g_return_val_if_fail(idata != NULL, 0);
+
+	return purple_input_add(sw_discovery_socket(idata->session),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->session);
 }
+
+void _mdns_stop(BonjourDnsSd *data) {
+	HowlSessionImplData *idata = data->mdns_impl_data;
+
+	if (idata == NULL || idata->session == NULL)
+		return;
+
+	sw_discovery_cancel(idata->session, idata->session_id);
+
+	/* TODO: should this really be g_free()'d ??? */
+	g_free(idata->session);
+
+	g_free(idata);
+
+	data->mdns_impl_data = NULL;
+}
+
+void _mdns_init_buddy(BonjourBuddy *buddy) {
+}
+
+void _mdns_delete_buddy(BonjourBuddy *buddy) {
+}
+
+void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+}
--- a/libpurple/protocols/bonjour/mdns_howl.h	Wed Aug 08 21:01:42 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU Library General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-#ifndef _BONJOUR_MDNS_HOWL
-#define _BONJOUR_MDNS_HOWL
-
-#include "config.h"
-
-#ifdef USE_BONJOUR_HOWL
-
-#include <howl.h>
-#include <glib.h>
-#include "mdns_types.h"
-
-/* callback functions */
-
-sw_result HOWL_API _publish_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_publish_status status, sw_opaque extra);
-
-sw_result HOWL_API _resolve_reply(sw_discovery discovery, sw_discovery_oid oid, sw_uint32 interface_index, sw_const_string name,
-	sw_const_string type, sw_const_string domain, sw_ipv4_address address, sw_port port, sw_octets text_record, 
-	sw_ulong text_record_len, sw_opaque extra);
-
-sw_result HOWL_API _browser_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_browse_status status,
-	sw_uint32 interface_index, sw_const_string name, sw_const_string type, sw_const_string domain, sw_opaque_t extra);
-
-
-/* interface functions */
-
-int _mdns_publish(BonjourDnsSd *data, PublishType type);
-void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition);
-
-#endif
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_interface.h	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,40 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _BONJOUR_MDNS_INTERFACE
+#define _BONJOUR_MDNS_INTERFACE
+
+#include "mdns_types.h"
+#include "buddy.h"
+
+gboolean _mdns_init_session(BonjourDnsSd *data);
+
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type);
+
+gboolean _mdns_browse(BonjourDnsSd *data);
+
+guint _mdns_register_to_mainloop(BonjourDnsSd *data);
+
+void _mdns_stop(BonjourDnsSd *data);
+
+void _mdns_init_buddy(BonjourBuddy *buddy);
+
+void _mdns_delete_buddy(BonjourBuddy *buddy);
+
+/* This doesn't quite belong here, but there really isn't any shared functionality */
+void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy);
+
+#endif
--- a/libpurple/protocols/bonjour/mdns_types.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_types.h	Wed Aug 08 21:05:47 2007 +0000
@@ -19,31 +19,14 @@
 
 #include <glib.h>
 #include "account.h"
-#include "config.h"
-
-#ifdef USE_BONJOUR_APPLE
-#include "dns_sd_proxy.h"
-#else /* USE_BONJOUR_HOWL */
-#include <howl.h>
-#endif
 
 #define ICHAT_SERVICE "_presence._tcp."
 
 /**
  * Data to be used by the dns-sd connection.
  */
-typedef struct _BonjourDnsSd
-{
-#ifdef USE_BONJOUR_APPLE
-	DNSServiceRef advertisement;
-	DNSServiceRef browser;
-
-	int advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */
-#else /* USE_BONJOUR_HOWL */
-	sw_discovery session;
-	sw_discovery_oid session_id;
-#endif
-
+typedef struct _BonjourDnsSd {
+	gpointer mdns_impl_data;
 	PurpleAccount *account;
 	gchar *first;
 	gchar *last;
@@ -59,5 +42,4 @@
 	PUBLISH_UPDATE
 } PublishType;
 
-
 #endif
--- a/libpurple/protocols/bonjour/mdns_win32.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Wed Aug 08 21:05:47 2007 +0000
@@ -15,22 +15,44 @@
  */
 
 #include "internal.h"
-#include "mdns_win32.h"
+#include "debug.h"
 
-#include "debug.h"
+#include "buddy.h"
+#include "mdns_interface.h"
+#include "dns_sd_proxy.h"
+#include "dnsquery.h"
+
 
 /* data structure for the resolve callback */
-typedef struct _ResolveCallbackArgs
-{
+typedef struct _ResolveCallbackArgs {
 	DNSServiceRef resolver;
-	int resolver_fd;
+	guint resolver_handler;
 
 	PurpleDnsQueryData *query;
-	gchar *fqn;
 
 	BonjourBuddy* buddy;
 } ResolveCallbackArgs;
 
+/* data used by win32 bonjour implementation */
+typedef struct _win32_session_impl_data {
+	DNSServiceRef advertisement;
+	DNSServiceRef browser;
+
+	guint advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */
+} Win32SessionImplData;
+
+typedef struct _win32_buddy_impl_data {
+	DNSServiceRef txt_query;
+	guint txt_query_handler;
+	DNSServiceRef null_query;
+	guint null_query_handler;
+} Win32BuddyImplData;
+
+static void
+_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) {
+	DNSServiceProcessResult((DNSServiceRef) data);
+}
+
 static void
 _mdns_parse_text_record(BonjourBuddy* buddy, const char* record, uint16_t record_len)
 {
@@ -51,13 +73,32 @@
 	uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata,
 	uint32_t ttl, void *context)
 {
+
 	if (kDNSServiceErr_NoError != errorCode)
-		purple_debug_error("bonjour", "text record query - callback error.\n");
+		purple_debug_error("bonjour", "record query - callback error.\n");
 	else if (flags & kDNSServiceFlagsAdd)
 	{
-		BonjourBuddy *buddy = (BonjourBuddy*)context;
-		_mdns_parse_text_record(buddy, rdata, rdlen);
-		bonjour_buddy_add_to_purple(buddy);
+		if (rrtype == kDNSServiceType_TXT) {
+			/* New Buddy */
+			BonjourBuddy *buddy = (BonjourBuddy*) context;
+			_mdns_parse_text_record(buddy, rdata, rdlen);
+			bonjour_buddy_add_to_purple(buddy);
+		} else if (rrtype == kDNSServiceType_NULL) {
+			/* Buddy Icon response */
+			BonjourBuddy *buddy = (BonjourBuddy*) context;
+			Win32BuddyImplData *idata = buddy->mdns_impl_data;
+
+			g_return_if_fail(idata != NULL);
+
+			purple_buddy_icons_set_for_user(buddy->account, buddy->name,
+							g_memdup(rdata, rdlen), rdlen, buddy->phsh);
+
+			/* We've got what we need; stop listening */
+			purple_input_remove(idata->null_query_handler);
+			idata->null_query_handler = -1;
+			DNSServiceRefDeallocate(idata->null_query);
+			idata->null_query = NULL;
+		}
 	}
 }
 
@@ -68,24 +109,26 @@
 
 	if (!hosts || !hosts->data)
 		purple_debug_error("bonjour", "host resolution - callback error.\n");
-	else
-	{
+	else {
 		struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1);
 		BonjourBuddy* buddy = args->buddy;
+		Win32BuddyImplData *idata = buddy->mdns_impl_data;
+
+		g_return_if_fail(idata != NULL);
 
 		buddy->ip = g_strdup(inet_ntoa(addr->sin_addr));
 
 		/* finally, set up the continuous txt record watcher, and add the buddy to purple */
 
-		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&buddy->txt_query, 0, 0, args->fqn,
-				kDNSServiceType_TXT, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy))
-		{
-			gint fd = DNSServiceRefSockFD(buddy->txt_query);
-			buddy->txt_query_fd = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, buddy->txt_query);
+		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, 0, 0, buddy->full_service_name,
+				kDNSServiceType_TXT, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) {
+			int fd = DNSServiceRefSockFD(idata->txt_query);
+			idata->txt_query_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query);
 
 			bonjour_buddy_add_to_purple(buddy);
-		}
-		else
+
+			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj);
+		} else
 			bonjour_buddy_delete(buddy);
 
 	}
@@ -95,7 +138,6 @@
 
 	/* free the remaining args memory */
 	purple_dnsquery_destroy(args->query);
-	g_free(args->fqn);
 	g_free(args);
 }
 
@@ -106,7 +148,7 @@
 	ResolveCallbackArgs *args = (ResolveCallbackArgs*)context;
 
 	/* remove the input fd and destroy the service ref */
-	purple_input_remove(args->resolver_fd);
+	purple_input_remove(args->resolver_handler);
 	DNSServiceRefDeallocate(args->resolver);
 
 	if (kDNSServiceErr_NoError != errorCode)
@@ -123,14 +165,13 @@
 		_mdns_parse_text_record(args->buddy, txtRecord, txtLen);
 
 		/* set more arguments, and start the host resolver */
-		args->fqn = g_strdup(fullname);
+		args->buddy->full_service_name = g_strdup(fullname);
 
 		if (!(args->query =
 			purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args)))
 		{
 			purple_debug_error("bonjour", "service resolver - host resolution failed.\n");
 			bonjour_buddy_delete(args->buddy);
-			g_free(args->fqn);
 			g_free(args);
 		}
 	}
@@ -148,7 +189,7 @@
 		purple_debug_info("bonjour", "service advertisement - callback.\n");
 }
 
-void DNSSD_API
+static void DNSSD_API
 _mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
     DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context)
 {
@@ -157,50 +198,52 @@
 
 	if (kDNSServiceErr_NoError != errorCode)
 		purple_debug_error("bonjour", "service browser - callback error");
-	else if (flags & kDNSServiceFlagsAdd)
-	{
+	else if (flags & kDNSServiceFlagsAdd) {
 		/* A presence service instance has been discovered... check it isn't us! */
-		if (g_ascii_strcasecmp(serviceName, account->username) != 0)
-		{
+		if (g_ascii_strcasecmp(serviceName, account->username) != 0) {
 			/* OK, lets go ahead and resolve it to add to the buddy list */
 			ResolveCallbackArgs *args = g_new0(ResolveCallbackArgs, 1);
 			args->buddy = bonjour_buddy_new(serviceName, account);
 
-			if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args))
-			{
+			if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args)) {
 				bonjour_buddy_delete(args->buddy);
 				g_free(args);
 				purple_debug_error("bonjour", "service browser - failed to resolve service.\n");
-			}
-			else
-			{
+			} else {
 				/* get a file descriptor for this service ref, and add it to the input list */
-				gint resolver_fd = DNSServiceRefSockFD(args->resolver);
-				args->resolver_fd = purple_input_add(resolver_fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver);
+				gint fd = DNSServiceRefSockFD(args->resolver);
+				args->resolver_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver);
 			}
 		}
-	}
-	else
-	{
+	} else {
 		/* A peer has sent a goodbye packet, remove them from the buddy list */
 		purple_debug_info("bonjour", "service browser - remove notification\n");
 		gb = purple_find_buddy(account, serviceName);
-		if (gb != NULL)
-		{
+		if (gb != NULL) {
 			bonjour_buddy_delete(gb->proto_data);
 			purple_blist_remove_buddy(gb);
 		}
 	}
 }
 
-int
-_mdns_publish(BonjourDnsSd *data, PublishType type)
-{
+/****************************
+ * mdns_interface functions *
+ ****************************/
+
+gboolean _mdns_init_session(BonjourDnsSd *data) {
+	data->mdns_impl_data = g_new0(Win32SessionImplData, 1);
+	return TRUE;
+}
+
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) {
 	TXTRecordRef dns_data;
 	char portstring[6];
-	int ret = 0;
+	gboolean ret = TRUE;
 	const char *jid, *aim, *email;
 	DNSServiceErrorType set_ret;
+	Win32SessionImplData *idata = data->mdns_impl_data;
+
+	g_return_val_if_fail(idata != NULL, FALSE);
 
 	TXTRecordCreate(&dns_data, 256, NULL);
 
@@ -248,40 +291,34 @@
 
 	/* TODO: ext, nick, node */
 
-	if (set_ret != kDNSServiceErr_NoError)
-	{
+	if (set_ret != kDNSServiceErr_NoError) {
 		purple_debug_error("bonjour", "Unable to allocate memory for text record.\n");
-		ret = -1;
-	}
-	else
-	{
+		ret = FALSE;
+	} else {
 		DNSServiceErrorType err = kDNSServiceErr_NoError;
 
 		/* OK, we're done constructing the text record, (re)publish the service */
 
-		switch (type)
-		{
+		switch (type) {
 			case PUBLISH_START:
-				err = DNSServiceRegister(&data->advertisement, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE,
+				purple_debug_info("bonjour", "Registering service on port %d\n", data->port_p2pj);
+				err = DNSServiceRegister(&idata->advertisement, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE,
 					NULL, NULL, htons(data->port_p2pj), TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data),
 					_mdns_service_register_callback, NULL);
 				break;
 
 			case PUBLISH_UPDATE:
-				err = DNSServiceUpdateRecord(data->advertisement, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
+				err = DNSServiceUpdateRecord(idata->advertisement, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
 				break;
 		}
 
-		if (kDNSServiceErr_NoError != err)
-		{
+		if (err != kDNSServiceErr_NoError) {
 			purple_debug_error("bonjour", "Failed to publish presence service.\n");
-			ret = -1;
-		}
-		else if (PUBLISH_START == type)
-		{
+			ret = FALSE;
+		} else if (type == PUBLISH_START) {
 			/* hack: Bonjour on windows is broken. We don't care about the callback but we have to listen anyway */
-			gint advertisement_fd = DNSServiceRefSockFD(data->advertisement);
-			data->advertisement_handler = purple_input_add(advertisement_fd, PURPLE_INPUT_READ, _mdns_handle_event, data->advertisement);
+			gint fd = DNSServiceRefSockFD(idata->advertisement);
+			idata->advertisement_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->advertisement);
 		}
 	}
 
@@ -290,8 +327,84 @@
 	return ret;
 }
 
-void
-_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition)
-{
-	DNSServiceProcessResult((DNSServiceRef)data);
+gboolean _mdns_browse(BonjourDnsSd *data) {
+	Win32SessionImplData *idata = data->mdns_impl_data;
+
+	g_return_val_if_fail(idata != NULL, FALSE);
+
+	return (DNSServiceBrowse(&idata->browser, 0, 0, ICHAT_SERVICE, NULL,
+				 _mdns_service_browse_callback, data->account)
+			== kDNSServiceErr_NoError);
+}
+
+guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
+	Win32SessionImplData *idata = data->mdns_impl_data;
+
+	g_return_val_if_fail(idata != NULL, 0);
+
+	return purple_input_add(DNSServiceRefSockFD(idata->browser),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->browser);
+}
+
+void _mdns_stop(BonjourDnsSd *data) {
+	Win32SessionImplData *idata = data->mdns_impl_data;
+
+	if (idata == NULL || idata->advertisement == NULL || idata->browser == NULL)
+		return;
+
+	/* hack: for win32, we need to stop listening to the advertisement pipe too */
+	purple_input_remove(idata->advertisement_handler);
+
+	DNSServiceRefDeallocate(idata->advertisement);
+	DNSServiceRefDeallocate(idata->browser);
+
+	g_free(idata);
+
+	data->mdns_impl_data = NULL;
+}
+
+void _mdns_init_buddy(BonjourBuddy *buddy) {
+	buddy->mdns_impl_data = g_new0(Win32BuddyImplData, 1);
 }
+
+void _mdns_delete_buddy(BonjourBuddy *buddy) {
+	Win32BuddyImplData *idata = buddy->mdns_impl_data;
+
+	g_return_if_fail(idata != NULL);
+
+	if (idata->txt_query != NULL) {
+		purple_input_remove(idata->txt_query_handler);
+		DNSServiceRefDeallocate(idata->txt_query);
+	}
+
+	if (idata->null_query != NULL) {
+		purple_input_remove(idata->null_query_handler);
+		DNSServiceRefDeallocate(idata->null_query);
+	}
+
+	g_free(idata);
+
+	buddy->mdns_impl_data = NULL;
+}
+
+void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+	Win32BuddyImplData *idata = buddy->mdns_impl_data;
+
+	g_return_if_fail(idata != NULL);
+
+	/* Cancel any existing query */
+	if (idata->null_query != NULL) {
+		purple_input_remove(idata->null_query_handler);
+		idata->null_query_handler = 0;
+		DNSServiceRefDeallocate(idata->null_query);
+		idata->null_query = NULL;
+	}
+
+	if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, 0, buddy->full_service_name,
+			kDNSServiceType_NULL, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) {
+		int fd = DNSServiceRefSockFD(idata->null_query);
+		idata->null_query_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->null_query);
+	}
+
+}
+
--- a/libpurple/protocols/bonjour/mdns_win32.h	Wed Aug 08 21:01:42 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU Library General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-#ifndef _BONJOUR_MDNS_WIN32
-#define _BONJOUR_MDNS_WIN32
-
-#ifdef USE_BONJOUR_APPLE
-
-#include <glib.h>
-#include "mdns_types.h"
-#include "buddy.h"
-#include "dnsquery.h"
-#include "dns_sd_proxy.h"
-
-/* Bonjour async callbacks */
-
-void DNSSD_API _mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
-    DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context);
-
-/* interface functions */
-
-int _mdns_publish(BonjourDnsSd *data, PublishType type);
-void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition);
-
-#endif
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/parser.c	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,194 @@
+/*
+ * purple - Bonjour Jabber XML parser stuff
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#include "internal.h"
+
+#include <libxml/parser.h>
+
+#include "connection.h"
+#include "debug.h"
+#include "jabber.h"
+#include "parser.h"
+#include "util.h"
+#include "xmlnode.h"
+
+static void
+bonjour_parser_element_start_libxml(void *user_data,
+				   const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace,
+				   int nb_namespaces, const xmlChar **namespaces,
+				   int nb_attributes, int nb_defaulted, const xmlChar **attributes)
+{
+	PurpleBuddy *pb = user_data;
+	BonjourBuddy *bb = pb->proto_data;
+	BonjourJabberConversation *bconv = bb->conversation;
+
+	xmlnode *node;
+	int i;
+
+	if(!element_name) {
+		return;
+	} else if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
+		bconv->recv_stream_start = TRUE;
+		bonjour_jabber_stream_started(pb);
+	} else {
+
+		if(bconv->current)
+			node = xmlnode_new_child(bconv->current, (const char*) element_name);
+		else
+			node = xmlnode_new((const char*) element_name);
+		xmlnode_set_namespace(node, (const char*) namespace);
+
+		for(i=0; i < nb_attributes * 5; i+=5) {
+			char *txt;
+			int attrib_len = attributes[i+4] - attributes[i+3];
+			char *attrib = g_malloc(attrib_len + 1);
+			char *attrib_ns = NULL;
+
+			if (attributes[i+2]) {
+				attrib_ns = g_strdup((char*)attributes[i+2]);;
+			}
+
+			memcpy(attrib, attributes[i+3], attrib_len);
+			attrib[attrib_len] = '\0';
+
+			txt = attrib;
+			attrib = purple_unescape_html(txt);
+			g_free(txt);
+			xmlnode_set_attrib_with_namespace(node, (const char*) attributes[i], attrib_ns, attrib);
+			g_free(attrib);
+			g_free(attrib_ns);
+		}
+
+		bconv->current = node;
+	}
+}
+
+static void
+bonjour_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
+				 const xmlChar *prefix, const xmlChar *namespace)
+{
+	PurpleBuddy *pb = user_data;
+	BonjourBuddy *bb = pb->proto_data;
+	BonjourJabberConversation *bconv = bb->conversation;
+
+	if(!bconv->current) {
+		/* We don't keep a reference to the start stream xmlnode,
+		 * so we have to check for it here to close the conversation */
+		if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
+			bonjour_jabber_stream_ended(pb);
+		}
+		return;
+	}
+
+	if(bconv->current->parent) {
+		if(!xmlStrcmp((xmlChar*) bconv->current->name, element_name))
+			bconv->current = bconv->current->parent;
+	} else {
+		xmlnode *packet = bconv->current;
+		bconv->current = NULL;
+		bonjour_jabber_process_packet(pb, packet);
+		xmlnode_free(packet);
+	}
+}
+
+static void
+bonjour_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len)
+{
+	PurpleBuddy *pb = user_data;
+	BonjourBuddy *bb = pb->proto_data;
+	BonjourJabberConversation *bconv = bb->conversation;
+
+	if(!bconv->current)
+		return;
+
+	if(!text || !text_len)
+		return;
+
+	xmlnode_insert_data(bconv->current, (const char*) text, text_len);
+}
+
+static xmlSAXHandler bonjour_parser_libxml = {
+	.internalSubset         = NULL,
+	.isStandalone           = NULL,
+	.hasInternalSubset      = NULL,
+	.hasExternalSubset      = NULL,
+	.resolveEntity          = NULL,
+	.getEntity              = NULL,
+	.entityDecl             = NULL,
+	.notationDecl           = NULL,
+	.attributeDecl          = NULL,
+	.elementDecl            = NULL,
+	.unparsedEntityDecl     = NULL,
+	.setDocumentLocator     = NULL,
+	.startDocument          = NULL,
+	.endDocument            = NULL,
+	.startElement           = NULL,
+	.endElement             = NULL,
+	.reference              = NULL,
+	.characters             = bonjour_parser_element_text_libxml,
+	.ignorableWhitespace    = NULL,
+	.processingInstruction  = NULL,
+	.comment                = NULL,
+	.warning                = NULL,
+	.error                  = NULL,
+	.fatalError             = NULL,
+	.getParameterEntity     = NULL,
+	.cdataBlock             = NULL,
+	.externalSubset         = NULL,
+	.initialized            = XML_SAX2_MAGIC,
+	._private               = NULL,
+	.startElementNs         = bonjour_parser_element_start_libxml,
+	.endElementNs           = bonjour_parser_element_end_libxml,
+	.serror                 = NULL
+};
+
+void
+bonjour_parser_setup(BonjourJabberConversation *bconv)
+{
+
+	/* This seems backwards, but it makes sense. The libxml code creates
+	 * the parser context when you try to use it (this way, it can figure
+	 * out the encoding at creation time. So, setting up the parser is
+	 * just a matter of destroying any current parser. */
+	if (bconv->context) {
+		xmlParseChunk(bconv->context, NULL,0,1);
+		xmlFreeParserCtxt(bconv->context);
+		bconv->context = NULL;
+	}
+}
+
+
+void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len)
+{
+	BonjourBuddy *bb = pb->proto_data;
+
+	if (bb->conversation->context ==  NULL) {
+		/* libxml inconsistently starts parsing on creating the
+		 * parser, so do a ParseChunk right afterwards to force it. */
+		bb->conversation->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, pb, buf, len, NULL);
+		xmlParseChunk(bb->conversation->context, "", 0, 0);
+	} else if (xmlParseChunk(bb->conversation->context, buf, len, 0) < 0) {
+		/* TODO: What should we do here - I assume we should display an error or something (maybe just print something to the conv?) */
+		purple_debug_error("bonjour", "Error parsing xml.\n");
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/parser.h	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,33 @@
+/**
+ * @file parser.h Bonjour Jabber XML parser functions
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _PURPLE_BONJOUR_PARSER_H_
+#define _PURPLE_BONJOUR_PARSER_H_
+
+#include "buddy.h"
+#include "jabber.h"
+
+void bonjour_parser_setup(BonjourJabberConversation *bconv);
+void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len);
+
+#endif /* _PURPLE_BONJOUR_PARSER_H_ */
--- a/libpurple/protocols/jabber/google.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/jabber/google.c	Wed Aug 08 21:05:47 2007 +0000
@@ -119,8 +119,8 @@
 	g_free(to_name);
 	g_free(tos);
 	g_free(froms);
-	for (; i >= 0; i--)
-		g_free(subjects[i]);
+	for (; i > 0; i--)
+		g_free(subjects[i - 1]);
 	g_free(subjects);
 	g_free(urls);
 
--- a/libpurple/protocols/jabber/xdata.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/jabber/xdata.c	Wed Aug 08 21:05:47 2007 +0000
@@ -299,7 +299,7 @@
 					continue;
 
 				if(!(lbl = xmlnode_get_attrib(optnode, "label")))
-					label = value;
+					lbl = value;
 
 				data->values = g_slist_prepend(data->values, value);
 
--- a/libpurple/protocols/oscar/family_feedbag.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/oscar/family_feedbag.c	Wed Aug 08 21:05:47 2007 +0000
@@ -695,18 +695,6 @@
 		cur = cur->next;
 	}
 
-	/* Check if there are empty groups and delete them */
-	cur = od->ssi.local;
-	while (cur) {
-		next = cur->next;
-		if (cur->type == AIM_SSI_TYPE_GROUP) {
-			aim_tlv_t *tlv = aim_tlv_gettlv(cur->data, 0x00c8, 1);
-			if (!tlv || !tlv->length)
-				aim_ssi_itemlist_del(&od->ssi.local, cur);
-		}
-		cur = next;
-	}
-
 	/* Check if the master group is empty */
 	if ((cur = aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000)) && (!cur->data))
 		aim_ssi_itemlist_del(&od->ssi.local, cur);
@@ -841,18 +829,39 @@
 	/* Modify the parent group */
 	aim_ssi_itemlist_rebuildgroup(od->ssi.local, group);
 
-	/* Check if we should delete the parent group */
-	if ((del = aim_ssi_itemlist_finditem(od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP)) && (!del->data)) {
-		aim_ssi_itemlist_del(&od->ssi.local, del);
+	/* Sync our local list with the server list */
+	return aim_ssi_sync(od);
+}
+
+/**
+ * Deletes a group from the list.
+ *
+ * @param od The oscar odion.
+ * @param group The name of the group.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+int aim_ssi_delgroup(OscarData *od, const char *group)
+{
+	struct aim_ssi_item *del;
+	aim_tlv_t *tlv;
 
-		/* Modify the parent group */
-		aim_ssi_itemlist_rebuildgroup(od->ssi.local, NULL);
+	if (!od)
+		return -EINVAL;
+
+	/* Find the group */
+	if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP)))
+		return -EINVAL;
 
-		/* Check if we should delete the parent's parent (the master group) */
-		if ((del = aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000)) && (!del->data)) {
-			aim_ssi_itemlist_del(&od->ssi.local, del);
-		}
-	}
+	/* Don't delete the group if it's not empty */
+	tlv = aim_tlv_gettlv(del->data, 0x00c8, 1);
+	if (tlv && tlv->length > 0)
+		return -EINVAL;
+
+	/* Remove the item from the list */
+	aim_ssi_itemlist_del(&od->ssi.local, del);
+
+	/* Modify the parent group */
+	aim_ssi_itemlist_rebuildgroup(od->ssi.local, group);
 
 	/* Sync our local list with the server list */
 	return aim_ssi_sync(od);
--- a/libpurple/protocols/oscar/flap_connection.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Wed Aug 08 21:05:47 2007 +0000
@@ -129,8 +129,8 @@
 
 			new_current = rateclass_get_new_current(conn, rateclass, &now);
 
+			/* (Add 100ms padding to account for inaccuracies in the calculation) */
 			if (new_current < rateclass->alert + 100)
-				/* (Add 100ms padding to account for inaccuracies in the calculation) */
 				/* Not ready to send this SNAC yet--keep waiting. */
 				return TRUE;
 
@@ -186,9 +186,9 @@
 		gettimeofday(&now, NULL);
 		new_current = rateclass_get_new_current(conn, rateclass, &now);
 
+		/* (Add 100ms padding to account for inaccuracies in the calculation) */
 		if (new_current < rateclass->alert + 100)
 		{
-			/* (Add 100ms padding to account for inaccuracies in the calculation) */
 			enqueue = TRUE;
 		}
 		else
--- a/libpurple/protocols/oscar/libaim.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Wed Aug 08 21:05:47 2007 +0000
@@ -78,7 +78,7 @@
 	oscar_convo_closed,		/* convo_closed */
 	oscar_normalize,		/* normalize */
 	oscar_set_icon,			/* set_buddy_icon */
-	NULL,					/* remove_group */
+	oscar_remove_group,		/* remove_group */
 	NULL,					/* get_cb_real_name */
 	NULL,					/* set_chat_topic */
 	NULL,					/* find_blist_chat */
--- a/libpurple/protocols/oscar/libicq.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Wed Aug 08 21:05:47 2007 +0000
@@ -78,7 +78,7 @@
 	oscar_convo_closed,		/* convo_closed */
 	oscar_normalize,		/* normalize */
 	oscar_set_icon,			/* set_buddy_icon */
-	NULL,					/* remove_group */
+	oscar_remove_group,		/* remove_group */
 	NULL,					/* get_cb_real_name */
 	NULL,					/* set_chat_topic */
 	NULL,					/* find_blist_chat */
--- a/libpurple/protocols/oscar/oscar.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Wed Aug 08 21:05:47 2007 +0000
@@ -161,7 +161,6 @@
 static int purple_conv_chat_info_update (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_conv_chat_incoming_msg(OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_email_parseupdate(OscarData *, FlapConnection *, FlapFrame *, ...);
-static int purple_icon_error       (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_icon_parseicon   (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int oscar_icon_req        (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_parse_msgack     (OscarData *, FlapConnection *, FlapFrame *, ...);
@@ -195,7 +194,7 @@
 static int purple_ssi_authreply    (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_ssi_gotadded     (OscarData *, FlapConnection *, FlapFrame *, ...);
 
-static gboolean purple_icon_timerfunc(gpointer data);
+static void purple_icons_fetch(PurpleConnection *gc);
 
 static void recent_buddies_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data);
 void oscar_set_info(PurpleConnection *gc, const char *info);
@@ -1156,8 +1155,7 @@
 
 	od->iconconnecting = FALSE;
 
-	if (od->icontimer == 0)
-		od->icontimer = purple_timeout_add(100, purple_icon_timerfunc, gc);
+	purple_icons_fetch(gc);
 }
 
 static int
@@ -1203,7 +1201,6 @@
 	oscar_data_addhandler(od, SNAC_FAMILY_AUTH, 0x0003, purple_parse_auth_resp, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_AUTH, 0x0007, purple_parse_login, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_AUTH, SNAC_SUBTYPE_AUTH_SECURID_REQUEST, purple_parse_auth_securid_request, 0);
-	oscar_data_addhandler(od, SNAC_FAMILY_BART, SNAC_SUBTYPE_BART_ERROR, purple_icon_error, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_BART, SNAC_SUBTYPE_BART_RESPONSE, purple_icon_parseicon, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_BOS, 0x0001, purple_parse_genericerr, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_BOS, 0x0003, purple_bosrights, 0);
@@ -1873,13 +1870,12 @@
 			saved_b16 = purple_buddy_icons_get_checksum_for_user(b);
 
 		if (!b16 || !saved_b16 || strcmp(b16, saved_b16)) {
-			GSList *cur = od->requesticon;
-			while (cur && aim_sncmp((char *)cur->data, info->sn))
-				cur = cur->next;
-			if (!cur) {
-				od->requesticon = g_slist_append(od->requesticon, g_strdup(purple_normalize(account, info->sn)));
-				if (od->icontimer == 0)
-					od->icontimer = purple_timeout_add(500, purple_icon_timerfunc, gc);
+			if (g_slist_find_custom(od->requesticon, info->sn,
+					(GCompareFunc)aim_sncmp) == NULL)
+			{
+				od->requesticon = g_slist_prepend(od->requesticon,
+						g_strdup(purple_normalize(account, info->sn)));
+				purple_icons_fetch(gc);
 			}
 		}
 		g_free(b16);
@@ -3237,24 +3233,8 @@
 	return 1;
 }
 
-static int purple_icon_error(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) {
-	PurpleConnection *gc = od->gc;
-	char *sn;
-
-	sn = od->requesticon->data;
-	purple_debug_misc("oscar", "removing %s from hash table\n", sn);
-	od->requesticon = g_slist_remove(od->requesticon, sn);
-	g_free(sn);
-
-	if (od->icontimer == 0)
-		od->icontimer = purple_timeout_add(500, purple_icon_timerfunc, gc);
-
-	return 1;
-}
-
 static int purple_icon_parseicon(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) {
 	PurpleConnection *gc = od->gc;
-	GSList *cur;
 	va_list ap;
 	char *sn;
 	guint8 iconcsumtype, *iconcsum, *icon;
@@ -3280,38 +3260,23 @@
 		g_free(b16);
 	}
 
-	cur = od->requesticon;
-	while (cur) {
-		char *cursn = cur->data;
-		if (!aim_sncmp(cursn, sn)) {
-			od->requesticon = g_slist_remove(od->requesticon, cursn);
-			g_free(cursn);
-			cur = od->requesticon;
-		} else
-			cur = cur->next;
-	}
-
-	if (od->icontimer == 0)
-		od->icontimer = purple_timeout_add(250, purple_icon_timerfunc, gc);
-
 	return 1;
 }
 
-static gboolean purple_icon_timerfunc(gpointer data) {
-	PurpleConnection *gc = data;
+static void
+purple_icons_fetch(PurpleConnection *gc)
+{
 	OscarData *od = gc->proto_data;
 	aim_userinfo_t *userinfo;
 	FlapConnection *conn;
 
-	od->icontimer = 0;
-
 	conn = flap_connection_getbytype(od, SNAC_FAMILY_BART);
 	if (!conn) {
 		if (!od->iconconnecting) {
 			aim_srv_requestnew(od, SNAC_FAMILY_BART);
 			od->iconconnecting = TRUE;
 		}
-		return FALSE;
+		return;
 	}
 
 	if (od->set_icon) {
@@ -3322,32 +3287,24 @@
 		} else {
 			purple_debug_info("oscar",
 				   "Uploading icon to icon server\n");
-			aim_bart_upload(od, purple_imgstore_get_data(img), 
+			aim_bart_upload(od, purple_imgstore_get_data(img),
 			                purple_imgstore_get_size(img));
 			purple_imgstore_unref(img);
 		}
 		od->set_icon = FALSE;
 	}
 
-	if (!od->requesticon) {
-		purple_debug_misc("oscar",
-				   "no more icons to request\n");
-		return FALSE;
-	}
-
-	userinfo = aim_locate_finduserinfo(od, (char *)od->requesticon->data);
-	if ((userinfo != NULL) && (userinfo->iconcsumlen > 0)) {
-		aim_bart_request(od, od->requesticon->data, userinfo->iconcsumtype, userinfo->iconcsum, userinfo->iconcsumlen);
-		return FALSE;
-	} else {
-		gchar *sn = od->requesticon->data;
-		od->requesticon = g_slist_remove(od->requesticon, sn);
-		g_free(sn);
-	}
-
-	od->icontimer = purple_timeout_add(100, purple_icon_timerfunc, gc);
-
-	return FALSE;
+	while (od->requesticon != NULL)
+	{
+		userinfo = aim_locate_finduserinfo(od, (char *)od->requesticon->data);
+		if ((userinfo != NULL) && (userinfo->iconcsumlen > 0))
+			aim_bart_request(od, od->requesticon->data, userinfo->iconcsumtype, userinfo->iconcsum, userinfo->iconcsumlen);
+
+		g_free(od->requesticon->data);
+		od->requesticon = g_slist_delete_link(od->requesticon, od->requesticon);
+	}
+
+	purple_debug_misc("oscar", "no more icons to request\n");
 }
 
 /*
@@ -4523,9 +4480,9 @@
 		{
 			status_text = purple_markup_strip_html(status_html);
 			/* If the status_text is longer than 60 character then truncate it */
-			if (strlen(status_text) > 60)
+			if (strlen(status_text) > MAXAVAILMSGLEN)
 			{
-				char *tmp = g_utf8_find_prev_char(status_text, &status_text[58]);
+				char *tmp = g_utf8_find_prev_char(status_text, &status_text[MAXAVAILMSGLEN - 2]);
 				strcpy(tmp, "...");
 			}
 		}
@@ -4709,6 +4666,11 @@
 	}
 }
 
+void oscar_remove_group(PurpleConnection *gc, PurpleGroup *group)
+{
+	aim_ssi_delgroup(gc->proto_data, group->name);
+}
+
 static gboolean purple_ssi_rerequestdata(gpointer data) {
 	OscarData *od = data;
 
@@ -4902,29 +4864,34 @@
 
 	/* Add from server list to local list */
 	for (curitem=od->ssi.local; curitem; curitem=curitem->next) {
-		if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL)))
+	  if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL)))
 		switch (curitem->type) {
 			case 0x0000: { /* Buddy */
 				if (curitem->name) {
-					char *gname = aim_ssi_itemlist_findparentname(od->ssi.local, curitem->name);
+					struct aim_ssi_item *groupitem = aim_ssi_itemlist_find(od->ssi.local, curitem->gid, 0x0000);
+					char *gname = groupitem ? groupitem->name : NULL;
 					char *gname_utf8 = gname ? oscar_utf8_try_convert(gc->account, gname) : NULL;
 					char *alias = aim_ssi_getalias(od->ssi.local, gname, curitem->name);
 					char *alias_utf8;
 
+					g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans"));
+					if (g == NULL) {
+						g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans"));
+						purple_blist_add_group(g, NULL);
+					}
+
 					if (alias != NULL)
 					{
 						if (g_utf8_validate(alias, -1, NULL))
 							alias_utf8 = g_strdup(alias);
 						else
 							alias_utf8 = oscar_utf8_try_convert(account, alias);
+						g_free(alias);
 					}
 					else
 						alias_utf8 = NULL;
 
-					b = purple_find_buddy(gc->account, curitem->name);
-					/* Should gname be freed here? -- elb */
-					/* Not with the current code, but that might be cleaner -- med */
-					g_free(alias);
+					b = purple_find_buddy_in_group(gc->account, curitem->name, g);
 					if (b) {
 						/* Get server stored alias */
 						if (alias_utf8) {
@@ -4934,13 +4901,8 @@
 					} else {
 						b = purple_buddy_new(gc->account, curitem->name, alias_utf8);
 
-						if (!(g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")))) {
-							g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans"));
-							purple_blist_add_group(g, NULL);
-						}
-
 						purple_debug_info("oscar",
-								   "ssi: adding buddy %s to group %s to local list\n", curitem->name, gname_utf8 ? gname_utf8 : _("Orphans"));
+								   "ssi: adding buddy %s to group %s to local list\n", curitem->name, g->name);
 						purple_blist_add_buddy(b, NULL, g, NULL);
 					}
 					if (!aim_sncmp(curitem->name, account->username)) {
@@ -4957,7 +4919,12 @@
 			} break;
 
 			case 0x0001: { /* Group */
-				/* Shouldn't add empty groups */
+				char *gname = curitem->name;
+				char *gname_utf8 = gname ? oscar_utf8_try_convert(gc->account, gname) : NULL;
+				if (gname_utf8 != NULL && purple_find_group(gname_utf8) == NULL) {
+					g = purple_group_new(gname_utf8);
+					purple_blist_add_group(g, NULL);
+				}
 			} break;
 
 			case 0x0002: { /* Permit buddy */
--- a/libpurple/protocols/oscar/oscar.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Wed Aug 08 21:05:47 2007 +0000
@@ -114,6 +114,11 @@
  */
 #define MAXCHATMSGLEN 512
 
+/*
+ * Found by trial and error.
+ */
+#define MAXAVAILMSGLEN 251
+
 /**
  * Maximum length for the password of an ICQ account
  */
@@ -444,7 +449,6 @@
 	GSList *requesticon;
 
 	gboolean icq;
-	guint icontimer;
 	guint getblisttimer;
 	guint getinfotimer;
 
@@ -1211,6 +1215,7 @@
 int aim_ssi_addpermit(OscarData *od, const char *name);
 int aim_ssi_adddeny(OscarData *od, const char *name);
 int aim_ssi_delbuddy(OscarData *od, const char *name, const char *group);
+int aim_ssi_delgroup(OscarData *od, const char *group);
 int aim_ssi_delpermit(OscarData *od, const char *name);
 int aim_ssi_deldeny(OscarData *od, const char *name);
 int aim_ssi_movebuddy(OscarData *od, const char *oldgn, const char *newgn, const char *sn);
--- a/libpurple/protocols/oscar/oscar_data.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/oscar/oscar_data.c	Wed Aug 08 21:05:47 2007 +0000
@@ -95,8 +95,6 @@
 	g_free(od->email);
 	g_free(od->newp);
 	g_free(od->oldp);
-	if (od->icontimer > 0)
-		purple_timeout_remove(od->icontimer);
 	if (od->getblisttimer > 0)
 		purple_timeout_remove(od->getblisttimer);
 	if (od->getinfotimer > 0)
--- a/libpurple/protocols/oscar/oscarcommon.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Wed Aug 08 21:05:47 2007 +0000
@@ -82,6 +82,7 @@
 void oscar_convo_closed(PurpleConnection *gc, const char *who);
 const char *oscar_normalize(const PurpleAccount *account, const char *str);
 void oscar_set_icon(PurpleConnection *gc, PurpleStoredImage *img);
+void oscar_remove_group(PurpleConnection *gc, PurpleGroup *group);
 gboolean oscar_can_receive_file(PurpleConnection *gc, const char *who);
 void oscar_send_file(PurpleConnection *gc, const char *who, const char *file);
 PurpleXfer *oscar_new_xfer(PurpleConnection *gc, const char *who);
--- a/libpurple/protocols/qq/buddy_opt.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/buddy_opt.c	Wed Aug 08 21:05:47 2007 +0000
@@ -270,11 +270,11 @@
 	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
 		read_packet_b(data, &cursor, len, &reply);
 		if (reply != QQ_ADD_BUDDY_AUTH_REPLY_OK) {
-			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Add buddy with auth request fails\n");
+			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Add buddy with auth request failed\n");
 			if (NULL == (segments = split_data(data, len, "\x1f", 2)))
 				return;
 			msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
-			purple_notify_error(gc, NULL, _("Add buddy with auth request fails"), msg_utf8);
+			purple_notify_error(gc, NULL, _("Add buddy with auth request failed"), msg_utf8);
 			g_free(msg_utf8);
 		} else {
 			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Add buddy with auth request OK\n");
@@ -305,6 +305,7 @@
 			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove buddy fails\n");
 		} else {		/* if reply */
 			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove buddy OK\n");
+			/* TODO: We don't really need to notify the user about this, do we? */
 			purple_notify_info(gc, NULL, _("You have successfully removed a buddy"), NULL);
 		}
 	} else {
@@ -333,7 +334,8 @@
 			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove self fails\n");
 		else {		/* if reply */
 			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove self from a buddy OK\n");
-			purple_notify_info(gc, NULL, _("You have successfully removed yourself from a buddy"), NULL);
+			/* TODO: Does the user really need to be notified about this? */
+			purple_notify_info(gc, NULL, _("You have successfully removed yourself from your friend's buddy list"), NULL);
 		}
 	} else {
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt remove self reply\n");
@@ -413,7 +415,7 @@
 			g_free(nombre);
 		} else {	/* add OK */
 			qq_add_buddy_by_recv_packet(gc, for_uid, TRUE, TRUE);
-			msg = g_strdup_printf(_("You have added %d in buddy list"), for_uid);
+			msg = g_strdup_printf(_("You have added %d to buddy list"), for_uid);
 			purple_notify_info(gc, NULL, msg, NULL);
 			g_free(msg);
 		}
--- a/libpurple/protocols/qq/group.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/group.c	Wed Aug 08 21:05:47 2007 +0000
@@ -117,7 +117,7 @@
 	purple_roomlist_set_in_progress(qd->roomlist, TRUE);
 
 	purple_request_input(gc, _("QQ Qun"),
-			   _("Please input external group ID"),
+			   _("Please enter external group ID"),
 			   _("You can only search for permanent QQ groups\n"),
 			   NULL, FALSE, FALSE, NULL, 
 			   _("Search"), G_CALLBACK(_qq_group_search_callback), 
--- a/libpurple/protocols/qq/group_im.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/group_im.c	Wed Aug 08 21:05:47 2007 +0000
@@ -123,7 +123,7 @@
 
 	convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);
 
-	msg = g_strdup_printf(_("User %d applied to join group %d"), user_uid, external_group_id);
+	msg = g_strdup_printf(_("User %d requested to join group %d"), user_uid, external_group_id);
 	reason = g_strdup_printf(_("Reason: %s"), reason_utf8);
 
 	g = g_new0(group_member_opt, 1);
@@ -177,7 +177,7 @@
 	convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);
 
 	msg = g_strdup_printf
-	    (_("You request to join group %d has been rejected by admin %d"), external_group_id, admin_uid);
+	    (_("Your request to join group %d has been rejected by admin %d"), external_group_id, admin_uid);
 	reason = g_strdup_printf(_("Reason: %s"), reason_utf8);
 
 	purple_notify_warning(gc, _("QQ Qun Operation"), msg, reason);
@@ -218,7 +218,7 @@
 	convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);
 
 	msg = g_strdup_printf
-	    (_("You request to join group %d has been approved by admin %d"), external_group_id, admin_uid);
+	    (_("Your request to join group %d has been approved by admin %d"), external_group_id, admin_uid);
 
 	purple_notify_warning(gc, _("QQ Qun Operation"), msg, NULL);
 
@@ -254,7 +254,7 @@
 
 	g_return_if_fail(external_group_id > 0 && uid > 0);
 
-	msg = g_strdup_printf(_("You [%d] has exit group \"%d\""), uid, external_group_id);
+	msg = g_strdup_printf(_("You [%d] have left group \"%d\""), uid, external_group_id);
 	purple_notify_info(gc, _("QQ Qun Operation"), msg, NULL);
 
 	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
@@ -288,7 +288,7 @@
 
 	g_return_if_fail(external_group_id > 0 && uid > 0);
 
-	msg = g_strdup_printf(_("You [%d] has been added by group \"%d\""), uid, external_group_id);
+	msg = g_strdup_printf(_("You [%d] have been added to group \"%d\""), uid, external_group_id);
 	purple_notify_info(gc, _("QQ Qun Operation"), msg, _("This group has been added to your buddy list"));
 
 	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
--- a/libpurple/protocols/qq/group_internal.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/group_internal.c	Wed Aug 08 21:05:47 2007 +0000
@@ -38,7 +38,7 @@
 
 	switch (group->my_status) {
 	case QQ_GROUP_MEMBER_STATUS_NOT_MEMBER:
-		status_desc = _("I am not member");
+		status_desc = _("I am not a member");
 		break;
 	case QQ_GROUP_MEMBER_STATUS_IS_MEMBER:
 		status_desc = _("I am a member");
--- a/libpurple/protocols/qq/group_join.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/group_join.c	Wed Aug 08 21:05:47 2007 +0000
@@ -230,7 +230,7 @@
 				purple_blist_remove_chat(chat);
 			qq_group_delete_internal_record(qd, internal_group_id);
 		}
-		purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully exited the group"), NULL);
+		purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully left the group"), NULL);
 	} else {
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
 			   "Invalid exit group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
@@ -254,8 +254,8 @@
 
 	if (bytes == expected_bytes)
 		purple_notify_info
-		    (gc, _("QQ Group Auth"), 
-		     _("Your authorization operation has been accepted by the QQ server"), NULL);
+		    (gc, _("QQ Group Auth"),
+		     _("Your authorization request has been accepted by the QQ server"), NULL);
 	else
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
 			   "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
@@ -325,8 +325,8 @@
 	errno = 0;
 	external_group_id = strtol(external_group_id_ptr, NULL, 10);
 	if (errno != 0) {
-		purple_notify_error(gc, _("Error"), 
-				_("You inputted a group id outside the acceptable range"), NULL);
+		purple_notify_error(gc, _("Error"),
+				_("You entered a group ID outside the acceptable range"), NULL);
 		return;
 	}
 
@@ -357,12 +357,12 @@
 	g->uid = internal_group_id;
 
 	purple_request_action(gc, _("QQ Qun Operation"),
-			    _("Are you sure to exit this Qun?"),
+			    _("Are you sure you want to leave this Qun?"),
 			    _
 			    ("Note, if you are the creator, \nthis operation will eventually remove this Qun."),
 			    1,
 				purple_connection_get_account(gc), NULL, NULL,
 			    g, 2, _("Cancel"),
 			    G_CALLBACK(qq_do_nothing_with_gc_and_uid),
-			    _("Go ahead"), G_CALLBACK(_qq_group_exit_with_gc_and_id));
+			    _("Continue"), G_CALLBACK(_qq_group_exit_with_gc_and_id));
 }
--- a/libpurple/protocols/qq/group_opt.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/group_opt.c	Wed Aug 08 21:05:47 2007 +0000
@@ -120,8 +120,8 @@
 {
 	g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0);
 
-	qq_send_packet_get_info(g->gc, g->member, TRUE);	/* we wanna see window */
-	purple_request_action(g->gc, NULL, _("Do you wanna approve the request?"), "", 2,
+	qq_send_packet_get_info(g->gc, g->member, TRUE);	/* we want to see window */
+	purple_request_action(g->gc, NULL, _("Do you want to approve the request?"), "", 2,
 					purple_connection_get_account(g->gc), NULL, NULL,
 					g, 2,
 					_("Reject"), G_CALLBACK(qq_group_reject_application_with_struct),
@@ -134,7 +134,7 @@
 	g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0);
 
 	msg1 = g_strdup_printf(_("You rejected %d's request"), g->member);
-	msg2 = g_strdup(_("Input your reason:"));
+	msg2 = g_strdup(_("Enter your reason:"));
 
 	nombre = uid_to_purple_name(g->member);
 	purple_request_input(g->gc, /* title */ NULL, msg1, msg2,
@@ -232,7 +232,7 @@
 
 	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Succeed in modify members for Qun %d\n", group->external_group_id);
 
-	purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun member"), NULL);
+	purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modified Qun member"), NULL);
 }
 
 void qq_group_modify_info(PurpleConnection *gc, qq_group *group)
@@ -302,7 +302,7 @@
 	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Succeed in modify info for Qun %d\n", group->external_group_id);
 	qq_group_refresh(gc, group);
 
-	purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun information"), NULL);
+	purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modified Qun information"), NULL);
 }
 
 /* we create a very simple group first, and then let the user to modify */
--- a/libpurple/protocols/qq/im.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/im.c	Wed Aug 08 21:05:47 2007 +0000
@@ -573,7 +573,7 @@
 		read_packet_b(data, &cursor, len, &reply);
 		if (reply != QQ_SEND_IM_REPLY_OK) {
 			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Send IM fail\n");
-			purple_notify_error(gc, _("Server ACK"), _("Failed to send IM."), NULL);
+			purple_notify_error(gc, _("Error"), _("Failed to send IM."), NULL);
 		}
 		else
 			purple_debug(PURPLE_DEBUG_INFO, "QQ", "IM ACK OK\n");
--- a/libpurple/protocols/qq/keep_alive.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/keep_alive.c	Wed Aug 08 21:05:47 2007 +0000
@@ -84,7 +84,7 @@
 		/* segments[0] and segment[1] are all 0x30 ("0") */
 		qd->all_online = strtol(segments[2], NULL, 10);
 		if(0 == qd->all_online)
-			purple_connection_error(gc, _("Keep alive error, seems connection lost!"));
+			purple_connection_error(gc, _("Keep alive error"));
 		g_free(qd->my_ip);
 		qd->my_ip = g_strdup(segments[3]);
 		qd->my_port = strtol(segments[4], NULL, 10);
--- a/libpurple/protocols/qq/login_logout.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/login_logout.c	Wed Aug 08 21:05:47 2007 +0000
@@ -405,7 +405,7 @@
            		           ">>> %d bytes -> [default] decrypt and dump\n%s",
 	                           buf_len, hex_dump);
                		try_dump_as_gbk(buf, buf_len);
-		purple_connection_error(gc, _("Request login token error!"));
+		purple_connection_error(gc, _("Error requesting login token"));
 	}		
 	g_free(hex_dump);
 }
--- a/libpurple/protocols/qq/qq.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/qq.c	Wed Aug 08 21:05:47 2007 +0000
@@ -195,7 +195,8 @@
 {
 	qq_buddy *q_bud;
 	gchar *ip_str;
-	char *tmp, *tmp2;
+	char *tmp;
+	const char *tmp2;
 
 	g_return_if_fail(b != NULL);
 
@@ -206,11 +207,12 @@
 	{
 		ip_str = gen_ip_str(q_bud->ip);
 		if (strlen(ip_str) != 0) {
-			tmp = g_strdup_printf(_("%s Address"),
-						  ((q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) ? "TCP" : "UDP"));
-			tmp2 = g_strdup_printf("%s:%d", ip_str, q_bud->port);
-			purple_notify_user_info_add_pair(user_info, tmp, tmp2);
-			g_free(tmp2);
+			if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE)
+				tmp2 = _("TCP Address");
+			else
+				tmp2 = _("UDP Address");
+			tmp = g_strdup_printf("%s:%d", ip_str, q_bud->port);
+			purple_notify_user_info_add_pair(user_info, tmp2, tmp);
 			g_free(tmp);
 		}
 		g_free(ip_str);
@@ -238,7 +240,7 @@
 		if (q_bud->level) {
 			tmp = g_strdup_printf("%d", q_bud->level);
 			purple_notify_user_info_add_pair(user_info, _("Level"), tmp);
-			g_free(tmp);			
+			g_free(tmp);
 		}
 		/* For debugging */
 		/*
@@ -275,19 +277,19 @@
 	GList *types = NULL;
 
 	status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE,
-			"available", _("QQ: Available"), FALSE, TRUE, FALSE);
+			"available", _("Available"), FALSE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_AWAY,
-			"away", _("QQ: Away"), FALSE, TRUE, FALSE);
+			"away", _("Away"), FALSE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE,
-			"invisible", _("QQ: Invisible"), FALSE, TRUE, FALSE);
+			"invisible", _("Invisible"), FALSE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE,
-			"offline", _("QQ: Offline"), FALSE, TRUE, FALSE);
+			"offline", _("Offline"), FALSE, TRUE, FALSE);
 	types = g_list_append(types, status);
 
 	status = purple_status_type_new_full(PURPLE_STATUS_MOBILE,
@@ -416,7 +418,7 @@
 	g->uid = uid;
 
 	purple_request_action(gc, _("Block Buddy"),
-			    _("Are you sure to block this buddy?"), NULL,
+			    _("Are you sure you want to block this buddy?"), NULL,
 			    1, g, 2,
 			    _("Cancel"),
 			    G_CALLBACK(qq_do_nothing_with_gc_and_uid),
@@ -470,7 +472,7 @@
 	PurpleConnection *gc = (PurpleConnection *) action->context;
 	purple_request_input(gc, _("Create QQ Qun"),
 			   _("Input Qun name here"),
-			   _("Only QQ member can create permanent Qun"),
+			   _("Only QQ members can create permanent Qun"),
 			   "OpenQ", FALSE, FALSE, NULL,
 			   _("Create"), G_CALLBACK(qq_group_create_with_name), _("Cancel"), NULL, gc);
 }
@@ -528,7 +530,7 @@
 	PurplePluginAction *act;
 
 	m = NULL;
-	act = purple_plugin_action_new(_("Modify My Information"), _qq_menu_modify_my_info);
+	act = purple_plugin_action_new(_("Set My Information"), _qq_menu_modify_my_info);
 	m = g_list_append(m, act);
 
 	act = purple_plugin_action_new(_("Change Password"), _qq_menu_change_password);
@@ -555,7 +557,7 @@
 	PurpleMenuAction *act;
 
 	m = NULL;
-	act = purple_menu_action_new(_("Exit this QQ Qun"), PURPLE_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL);
+	act = purple_menu_action_new(_("Leave this QQ Qun"), PURPLE_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL);
 	m = g_list_append(m, act);
 
 	/* TODO: enable this
--- a/libpurple/protocols/qq/qq_proxy.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/qq_proxy.c	Wed Aug 08 21:05:47 2007 +0000
@@ -493,13 +493,8 @@
 		errno = 0;
 		ret = send(qd->fd, data, len, 0);
 	}
-	if (ret == -1) {
-		purple_connection_error(qd->gc, _("Socket send error"));
-		return ret;
-	} else if (errno == ECONNREFUSED) {
-		purple_connection_error(qd->gc, _("Connection refused"));
-		return ret;
-	}
+	if (ret == -1)
+		purple_connection_error(qd->gc, strerror(errno));
 
 	return ret;
 }
--- a/libpurple/protocols/qq/sys_msg.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/qq/sys_msg.c	Wed Aug 08 21:05:47 2007 +0000
@@ -80,12 +80,11 @@
 	uid = g->uid;
 	g_return_if_fail(gc != 0 && uid != 0);
 
-	qq_send_packet_get_info(gc, uid, TRUE);	/* we wanna see window */
+	qq_send_packet_get_info(gc, uid, TRUE);	/* we want to see window */
 
 	nombre = uid_to_purple_name(uid);
-	/* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze. */
 	purple_request_action
-	    (gc, NULL, _("Do you wanna approve the request?"), "", 2,
+	    (gc, NULL, _("Do you want to approve the request?"), "", 2,
 		 purple_connection_get_account(gc), nombre, NULL,
 		 g, 2,
 	     _("Reject"), G_CALLBACK(qq_reject_add_request_with_gc_and_uid),
@@ -105,11 +104,10 @@
 	uid = g->uid;
 	g_return_if_fail(gc != 0 && uid != 0);
 
-	qq_send_packet_get_info(gc, uid, TRUE);	/* we wanna see window */
-	/* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze. */
+	qq_send_packet_get_info(gc, uid, TRUE);	/* we want to see window */
 	nombre = uid_to_purple_name(uid);
 	purple_request_action
-	    (gc, NULL, _("Do you wanna add this buddy?"), "", 2,
+	    (gc, NULL, _("Do you want to add this buddy?"), "", 2,
 		 purple_connection_get_account(gc), nombre, NULL,
 		 g, 2,
 	     _("Cancel"), NULL,
@@ -175,7 +173,7 @@
 					_("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid),
 				    _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid));
 	} else {
-		message = g_strdup_printf(_("%s has added you [%s]"), from, to);
+		message = g_strdup_printf(_("%s has added you [%s] to his or her buddy list"), from, to);
 		_qq_sys_msg_log_write(gc, message, from);
 		purple_notify_info(gc, NULL, message, NULL);
 	}
@@ -211,7 +209,7 @@
 	qd = (qq_data *) gc->proto_data;
 	qq_add_buddy_by_recv_packet(gc, strtol(from, NULL, 10), TRUE, TRUE);
 
-	message = g_strdup_printf(_("User %s has approved your request"), from);
+	message = g_strdup_printf(_("User %s approved your request"), from);
 	_qq_sys_msg_log_write(gc, message, from);
 	purple_notify_info(gc, NULL, message, NULL);
 
@@ -236,9 +234,8 @@
 
 	name = uid_to_purple_name(uid);
 
-	/* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze */
 	/* TODO: this should go through purple_account_request_authorization() */
-	message = g_strdup_printf(_("%s wanna add you [%s] as friends"), from, to);
+	message = g_strdup_printf(_("%s wants to add you [%s] as a friend"), from, to);
 	reason = g_strdup_printf(_("Message: %s"), msg_utf8);
 	_qq_sys_msg_log_write(gc, message, from);
 
--- a/libpurple/protocols/yahoo/Makefile.am	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/yahoo/Makefile.am	Wed Aug 08 21:05:47 2007 +0000
@@ -9,6 +9,8 @@
 	yahoo.h \
 	yahoochat.h \
 	yahoochat.c \
+	yahoo_aliases.c \
+	yahoo_alisaes.h \
 	yahoo_auth.c \
 	yahoo_auth.h \
 	yahoo_crypt.h \
--- a/libpurple/protocols/yahoo/Makefile.mingw	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/yahoo/Makefile.mingw	Wed Aug 08 21:05:47 2007 +0000
@@ -40,6 +40,7 @@
 C_SRC =			util.c \
 			yahoo.c \
 			yahoochat.c \
+			yahoo_aliases.c \
 			yahoo_auth.c \
 			yahoo_crypt.c \
 			yahoo_doodle.c \
--- a/libpurple/protocols/yahoo/yahoo.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Wed Aug 08 21:05:47 2007 +0000
@@ -41,6 +41,7 @@
 
 #include "yahoo.h"
 #include "yahoochat.h"
+#include "yahoo_aliases.h"
 #include "yahoo_auth.h"
 #include "yahoo_crypt.h"
 #include "yahoo_doodle.h"
@@ -196,6 +197,8 @@
 	GSList *l = pkt->hash;
 	YahooFriend *f = NULL;
 	char *name = NULL;
+	gboolean unicode = FALSE;
+	char *message = NULL;
 
 	if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) {
 		gc->wants_to_die = TRUE;
@@ -269,7 +272,7 @@
 			break;
 		case 19: /* custom message */
 			if (f)
-				yahoo_friend_set_status_message(f, yahoo_string_decode(gc, pair->value, FALSE));
+				message = pair->value;
 			break;
 		case 11: /* this is the buddy's session id */
 			break;
@@ -380,6 +383,10 @@
 				g_free(tmp);
 			}
 			break;
+		case 97: /* Unicode status message */
+			unicode = !strcmp(pair->value, "1");
+			break;
+
 		default:
 			purple_debug(PURPLE_DEBUG_ERROR, "yahoo",
 					   "Unknown status key %d\n", pair->key);
@@ -389,6 +396,9 @@
 		l = l->next;
 	}
 
+	if (message && f)
+		yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode));
+
 	if (name && f) /* update the last buddy */
 		yahoo_update_status(gc, name, f);
 }
@@ -484,7 +494,8 @@
 		if (yd->cookie_t)
 			g_free(yd->cookie_t);
 		yd->cookie_t = _getcookie(c);
-	}
+	} else
+		purple_debug_info("yahoo", "Ignoring unrecognized cookie '%c'\n", c[0]);
 }
 
 static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt)
@@ -508,7 +519,7 @@
 		l = l->next;
 
 		switch (pair->key) {
-		case 302: 
+		case 302:
 			/* This is always 318 before a group, 319 before the first s/n in a group, 320 before any ignored s/n.
 			 * It is not sent for s/n's in a group after the first.
 			 * All ignored s/n's are listed last, so when we see a 320 we clear the group and begin marking the
@@ -548,7 +559,7 @@
 
 			} else {
 				/* This buddy is on the ignore list (and therefore in no group) */
-				purple_privacy_deny_add(account, norm_bud, 1);				
+				purple_privacy_deny_add(account, norm_bud, 1);
 			}
 			break;
 		case 241: /* another protocol user */
@@ -702,6 +713,8 @@
 		yd->tmp_serv_plist = NULL;
 
 	}
+	/* Now that we've got the list, request aliases */
+	yahoo_fetch_aliases(gc);
 }
 
 static void yahoo_process_notify(PurpleConnection *gc, struct yahoo_packet *pkt)
@@ -816,7 +829,7 @@
 	{
 		g_hash_table_replace(yd->imvironments, g_strdup(im->from), g_strdup(imv));
 
-		if (strcmp(imv, "doodle;11") == 0)
+		if (strstr(imv, "doodle;") != NULL)
 		{
 			PurpleWhiteboard *wb;
 
@@ -825,6 +838,8 @@
 				return;
 			}
 
+			/* I'm not sure the following ever happens -DAA */
+
 			wb = purple_whiteboard_get_session(gc->account, im->from);
 
 			/* If a Doodle session doesn't exist between this user */
@@ -868,17 +883,17 @@
 			PurpleAccount *account;
 			PurpleConversation *c;
 			char *username, *str;
-			
+
 			account = purple_connection_get_account(gc);
 			c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, im->from);
-			
+
 			if ((buddy = purple_find_buddy(account, im->from)) != NULL)
 				username = g_markup_escape_text(purple_buddy_get_alias(buddy), -1);
 			else
 				username = g_markup_escape_text(im->from, -1);
-			
+
 			str = g_strdup_printf(_("%s just sent you a Buzz!"), username);
-			
+
 			purple_conversation_write(c, NULL, str, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, im->time);
 
 			g_free(username);
@@ -984,7 +999,7 @@
 static void
 yahoo_buddy_add_deny_reason_cb(struct yahoo_add_request *add_req) {
 	purple_request_input(add_req->gc, NULL, _("Authorization denied message:"),
-			NULL, _("No reason given."), TRUE, FALSE, NULL, 
+			NULL, _("No reason given."), TRUE, FALSE, NULL,
 			_("OK"), G_CALLBACK(yahoo_buddy_add_deny_cb),
 			_("Cancel"), G_CALLBACK(yahoo_buddy_add_deny_noreason_cb),
 			purple_connection_get_account(add_req->gc), add_req->who, NULL,
@@ -1027,7 +1042,7 @@
 		 */
 		 purple_account_request_authorization(purple_connection_get_account(gc), add_req->who, add_req->id,
                                                     NULL, add_req->msg, purple_find_buddy(purple_connection_get_account(gc),add_req->who) != NULL,
-						    G_CALLBACK(yahoo_buddy_add_authorize_cb), 
+						    G_CALLBACK(yahoo_buddy_add_authorize_cb),
 						    G_CALLBACK(yahoo_buddy_add_deny_reason_cb),
                                                     add_req);
 	} else {
@@ -1152,10 +1167,10 @@
 {
 	PurpleAccount *account = purple_connection_get_account(gc);
 	struct yahoo_data *yd = gc->proto_data;
-	char *who = NULL;
-	char *email = NULL;
-	char *subj = NULL;
-	char *yahoo_mail_url = (yd->jp? YAHOOJP_MAIL_URL: YAHOO_MAIL_URL);
+	const char *who = NULL;
+	const char *email = NULL;
+	const char *subj = NULL;
+	const char *yahoo_mail_url = (yd->jp? YAHOOJP_MAIL_URL: YAHOO_MAIL_URL);
 	int count = 0;
 	GSList *l = pkt->hash;
 
@@ -3242,11 +3257,79 @@
 	purple_connection_set_display_name(gc, entry);
 }
 
+static void
+yahoo_get_inbox_token_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *token, size_t len, const gchar *error_message)
+{
+	PurpleConnection *gc = user_data;
+	gboolean set_cookie = FALSE;
+	char *url;
+
+	g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
+
+	if (error_message != NULL)
+		purple_debug_error("yahoo", "Requesting mail login token failed: %s\n", error_message);
+	else if (len > 0 && token && *token) {
+	 	/* Should we not be hardcoding the rd url? */
+		url = g_strdup_printf(
+			"http://login.yahoo.com/config/reset_cookies_token?"
+			".token=%s"
+			"&.done=http://us.rd.yahoo.com/messenger/client/%%3fhttp://mail.yahoo.com/",
+			token);
+		set_cookie = TRUE;
+	}
+
+	if (!set_cookie) {
+		struct yahoo_data *yd = gc->proto_data;
+		purple_debug_error("yahoo", "No mail login token; forwarding to login screen.");
+		url = g_strdup(yd->jp ? YAHOOJP_MAIL_URL : YAHOO_MAIL_URL);
+	}
+
+	/* Open the mailbox with the parsed url data */
+	purple_notify_uri(gc, url);
+
+	g_free(url);
+}
+
+
+static void yahoo_show_inbox(PurplePluginAction *action)
+{
+	/* Setup a cookie that can be used by the browser */
+	/* XXX I have no idea how this will work with Yahoo! Japan. */
+
+	PurpleConnection *gc = action->context;
+	struct yahoo_data *yd = gc->proto_data;
+
+	PurpleUtilFetchUrlData *url_data;
+	const char* base_url = "http://login.yahoo.com";
+	char *request = g_strdup_printf(
+		"POST /config/cookie_token HTTP/1.0\r\n"
+		"Cookie: T=%s; path=/; domain=.yahoo.com; Y=%s;\r\n"
+		"User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
+		"Host: login.yahoo.com\r\n"
+		"Content-Length: 0\r\n\r\n",
+		yd->cookie_t, yd->cookie_y);
+
+	url_data = purple_util_fetch_url_request(base_url, FALSE,
+			"Mozilla/4.0 (compatible; MSIE 5.5)", TRUE, request, FALSE,
+			yahoo_get_inbox_token_cb, gc);
+
+	g_free(request);
+
+	if (url_data == NULL) {
+		const char *yahoo_mail_url = (yd->jp ? YAHOOJP_MAIL_URL : YAHOO_MAIL_URL);
+		purple_debug_error("yahoo",
+				   "Unable to request mail login token; forwarding to login screen.");
+		purple_notify_uri(gc, yahoo_mail_url);
+	}
+
+}
+
+
 static void yahoo_show_act_id(PurplePluginAction *action)
 {
 	PurpleConnection *gc = (PurpleConnection *) action->context;
-	/* XXX Typo: This should be _("Activate which ID?") - fix after string freeze is over */
-	purple_request_input(gc, NULL, _("Active which ID?"), NULL,
+	purple_request_input(gc, NULL, _("Activate which ID?"), NULL,
 					   purple_connection_get_display_name(gc), FALSE, FALSE, NULL,
 					   _("OK"), G_CALLBACK(yahoo_act_id),
 					   _("Cancel"), NULL,
@@ -3277,6 +3360,11 @@
 			yahoo_show_chat_goto);
 	m = g_list_append(m, act);
 
+	m = g_list_append(m, NULL);
+	act = purple_plugin_action_new(_("Open Inbox"),
+			yahoo_show_inbox);
+	m = g_list_append(m, act);
+
 	return m;
 }
 
@@ -3315,7 +3403,7 @@
 	 */
 	wb = purple_whiteboard_get_session(gc->account, who);
 	if (wb)
-		yahoo_packet_hash_str(pkt, 63, "doodle;11");
+		yahoo_packet_hash_str(pkt, 63, DOODLE_IMV_KEY);
 	else
 	{
 		const char *imv;
@@ -3377,6 +3465,7 @@
 	const char *msg = NULL;
 	char *tmp = NULL;
 	char *conv_msg = NULL;
+	gboolean utf8 = TRUE;
 
 	if (!purple_status_is_active(status))
 		return;
@@ -3393,13 +3482,13 @@
 		msg = purple_status_get_attr_string(status, "message");
 
 		if (purple_status_is_available(status)) {
-			tmp = yahoo_string_encode(gc, msg, NULL);
+			tmp = yahoo_string_encode(gc, msg, &utf8);
 			conv_msg = purple_markup_strip_html(tmp);
 			g_free(tmp);
 		} else {
 			if ((msg == NULL) || (*msg == '\0'))
 				msg = _("Away");
-			tmp = yahoo_string_encode(gc, msg, NULL);
+			tmp = yahoo_string_encode(gc, msg, &utf8);
 			conv_msg = purple_markup_strip_html(tmp);
 			g_free(tmp);
 		}
@@ -3417,6 +3506,7 @@
 	yahoo_packet_hash_int(pkt, 10, yd->current_status);
 
 	if (yd->current_status == YAHOO_STATUS_CUSTOM) {
+		yahoo_packet_hash_str(pkt, 97, utf8 ? "1" : 0);
 		yahoo_packet_hash_str(pkt, 19, conv_msg);
 	} else {
 		yahoo_packet_hash_str(pkt, 19, "");
@@ -4017,7 +4107,7 @@
 	NULL, /* register_user */
 	NULL, /* get_cb_info */
 	NULL, /* get_cb_away */
-	NULL, /* alias_buddy */
+	yahoo_update_alias, /* alias_buddy */
 	yahoo_change_buddys_group,
 	yahoo_rename_group,
 	NULL, /* buddy_free */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/yahoo/yahoo_aliases.c	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,245 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+
+#include "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "debug.h"
+#include "util.h"
+#include "version.h"
+#include "yahoo.h"
+#include "yahoo_aliases.h"
+#include "yahoo_packet.h"
+
+/* I hate hardcoding this stuff, but Yahoo never sends us anything to use.  Someone in the know may be able to tweak this URL */
+#define YAHOO_ALIAS_FETCH_URL "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us&diffs=1&t=0&tags=short&rt=0&prog-ver=8.1.0.249&useutf8=1&legenc=codepage-1252"
+#define YAHOO_ALIAS_UPDATE_URL "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us&sync=1&tags=short&noclear=1&useutf8=1&legenc=codepage-1252"
+
+void yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias);
+
+/**
+ * Stuff we want passed to the callback function
+ */
+struct callback_data {
+	PurpleConnection *gc;
+	const char *id;
+};
+
+
+/**************************************************************************
+ * Alias Fetch Functions
+ **************************************************************************/
+
+static void
+yahoo_fetch_aliases_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,const gchar *url_text, size_t len, const gchar *error_message)
+{
+	if (len == 0) {
+		purple_debug_info("yahoo","No Aliases to process\n");
+	} else {
+		const char *yid, *full_name, *nick_name, *alias, *id, *fn, *ln, *nn;
+		struct callback_data *cb = user_data;
+		PurpleBuddy *b = NULL;
+		xmlnode *item, *contacts;
+
+		/* Put our web response into a xmlnode for easy management */
+		contacts = xmlnode_from_str(url_text, -1);
+
+		if (contacts == NULL) {
+			purple_debug_error("yahoo_aliases","Badly formed XML\n");
+			return;
+		}
+		purple_debug_info("yahoo", "Fetched %i bytes of alias data\n", len);
+
+		/* Loop around and around and around until we have gone through all the received aliases  */
+		for(item = xmlnode_get_child(contacts, "ct"); item; item = xmlnode_get_next_twin(item)) {
+			/* Yahoo replies with two types of contact (ct) record, we are only interested in the alias ones */
+			if ((yid = xmlnode_get_attrib(item, "yi"))) {
+		                /* Grab all the bits of information we can */
+				fn = xmlnode_get_attrib(item,"fn");
+				ln = xmlnode_get_attrib(item,"ln");
+				nn = xmlnode_get_attrib(item,"nn");
+				id = xmlnode_get_attrib(item,"id");
+
+		                /* Yahoo stores first and last names separately, lets put them together into a full name */
+				full_name = g_strstrip(g_strdup_printf("%s %s", (fn != NULL ? fn : "") , (ln != NULL ? ln : "")));
+				nick_name = (nn != NULL ? g_strstrip(g_strdup_printf("%s", nn)) : NULL);
+
+				if (nick_name != NULL)
+					alias = nick_name;   /* If we have a nickname from Yahoo, let's use it */
+				else if (strlen(full_name) != 0)
+					alias = full_name;  /* If no Yahoo nickname, we can use the full_name created above */
+				else
+					alias = NULL;  /* No nickname, first name or last name, then you get no alias !!  */
+
+				/*  Find the local buddy that matches */
+				b = purple_find_buddy(cb->gc->account, yid);
+
+				/*  If we don't find a matching buddy, ignore the alias !!  */
+				if (b != NULL) {
+					/* Create an object that we can attach to the buddies proto_data pointer */
+					struct YahooUser *yu;
+					yu = g_new0(struct YahooUser, 1);
+					yu->id = g_strdup(id);
+					yu->firstname = g_strdup(fn);
+					yu->lastname = g_strdup(ln);
+					yu->nickname = g_strdup(nn);
+					b->proto_data=yu;
+
+					/* Finally, if we received an alias, we better update the buddy list */
+					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) {
+					/* 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);
+					}
+				} else {
+					purple_debug_info("yahoo", "Bizarre, received alias for %s, but they are not on your list...\n", yid);
+				}
+			}
+		}
+		xmlnode_free(contacts);
+		g_free(cb);
+	}
+}
+
+void
+yahoo_fetch_aliases(PurpleConnection *gc)
+{
+	struct yahoo_data *yd = gc->proto_data;
+	struct callback_data *cb;
+	char *url, *request, *webpage, *webaddress, *strtmp;
+	int inttmp;
+
+	/* Using callback_data so I have access to gc in the callback function */
+	cb = g_new0(struct callback_data, 1);
+	cb->gc = gc;
+
+	/*  Build all the info to make the web request */
+	url = g_strdup(YAHOO_ALIAS_FETCH_URL);
+	purple_url_parse(url, &webaddress, &inttmp, &webpage, &strtmp, &strtmp);
+	request = g_strdup_printf("GET /%s HTTP/1.1\r\n"
+				 "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
+				 "Cookie: T=%s; Y=%s\r\n"
+				 "Host: %s\r\n"
+				 "Cache-Control: no-cache\r\n\r\n",
+				 webpage, yd->cookie_t,yd->cookie_y, webaddress);
+
+	/* We have a URL and some header information, let's connect and get some aliases  */
+	purple_util_fetch_url_request(url, FALSE, NULL, TRUE, request, FALSE, yahoo_fetch_aliases_cb, cb);
+
+	g_free(url);
+	g_free(request);
+}
+
+/**************************************************************************
+ * Alias Update Functions
+ **************************************************************************/
+
+static void
+yahoo_update_alias_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,const gchar *url_text, size_t len, const gchar *error_message)
+{
+	xmlnode *node, *result;
+	struct callback_data *cb = user_data;
+
+	result = xmlnode_from_str(url_text, -1);
+
+	purple_debug_info("yahoo", "ID: %s, Return data: %s\n",cb->id, url_text);
+
+	if (result == NULL) {
+		purple_debug_error("yahoo","Alias update faild: Badly formed response\n");
+		return;
+	}
+
+	if ((node = xmlnode_get_child(result, "ct"))) {
+		if (g_ascii_strncasecmp(xmlnode_get_attrib(node, "id"), cb->id, strlen(cb->id))==0)
+			purple_debug_info("yahoo", "Alias update succeeded\n");
+		else
+			purple_debug_error("yahoo", "Alias update failed (Contact record return mismatch)\n");
+	} else {
+		purple_debug_info("yahoo", "Alias update failed (No contact record returned)\n");
+	}
+
+	g_free(cb);
+	xmlnode_free(result);
+}
+
+void
+yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias)
+{
+	struct yahoo_data *yd;
+	struct YahooUser *yu;
+	char *content, *url, *request, *webpage, *webaddress, *strtmp;
+	int inttmp;
+	struct callback_data *cb;
+	PurpleBuddy *buddy;
+   
+	g_return_if_fail(alias!= NULL);
+	g_return_if_fail(who!=NULL);
+	g_return_if_fail(gc!=NULL);
+
+	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) {
+		purple_debug_info("yahoo", "Missing proto_data (get_yahoo_aliases must have failed), bailing out\n");
+		return;
+	}
+
+	yd = gc->proto_data;
+	yu = buddy->proto_data;
+
+	/* Using callback_data so I have access to gc in the callback function */
+	cb = g_new0(struct callback_data, 1);
+	cb->id = g_strdup(yu->id);
+
+	/*  Build all the info to make the web request */
+	url = g_strdup(YAHOO_ALIAS_UPDATE_URL);
+	purple_url_parse(url, &webaddress, &inttmp, &webpage, &strtmp, &strtmp);
+
+	content = g_strdup_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?><ab k=\"%s\" cc=\"1\">\n"
+				  "<ct e=\"1\"  yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n",
+				  gc->account->username, who, yu->id, g_markup_escape_text(alias, strlen(alias)));
+
+	request = g_strdup_printf("POST /%s HTTP/1.1\r\n"
+				  "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5)\r\n"
+				  "Cookie: T=%s; Y=%s\r\n"
+				  "Host: %s\r\n"
+				  "Content-Length: %" G_GSIZE_FORMAT "\r\n"
+				  "Cache-Control: no-cache\r\n\r\n"
+				  "%s",
+				  webpage, yd->cookie_t,yd->cookie_y, webaddress,
+			 	  strlen(content), content);
+
+	/* We have a URL and some header information, let's connect and update the alias  */
+	purple_util_fetch_url_request(url, FALSE, NULL, TRUE, request, FALSE, yahoo_update_alias_cb, cb);
+
+	g_free(content);
+	g_free(url);
+	g_free(request);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/yahoo/yahoo_aliases.h	Wed Aug 08 21:05:47 2007 +0000
@@ -0,0 +1,50 @@
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+
+#include "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "debug.h"
+#include "util.h"
+#include "version.h"
+#include "yahoo.h"
+#include "yahoo_packet.h"
+
+
+/**
+ * The additional protocol specific info attached to each buddy.  We need
+ * to store the unique numeric id number to allow us to push alias changes.
+ */
+struct YahooUser
+{
+    const char *id;             /* The yahoo accountid for this buddy (not YahooID but numeric value) */
+    char *firstname;            /* Storing this information for no real reason, just because */
+    char *lastname;             /* Storing this information for no real reason, just because */
+    char *nickname;             /* Storing this information for no real reason, just because */
+};
+
+void yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias);
+void yahoo_fetch_aliases(PurpleConnection *gc);
--- a/libpurple/protocols/yahoo/yahoo_doodle.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_doodle.c	Wed Aug 08 21:05:47 2007 +0000
@@ -125,47 +125,12 @@
 	 * sessions
 	 */
 
+	yahoo_doodle_command_send_ready(gc, to);
 	yahoo_doodle_command_send_request(gc, to);
-	yahoo_doodle_command_send_ready(gc, to);
 
 }
 
-void yahoo_doodle_process(PurpleConnection *gc, const char *me, const char *from,
-						  const char *command, const char *message)
-{
-	if(!command)
-		return;
-
-	/* Now check to see what sort of Doodle message it is */
-	switch(atoi(command))
-	{
-		case DOODLE_CMD_REQUEST:
-			yahoo_doodle_command_got_request(gc, from);
-			break;
-
-		case DOODLE_CMD_READY:
-			yahoo_doodle_command_got_ready(gc, from);
-			break;
-
-		case DOODLE_CMD_CLEAR:
-			yahoo_doodle_command_got_clear(gc, from);
-			break;
-
-		case DOODLE_CMD_DRAW:
-			yahoo_doodle_command_got_draw(gc, from, message);
-			break;
-
-		case DOODLE_CMD_EXTRA:
-			yahoo_doodle_command_got_extra(gc, from, message);
-			break;
-
-		case DOODLE_CMD_CONFIRM:
-			yahoo_doodle_command_got_confirm(gc, from);
-			break;
-	}
-}
-
-void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from)
+static void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from)
 {
 	PurpleAccount *account;
 	PurpleWhiteboard *wb;
@@ -197,7 +162,7 @@
 
 		purple_whiteboard_create(account, from, DOODLE_STATE_REQUESTED);
 
-		yahoo_doodle_command_send_request(gc, from);
+		yahoo_doodle_command_send_ready(gc, from);
 	}
 
 	/* TODO Might be required to clear the canvas of an existing doodle
@@ -205,12 +170,12 @@
 	 */
 }
 
-void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from)
+static void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from)
 {
 	PurpleAccount *account;
 	PurpleWhiteboard *wb;
 
-	purple_debug_info("yahoo", "doodle: Got Ready (%s)\n", from);
+	purple_debug_info("yahoo", "doodle: Got Ready(%s)\n", from);
 
 	account = purple_connection_get_account(gc);
 
@@ -230,8 +195,7 @@
 
 		yahoo_doodle_command_send_confirm(gc, from);
 	}
-
-	if(wb->state == DOODLE_STATE_ESTABLISHED)
+	else if(wb->state == DOODLE_STATE_ESTABLISHED)
 	{
 		/* TODO Ask whether to save picture too */
 		purple_whiteboard_clear(wb);
@@ -239,16 +203,16 @@
 
 	/* NOTE Not sure about this... I am trying to handle if the remote user
 	 * already thinks we're in a session with them (when their chat message
-	 * contains the doodle;11 imv key)
+	 * contains the doodle imv key)
 	 */
-	if(wb->state == DOODLE_STATE_REQUESTED)
+	else if(wb->state == DOODLE_STATE_REQUESTED)
 	{
 		/* purple_whiteboard_start(wb); */
-		yahoo_doodle_command_send_request(gc, from);
+		yahoo_doodle_command_send_ready(gc, from);
 	}
 }
 
-void yahoo_doodle_command_got_draw(PurpleConnection *gc, const char *from, const char *message)
+static void yahoo_doodle_command_got_draw(PurpleConnection *gc, const char *from, const char *message)
 {
 	PurpleAccount *account;
 	PurpleWhiteboard *wb;
@@ -304,7 +268,8 @@
 	g_list_free(d_list);
 }
 
-void yahoo_doodle_command_got_clear(PurpleConnection *gc, const char *from)
+
+static void yahoo_doodle_command_got_clear(PurpleConnection *gc, const char *from)
 {
 	PurpleAccount *account;
 	PurpleWhiteboard *wb;
@@ -329,7 +294,8 @@
 	}
 }
 
-void
+
+static void
 yahoo_doodle_command_got_extra(PurpleConnection *gc, const char *from, const char *message)
 {
 	purple_debug_info("yahoo", "doodle: Got Extra (%s)\n", from);
@@ -340,7 +306,7 @@
 	yahoo_doodle_command_send_extra(gc, from, DOODLE_EXTRA_NONE);
 }
 
-void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from)
+static void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from)
 {
 	PurpleAccount *account;
 	PurpleWhiteboard *wb;
@@ -361,14 +327,14 @@
 	/* TODO Combine the following IF's? */
 
 	/* Check if we requested a doodle session */
-	if(wb->state == DOODLE_STATE_REQUESTING)
+	/*if(wb->state == DOODLE_STATE_REQUESTING)
 	{
 		wb->state = DOODLE_STATE_ESTABLISHED;
 
 		purple_whiteboard_start(wb);
 
 		yahoo_doodle_command_send_confirm(gc, from);
-	}
+	}*/
 
 	/* Check if we accepted a request for a doodle session */
 	if(wb->state == DOODLE_STATE_REQUESTED)
@@ -395,25 +361,21 @@
 	 */
 	wb = purple_whiteboard_get_session(account, from);
 
-	/* TODO Ask if user wants to save picture before the session is closed */
-
-	/* If this session doesn't exist, don't try and kill it */
 	if(wb == NULL)
 		return;
-	else
-	{
-		purple_whiteboard_destroy(wb);
+
+	/* TODO Ask if user wants to save picture before the session is closed */
 
-		/* yahoo_doodle_command_send_shutdown(gc, from); */
-	}
+	wb->state = DOODLE_STATE_CANCELED;
+	purple_whiteboard_destroy(wb);
 }
 
 static void yahoo_doodle_command_send_generic(const char *type,
 											  PurpleConnection *gc,
 											  const char *to,
 											  const char *message,
-											  const char *thirteen,
-											  const char *sixtythree,
+											  int command,
+											  const char *imv,
 											  const char *sixtyfour)
 {
 	struct yahoo_data *yd;
@@ -428,48 +390,48 @@
 	yahoo_packet_hash_str(pkt, 49,  "IMVIRONMENT");
 	yahoo_packet_hash_str(pkt, 1,    purple_account_get_username(gc->account));
 	yahoo_packet_hash_str(pkt, 14,   message);
-	yahoo_packet_hash_str(pkt, 13,   thirteen);
+	yahoo_packet_hash_int(pkt, 13,   command);
 	yahoo_packet_hash_str(pkt, 5,    to);
-	yahoo_packet_hash_str(pkt, 63,   sixtythree ? sixtythree : "doodle;11");
+	yahoo_packet_hash_str(pkt, 63,   imv ? imv : DOODLE_IMV_KEY);
 	yahoo_packet_hash_str(pkt, 64,   sixtyfour);
 	yahoo_packet_hash_str(pkt, 1002, "1");
 
 	yahoo_packet_send_and_free(pkt, yd);
 }
 
+void yahoo_doodle_command_send_ready(PurpleConnection *gc, const char *to)
+{
+	yahoo_doodle_command_send_generic("Ready", gc, to, "1", DOODLE_CMD_READY, NULL, "1");
+}
+
 void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to)
 {
-	yahoo_doodle_command_send_generic("Request", gc, to, "1", "1", NULL, "1");
-}
-
-void yahoo_doodle_command_send_ready(PurpleConnection *gc, const char *to)
-{
-	yahoo_doodle_command_send_generic("Ready", gc, to, "", "0", NULL, "0");
+	yahoo_doodle_command_send_generic("Request", gc, to, "", DOODLE_CMD_REQUEST, NULL, "0");
 }
 
 void yahoo_doodle_command_send_draw(PurpleConnection *gc, const char *to, const char *message)
 {
-	yahoo_doodle_command_send_generic("Draw", gc, to, message, "3", NULL, "1");
+	yahoo_doodle_command_send_generic("Draw", gc, to, message, DOODLE_CMD_DRAW, NULL, "1");
 }
 
 void yahoo_doodle_command_send_clear(PurpleConnection *gc, const char *to)
 {
-	yahoo_doodle_command_send_generic("Clear", gc, to, " ", "2", NULL, "1");
+	yahoo_doodle_command_send_generic("Clear", gc, to, " ", DOODLE_CMD_CLEAR, NULL, "1");
 }
 
 void yahoo_doodle_command_send_extra(PurpleConnection *gc, const char *to, const char *message)
 {
-	yahoo_doodle_command_send_generic("Extra", gc, to, message, "4", NULL, "1");
+	yahoo_doodle_command_send_generic("Extra", gc, to, message, DOODLE_CMD_EXTRA, NULL, "1");
 }
 
 void yahoo_doodle_command_send_confirm(PurpleConnection *gc, const char *to)
 {
-	yahoo_doodle_command_send_generic("Confirm", gc, to, "1", "5", NULL, "1");
+	yahoo_doodle_command_send_generic("Confirm", gc, to, "1", DOODLE_CMD_CONFIRM, NULL, "1");
 }
 
 void yahoo_doodle_command_send_shutdown(PurpleConnection *gc, const char *to)
 {
-	yahoo_doodle_command_send_generic("Shutdown", gc, to, "", "0", ";0", "0");
+	yahoo_doodle_command_send_generic("Shutdown", gc, to, "", DOODLE_CMD_SHUTDOWN, ";0", "0");
 }
 
 void yahoo_doodle_start(PurpleWhiteboard *wb)
@@ -491,7 +453,7 @@
 
 	/* g_debug_debug("yahoo", "doodle: yahoo_doodle_end()\n"); */
 
-	if (gc)
+	if (gc && wb->state != DOODLE_STATE_CANCELED)
 		yahoo_doodle_command_send_shutdown(gc, wb->who);
 
 	g_free(wb->proto_data);
@@ -530,7 +492,7 @@
 	g_return_if_fail(draw_list != NULL);
 
 	message = yahoo_doodle_build_draw_string(ds, draw_list);
-		yahoo_doodle_command_send_draw(wb->account->gc, wb->who, message);
+	yahoo_doodle_command_send_draw(wb->account->gc, wb->who, message);
 	g_free(message);
 }
 
@@ -604,3 +566,37 @@
 	purple_whiteboard_set_brush(wb, size, color);
 }
 
+void yahoo_doodle_process(PurpleConnection *gc, const char *me, const char *from,
+						  const char *command, const char *message)
+{
+	if(!command)
+		return;
+
+	/* Now check to see what sort of Doodle message it is */
+	switch(atoi(command))
+	{
+		case DOODLE_CMD_REQUEST:
+			yahoo_doodle_command_got_request(gc, from);
+			break;
+
+		case DOODLE_CMD_READY:
+			yahoo_doodle_command_got_ready(gc, from);
+			break;
+
+		case DOODLE_CMD_CLEAR:
+			yahoo_doodle_command_got_clear(gc, from);
+			break;
+
+		case DOODLE_CMD_DRAW:
+			yahoo_doodle_command_got_draw(gc, from, message);
+			break;
+
+		case DOODLE_CMD_EXTRA:
+			yahoo_doodle_command_got_extra(gc, from, message);
+			break;
+
+		case DOODLE_CMD_CONFIRM:
+			yahoo_doodle_command_got_confirm(gc, from);
+			break;
+	}
+}
--- a/libpurple/protocols/yahoo/yahoo_doodle.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_doodle.h	Wed Aug 08 21:05:47 2007 +0000
@@ -31,17 +31,19 @@
 #include "whiteboard.h"
 #include "cmds.h"
 
+#define DOODLE_IMV_KEY "doodle;103"
+
 /******************************************************************************
  * Defines
  *****************************************************************************/
 /* Doodle communication commands */
 /* TODO: Should be an enum. */
-#define DOODLE_CMD_REQUEST  0
-#define DOODLE_CMD_READY    1
-#define DOODLE_CMD_CLEAR    2
-#define DOODLE_CMD_DRAW     3
-#define DOODLE_CMD_EXTRA    4
-#define DOODLE_CMD_CONFIRM  5
+#define DOODLE_CMD_REQUEST	0
+#define DOODLE_CMD_CLEAR	1
+#define DOODLE_CMD_DRAW		2
+#define DOODLE_CMD_EXTRA	3
+#define DOODLE_CMD_READY	4
+#define DOODLE_CMD_CONFIRM	5
 /* Doodle communication command for shutting down (also 0) */
 #define DOODLE_CMD_SHUTDOWN 0
 
@@ -54,6 +56,7 @@
 #define DOODLE_STATE_REQUESTING  0
 #define DOODLE_STATE_REQUESTED   1
 #define DOODLE_STATE_ESTABLISHED 2
+#define DOODLE_STATE_CANCELED    3
 
 /* Doodle canvas dimensions */
 #define DOODLE_CANVAS_WIDTH  368
@@ -104,12 +107,6 @@
 						  const char *command, const char *message);
 void yahoo_doodle_initiate(PurpleConnection *gc, const char *to);
 
-void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from);
-void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from);
-void yahoo_doodle_command_got_draw(PurpleConnection *gc, const char *from, const char *message);
-void yahoo_doodle_command_got_clear(PurpleConnection *gc, const char *from);
-void yahoo_doodle_command_got_extra(PurpleConnection *gc, const char *from, const char *message);
-void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from);
 void yahoo_doodle_command_got_shutdown(PurpleConnection *gc, const char *from);
 
 void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to);
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Wed Aug 08 21:05:47 2007 +0000
@@ -484,7 +484,7 @@
 	if(service != NULL && imv != NULL && !strcmp(service, "IMVIRONMENT"))
 	{
 		/* Check for a Doodle packet and handle it accordingly */
-		if(!strcmp(imv, "doodle;11"))
+		if(strstr(imv, "doodle;") != NULL)
 			yahoo_doodle_process(gc, me, from, command, message);
 
 		/* If an IMVIRONMENT packet comes without a specific imviroment name */
@@ -622,12 +622,12 @@
 {
 	PurpleXfer *xfer;
 	struct yahoo_xfer_data *xfer_data;
-	
+
 	g_return_val_if_fail(who != NULL, NULL);
-	
+
 	xfer_data = g_new0(struct yahoo_xfer_data, 1);
 	xfer_data->gc = gc;
-	
+
 	/* Build the file transfer handle. */
 	xfer = purple_xfer_new(gc->account, PURPLE_XFER_SEND, who);
 	if (xfer)
--- a/libpurple/protocols/yahoo/yahoo_packet.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.h	Wed Aug 08 21:05:47 2007 +0000
@@ -117,7 +117,7 @@
 };
 
 #define YAHOO_WEBMESSENGER_PROTO_VER 0x0065
-#define YAHOO_PROTO_VER 0x000c
+#define YAHOO_PROTO_VER 0x000f
 #define YAHOO_PROTO_VER_JAPAN 0x000c
 
 #define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4)
--- a/libpurple/stringref.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/stringref.h	Wed Aug 08 21:05:47 2007 +0000
@@ -1,3 +1,5 @@
+/* TODO: Can we just replace this whole thing with a GCache */
+
 /**
  * @file stringref.h Reference-counted immutable strings
  * @ingroup core
--- a/libpurple/stun.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/libpurple/stun.c	Wed Aug 08 21:05:47 2007 +0000
@@ -429,5 +429,4 @@
 
 void purple_stun_init() {
 	purple_prefs_add_string("/purple/network/stun_server", "");
-	purple_stun_discover(NULL);
 }
--- a/pidgin.spec.in	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin.spec.in	Wed Aug 08 21:05:47 2007 +0000
@@ -244,6 +244,7 @@
 
 # Delete files that we don't want to put in any of the RPMs
 rm -f $RPM_BUILD_ROOT%{_libdir}/finch/*.la
+rm -f $RPM_BUILD_ROOT%{_libdir}/gnt/*.la
 rm -f $RPM_BUILD_ROOT%{_libdir}/pidgin/*.la
 rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/*.la
 rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/liboscar.so
@@ -297,8 +298,6 @@
 
 # files -f file can only take one filename :(
 cat %{name}.lang >> %{name}-%{version}-purpleplugins
-cat %{name}.lang >> %{name}-%{version}-pidginplugins
-cat %{name}.lang >> %{name}-%{version}-finchplugins
 
 %clean
 rm -rf %{buildroot}
@@ -442,6 +441,8 @@
 %doc %{_mandir}/man1/finch.*
 %{_bindir}/finch
 %{_libdir}/libgnt.so.*
+%{_libdir}/gnt/irssi.so
+%{_libdir}/gnt/s.so
 
 %files -n finch-devel
 %defattr(-, root, root)
--- a/pidgin/gtkaccount.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkaccount.c	Wed Aug 08 21:05:47 2007 +0000
@@ -1162,6 +1162,11 @@
 	{
 		const char *screenname;
 
+		if (purple_accounts_get_all() == NULL) {
+			/* We're adding our first account.  Be polite and show the buddy list */
+			purple_blist_set_visible(TRUE);
+		}
+
 		screenname = gtk_entry_get_text(GTK_ENTRY(dialog->screenname_entry));
 		account = purple_account_new(screenname, dialog->protocol_id);
 		new = TRUE;
--- a/pidgin/gtkblist.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkblist.c	Wed Aug 08 21:05:47 2007 +0000
@@ -3338,7 +3338,7 @@
 
 	presence = purple_buddy_get_presence(b);
 
-	if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
+	if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons") && aliased)
 	{
 		if (!selected && purple_presence_is_idle(presence))
 		{
--- a/pidgin/gtkblist.h	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkblist.h	Wed Aug 08 21:05:47 2007 +0000
@@ -365,7 +365,7 @@
  *
  * @param buddy The buddy to return markup from
  * @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
+ * @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
  */
 gchar *pidgin_blist_get_name_markup(PurpleBuddy *buddy, gboolean selected, gboolean aliased);
--- a/pidgin/gtkcellrendererexpander.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkcellrendererexpander.c	Wed Aug 08 21:05:47 2007 +0000
@@ -285,5 +285,5 @@
 	else
 		gtk_tree_view_expand_row(GTK_TREE_VIEW(widget),path,FALSE);
 	gtk_tree_path_free(path);
-	return TRUE;
+	return FALSE;
 }
--- a/pidgin/gtkconv.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkconv.c	Wed Aug 08 21:05:47 2007 +0000
@@ -187,6 +187,9 @@
 static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv);
 static gboolean infopane_press_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *conv);
 
+static void pidgin_conv_set_position_size(PidginWindow *win, int x, int y,
+		int width, int height);
+
 static GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) {
 	static GdkColor col;
 	GtkStyle *style = gtk_widget_get_style(gtkconv->imhtml);
@@ -210,8 +213,8 @@
  * Callbacks
  **************************************************************************/
 
-static gint
-close_conv_cb(GtkWidget *w, PidginConversation *gtkconv)
+static gboolean
+close_conv_cb(GtkWidget *w, GdkEventButton *event, PidginConversation *gtkconv)
 {
 	GList *list = g_list_copy(gtkconv->convs);
 
@@ -1328,7 +1331,7 @@
 {
 	PidginWindow *win = data;
 
-	close_conv_cb(NULL, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win)));
+	close_conv_cb(NULL, NULL, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win)));
 }
 
 static void
@@ -5290,7 +5293,7 @@
 		gtk_font_options_all |= GTK_IMHTML_USE_SMOOTHSCROLLING;
 
 	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>", gtk_font_options_all);
+		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
 
 	/* First message in a conversation. */
 	if (gtkconv->newday == 0)
@@ -5478,7 +5481,7 @@
 				   color, sml_attrib ? sml_attrib : "", mdate, str);
 		}
 
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all);
+		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
 
 		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
 		    !(flags & PURPLE_MESSAGE_SEND)) {
@@ -6253,13 +6256,15 @@
 			(fields & PIDGIN_CONV_SET_TITLE) ||
     			(fields & PIDGIN_CONV_TOPIC))
 	{
-		char *title;
+		char *title, *truncate = NULL, truncchar = '\0';
 		PurpleConvIm *im = NULL;
 		PurpleAccount *account = purple_conversation_get_account(conv);
+	 	PurpleBuddy *buddy = NULL;
+		PurplePresence *p = NULL;
 		char *markup = NULL;
 		AtkObject *accessibility_obj;
 		/* I think this is a little longer than it needs to be but I'm lazy. */
-		char style[51];
+		char *style, *status_style;
 
 		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
 			im = PURPLE_CONV_IM(conv);
@@ -6272,12 +6277,20 @@
 		else
 			title = g_strdup(purple_conversation_get_title(conv));
 
+		if ((truncate = strchr(title, ' ')) || 
+		    (truncate = strchr(title, '@'))) {
+			truncchar = *truncate;
+			*truncate = '\0';
+		}
+
 		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
-		 	PurpleBuddy *buddy = purple_find_buddy(account, conv->name);
-			if (buddy)
+			buddy = purple_find_buddy(account, conv->name);
+			if (buddy) {
+				p = purple_buddy_get_presence(buddy);
 				markup = pidgin_blist_get_name_markup(buddy, FALSE, FALSE);
-			else
+			} else {
 				markup = title;
+			}
 		} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
 			PurpleConvChat *chat = PURPLE_CONV_CHAT(conv);
 			const char *topic = purple_conv_chat_get_topic(chat);
@@ -6293,54 +6306,56 @@
 		if (title != markup)
 			g_free(markup);
 
-		*style = '\0';
-
 		if (!GTK_WIDGET_REALIZED(gtkconv->tab_label))
 			gtk_widget_realize(gtkconv->tab_label);
 
 		accessibility_obj = gtk_widget_get_accessible(gtkconv->tab_cont);
 		if (im != NULL &&
-		    purple_conv_im_get_typing_state(im) == PURPLE_TYPING)
-		{
+		    purple_conv_im_get_typing_state(im) == PURPLE_TYPING) {
 			atk_object_set_description(accessibility_obj, _("Typing"));
-			strncpy(style, "color=\"#4e9a06\"", sizeof(style));
-		}
-		else if (im != NULL &&
-		         purple_conv_im_get_typing_state(im) == PURPLE_TYPED)
-		{
+			style = "color=\"#4e9a06\"";
+		} else if (im != NULL &&
+		         purple_conv_im_get_typing_state(im) == PURPLE_TYPED) {
 			atk_object_set_description(accessibility_obj, _("Stopped Typing"));
-			strncpy(style, "color=\"#c4a000\"", sizeof(style));
-		}
-		else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK)
-		{
+			style = "color=\"#c4a000\"";
+		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK)	{
 			atk_object_set_description(accessibility_obj, _("Nick Said"));
-			strncpy(style, "color=\"#204a87\" style=\"italic\" weight=\"bold\"", sizeof(style));
-		}
-		else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT)
-		{
+			style = "color=\"#204a87\" weight=\"bold\"";
+		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT)	{
 			atk_object_set_description(accessibility_obj, _("Unread Messages"));
-			strncpy(style, "color=\"#cc0000\" weight=\"bold\"", sizeof(style));
+			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\"";
+		} else {
+			style = "";
 		}
-		else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT)
-		{
-			atk_object_set_description(accessibility_obj, _("New Event"));
-			strncpy(style, "color=\"#888a85\" style=\"italic\"", sizeof(style));
+		
+		if (p && purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE)) {
+			status_style = "strikethrough='true'";
+		} else if (p && !purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AVAILABLE) &&
+			 !purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE)) {
+			status_style = "style='italic'";
+		} else {
+			status_style = "";
 		}
 
-		if (*style != '\0')
+		if (*style != '\0' || *status_style != '\0')
 		{
 			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, status_style, 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 (truncate)
+			*truncate = truncchar;
 
 		if (pidgin_conv_window_is_active_conversation(conv))
 			update_typing_icon(gtkconv);
@@ -6584,8 +6599,15 @@
 
 	event = gtk_event_box_new();
 	gtk_container_add(GTK_CONTAINER(gtkconv->u.im->icon_container), event);
+	gtk_event_box_set_visible_window(GTK_EVENT_BOX(event), FALSE);
+	gtk_widget_add_events(event,
+                              GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
 	g_signal_connect(G_OBJECT(event), "button-press-event",
 					 G_CALLBACK(icon_menu), gtkconv);
+        g_signal_connect(G_OBJECT(event), "motion-notify-event",
+                         G_CALLBACK(pidgin_conv_motion_cb), gtkconv);
+        g_signal_connect(G_OBJECT(event), "leave-notify-event",
+                         G_CALLBACK(pidgin_conv_leave_cb), gtkconv);
 	gtk_widget_show(event);
 
 	gtkconv->u.im->icon = gtk_image_new_from_pixbuf(scale);
@@ -7151,30 +7173,37 @@
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/tabs", TRUE);
 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/tab_side", GTK_POS_TOP);
 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/scrollback_lines", 4000);
-	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/x", 0);
-	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/y", 0);
 
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/use_theme_font", TRUE);
 	purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/custom_font", "");
 
 	/* Conversations -> Chat */
 	purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations/chat");
-	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_width", 410);
-	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_height", 160);
 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height", 50);
 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/userlist_width", 80);
+	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/x", 0);
+	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/y", 0);
+	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/width", 0);
+	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/chat/height", 0);
+
 	/* Conversations -> IM */
 	purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations/im");
+	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/x", 0);
+	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/y", 0);
+	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/width", 0);
+	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/height", 0);
 
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons", TRUE);
 
-	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width", 410);
-	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height", 160);
 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height", 50);
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", TRUE);
 
 	purple_prefs_add_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "never");
 
+#ifdef _WIN32
+	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/win32/minimize_new_convs", FALSE);
+#endif
+
 	/* Connect callbacks. */
 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/close_on_tabs",
 								close_on_tabs_pref_cb, NULL);
@@ -7467,7 +7496,7 @@
 							_("Confirm close"),
 							GTK_WINDOW(gtkwin->window), GTK_DIALOG_MODAL,
 							GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-							PIDGIN_STOCK_CLOSE_TABS, GTK_RESPONSE_OK, NULL);
+							GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL);
 
 	gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog),
 	                                GTK_RESPONSE_OK);
@@ -7759,7 +7788,7 @@
 			return FALSE;
 
 		gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, tab_clicked);
-		close_conv_cb(NULL, gtkconv);
+		close_conv_cb(NULL, NULL, gtkconv);
 		return TRUE;
 	}
 
@@ -8018,7 +8047,7 @@
 
 		if (gconv != gtkconv)
 		{
-			close_conv_cb(NULL, gconv);
+			close_conv_cb(NULL, NULL, gconv);
 		}
 	}
 }
@@ -8030,7 +8059,7 @@
 	gtkconv = g_object_get_data(menu, "clicked_tab");
 
 	if (gtkconv)
-		close_conv_cb(NULL, gtkconv);
+		close_conv_cb(NULL, NULL, gtkconv);
 }
 
 static gboolean
@@ -8309,10 +8338,8 @@
 		return FALSE;
 	
 	/* don't save if nothing changed */
-	if (x == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/x") &&
-	    y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/y") &&
-	    event->width  == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width") &&
-	    event->height == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height"))
+	if (x == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/x") &&
+			y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/y"))
 		return FALSE; /* carry on normally */
 		
 	/* don't save off-screen positioning */
@@ -8322,9 +8349,9 @@
 	    y > gdk_screen_height())
 		return FALSE; /* carry on normally */
 
-        /* store the position */
-        purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/x", x);
-	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/y", y);
+	/* store the position */
+	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/x", x);
+	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/y", y);
 	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/width",  event->width);
 	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/height", event->height);
 
@@ -8334,35 +8361,38 @@
 }
 
 static void
-pidgin_conv_restore_position(PidginWindow *win) {
-	int conv_x, conv_y, conv_width, conv_height;
-
-	conv_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/width");
-
+pidgin_conv_set_position_size(PidginWindow *win, int conv_x, int conv_y,
+		int conv_width, int conv_height)
+{
 	 /* if the window exists, is hidden, we're saving positions, and the
           * position is sane... */
-        if (win && win->window &&
-                !GTK_WIDGET_VISIBLE(win->window) && conv_width != 0) {
-
-                conv_x      = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/x");
-                conv_y      = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/y");
-                conv_height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/height");
-
-	       /* ...check position is on screen... */
-                if (conv_x >= gdk_screen_width())
-                        conv_x = gdk_screen_width() - 100;
-                else if (conv_x + conv_width < 0)
-                        conv_x = 100;
-
-                if (conv_y >= gdk_screen_height())
-                        conv_y = gdk_screen_height() - 100;
-                else if (conv_y + conv_height < 0)
-                        conv_y = 100;
-
-                /* ...and move it back. */
-                gtk_window_move(GTK_WINDOW(win->window), conv_x, conv_y);
-                gtk_window_resize(GTK_WINDOW(win->window), conv_width, conv_height);
-        }
+	if (win && win->window &&
+			!GTK_WIDGET_VISIBLE(win->window) && conv_width != 0) {
+
+		/* ...check position is on screen... */
+		if (conv_x >= gdk_screen_width())
+			conv_x = gdk_screen_width() - 100;
+		else if (conv_x + conv_width < 0)
+			conv_x = 100;
+
+		if (conv_y >= gdk_screen_height())
+			conv_y = gdk_screen_height() - 100;
+		else if (conv_y + conv_height < 0)
+			conv_y = 100;
+
+		/* ...and move it back. */
+		gtk_window_move(GTK_WINDOW(win->window), conv_x, conv_y);
+		gtk_window_resize(GTK_WINDOW(win->window), conv_width, conv_height);
+	}
+}
+
+static void
+pidgin_conv_restore_position(PidginWindow *win) {
+	pidgin_conv_set_position_size(win,
+			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/x"),
+			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/y"),
+			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/width"),
+			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/height"));
 }
 
 PidginWindow *
@@ -8387,8 +8417,6 @@
 
 	g_signal_connect(G_OBJECT(win->window), "delete_event",
 	                 G_CALLBACK(close_win_cb), win);
-	g_signal_connect(G_OBJECT(win->window), "configure_event", 
-			 G_CALLBACK(gtk_conv_configure_cb), NULL);
 	g_signal_connect(G_OBJECT(win->window), "focus_in_event",
 	                 G_CALLBACK(focus_win_cb), win);
 
@@ -8526,7 +8554,6 @@
 	GtkWidget *close_image;
 	PurpleConversationType conv_type;
 	const gchar *tmp_lab;
-	gint close_button_width, close_button_height, focus_width, focus_pad;
 
 	conv_type = purple_conversation_get_type(conv);
 
@@ -8538,28 +8565,16 @@
 
 
 	/* Close button. */
-	gtkconv->close = gtk_button_new();
-	gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &close_button_width, &close_button_height);
-	if (gtk_check_version(2, 4, 2) == NULL) {
-		/* Need to account for extra padding around the gtkbutton */
-		gtk_widget_style_get(GTK_WIDGET(gtkconv->close),
-		                     "focus-line-width", &focus_width,
-		                     "focus-padding", &focus_pad,
-		                     NULL);
-		close_button_width += (focus_width + focus_pad) * 2;
-		close_button_height += (focus_width + focus_pad) * 2;
-	}
-	gtk_widget_set_size_request(GTK_WIDGET(gtkconv->close),
-	                            close_button_width, close_button_height);
-
-	gtk_button_set_relief(GTK_BUTTON(gtkconv->close), GTK_RELIEF_NONE);
-	close_image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+	gtkconv->close = gtk_event_box_new();
+	gtk_event_box_set_visible_window(GTK_EVENT_BOX(gtkconv->close), FALSE);
+	close_image = gtk_label_new(NULL);
+	gtk_label_set_markup(GTK_LABEL(close_image),"<b>×</b>");
 	gtk_widget_show(close_image);
 	gtk_container_add(GTK_CONTAINER(gtkconv->close), close_image);
 	gtk_tooltips_set_tip(gtkconv->tooltips, gtkconv->close,
 	                     _("Close conversation"), NULL);
 
-	g_signal_connect(G_OBJECT(gtkconv->close), "clicked",
+	g_signal_connect(G_OBJECT(gtkconv->close), "button-press-event",
 	                 G_CALLBACK(close_conv_cb), gtkconv);
 
 #if !GTK_CHECK_VERSION(2,6,0)
@@ -8591,6 +8606,7 @@
 	gtk_misc_set_alignment(GTK_MISC(gtkconv->menu_label), 0, 0);
 
 	gtk_widget_show(gtkconv->menu_tabby);
+	gtk_widget_set_size_request(gtkconv->menu_tabby, 0, -1);
 
 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
 		pidgin_conv_update_buddy_icon(conv);
@@ -8650,8 +8666,8 @@
 			MIN(g_utf8_strlen(gtk_label_get_text(GTK_LABEL(gtkconv->tab_label)), -1), 12)
 		);
 	}
-	if (angle)
-		gtk_label_set_angle(GTK_LABEL(gtkconv->tab_label), angle);
+
+	gtk_label_set_angle(GTK_LABEL(gtkconv->tab_label), angle);
 #endif
 
 #if 0
@@ -8707,7 +8723,7 @@
 					   TRUE, GTK_PACK_START);
 
 	/* show the widgets */
-	gtk_widget_show(gtkconv->icon);
+/*	gtk_widget_show(gtkconv->icon); */
 	gtk_widget_show(gtkconv->tab_label);
 	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/close_on_tabs"))
 		gtk_widget_show(gtkconv->close);
@@ -8914,6 +8930,9 @@
 	if (win == NULL) {
 		win = pidgin_conv_window_new();
 
+		g_signal_connect(G_OBJECT(win->window), "configure_event", 
+				G_CALLBACK(gtk_conv_configure_cb), NULL);
+
 		pidgin_conv_window_add_gtkconv(win, conv);
 		pidgin_conv_window_show(win);
 	} else {
@@ -8922,6 +8941,53 @@
 }
 
 /* This one places conversations in the last made window of the same type. */
+static gboolean
+conv_placement_last_created_win_type_configured_cb(GtkWidget *w,
+		GdkEventConfigure *event, PidginConversation *conv)
+{
+	int x, y;	
+	PurpleConversationType type = purple_conversation_get_type(conv->active_conv);
+	GList *all;
+
+	if (GTK_WIDGET_VISIBLE(w))
+		gtk_window_get_position(GTK_WINDOW(w), &x, &y);
+	else
+		return FALSE; /* carry on normally */
+
+	/* Workaround for GTK+ bug # 169811 - "configure_event" is fired
+	* when the window is being maximized */
+	if (gdk_window_get_state(w->window) & GDK_WINDOW_STATE_MAXIMIZED)
+		return FALSE;
+	
+	/* don't save off-screen positioning */
+	if (x + event->width < 0 ||
+	    y + event->height < 0 ||
+	    x > gdk_screen_width() ||
+	    y > gdk_screen_height())
+		return FALSE; /* carry on normally */
+
+	for (all = conv->convs; all != NULL; all = all->next) {
+		if (type != purple_conversation_get_type(all->data)) {
+			/* this window has different types of conversation, don't save */
+			return FALSE;
+		}
+	}
+
+	if (type == PURPLE_CONV_TYPE_IM) {
+		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/x", x);
+		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/y", y);
+		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/width",  event->width);
+		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/height", event->height);
+	} else if (type == PURPLE_CONV_TYPE_CHAT) {
+		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/x", x);
+		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/y", y);
+		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/width",  event->width);
+		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/chat/height", event->height);
+	}
+
+	return FALSE;
+}
+
 static void
 conv_placement_last_created_win_type(PidginConversation *conv)
 {
@@ -8932,8 +8998,26 @@
 	if (win == NULL) {
 		win = pidgin_conv_window_new();
 
+		if (PURPLE_CONV_TYPE_IM == purple_conversation_get_type(conv->active_conv) ||
+				purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/width") == 0) {
+			pidgin_conv_set_position_size(win,
+				purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/x"),
+				purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/y"),
+				purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/width"),
+				purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/height"));
+		} else if (PURPLE_CONV_TYPE_CHAT == purple_conversation_get_type(conv->active_conv)) {
+			pidgin_conv_set_position_size(win,
+				purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/x"),
+				purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/y"),
+				purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/width"),
+				purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/height"));
+		}
+				
 		pidgin_conv_window_add_gtkconv(win, conv);
 		pidgin_conv_window_show(win);
+
+		g_signal_connect(G_OBJECT(win->window), "configure_event", 
+				G_CALLBACK(conv_placement_last_created_win_type_configured_cb), conv);
 	} else
 		pidgin_conv_window_add_gtkconv(win, conv);
 }
@@ -8945,6 +9029,8 @@
 	PidginWindow *win;
 
 	win = pidgin_conv_window_new();
+	g_signal_connect(G_OBJECT(win->window), "configure_event", 
+			G_CALLBACK(gtk_conv_configure_cb), NULL);
 
 	pidgin_conv_window_add_gtkconv(win, conv);
 
--- a/pidgin/gtkdebug.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkdebug.c	Wed Aug 08 21:05:47 2007 +0000
@@ -183,7 +183,7 @@
 	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(win->find)->vbox),
 					  hbox);
 	img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
-								   GTK_ICON_SIZE_DIALOG);
+				       gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
 	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
 
 	gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
--- a/pidgin/gtkdialogs.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkdialogs.c	Wed Aug 08 21:05:47 2007 +0000
@@ -75,7 +75,6 @@
 	{"Ka-Hing Cheung",				N_("developer"), NULL},
 	{"Sadrul Habib Chowdhury",		N_("developer"), NULL},
 	{"Mark 'KingAnt' Doliner",		N_("developer"), NULL},
-	{"Christian 'ChipX86' Hammond",	N_("developer & webmaster"), NULL},
 	{"Casey Harkins",               N_("developer"),   NULL},
 	{"Gary 'grim' Kramlich",		N_("developer"), NULL},
 	{"Richard 'rlaager' Laager",	N_("developer"), NULL},
@@ -95,11 +94,7 @@
 /* Order: Alphabetical by Last Name */
 static struct developer patch_writers[] = {
 	{"John 'rekkanoryo' Bailey",	NULL,	NULL},
-	{"Felipe 'shx' Contreras",		NULL,	NULL},
-	{"Decklin Foster",				NULL,	NULL},
 	{"Peter 'Bleeter' Lawler",      NULL,   NULL},
-	{"Robert 'Robot101' McQueen",	NULL,	NULL},
-	{"Benjamin Miller",				NULL,	NULL},
 	{"Kevin 'SimGuy' Stange",		NULL,	NULL},
 	{NULL, NULL, NULL}
 };
@@ -110,6 +105,7 @@
 	{"Jim Duchek",			N_("maintainer"), "jim@linuxpimps.com"},
 	{"Rob Flynn",			N_("maintainer"), NULL},
 	{"Adam Fritzler",		N_("libfaim maintainer"), NULL},
+	{"Christian 'ChipX86' Hammond",	N_("developer & webmaster"), NULL},
 	/* If "lazy bum" translates literally into a serious insult, use something else or omit it. */
 	{"Syd Logan",			N_("hacker and designated driver [lazy bum]"), NULL},
 	{"Jim Seymour",			N_("XMPP developer"), NULL},
@@ -118,6 +114,15 @@
 	{NULL, NULL, NULL}
 };
 
+/* Order: Alphabetical by Last Name */
+static struct developer retired_patch_writers[] = {
+	{"Felipe 'shx' Contreras",		NULL,	NULL},
+	{"Decklin Foster",				NULL,	NULL},
+	{"Robert 'Robot101' McQueen",	NULL,	NULL},
+	{"Benjamin Miller",				NULL,	NULL},
+	{NULL, NULL, NULL}
+};
+
 /* Order: Code, then Alphabetical by Last Name */
 static struct translator current_translators[] = {
 	{N_("Afrikaans"),           "af", "Friedel Wolff", "friedel@translate.org.za"},
@@ -432,6 +437,21 @@
 	}
 	g_string_append(str, "<BR/>");
 
+	/* Retired Crazy Patch Writers */
+	g_string_append_printf(str, "<FONT SIZE=\"4\">%s:</FONT><BR/>",
+						   _("Retired Crazy Patch Writers"));
+	for (i = 0; retired_patch_writers[i].name != NULL; i++) {
+		if (retired_patch_writers[i].email != NULL) {
+			g_string_append_printf(str, "  %s &lt;<a href=\"mailto:%s\">%s</a>&gt;<br/>",
+					retired_patch_writers[i].name,
+					retired_patch_writers[i].email, patch_writers[i].email);
+		} else {
+			g_string_append_printf(str, "  %s<br/>",
+					retired_patch_writers[i].name);
+		}
+	}
+	g_string_append(str, "<BR/>");
+
 	/* Artists */
         g_string_append_printf(str, "<FONT SIZE=\"4\">%s:</FONT><BR/>",
                                                    _("Artists"));
--- a/pidgin/gtkdocklet.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkdocklet.c	Wed Aug 08 21:05:47 2007 +0000
@@ -121,7 +121,7 @@
 	/* determine if any ims have unseen messages */
 	convs = get_pending_list(DOCKLET_TOOLTIP_LINE_LIMIT);
 
-	if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/docklet/show"), "pending")) {
+	if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/docklet/show"), "always")) {
 		if (convs && ui_ops->create && !visible) {
 			g_list_free(convs);
 			ui_ops->create();
--- a/pidgin/gtkimhtml.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkimhtml.c	Wed Aug 08 21:05:47 2007 +0000
@@ -1053,7 +1053,7 @@
 	if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml)))
 		return;
 
-	if (selection_data->length < 0) {
+	if (imhtml->wbfo || selection_data->length < 0) {
 		gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml);
 		return;
 	} else {
@@ -2270,10 +2270,11 @@
 	GtkIMHtml *imhtml = data;
 	GtkAdjustment *adj = GTK_TEXT_VIEW(imhtml)->vadjustment;
 	gdouble max_val = adj->upper - adj->page_size;
+	gdouble scroll_val = gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3);
 
 	g_return_val_if_fail(imhtml->scroll_time != NULL, FALSE);
 
-	if (g_timer_elapsed(imhtml->scroll_time, NULL) > MAX_SCROLL_TIME) {
+	if (g_timer_elapsed(imhtml->scroll_time, NULL) > MAX_SCROLL_TIME || scroll_val >= max_val) {
 		/* time's up. jump to the end and kill the timer */
 		gtk_adjustment_set_value(adj, max_val);
 		g_timer_destroy(imhtml->scroll_time);
@@ -2282,7 +2283,7 @@
 	}
 
 	/* scroll by 1/3rd the remaining distance */
-	gtk_adjustment_set_value(adj, gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3));
+	gtk_adjustment_set_value(adj, scroll_val);
 	return TRUE;
 }
 
--- a/pidgin/gtkimhtmltoolbar.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Wed Aug 08 21:05:47 2007 +0000
@@ -815,6 +815,9 @@
 	gboolean bold, italic, underline;
 	char *tmp;
 	char *tmp2;
+	GtkLabel *label = g_object_get_data(G_OBJECT(toolbar), "font_label");
+
+	gtk_label_set_label(label, _("_Font"));
 
 	gtk_imhtml_get_current_format(GTK_IMHTML(toolbar->imhtml),
 								  &bold, &italic, &underline);
@@ -822,7 +825,6 @@
 	if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->bold)) != bold)
 		toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->bold), bold,
 									   toolbar);
-
 	if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->italic)) != italic)
 		toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->italic), italic,
 									   toolbar);
@@ -835,20 +837,57 @@
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smaller_size), FALSE);
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->larger_size), FALSE);
 
+	if (bold) {
+		gchar *markup = g_strdup_printf("<b>%s</b>",
+				gtk_label_get_label(label));
+		gtk_label_set_markup_with_mnemonic(label, markup);
+		g_free(markup);
+	}
+	if (italic) {
+		gchar *markup = g_strdup_printf("<i>%s</i>",
+				gtk_label_get_label(label));
+		gtk_label_set_markup_with_mnemonic(label, markup);
+		g_free(markup);
+	}
+	if (underline) {
+		gchar *markup = g_strdup_printf("<u>%s</u>",
+				gtk_label_get_label(label));
+		gtk_label_set_markup_with_mnemonic(label, markup);
+		g_free(markup);
+	}
+
 	tmp = gtk_imhtml_get_current_fontface(GTK_IMHTML(toolbar->imhtml));
 	toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->font),
 								   (tmp != NULL), toolbar);
+	if (tmp != NULL) {
+		gchar *markup = g_strdup_printf("<span font_desc=\"%s\">%s</span>",
+				tmp, gtk_label_get_label(label));
+		gtk_label_set_markup_with_mnemonic(label, markup);
+		g_free(markup);
+	}
 	g_free(tmp);
 
 	tmp = gtk_imhtml_get_current_forecolor(GTK_IMHTML(toolbar->imhtml));
 	toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->fgcolor),
 								   (tmp != NULL), toolbar);
+	if (tmp != NULL) {
+		gchar *markup = g_strdup_printf("<span foreground=\"%s\">%s</span>",
+				tmp, gtk_label_get_label(label));
+		gtk_label_set_markup_with_mnemonic(label, markup);
+		g_free(markup);
+	}
 	g_free(tmp);
 
 	tmp = gtk_imhtml_get_current_backcolor(GTK_IMHTML(toolbar->imhtml));
 	tmp2 = gtk_imhtml_get_current_background(GTK_IMHTML(toolbar->imhtml));
 	toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->bgcolor),
 								   (tmp != NULL || tmp2 != NULL), toolbar);
+	if (tmp != NULL) {
+		gchar *markup = g_strdup_printf("<span background=\"%s\">%s</span>",
+				tmp, gtk_label_get_label(label));
+		gtk_label_set_markup_with_mnemonic(label, markup);
+		g_free(markup);
+	}
 	g_free(tmp);
 	g_free(tmp2);
 }
@@ -900,7 +939,7 @@
 		*y -= widget->allocation.height;
 }
 
-static void pidgin_menu_clicked(GtkWidget *button, GtkMenu *menu)
+static void pidgin_menu_clicked(GtkWidget *button, GdkEventButton *event, GtkMenu *menu)
 {
 	gtk_widget_show_all(GTK_WIDGET(menu));
 	gtk_menu_popup(menu, NULL, NULL, menu_position_func, button, 0, gtk_get_current_event_time());
@@ -920,6 +959,7 @@
 gtk_imhtmltoolbar_finalize (GObject *object)
 {
 	GtkIMHtmlToolbar *toolbar = GTK_IMHTMLTOOLBAR(object);
+	GtkWidget *menu;
 
 	if (toolbar->image_dialog != NULL)
 	{
@@ -944,6 +984,13 @@
 	free(toolbar->sml);
 	gtk_object_sink(GTK_OBJECT(toolbar->tooltips));
 
+	menu = g_object_get_data(object, "font_menu");
+	if (menu)
+		gtk_widget_destroy(menu);
+	menu = g_object_get_data(object, "insert_menu");
+	if (menu)
+		gtk_widget_destroy(menu);
+
 	G_OBJECT_CLASS(parent_class)->finalize (object);
 }
 
@@ -1027,6 +1074,12 @@
 	g_signal_connect(G_OBJECT(button), "clicked",
 			 G_CALLBACK(insert_smiley_cb), toolbar);
 	toolbar->smiley = button;
+
+	/* Reset formatting */
+	button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_SMILEY);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(clear_formatting_cb), toolbar);
+	toolbar->clear = button;
 }
 
 static void
@@ -1043,6 +1096,13 @@
 	g_signal_handlers_unblock_by_func(G_OBJECT(item), G_CALLBACK(gtk_button_clicked), button);
 }
 
+static void
+enable_markup(GtkWidget *widget, gpointer null)
+{
+	if (GTK_IS_LABEL(widget))
+		g_object_set(G_OBJECT(widget), "use-markup", TRUE, NULL);
+}
+
 static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar)
 {
 	GtkWidget *hbox = GTK_WIDGET(toolbar);
@@ -1054,25 +1114,29 @@
 	GtkWidget *font_menu;
 	GtkWidget *insert_menu;
 	GtkWidget *menuitem;
-	GtkWidget *button;
 	GtkWidget *sep;
 	int i;
 	struct {
 		const char *label;
 		GtkWidget **button;
+		gboolean check;
 	} buttons[] = {
-		{_("_Bold"), &toolbar->bold},
-		{_("_Italic"), &toolbar->italic},
-		{_("_Underline"), &toolbar->underline},
-		{_("_Larger"), &toolbar->larger_size},
+		{_("<b>_Bold</b>"), &toolbar->bold, TRUE},
+		{_("<i>_Italic</i>"), &toolbar->italic, TRUE},
+		{_("<u>_Underline</u>"), &toolbar->underline, TRUE},
+		{_("<span size='larger'>_Larger</span>"), &toolbar->larger_size, TRUE},
 #if 0
-		{_("_Normal"), &toolbar->normal_size},
+		{_("_Normal"), &toolbar->normal_size, TRUE},
 #endif
-		{_("_Smaller"), &toolbar->smaller_size},
-		{_("_Font face"), &toolbar->font},
-		{_("_Foreground color"), &toolbar->fgcolor},
-		{_("_Background color"), &toolbar->bgcolor},
-		{NULL, NULL}
+		{_("<span size='smaller'>_Smaller</span>"), &toolbar->smaller_size, TRUE},
+		/* If we want to show the formatting for the following items, we would
+		 * need to update them when formatting changes. The above items don't need
+		 * no updating nor nothin' */
+		{_("_Font face"), &toolbar->font, TRUE},
+		{_("Foreground _color"), &toolbar->fgcolor, TRUE},
+		{_("Bac_kground color"), &toolbar->bgcolor, TRUE},
+		{_("_Reset formatting"), &toolbar->clear, FALSE},
+		{NULL, NULL, FALSE}
 	};
 
 
@@ -1098,47 +1162,34 @@
 	image = gtk_image_new_from_stock(GTK_STOCK_BOLD, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
 	gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
 	label = gtk_label_new_with_mnemonic(_("_Font"));
+	gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
+	g_object_set_data(G_OBJECT(hbox), "font_label", label);
 	gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(hbox), font_button, FALSE, FALSE, 0);
 	gtk_widget_show_all(font_button);
 
 	font_menu = gtk_menu_new();
+	g_object_set_data(G_OBJECT(toolbar), "font_menu", font_menu);
 
-	
 	for (i = 0; buttons[i].label; i++) {
 		GtkWidget *old = *buttons[i].button;
-		menuitem = gtk_check_menu_item_new_with_mnemonic(buttons[i].label);
+		if (buttons[i].check) {
+			menuitem = gtk_check_menu_item_new_with_mnemonic(buttons[i].label);
+			g_signal_connect_after(G_OBJECT(old), "toggled",
+						G_CALLBACK(update_menuitem), menuitem);
+		} else {
+			menuitem = gtk_menu_item_new_with_mnemonic(buttons[i].label);
+		}
 		g_signal_connect_swapped(G_OBJECT(menuitem), "activate",
 				G_CALLBACK(gtk_button_clicked), old);
-		g_signal_connect_after(G_OBJECT(old), "toggled",
-				G_CALLBACK(update_menuitem), menuitem);
 		gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), menuitem);
 		g_signal_connect(G_OBJECT(old), "notify::sensitive",
 				G_CALLBACK(button_sensitiveness_changed), menuitem);
+		gtk_container_foreach(GTK_CONTAINER(menuitem), (GtkCallback)enable_markup, NULL);
 	}
-  
-	g_signal_connect(G_OBJECT(font_button), "clicked", G_CALLBACK(pidgin_menu_clicked), font_menu);
-	g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button);
-
-	/* Sep */
-	sep = gtk_vseparator_new();
-	gtk_box_pack_start(GTK_BOX(hbox), sep, FALSE, FALSE, 0);
-	gtk_widget_show_all(sep);
 
-	/* Reset Formatting */
-	button = gtk_toggle_button_new();
-	gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
-	bbox = gtk_hbox_new(FALSE, 3);
-	gtk_container_add(GTK_CONTAINER(button), bbox);
-	image = gtk_image_new_from_stock(PIDGIN_STOCK_CLEAR, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
-	gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
-	label = gtk_label_new_with_mnemonic(_("_Reset font"));
-	gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0);
-	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
-	gtk_widget_show_all(button);
-	g_signal_connect(G_OBJECT(button), "clicked",
-			 G_CALLBACK(clear_formatting_cb), toolbar);
-	toolbar->clear = button;
+	g_signal_connect(G_OBJECT(font_button), "button-press-event", G_CALLBACK(pidgin_menu_clicked), font_menu);
+	g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button);
 
 	/* Sep */
 	sep = gtk_vseparator_new();
@@ -1158,6 +1209,7 @@
 	gtk_widget_show_all(insert_button);
 
 	insert_menu = gtk_menu_new();
+	g_object_set_data(G_OBJECT(toolbar), "insert_menu", insert_menu);
 
 	menuitem = gtk_menu_item_new_with_mnemonic(_("_Smiley"));
 	g_signal_connect_swapped(G_OBJECT(menuitem), "activate", G_CALLBACK(gtk_button_clicked), toolbar->smiley);
@@ -1177,7 +1229,7 @@
 	g_signal_connect(G_OBJECT(toolbar->link), "notify::sensitive",
 			G_CALLBACK(button_sensitiveness_changed), menuitem);
 
-	g_signal_connect(G_OBJECT(insert_button), "clicked", G_CALLBACK(pidgin_menu_clicked), insert_menu);
+	g_signal_connect(G_OBJECT(insert_button), "button-press-event", G_CALLBACK(pidgin_menu_clicked), insert_menu);
 	g_signal_connect(G_OBJECT(insert_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), insert_button);
 	toolbar->sml = NULL;
 }
--- a/pidgin/gtkmain.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkmain.c	Wed Aug 08 21:05:47 2007 +0000
@@ -31,6 +31,7 @@
 #include "eventloop.h"
 #include "ft.h"
 #include "log.h"
+#include "network.h"
 #include "notify.h"
 #include "prefs.h"
 #include "prpl.h"
@@ -694,7 +695,9 @@
 		return 1;
 	}
 
+#if GLIB_CHECK_VERSION(2,2,0)
 	g_set_application_name(_("Pidgin"));
+#endif /* glib-2.0 >= 2.2.0 */
 
 #ifdef _WIN32
 	winpidgin_init(hint);
@@ -760,7 +763,6 @@
 #endif
 		return 0;
 	}
-		
 
 	/* TODO: Move blist loading into purple_blist_init() */
 	purple_set_blist(purple_blist_new());
@@ -777,6 +779,10 @@
 	/* TODO: Move pounces loading into purple_pounces_init() */
 	purple_pounces_load();
 
+	/* Call this early on to try to auto-detect our IP address and
+	 * hopefully save some time later.
+	 * TODO: move this (back) into purple_core_init() when purple_prefs_load() is in purple_prefs_init() */
+	 purple_network_get_my_ip(-1);
 
 	/* HACK BY SEANEGAN:
 	 * We've renamed prpl-oscar to prpl-aim and prpl-icq, accordingly.
--- a/pidgin/gtkprefs.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkprefs.c	Wed Aug 08 21:05:47 2007 +0000
@@ -994,7 +994,7 @@
 	pidgin_prefs_checkbox(_("Show _formatting on incoming messages"),
 				PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting", vbox);
 
-	iconpref1 = pidgin_prefs_checkbox(strchr(_("/Buddies/Show Buddy _Details")+1,'/')+1,
+	iconpref1 = pidgin_prefs_checkbox(_("Show _detailed information"),
 			PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", vbox);
 	iconpref2 = pidgin_prefs_checkbox(_("Enable buddy ic_on animation"),
 			PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons", vbox);
@@ -1947,7 +1947,7 @@
 						   "/purple/away/away_when_idle", vbox);
 
 	select = pidgin_prefs_labeled_spin_button(vbox,
-			_("_Minutes before changing status:"), "/purple/away/mins_before_away",
+			_("_Minutes before becoming idle:"), "/purple/away/mins_before_away",
 			1, 24 * 60, sg);
 	g_signal_connect(G_OBJECT(button), "clicked",
 					 G_CALLBACK(pidgin_toggle_sensitive), select);
@@ -2236,4 +2236,13 @@
 	purple_prefs_remove(PIDGIN_PREFS_ROOT "/away/queue_messages");
 	purple_prefs_remove(PIDGIN_PREFS_ROOT "/away");
 	purple_prefs_remove("/plugins/gtk/docklet/queue_messages");
+
+	purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/chat/default_width");
+	purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/chat/default_height");
+	purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/im/default_width");
+	purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/im/default_height");
+	purple_prefs_rename(PIDGIN_PREFS_ROOT "/conversations/x",
+			PIDGIN_PREFS_ROOT "/conversations/im/x");
+	purple_prefs_rename(PIDGIN_PREFS_ROOT "/conversations/y",
+			PIDGIN_PREFS_ROOT "/conversations/im/y");
 }
--- a/pidgin/gtkrequest.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkrequest.c	Wed Aug 08 21:05:47 2007 +0000
@@ -1003,9 +1003,6 @@
 	if (purple_request_field_list_get_multi_select(field))
 		gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
 
-	g_signal_connect(G_OBJECT(sel), "changed",
-					 G_CALLBACK(list_field_select_changed_cb), field);
-
 	column = gtk_tree_view_column_new();
 	gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
 
@@ -1028,6 +1025,17 @@
 			gtk_tree_selection_select_iter(sel, &iter);
 	}
 
+	/*
+	 * We only want to catch changes made by the user, so it's important
+	 * that we wait until after the list is created to connect this
+	 * handler.  If we connect the handler before the loop above and
+	 * there are multiple items selected, then selecting the first iter
+	 * in the tree causes list_field_select_changed_cb to be triggered
+	 * which clears out the rest of the list of selected items.
+	 */
+	g_signal_connect(G_OBJECT(sel), "changed",
+					 G_CALLBACK(list_field_select_changed_cb), field);
+
 	gtk_container_add(GTK_CONTAINER(sw), treeview);
 	gtk_widget_show(treeview);
 
--- a/pidgin/gtkutils.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/gtkutils.c	Wed Aug 08 21:05:47 2007 +0000
@@ -2047,11 +2047,11 @@
 														entry.entry.buddy->account,
 														entry.entry.buddy->name
 													 );
+#else
+					item->data = g_strdup(entry.entry.buddy->name);
+					g_completion_add_items(data->completion, item);
+#endif /* NEW_STYLE_COMPLETION */
 				}
-#else
-				item->data = g_strdup(buddy->name);
-				g_completion_add_items(data->completion, item);
-#endif /* NEW_STYLE_COMPLETION */
 			}
 		}
 	}
--- a/pidgin/pidginstock.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/pidginstock.c	Wed Aug 08 21:05:47 2007 +0000
@@ -111,7 +111,7 @@
 	{ PIDGIN_STOCK_STATUS_LOGOUT, 	"status", "log-out.png",	TRUE, TRUE, TRUE, TRUE, FALSE, FALSE , NULL },
 	{ PIDGIN_STOCK_STATUS_OFFLINE, 	"status", "offline.png",	TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, PIDGIN_STOCK_STATUS_OFFLINE_I  },
 	{ PIDGIN_STOCK_STATUS_PERSON, 	"status", "person.png",		TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL  },
-	{ PIDGIN_STOCK_STATUS_MESSAGE, 	"status", "message-pending.png",TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
+	{ PIDGIN_STOCK_STATUS_MESSAGE, 	"toolbar", "message-new.png",TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
 	
 	{ PIDGIN_STOCK_STATUS_IGNORED,	"emblems", "blocked.png",	TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_STATUS_FOUNDER,	"emblems", "founder.png",	TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
@@ -155,7 +155,7 @@
 	{ PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE, "toolbar", "insert-image.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TOOLBAR_INSERT_LINK, "toolbar", "insert-link.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, "toolbar", "message-new.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
-	{ PIDGIN_STOCK_TOOLBAR_PENDING, "status", "message-pending.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
+	{ PIDGIN_STOCK_TOOLBAR_PENDING, "toolbar", "message-new.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TOOLBAR_PLUGINS, "toolbar", "plugins.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TOOLBAR_TYPING, "toolbar", "typing.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TOOLBAR_UNBLOCK, "toolbar", "unblock.png", TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
--- a/pidgin/pixmaps/Makefile.am	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/pixmaps/Makefile.am	Wed Aug 08 21:05:47 2007 +0000
@@ -12,7 +12,7 @@
 		pidgin.ico
 
 pidginbuttonpixdir = $(datadir)/pixmaps/pidgin/buttons
-pidginbuttonpix_DATA = edit.png pause.png
+pidginbuttonpix_DATA = edit.png pause.png 
 
 pidgindistpixdir = $(datadir)/pixmaps/pidgin
 pidgindistpix_DATA = logo.png arrow-down.xpm arrow-left.xpm arrow-right.xpm arrow-up.xpm
--- a/pidgin/pixmaps/emotes/default/22/default.theme.in	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/pixmaps/emotes/default/22/default.theme.in	Wed Aug 08 21:05:47 2007 +0000
@@ -29,7 +29,7 @@
 smile.png           :-)     :)
 wink.png            ;-)     ;)
 sad.png             :-(     :(
-tongue.png          :-P     :P
+tongue.png          :-P     :P      :-p    :p
 shock.png           =-O
 kiss.png            :-*
 shout.png           >:o
@@ -90,7 +90,7 @@
 good.png            (Y)     (y)
 bad.png             (N)     (n)
 vampire.png         :[      :-[
-goat.png           (nah)
+goat.png            (nah)
 sun.png             (#)
 rainbow.png         (R)     (r)
 quiet.png           :-#
@@ -122,141 +122,143 @@
 party.png           <:o)
 eyeroll.png         8-)
 yawn.png            |-) 
-goat.png            (nah)
 ! skywalker.png     C:-)    c:-)    C:)     c:)
 ! monkey.png        :-(|)
+
+### Hidden MSN emotes
 sigarette.png      	(ci)    (CI)
 handcuffs.png       (%)
-console.png	     		(xx)    (XX)
+console.png			(xx)    (XX)
 fingers-crossed.png	(yn)    (YN)
 
 ### Following QQ 2006
 [QQ]
-shock.png             /:O      /jy       /surprised
-curl-lip.png          /:~      /pz       /curl_lip
-desire.png            /:*      /se       /desire
-dazed.png             /:|      /dazed
-party.png             /8-)     /dy       /revel
-crying.png            /:<      /ll       /cry
-bashful.png           /:$      /hx       /bashful
-shut-mouth.png        /:X      /bz       /shut_mouth
-sleepy.png            /:Z      /shui     /sleep
-weep.png              /:'(     /dk       /weep
-embarrassed.png       /:-|     /gg       /embarassed
-pissed-off.png        /:@      /fn       /pissed_off
-act-up.png            /:P      /tp       /act_up
-smile-big.png         /:D      /cy       /toothy_smile
-smile.png             /:)      /wx       /small_smile
-sad.png               /:(      /ng       /sad
-glasses-cool.png      /:+      /kuk      /cool
-doctor.png            /:#      /feid     /SARS
-silly.png             /:Q      /zk       /crazy
-sick.png              /:T      /tu       /vomit
-snicker.png           /;p      /tx       /titter
-cute.png              /;-D     /ka       /cute
-disdain.png           /;d      /by       /disdain
-arrogant.png          /;o      /am       /arrogant
-starving.png          /:g      /jie      /starving
-yawn.png              /|-)     /kun      /sleepy
-terror.png            /:!      /jk       /terror
-sweat.png             /:L      /sweat
-smirk.png             /:>      /hanx     /smirk
-soldier.png           /:;      /db       /soldier
-struggle.png          /;f      /fendou   /struggle
-curse.png             /:-S     /zhm      /curse
-question.png          /?       /yiw      /question
-quiet.png             /;x      /xu       /shh
-hypnotized.png        /;@      /yun      /dizzy
-excruciating.png      /:8      /zhem     /excrutiating
-freaked-out.png       /;!      /shuai    /freaked_out
-skeleton.png          /!!!     /kl       /skeleton
-hammer.png            /xx      /qiao     /hammer
-bye.png               /bye     /zj       /bye
-go-away.png           /go      /shan     /go
-tremble.png           /shake   /fad      /shake
-in-love.png           /love    /aiq      /love
-jump.png              /jump    /tiao     /jump
-search.png            /find    /zhao     /search
-lashes.png            /&       /mm       /beautiful_eyebrows
-pig.png               /pig     /zt       /pig
-cat.png               /cat     /mm       /cat
-dog.png               /dog     /xg       /dog
-hug-left.png          /hug     /yb       /hug
-coins.png             /$       /qianc    /money
-lamp.png              /!       /dp       /lightbulb
-bowl.png              /cup     /bei      /cup
-cake.png              /cake    /dg       /cake
-thunder.png           /li      /shd      /lightning
-bomb.png              /bome    /zhd      /bomb
-knife.png             /kn      /dao      /knife
-soccerball.png        /footb   /zq       /soccer
-musical-note.png      /music   /yy       /music
-poop.png              /shit    /bb       /shit
-coffee.png            /coffee  /kf       /coffee
-eat.png               /eat     /fan      /eat
-pill.png              /pill    /yw       /pill
-rose.png              /rose    /mg       /rose
-wilt.png              /fade    /dx       /wilt
-kiss.png              /kiss    /wen      /kiss
-love.png              /heart   /xin      /heart
-love-over.png         /break   /xs       /broken_heart
-meeting.png           /meeting /hy       /meeting
-present.png           /gift    /lw       /gift
-phone.png             /phone   /dh       /phone
-clock.png             /time    /sj       /time
-mail.png              /email   /yj       /email
-tv.png                /TV      /ds       /TV
-sun.png               /sun     /ty       /sun
-moon.png              /moon    /yl       /moon
-good.png              /strong  /qiang    /thumbs_up
-bad.png               /weak    /ruo      /thumbs_down
-handshake.png         /share   /ws       /handshake
-victory.png           /v       /shl      /victory
-beauty.png            /<J>     /mn       /beauty
-qq.png                /<QQ>    /qz       /qq
-blowkiss.png          /<L>     /fw       /blow_kiss
-angry.png             /<O>     /oh       /angry
-liquor.png            /<B>     /bj       /baijiu
-can.png               /<U>     /qsh      /soda
-watermelon.png        /<W>     /xigua    /watermelon
-rain.png              /<!!>    /xy       /rain
-cloudy.png            /<~>     /duoy     /cloudy
-snowman.png           /<Z>     /xr       /snowman
-star.png              /<*>     /xixing   /star
-girl.png              /<00>    /nv       /woman
-boy.png               /<11>    /nan      /man
+shock.png           /:O      /jy       /surprised
+curl-lip.png        /:~      /pz       /curl_lip
+desire.png          /:*      /se       /desire
+dazed.png           /:|      /dazed
+party.png           /8-)     /dy       /revel
+crying.png          /:<      /ll       /cry
+bashful.png         /:$      /hx       /bashful
+shut-mouth.png      /:X      /bz       /shut_mouth
+sleepy.png          /:Z      /shui     /sleep
+weep.png            /:'(     /dk       /weep
+embarrassed.png     /:-|     /gg       /embarassed
+pissed-off.png      /:@      /fn       /pissed_off
+act-up.png          /:P      /tp       /act_up
+smile-big.png       /:D      /cy       /toothy_smile
+smile.png           /:)      /wx       /small_smile
+sad.png             /:(      /ng       /sad
+glasses-cool.png    /:+      /kuk      /cool
+doctor.png          /:#      /feid     /SARS
+silly.png           /:Q      /zk       /crazy
+sick.png            /:T      /tu       /vomit
+snicker.png         /;p      /tx       /titter
+cute.png            /;-D     /ka       /cute
+disdain.png         /;d      /by       /disdain
+arrogant.png        /;o      /am       /arrogant
+starving.png        /:g      /jie      /starving
+yawn.png            /|-)     /kun      /sleepy
+terror.png          /:!      /jk       /terror
+sweat.png           /:L      /sweat
+smirk.png           /:>      /hanx     /smirk
+soldier.png         /:;      /db       /soldier
+struggle.png        /;f      /fendou   /struggle
+curse.png           /:-S     /zhm      /curse
+question.png        /?       /yiw      /question
+quiet.png           /;x      /xu       /shh
+hypnotized.png      /;@      /yun      /dizzy
+excruciating.png    /:8      /zhem     /excrutiating
+freaked-out.png     /;!      /shuai    /freaked_out
+skeleton.png        /!!!     /kl       /skeleton
+hammer.png          /xx      /qiao     /hammer
+bye.png             /bye     /zj       /bye
+go-away.png         /go      /shan     /go
+tremble.png         /shake   /fad      /shake
+in-love.png         /love    /aiq      /love
+jump.png            /jump    /tiao     /jump
+search.png          /find    /zhao     /search
+lashes.png          /&       /mm       /beautiful_eyebrows
+pig.png             /pig     /zt       /pig
+cat.png             /cat     /mm       /cat
+dog.png             /dog     /xg       /dog
+hug-left.png        /hug     /yb       /hug
+coins.png           /$       /qianc    /money
+lamp.png            /!       /dp       /lightbulb
+bowl.png            /cup     /bei      /cup
+cake.png            /cake    /dg       /cake
+thunder.png         /li      /shd      /lightning
+bomb.png            /bome    /zhd      /bomb
+knife.png           /kn      /dao      /knife
+soccerball.png      /footb   /zq       /soccer
+musical-note.png    /music   /yy       /music
+poop.png            /shit    /bb       /shit
+coffee.png          /coffee  /kf       /coffee
+eat.png             /eat     /fan      /eat
+pill.png            /pill    /yw       /pill
+rose.png            /rose    /mg       /rose
+wilt.png            /fade    /dx       /wilt
+kiss.png            /kiss    /wen      /kiss
+love.png            /heart   /xin      /heart
+love-over.png       /break   /xs       /broken_heart
+meeting.png         /meeting /hy       /meeting
+present.png         /gift    /lw       /gift
+phone.png           /phone   /dh       /phone
+clock.png           /time    /sj       /time
+mail.png            /email   /yj       /email
+tv.png              /TV      /ds       /TV
+sun.png             /sun     /ty       /sun
+moon.png            /moon    /yl       /moon
+good.png            /strong  /qiang    /thumbs_up
+bad.png             /weak    /ruo      /thumbs_down
+handshake.png       /share   /ws       /handshake
+victory.png         /v       /shl      /victory
+beauty.png          /<J>     /mn       /beauty
+qq.png              /<QQ>    /qz       /qq
+blowkiss.png        /<L>     /fw       /blow_kiss
+angry.png           /<O>     /oh       /angry
+liquor.png          /<B>     /bj       /baijiu
+can.png             /<U>     /qsh      /soda
+watermelon.png      /<W>     /xigua    /watermelon
+rain.png            /<!!>    /xy       /rain
+cloudy.png          /<~>     /duoy     /cloudy
+snowman.png         /<Z>     /xr       /snowman
+star.png            /<*>     /xixing   /star
+girl.png            /<00>    /nv       /woman
+boy.png             /<11>    /nan      /man
 ! skywalker.png     C:-)    c:-)    C:)     c:)
 ! monkey.png        :-(|)
 
-### Following ICQ 5.1
+### Following ICQ 6.0
 [ICQ]
 smile.png           :-)     :)
+neutral.png         :-$
 sad.png             :-(     :(
+shock.png           =-O
 wink.png            ;-)     ;)
-tongue.png          :-P     :P
+tongue.png          :-P     :P      :-p     :p
+#[:-}
 laugh.png           *JOKINGLY*
+sleepy.png          *TIRED*
 crying.png          :'(
+sick.png            :-!
 #*KISSED*
+#*STOP*
 kiss.png            :-*
+#*KISSING* 
 embarrassed.png     :-[
+devil.png           ]:->
 angel.png           O:-)
-shut-mouth.png      :-X     :X
+rose.png            @}->--
+shut-mouth.png      :-X     :X      :-x     :x
+bomb.png            @=
 thinking.png        :-\\    :-/
+good.png            *THUMBS\ UP*
 shout.png           >:o     >:O
+beer.png            *DRINK*
 smile-big.png       :-D     :D
 moneymouth.png      :-$
-shock.png           =-O
 glasses-cool.png    8-)
-#[:-}
-sleepy.png          *TIRED*
-sick.png            :-!
-#*STOP*
-#*KISSING* 
-devil.png           ]:->
-rose.png            @}->--
-bomb.png            @=
-good.png            *THUMBS\ UP*
-beer.png            *DRINK*
 in-love.png         *IN\ LOVE*
 ! skywalker.png     C:-)    c:-)    C:)     c:)
 ! monkey.png        :-(|)
@@ -312,7 +314,7 @@
 love-over.png       =((
 sweat.png           #:-S    #:-s
 rotfl.png           =))    
-loser.png           L-)     l-)
+#loser              L-)     l-)     MISSING/YAHOO 6: "Loser!"
 party.png           <:-P    <:-p
 nailbiting.png      :-SS	:-Ss	:-sS	:-ss
 cowboy.png          <):)
@@ -349,8 +351,8 @@
 #youkiddingme.png   :-j :-J
 
 ### These only work in a certain IMvironment
-male-fighter.png   o->     O->
-#malefighter2.png   o=>     O=>
+#male-fighter1.png  o->     O->
+#male-fighter2.png  o=>     O=>
 female-fighter.png  o-+     O-+
 yin-yang.png        (%)
 
--- a/pidgin/pixmaps/icons/22/Makefile.am	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/pixmaps/icons/22/Makefile.am	Wed Aug 08 21:05:47 2007 +0000
@@ -2,7 +2,7 @@
 
 EXTRA_DIST = pidgin.png 
 
-pidginiconspixdir = $(datadir)/icons/hicolor/24x24/apps
+pidginiconspixdir = $(datadir)/icons/hicolor/22x22/apps
 
 pidginiconspix_DATA = $(EXTRA_DIST)
 
--- a/pidgin/pixmaps/status/16/Makefile.am	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/pixmaps/status/16/Makefile.am	Wed Aug 08 21:05:47 2007 +0000
@@ -8,7 +8,6 @@
 		invisible.png \
 		log-in.png \
 		log-out.png \
-		message-pending.png \
 		offline.png \
 		person.png 
 
Binary file pidgin/pixmaps/status/16/message-pending.png has changed
--- a/pidgin/win32/nsis/translations/swedish.nsh	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/win32/nsis/translations/swedish.nsh	Wed Aug 08 21:05:47 2007 +0000
@@ -1,4 +1,4 @@
-;;
+;;
 ;;  swedish.nsh
 ;;
 ;;  Swedish language strings for the Windows Pidgin NSIS installer.
@@ -8,56 +8,53 @@
 ;;  Author: Peter Hjalmarsson <xake@telia.com>, 2005.
 ;;  Version 3
 
-; Make sure to update the PIDGIN_MACRO_LANGUAGEFILE_END macro in
-; langmacros.nsh when updating this file
-
 ; Startup Checks
-!define INSTALLER_IS_RUNNING			"Installationsprogrammet körs redan."
-!define PIDGIN_IS_RUNNING			"En instans av Pidgin körs redan. Avsluta Pidgin och försök igen."
-!define GTK_INSTALLER_NEEDED			"Körmiljön GTK+ är antingen inte installerat eller behöver uppgraderas.$\rVar  god installera v${GTK_MIN_VERSION} eller högre av GTK+-körmiljön."
+!define INSTALLER_IS_RUNNING			"Installationsprogrammet körs redan."
+!define PIDGIN_IS_RUNNING			"En instans av Pidgin körs redan. Avsluta Pidgin och försök igen."
+!define GTK_INSTALLER_NEEDED			"Körmiljön GTK+ är antingen inte installerat eller behöver uppgraderas.$\rVar  god installera v${GTK_MIN_VERSION} eller högre av GTK+-körmiljön."
 
 ; License Page
-!define PIDGIN_LICENSE_BUTTON			"Nästa >"
-!define PIDGIN_LICENSE_BOTTOM_TEXT		"$(^Name) är utgivet under GPL. Licensen finns tillgänglig här för informationssyften enbart. $_CLICK"
+!define PIDGIN_LICENSE_BUTTON			"Nästa >"
+!define PIDGIN_LICENSE_BOTTOM_TEXT		"$(^Name) är utgivet under GPL. Licensen finns tillgänglig här för informationssyften enbart. $_CLICK"
 
 ; Components Page
 !define PIDGIN_SECTION_TITLE			"Pidgin Snabbmeddelandeklient (obligatorisk)"
-!define GTK_SECTION_TITLE			"GTK+-körmiljö (obligatorisk)"
-!define PIDGIN_SHORTCUTS_SECTION_TITLE 		"Genvägar"
+!define GTK_SECTION_TITLE			"GTK+-körmiljö (obligatorisk)"
+!define PIDGIN_SHORTCUTS_SECTION_TITLE 		"Genvägar"
 !define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE 	"Skrivbord"
 !define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Startmeny"
-!define PIDGIN_SECTION_DESCRIPTION		"Pidgins kärnfiler och DLL:er"
-!define GTK_SECTION_DESCRIPTION			"En GUI-verktygsuppsättning för flera olika plattformar som Pidgin använder."
+!define PIDGIN_SECTION_DESCRIPTION		"Pidgins kärnfiler och DLL:er"
+!define GTK_SECTION_DESCRIPTION			"En GUI-verktygsuppsättning för flera olika plattformar som Pidgin använder."
 
-!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION   	"Genvägar för att starta Pidgin"
-!define PIDGIN_DESKTOP_SHORTCUT_DESC   		"Skapar en genväg till Pidgin på skrivbordet"
-!define PIDGIN_STARTMENU_SHORTCUT_DESC   	"Skapar ett tillägg i startmenyn för Pidgin"
+!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION   	"Genvägar för att starta Pidgin"
+!define PIDGIN_DESKTOP_SHORTCUT_DESC   		"Skapar en genväg till Pidgin på skrivbordet"
+!define PIDGIN_STARTMENU_SHORTCUT_DESC   	"Skapar ett tillägg i startmenyn för Pidgin"
 
 ; GTK+ Directory Page
-!define GTK_UPGRADE_PROMPT			"En äldre version av GTK+ runtime hittades, vill du uppgradera den?$\rOBS! $(^Name) kommer kanske inte att fungera om du inte uppgraderar."
+!define GTK_UPGRADE_PROMPT			"En äldre version av GTK+ runtime hittades, vill du uppgradera den?$\rOBS! $(^Name) kommer kanske inte att fungera om du inte uppgraderar."
 
 ; Installer Finish Page
-!define PIDGIN_FINISH_VISIT_WEB_SITE		"Besök Windows-Pidgin hemsida"
+!define PIDGIN_FINISH_VISIT_WEB_SITE		"Besök Windows-Pidgin hemsida"
 
 ; Pidgin Section Prompts and Texts
-!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL	"Kunde inte avinstallera den nuvarande versionen av Pidgin. Den nya versionen kommer att installeras utan att ta bort den för närvarande installerade versionen."
+!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL	"Kunde inte avinstallera den nuvarande versionen av Pidgin. Den nya versionen kommer att installeras utan att ta bort den för närvarande installerade versionen."
 
 ; GTK+ Section Prompts
 !define GTK_INSTALL_ERROR			"Fel vid installation av GTK+ runtime."
-!define GTK_BAD_INSTALL_PATH			"Den sökväg du angivit går inte att komma åt eller skapa."
+!define GTK_BAD_INSTALL_PATH			"Den sökväg du angivit går inte att komma åt eller skapa."
 
 ; URL Handler section
 !define URI_HANDLERS_SECTION_TITLE		"URI Hanterare"
 
 ; Uninstall Section Prompts
-!define un.PIDGIN_UNINSTALL_ERROR_1         	"Avinstalleraren kunde inte hitta registervärden för Pidgin.$\rAntagligen har en annan användare installerat applikationen."
-!define un.PIDGIN_UNINSTALL_ERROR_2         	"Du har inte rättigheter att avinstallera den här applikationen."
+!define un.PIDGIN_UNINSTALL_ERROR_1         	"Avinstalleraren kunde inte hitta registervärden för Pidgin.$\rAntagligen har en annan användare installerat applikationen."
+!define un.PIDGIN_UNINSTALL_ERROR_2         	"Du har inte rättigheter att avinstallera den här applikationen."
 
 ; Spellcheck Section Prompts
-!define PIDGIN_SPELLCHECK_SECTION_TITLE		"Stöd för rättstavning"
-!define PIDGIN_SPELLCHECK_ERROR			"Fel vid installation för rättstavning"
-!define PIDGIN_SPELLCHECK_DICT_ERROR		"Fel vid installation av rättstavningsordlista"
-!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION	"Stöd för Rättstavning.  (Internetanslutning krävs för installation)"
+!define PIDGIN_SPELLCHECK_SECTION_TITLE		"Stöd för rättstavning"
+!define PIDGIN_SPELLCHECK_ERROR			"Fel vid installation för rättstavning"
+!define PIDGIN_SPELLCHECK_DICT_ERROR		"Fel vid installation av rättstavningsordlista"
+!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION	"Stöd för Rättstavning.  (Internetanslutning krävs för installation)"
 !define ASPELL_INSTALL_FAILED			"Installationen misslyckades"
 !define PIDGIN_SPELLCHECK_BRETON		"Bretonska"
 !define PIDGIN_SPELLCHECK_CATALAN		"Katalanska"
@@ -69,14 +66,14 @@
 !define PIDGIN_SPELLCHECK_ENGLISH		"Engelska"
 !define PIDGIN_SPELLCHECK_ESPERANTO		"Esperanto"
 !define PIDGIN_SPELLCHECK_SPANISH		"Spanska"
-!define PIDGIN_SPELLCHECK_FAROESE		"Färöiska"
+!define PIDGIN_SPELLCHECK_FAROESE		"Färöiska"
 !define PIDGIN_SPELLCHECK_FRENCH		"Franska"
 !define PIDGIN_SPELLCHECK_ITALIAN		"Italienska"
-!define PIDGIN_SPELLCHECK_DUTCH			"Nederländska"
+!define PIDGIN_SPELLCHECK_DUTCH			"Nederländska"
 !define PIDGIN_SPELLCHECK_NORWEGIAN		"Norska"
 !define PIDGIN_SPELLCHECK_POLISH		"Polska"
 !define PIDGIN_SPELLCHECK_PORTUGUESE		"Portugisiska"
-!define PIDGIN_SPELLCHECK_ROMANIAN		"Rumänska"
+!define PIDGIN_SPELLCHECK_ROMANIAN		"Rumänska"
 !define PIDGIN_SPELLCHECK_RUSSIAN		"Ryska"
 !define PIDGIN_SPELLCHECK_SLOVAK		"Slovakiska"
 !define PIDGIN_SPELLCHECK_SWEDISH		"Svenska"
--- a/pidgin/win32/winpidgin.c	Wed Aug 08 21:01:42 2007 +0000
+++ b/pidgin/win32/winpidgin.c	Wed Aug 08 21:05:47 2007 +0000
@@ -332,10 +332,10 @@
 			break;
 		case LANG_ROMANIAN: posix = "ro"; break;
 		case LANG_RUSSIAN: posix = "ru"; break;
-		/* LANG_CROATIAN == LANG_SERBIAN == LANG_BOSNIAN */
 		case LANG_SLOVAK: posix = "sk"; break;
 		case LANG_SLOVENIAN: posix = "sl"; break;
 		case LANG_ALBANIAN: posix = "sq"; break;
+		/* LANG_CROATIAN == LANG_SERBIAN == LANG_BOSNIAN */
 		case LANG_SERBIAN:
 			switch (sub_id) {
 				case SUBLANG_SERBIAN_LATIN:
@@ -538,6 +538,8 @@
 	char exe_name[MAX_PATH];
 	HMODULE hmod;
 	char *tmp;
+	int pidgin_argc = __argc;
+	char **pidgin_argv = __argv;
 
 	/* If debug or help or version flag used, create console for output */
 	if (strstr(lpszCmdLine, "-d") || strstr(lpszCmdLine, "-h") || strstr(lpszCmdLine, "-v")) {
@@ -601,8 +603,20 @@
 	/* Determine if we're running in portable mode */
 	if (strstr(lpszCmdLine, "--portable-mode")
 			|| (exe_name != NULL && strstr(exe_name, "-portable.exe"))) {
+		int i = 0, c = 0;
+
 		printf("Running in PORTABLE mode.\n");
 		portable_mode = TRUE;
+
+		/* Remove the --portable-mode arg from the args passed to pidgin so it doesn't choke */
+		pidgin_argv = malloc(sizeof(char*) * pidgin_argc);
+		for (; i < __argc; i++) {
+			if (strstr(__argv[i], "--portable-mode") == NULL) {
+				pidgin_argv[c] = __argv[i];
+				c++;
+			} else
+				pidgin_argc--;
+		}
 	}
 
 	if (portable_mode)
@@ -635,5 +649,5 @@
 		return 0;
 	}
 
-	return pidgin_main(hInstance, __argc, __argv);
+	return pidgin_main(hInstance, pidgin_argc, pidgin_argv);
 }
--- a/po/POTFILES.in	Wed Aug 08 21:01:42 2007 +0000
+++ b/po/POTFILES.in	Wed Aug 08 21:05:47 2007 +0000
@@ -200,6 +200,7 @@
 pidgin/plugins/gevolution/gevolution.c
 pidgin/plugins/gevolution/gevo-util.c
 pidgin/plugins/gevolution/new_person_dialog.c
+pidgin/plugins/gtkbuddynote.c
 pidgin/plugins/gtk-signals-test.c
 pidgin/plugins/history.c
 pidgin/plugins/iconaway.c