changeset 18820:25c2af20affe

merge of '5cd317ad290e840fa8a8624d17dd5f23f99330ac' and '5f2f745cd67ec501c85a6a740626a581415614f5'
author Sean Egan <seanegan@gmail.com>
date Mon, 06 Aug 2007 20:50:32 +0000
parents 8007c107054b (diff) 0622a7f08dde (current diff)
children 0765cf9331f6
files libpurple/protocols/bonjour/mdns_howl.h libpurple/protocols/bonjour/mdns_win32.h pidgin/gtkconv.c
diffstat 37 files changed, 1078 insertions(+), 526 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Mon Aug 06 20:49:41 2007 +0000
+++ b/configure.ac	Mon Aug 06 20:50:32 2007 +0000
@@ -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")
--- a/finch/libgnt/gnttree.c	Mon Aug 06 20:49:41 2007 +0000
+++ b/finch/libgnt/gnttree.c	Mon Aug 06 20:50:32 2007 +0000
@@ -1094,7 +1094,7 @@
 free_tree_col(gpointer data)
 {
 	GntTreeCol *col = data;
-	if (col->isbinary)
+	if (!col->isbinary)
 		g_free(col->text);
 	g_free(col);
 }
--- a/libpurple/Makefile.am	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/Makefile.am	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/account.h	Mon Aug 06 20:50:32 2007 +0000
@@ -51,20 +51,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);
@@ -193,7 +223,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/protocols/bonjour/Makefile.am	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/bonjour/Makefile.am	Mon Aug 06 20:50:32 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,18 +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_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
 
@@ -31,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) $(LIBXML_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
 
@@ -48,5 +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/buddy.c	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Mon Aug 06 20:50:32 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[] = {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.h	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_howl.c	Mon Aug 06 20:50:32 2007 +0000
@@ -15,12 +15,20 @@
  */
 
 #include "internal.h"
-#include "mdns_howl.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)
 {
@@ -46,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,
@@ -95,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,
@@ -137,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);
@@ -154,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 */
@@ -208,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	Mon Aug 06 20:49:41 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	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_types.h	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Mon Aug 06 20:50:32 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,26 +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);
 
 			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj);
-		}
-		else
+		} else
 			bonjour_buddy_delete(buddy);
 
 	}
@@ -97,7 +138,6 @@
 
 	/* free the remaining args memory */
 	purple_dnsquery_destroy(args->query);
-	g_free(args->fqn);
 	g_free(args);
 }
 
@@ -108,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)
@@ -125,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);
 		}
 	}
@@ -150,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)
 {
@@ -159,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);
 
@@ -250,41 +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:
 				purple_debug_info("bonjour", "Registering service on port %d\n", data->port_p2pj);
-				err = DNSServiceRegister(&data->advertisement, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE,
+				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);
 		}
 	}
 
@@ -293,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	Mon Aug 06 20:49:41 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
--- a/libpurple/protocols/oscar/flap_connection.c	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Mon Aug 06 20:50:32 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/oscar.c	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Mon Aug 06 20:50:32 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");
 }
 
 /*
--- a/libpurple/protocols/oscar/oscar.h	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Mon Aug 06 20:50:32 2007 +0000
@@ -449,7 +449,6 @@
 	GSList *requesticon;
 
 	gboolean icq;
-	guint icontimer;
 	guint getblisttimer;
 	guint getinfotimer;
 
--- a/libpurple/protocols/oscar/oscar_data.c	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/oscar/oscar_data.c	Mon Aug 06 20:50:32 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/qq/buddy_opt.c	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/buddy_opt.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/group.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/group_im.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/group_internal.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/group_join.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/group_opt.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/im.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/keep_alive.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/login_logout.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/qq.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/qq_proxy.c	Mon Aug 06 20:50:32 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	Mon Aug 06 20:49:41 2007 +0000
+++ b/libpurple/protocols/qq/sys_msg.c	Mon Aug 06 20:50:32 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/pidgin/gtkconv.c	Mon Aug 06 20:49:41 2007 +0000
+++ b/pidgin/gtkconv.c	Mon Aug 06 20:50:32 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);
@@ -7170,25 +7173,28 @@
 	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);
 
@@ -8332,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 */
@@ -8345,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);
 
@@ -8357,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 *
@@ -8410,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);
 
@@ -8926,6 +8931,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 {
@@ -8934,6 +8942,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)
 {
@@ -8944,8 +8999,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);
 }
@@ -8957,6 +9030,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/gtkimhtml.c	Mon Aug 06 20:49:41 2007 +0000
+++ b/pidgin/gtkimhtml.c	Mon Aug 06 20:50:32 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 {
--- a/pidgin/gtkimhtmltoolbar.c	Mon Aug 06 20:49:41 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Mon Aug 06 20:50:32 2007 +0000
@@ -939,12 +939,10 @@
 		*y -= widget->allocation.height;
 }
 
-static void pidgin_menu_clicked(GtkWidget *button, GtkMenu *menu)
+static void pidgin_menu_clicked(GtkWidget *button, GdkEventButton *event, GtkMenu *menu)
 {
-	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
-		gtk_widget_show_all(GTK_WIDGET(menu));
-		gtk_menu_popup(menu, NULL, NULL, menu_position_func, button, 0, gtk_get_current_event_time());
-	}
+	gtk_widget_show_all(GTK_WIDGET(menu));
+	gtk_menu_popup(menu, NULL, NULL, menu_position_func, button, 0, gtk_get_current_event_time());
 }
 
 static void pidgin_menu_deactivate(GtkWidget *menu, GtkToggleButton *button)
@@ -1179,7 +1177,7 @@
 				G_CALLBACK(button_sensitiveness_changed), menuitem);
 	}
 
-	g_signal_connect(G_OBJECT(font_button), "clicked", G_CALLBACK(pidgin_menu_clicked), font_menu);
+	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 */
@@ -1220,7 +1218,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/gtkprefs.c	Mon Aug 06 20:49:41 2007 +0000
+++ b/pidgin/gtkprefs.c	Mon Aug 06 20:50:32 2007 +0000
@@ -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/pixmaps/icons/22/Makefile.am	Mon Aug 06 20:49:41 2007 +0000
+++ b/pidgin/pixmaps/icons/22/Makefile.am	Mon Aug 06 20:50:32 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)