changeset 18810:580e0bfe7e33

merge of '412ec3e9ae5d5532710d56efecc7543837a91cbb' and '8f73315c03954666c69c055abbe62ee2e135c4cf'
author Richard Nelson <wabz@pidgin.im>
date Mon, 06 Aug 2007 01:45:24 +0000
parents 6af9201ac685 (diff) 5a5ca6438308 (current diff)
children b68ced0ab472
files
diffstat 20 files changed, 630 insertions(+), 122 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Mon Aug 06 01:36:57 2007 +0000
+++ b/configure.ac	Mon Aug 06 01:45:24 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/libpurple/protocols/bonjour/Makefile.am	Mon Aug 06 01:36:57 2007 +0000
+++ b/libpurple/protocols/bonjour/Makefile.am	Mon Aug 06 01:45:24 2007 +0000
@@ -1,6 +1,7 @@
 EXTRA_DIST = \
-		mdns_win32.c \
-		Makefile.mingw
+	mdns_win32.c \
+	dns_sd_proxy.h \
+	Makefile.mingw
 
 pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
 
@@ -9,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_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
 
@@ -30,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
 
@@ -47,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
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.c	Mon Aug 06 01:45:24 2007 +0000
@@ -111,5 +111,6 @@
 	_mdns_stop(data);
 
 	gc = purple_account_get_connection(data->account);
-	purple_input_remove(gc->inpa);
+	if (gc->inpa > 0)
+		purple_input_remove(gc->inpa);
 }
--- a/libpurple/protocols/bonjour/mdns_howl.c	Mon Aug 06 01:36:57 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_howl.c	Mon Aug 06 01:45:24 2007 +0000
@@ -145,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);
@@ -258,14 +258,14 @@
 			break;
 	}
 
+	/* Free the memory used by temp data */
+	sw_text_record_fina(dns_data);
+
 	if (publish_result != SW_OKAY) {
-		purple_debug_error("bonjour", "Unable to publish or change the status of the _presence._tcp service.\n");
+		purple_debug_error("bonjour", "Unable to publish or change the status of the " ICHAT_SERVICE " service.\n");
 		return FALSE;
 	}
 
-	/* Free the memory used by temp data */
-	sw_text_record_fina(dns_data);
-
 	return TRUE;
 }
 
@@ -283,7 +283,7 @@
 guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
 	HowlSessionImplData *idata = data->mdns_impl_data;
 
-	g_return_val_if_fail(idata != NULL, FALSE);
+	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);
--- a/libpurple/protocols/bonjour/mdns_win32.c	Mon Aug 06 01:36:57 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Mon Aug 06 01:45:24 2007 +0000
@@ -340,7 +340,7 @@
 guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
 	Win32SessionImplData *idata = data->mdns_impl_data;
 
-	g_return_val_if_fail(idata != NULL, FALSE);
+	g_return_val_if_fail(idata != NULL, 0);
 
 	return purple_input_add(DNSServiceRefSockFD(idata->browser),
 				PURPLE_INPUT_READ, _mdns_handle_event, idata->browser);
--- a/libpurple/protocols/qq/buddy_opt.c	Mon Aug 06 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/buddy_opt.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/group.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/group_im.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/group_internal.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/group_join.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/group_opt.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/im.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/keep_alive.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/login_logout.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/qq.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/qq_proxy.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/libpurple/protocols/qq/sys_msg.c	Mon Aug 06 01:45:24 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 01:36:57 2007 +0000
+++ b/pidgin/gtkconv.c	Mon Aug 06 01:45:24 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);
@@ -7169,25 +7172,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);
 
@@ -8331,10 +8337,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 */
@@ -8344,9 +8348,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);
 
@@ -8356,35 +8360,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 *
@@ -8409,8 +8416,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);
 
@@ -8937,6 +8942,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 {
@@ -8945,6 +8953,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)
 {
@@ -8955,8 +9010,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);
 }
@@ -8968,6 +9041,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/gtkprefs.c	Mon Aug 06 01:36:57 2007 +0000
+++ b/pidgin/gtkprefs.c	Mon Aug 06 01:45:24 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");
 }