changeset 27065:2dfb639b4f26

propagate from branch 'im.pidgin.pidgin' (head 191873f724e4551edc337f1e018980f32c41391b) to branch 'im.pidgin.cpw.malu.client_type' (head 622ef46be3dcff9f70c21918cbeed71f7508b85c)
author Marcus Lundblad <ml@update.uu.se>
date Wed, 13 May 2009 20:37:46 +0000
parents eeee4309d3d8 (diff) 007dff3cb9e4 (current diff)
children d2424873c666
files libpurple/protocols/jabber/buddy.c libpurple/protocols/jabber/buddy.h libpurple/protocols/jabber/jabber.c
diffstat 35 files changed, 431 insertions(+), 153 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Thu May 07 21:20:16 2009 +0000
+++ b/COPYRIGHT	Wed May 13 20:37:46 2009 +0000
@@ -424,6 +424,7 @@
 Lex Spoon
 Chris Stafford
 Kevin Stange
+Joshua Stein
 Jakub Steiner
 Richard Stellingwerff
 Charlie Stockman
--- a/ChangeLog	Thu May 07 21:20:16 2009 +0000
+++ b/ChangeLog	Wed May 13 20:37:46 2009 +0000
@@ -38,6 +38,9 @@
 	  contains formatting.
 	* Show when the user was last logged in when doing "Get Info" on an offline
 	  buddy, provided the server supports it.
+	* Support custom smileys in MUCs (only when all participants supports the
+	  "Bits of Binary" extension, and a maximum of 10 participants are in the
+	  chat (to avoid getting too many fetch requests).
 
 	Yahoo:
 	* P2P file transfers. (Sulabh Mahajan)
@@ -65,6 +68,8 @@
 	* The nicks of the persons who leave the chatroom are italicized in the
 	  chat's conversation history. The nicks are un-italicized when they
 	  rejoin.
+	* Always set unseen-count and unseen-state on conversations.
+	  (Joshua Stein)
 
 	Finch:
 	* The hardware cursor is updated correctly. This will be useful
--- a/ChangeLog.API	Thu May 07 21:20:16 2009 +0000
+++ b/ChangeLog.API	Wed May 13 20:37:46 2009 +0000
@@ -15,6 +15,7 @@
 			* account-destroying
 		* blist-node-added and blist-node-removed signals (see
 		  blist-signals.dox)
+		* Jabber plugin signals (see jabber-signals.dox)
 		* purple_buddy_destroy
 		* purple_buddy_get_protocol_data
 		* purple_buddy_set_protocol_data
--- a/Makefile.mingw	Thu May 07 21:20:16 2009 +0000
+++ b/Makefile.mingw	Wed May 13 20:37:46 2009 +0000
@@ -96,15 +96,15 @@
 	 -not \( -false $(EXTERNAL_DLLS_FIND_EXP) \) -exec $(STRIP) --strip-unneeded {} ';'
 
 installer: create_release_install_dir
-	$(MAKENSIS) /V3 /DPIDGIN_VERSION="$(PIDGIN_VERSION)" /DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" /DWITH_GTK /DPIDGIN_INSTALL_DIR="$(STRIPPED_RELEASE_DIR)" /DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi
+	$(MAKENSIS) $(MAKENSISOPT)V3 $(MAKENSISOPT)DPIDGIN_VERSION="$(PIDGIN_VERSION)" $(MAKENSISOPT)DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" $(MAKENSISOPT)DWITH_GTK $(MAKENSISOPT)DPIDGIN_INSTALL_DIR="$(STRIPPED_RELEASE_DIR)" $(MAKENSISOPT)DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi
 	mv pidgin/win32/nsis/pidgin-$(PIDGIN_VERSION).exe ./
 
 installer_nogtk: create_release_install_dir
-	$(MAKENSIS) /V3 /DPIDGIN_VERSION="$(PIDGIN_VERSION)" /DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" /DPIDGIN_INSTALL_DIR="$(STRIPPED_RELEASE_DIR)" /DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi
+	$(MAKENSIS) $(MAKENSISOPT)V3 $(MAKENSISOPT)DPIDGIN_VERSION="$(PIDGIN_VERSION)" $(MAKENSISOPT)DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" $(MAKENSISOPT)DPIDGIN_INSTALL_DIR="$(STRIPPED_RELEASE_DIR)" $(MAKENSISOPT)DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi
 	mv pidgin/win32/nsis/pidgin-$(PIDGIN_VERSION)-no-gtk.exe ./
 
 installer_debug: install
-	$(MAKENSIS) /V3 /DPIDGIN_VERSION="$(PIDGIN_VERSION)" /DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" /DPIDGIN_INSTALL_DIR="$(PIDGIN_INSTALL_DIR)" /DDEBUG /DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi
+	$(MAKENSIS) $(MAKENSISOPT)V3 $(MAKENSISOPT)DPIDGIN_VERSION="$(PIDGIN_VERSION)" $(MAKENSISOPT)DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" $(MAKENSISOPT)DPIDGIN_INSTALL_DIR="$(PIDGIN_INSTALL_DIR)" $(MAKENSISOPT)DDEBUG $(MAKENSISOPT)DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi
 	mv pidgin/win32/nsis/pidgin-$(PIDGIN_VERSION)-debug.exe ./
 
 installer_zip: create_release_install_dir
--- a/libpurple/dbus-analyze-functions.py	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/dbus-analyze-functions.py	Wed May 13 20:37:46 2009 +0000
@@ -31,6 +31,15 @@
     "purple_account_unregister",
     "purple_connection_new_unregister",
 
+    # These functions are excluded because they involve setting arbitrary
+    # data via pointers for protocols and UIs.  This just won't work.
+    "purple_blist_get_ui_data",
+    "purple_blist_set_ui_data",
+    "purple_blist_node_get_ui_data",
+    "purple_blist_node_set_ui_data",
+    "purple_buddy_get_protocol_data",
+    "purple_buddy_set_protocol_data",
+
     # This is excluded because this script treats PurpleLogReadFlags*
     # as pointer to a struct, instead of a pointer to an enum.  This
     # causes a compilation error. Someone should fix this script.
--- a/libpurple/protocols/jabber/buddy.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Wed May 13 20:37:46 2009 +0000
@@ -74,7 +74,7 @@
 	if (js->buddies == NULL)
 		return NULL;
 
-	if(!(realname = jabber_normalize(js->gc->account, name)))
+	if(!(realname = jabber_get_bare_jid(name)))
 		return NULL;
 
 	jb = g_hash_table_lookup(js->buddies, realname);
@@ -1741,7 +1741,7 @@
 		g_free(full_jid);
 	}
 
-	if (!jb->resources) {
+	if (!jb->resources && strchr(jid, '/') == NULL) {
 		/* user is offline, send a jabber:iq:last to find out last time online */
 		iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:last");
 		xmlnode_set_attrib(iq->node, "to", jid);
--- a/libpurple/protocols/jabber/buddy.h	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Wed May 13 20:37:46 2009 +0000
@@ -86,6 +86,7 @@
 		GList *exts;
 	} caps;
 	GList *commands;
+	gboolean commands_fetched;
 } JabberBuddyResource;
 
 void jabber_buddy_free(JabberBuddy *jb);
--- a/libpurple/protocols/jabber/caps.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/caps.c	Wed May 13 20:37:46 2009 +0000
@@ -35,13 +35,7 @@
 	GList *values;
 } JabberDataFormField;
 
-typedef struct _JabberCapsKey {
-	char *node;
-	char *ver;
-	char *hash;
-} JabberCapsKey;
-
-static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsClientInfo */
+static GHashTable *capstable = NULL; /* JabberCapsTuple -> JabberCapsClientInfo */
 static GHashTable *nodetable = NULL; /* char *node -> JabberCapsNodeExts */
 static guint       save_timer = 0;
 
@@ -86,7 +80,7 @@
 }
 
 static guint jabber_caps_hash(gconstpointer data) {
-	const JabberCapsKey *key = data;
+	const JabberCapsTuple *key = data;
 	guint nodehash = g_str_hash(key->node);
 	guint verhash  = g_str_hash(key->ver);
 	/*
@@ -99,22 +93,14 @@
 }
 
 static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) {
-	const JabberCapsKey *name1 = v1;
-	const JabberCapsKey *name2 = v2;
+	const JabberCapsTuple *name1 = v1;
+	const JabberCapsTuple *name2 = v2;
 
 	return g_str_equal(name1->node, name2->node) &&
 	       g_str_equal(name1->ver, name2->ver) &&
 	       purple_strequal(name1->hash, name2->hash);
 }
 
-void jabber_caps_destroy_key(gpointer data) {
-	JabberCapsKey *key = data;
-	g_free(key->node);
-	g_free(key->ver);
-	g_free(key->hash);
-	g_free(key);
-}
-
 static void
 jabber_caps_client_info_destroy(JabberCapsClientInfo *info)
 {
@@ -140,6 +126,10 @@
 
 	jabber_caps_node_exts_unref(info->exts);
 
+	g_free((char *)info->tuple.node);
+	g_free((char *)info->tuple.ver);
+	g_free((char *)info->tuple.hash);
+
 	g_free(info);
 }
 
@@ -176,16 +166,16 @@
 }
 
 static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) {
-	JabberCapsKey *clientinfo = key;
-	JabberCapsClientInfo *props = value;
+	const JabberCapsTuple *tuple = key;
+	const JabberCapsClientInfo *props = value;
 	xmlnode *root = user_data;
 	xmlnode *client = xmlnode_new_child(root, "client");
 	GList *iter;
 
-	xmlnode_set_attrib(client, "node", clientinfo->node);
-	xmlnode_set_attrib(client, "ver", clientinfo->ver);
-	if (clientinfo->hash)
-		xmlnode_set_attrib(client, "hash", clientinfo->hash);
+	xmlnode_set_attrib(client, "node", tuple->node);
+	xmlnode_set_attrib(client, "ver", tuple->ver);
+	if (tuple->hash)
+		xmlnode_set_attrib(client, "hash", tuple->hash);
 	for(iter = props->identities; iter; iter = g_list_next(iter)) {
 		JabberIdentity *id = iter->data;
 		xmlnode *identity = xmlnode_new_child(client, "identity");
@@ -255,8 +245,8 @@
 		if(client->type != XMLNODE_TYPE_TAG)
 			continue;
 		if(!strcmp(client->name, "client")) {
-			JabberCapsKey *key = g_new0(JabberCapsKey, 1);
 			JabberCapsClientInfo *value = g_new0(JabberCapsClientInfo, 1);
+			JabberCapsTuple *key = (JabberCapsTuple*)&value->tuple;
 			xmlnode *child;
 			JabberCapsNodeExts *exts = NULL;
 			key->node = g_strdup(xmlnode_get_attrib(client,"node"));
@@ -340,7 +330,7 @@
 void jabber_caps_init(void)
 {
 	nodetable = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_caps_node_exts_unref); 
-	capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, jabber_caps_destroy_key, (GDestroyNotify)jabber_caps_client_info_destroy);
+	capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, NULL, (GDestroyNotify)jabber_caps_client_info_destroy);
 	jabber_caps_load();
 }
 
@@ -356,6 +346,28 @@
 	capstable = nodetable = NULL;
 }
 
+gboolean jabber_caps_exts_known(const JabberCapsClientInfo *info,
+                                char **exts)
+{
+	int i;
+	g_return_val_if_fail(info != NULL, FALSE);
+
+	if (!exts)
+		return TRUE;
+
+	for (i = 0; exts[i]; ++i) {
+		/* Hack since we advertise the ext along with v1.5 caps but don't
+		 * store any exts */
+		if (g_str_equal(exts[i], "voice-v1") && !info->exts)
+			continue;
+		if (!info->exts ||
+				!g_hash_table_lookup(info->exts->exts, exts[i]))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
 typedef struct _jabber_caps_cbplususerdata {
 	guint ref;
 
@@ -429,7 +441,7 @@
 		"http://jabber.org/protocol/disco#info");
 	jabber_caps_cbplususerdata *userdata = data;
 	JabberCapsClientInfo *info = NULL, *value;
-	JabberCapsKey key;
+	JabberCapsTuple key;
 
 	if (!query || type == JABBER_IQ_ERROR) {
 		/* Any outstanding exts will be dealt with via ref-counting */
@@ -481,7 +493,7 @@
 		jabber_caps_client_info_destroy(info);
 		info = value;
 	} else {
-		JabberCapsKey *n_key = g_new(JabberCapsKey, 1);
+		JabberCapsTuple *n_key = (JabberCapsTuple *)&info->tuple;
 		n_key->node = userdata->node;
 		n_key->ver  = userdata->ver;
 		n_key->hash = userdata->hash;
@@ -549,16 +561,19 @@
 }
 
 void jabber_caps_get_info(JabberStream *js, const char *who, const char *node,
-        const char *ver, const char *hash, const char *ext,
+        const char *ver, const char *hash, char **exts,
         jabber_caps_get_info_cb cb, gpointer user_data)
 {
 	JabberCapsClientInfo *info;
-	JabberCapsKey key;
+	JabberCapsTuple key;
 	jabber_caps_cbplususerdata *userdata;
 
-	if (ext && *ext && hash)
+	if (exts && hash) {
 		purple_debug_info("jabber", "Ignoring exts in new-style caps from %s\n",
 		                     who);
+		g_strfreev(exts);
+		exts = NULL;
+	}
 
 	/* Using this in a read-only fashion, so the cast is OK */
 	key.node = (char *)node;
@@ -607,9 +622,8 @@
 	}
 
 	/* Are there any exts that we don't recognize? */
-	if (ext && *ext && !hash) {
+	if (exts) {
 		JabberCapsNodeExts *node_exts;
-		gchar **splat = g_strsplit(ext, " ", 0);
 		int i;
 
 		if (info) {
@@ -621,23 +635,23 @@
 			/* We'll put it in later once we have the client info */
 			node_exts = userdata->node_exts = jabber_caps_find_exts_by_node(node);
 
-		for (i = 0; splat[i]; ++i) {
-			userdata->exts = g_list_prepend(userdata->exts, splat[i]);
+		for (i = 0; exts[i]; ++i) {
+			userdata->exts = g_list_prepend(userdata->exts, exts[i]);
 			/* Look it up if we don't already know what it means */
-			if (!g_hash_table_lookup(node_exts->exts, splat[i])) {
+			if (!g_hash_table_lookup(node_exts->exts, exts[i])) {
 				JabberIq *iq;
 				xmlnode *query;
 				char *nodeext;
 				ext_iq_data *cbdata = g_new(ext_iq_data, 1);
 
-				cbdata->name = splat[i];
+				cbdata->name = exts[i];
 				cbdata->data = cbplususerdata_ref(userdata);
 
 				iq = jabber_iq_new_query(js, JABBER_IQ_GET,
 				            "http://jabber.org/protocol/disco#info");
 				query = xmlnode_get_child_with_namespace(iq->node, "query",
 				            "http://jabber.org/protocol/disco#info");
-				nodeext = g_strdup_printf("%s#%s", node, splat[i]);
+				nodeext = g_strdup_printf("%s#%s", node, exts[i]);
 				xmlnode_set_attrib(query, "node", nodeext);
 				g_free(nodeext);
 				xmlnode_set_attrib(iq->node, "to", who);
@@ -647,11 +661,11 @@
 
 				++userdata->extOutstanding;	
 			}
-			splat[i] = NULL;
+			exts[i] = NULL;
 		}
 		/* All the strings are now part of the GList, so don't need
 		 * g_strfreev. */
-		g_free(splat);
+		g_free(exts);
 	}
 
 	if (userdata->info && userdata->extOutstanding == 0) {
--- a/libpurple/protocols/jabber/caps.h	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/caps.h	Wed May 13 20:37:46 2009 +0000
@@ -30,11 +30,19 @@
 
 typedef struct _JabberCapsNodeExts JabberCapsNodeExts;
 
+typedef struct _JabberCapsTuple {
+	const char *node;
+	const char *ver;
+	const char *hash;
+} JabberCapsTuple;
+
 struct _JabberCapsClientInfo {
 	GList *identities; /* JabberIdentity */
 	GList *features; /* char * */
 	GList *forms; /* xmlnode * */
 	JabberCapsNodeExts *exts;
+
+	const JabberCapsTuple tuple;
 };
 
 /*
@@ -60,7 +68,10 @@
 void jabber_caps_init(void);
 void jabber_caps_uninit(void);
 
-void jabber_caps_destroy_key(gpointer value);
+/**
+ * Check whether all of the exts in a char* array are known to the given info.
+ */
+gboolean jabber_caps_exts_known(const JabberCapsClientInfo *info, char **exts);
 
 /**
  * Main entity capabilites function to get the capabilities of a contact.
@@ -68,10 +79,13 @@
  * The callback will be called synchronously if we already have the
  * capabilities for the specified (node,ver,hash) (and, if exts are specified,
  * if we know what each means)
+ *
+ * @param exts A g_strsplit'd (NULL-terminated) array of strings. This
+ *             function is responsible for freeing it.
  */
 void jabber_caps_get_info(JabberStream *js, const char *who, const char *node,
                           const char *ver, const char *hash,
-                          const char *ext, jabber_caps_get_info_cb cb,
+                          char **exts, jabber_caps_get_info_cb cb,
                           gpointer user_data);
 
 /**
--- a/libpurple/protocols/jabber/chat.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/chat.c	Wed May 13 20:37:46 2009 +0000
@@ -1162,5 +1162,59 @@
 	g_free(room_jid);
 }
 
+typedef struct {
+	const gchar *cap;
+	gboolean *all_support;
+	JabberBuddy *jb;
+} JabberChatCapsData;
 
+static void
+jabber_chat_all_participants_have_capability_foreach(gpointer key,
+                                                     gpointer value,
+                                                     gpointer user_data)
+{
+	const gchar *cap = ((JabberChatCapsData *) user_data)->cap;
+	gboolean *all_support = ((JabberChatCapsData *) user_data)->all_support;
+	JabberBuddy *jb = ((JabberChatCapsData *) user_data)->jb;
+	JabberChatMember *member = (JabberChatMember *) value;
+	const gchar *resource = member->handle;
+	JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
 
+	if (jbr) {
+		*all_support &= jabber_resource_has_capability(jbr, cap);
+	} else {
+		*all_support = FALSE;
+	}
+}
+
+gboolean
+jabber_chat_all_participants_have_capability(const JabberChat *chat,
+	const gchar *cap)
+{
+	gchar *chat_jid = NULL;
+	JabberBuddy *jb = NULL;
+	gboolean all_support = TRUE;
+	JabberChatCapsData data;
+
+	chat_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
+	jb = jabber_buddy_find(chat->js, chat_jid, FALSE);
+
+	if (jb) {
+		data.cap = cap;
+		data.all_support = &all_support;
+		data.jb = jb;
+
+		g_hash_table_foreach(chat->members, 
+			jabber_chat_all_participants_have_capability_foreach, &data);
+	} else {
+		all_support = FALSE;
+	}
+	g_free(chat_jid);
+	return all_support;
+}
+
+guint
+jabber_chat_get_num_participants(const JabberChat *chat)
+{
+	return g_hash_table_size(chat->members);
+}
--- a/libpurple/protocols/jabber/chat.h	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/chat.h	Wed May 13 20:37:46 2009 +0000
@@ -95,5 +95,8 @@
 
 char *jabber_roomlist_room_serialize(PurpleRoomlistRoom *room);
 
+gboolean jabber_chat_all_participants_have_capability(const JabberChat *chat,
+	const gchar *cap);
+guint jabber_chat_get_num_participants(const JabberChat *chat);
 
 #endif /* PURPLE_JABBER_CHAT_H_ */
--- a/libpurple/protocols/jabber/disco.h	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/disco.h	Wed May 13 20:37:46 2009 +0000
@@ -1,5 +1,5 @@
 /**
- * @file iq.h JabberID handlers
+ * @file disco.h service discovery handlers
  *
  * purple
  *
--- a/libpurple/protocols/jabber/google.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Wed May 13 20:37:46 2009 +0000
@@ -638,7 +638,6 @@
 	const char *to, *url;
 	const char *in_str;
 	char *to_name;
-	char *default_tos[1];
 
 	int i, count = 1, returned_count;
 
@@ -658,14 +657,20 @@
 
 	/* If Gmail doesn't tell us who the mail is to, let's use our JID */
 	to = xmlnode_get_attrib(packet, "to");
-	default_tos[0] = jabber_get_bare_jid(to);
 
 	message = xmlnode_get_child(child, "mail-thread-info");
 
 	if (count == 0 || !message) {
-		if (count > 0)
-			purple_notify_emails(js->gc, count, FALSE, NULL, NULL, (const char**) default_tos, NULL, NULL, NULL);
-		g_free(default_tos[0]);
+		if (count > 0) {
+			char *bare_jid = jabber_get_bare_jid(to);
+			const char *default_tos[2] = { bare_jid };
+
+			purple_notify_emails(js->gc, count, FALSE, NULL, NULL, default_tos, NULL, NULL, NULL);
+			g_free(bare_jid);
+		} else {
+			purple_notify_emails(js->gc, count, FALSE, NULL, NULL, NULL, NULL, NULL, NULL);
+		}
+
 		return;
 	}
 
@@ -673,10 +678,10 @@
 	 * accordingly */
 	for (returned_count = 0; message; returned_count++, message=xmlnode_get_next_twin(message));
 
-	froms    = g_new0(const char* , returned_count);
-	tos      = g_new0(const char* , returned_count);
-	subjects = g_new0(char* , returned_count);
-	urls     = g_new0(const char* , returned_count);
+	froms    = g_new0(const char* , returned_count + 1);
+	tos      = g_new0(const char* , returned_count + 1);
+	subjects = g_new0(char* , returned_count + 1);
+	urls     = g_new0(const char* , returned_count + 1);
 
 	to = xmlnode_get_attrib(packet, "to");
 	to_name = jabber_get_bare_jid(to);
@@ -726,16 +731,12 @@
 	if (i>0)
 		purple_notify_emails(js->gc, count, count == i, (const char**) subjects, froms, tos,
 				urls, NULL, NULL);
-	else
-		purple_notify_emails(js->gc, count, FALSE, NULL, NULL, (const char**) default_tos, NULL, NULL, NULL);
-
 
 	g_free(to_name);
 	g_free(tos);
-	g_free(default_tos[0]);
 	g_free(froms);
-	for (; i > 0; i--)
-		g_free(subjects[i - 1]);
+	for (i = 0; i < returned_count; i++)
+		g_free(subjects[i]);
 	g_free(subjects);
 	g_free(urls);
 
@@ -763,7 +764,8 @@
 
 	/* Acknowledge the notification */
 	iq = jabber_iq_new(js, JABBER_IQ_RESULT);
-	xmlnode_set_attrib(iq->node, "to", from);
+	if (from)
+		xmlnode_set_attrib(iq->node, "to", from);
 	xmlnode_set_attrib(iq->node, "id", id);
 	jabber_iq_send(iq);
 
--- a/libpurple/protocols/jabber/iq.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Wed May 13 20:37:46 2009 +0000
@@ -42,7 +42,7 @@
 #endif
 
 GHashTable *iq_handlers = NULL;
-
+GHashTable *signal_iq_handlers = NULL;
 
 JabberIq *jabber_iq_new(JabberStream *js, JabberIqType type)
 {
@@ -289,6 +289,16 @@
 	const char *xmlns;
 	const char *iq_type, *id, *from;
 	JabberIqType type = JABBER_IQ_NONE;
+	gboolean signal_return;
+
+	from = xmlnode_get_attrib(packet, "from");
+	id = xmlnode_get_attrib(packet, "id");
+	iq_type = xmlnode_get_attrib(packet, "type");
+
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+			"jabber-receiving-iq", js->gc, iq_type, id, from, packet));
+	if (signal_return)
+		return;
 
 	/*
 	 * child will be either the first tag child or NULL if there is no child.
@@ -301,10 +311,6 @@
 			break;
 	}
 
-	iq_type = xmlnode_get_attrib(packet, "type");
-	from = xmlnode_get_attrib(packet, "from");
-	id = xmlnode_get_attrib(packet, "id");
-
 	if (iq_type) {
 		if (!strcmp(iq_type, "get"))
 			type = JABBER_IQ_GET;
@@ -361,12 +367,23 @@
 		}
 	}
 
-	/* Apparently not, so lets see if we have a pre-defined handler */
+	/*
+	 * Apparently not, so let's see if we have a pre-defined handler
+	 * or if an outside plugin is interested.
+	 */
 	if(child && (xmlns = xmlnode_get_namespace(child))) {
 		char *key = g_strdup_printf("%s %s", child->name, xmlns);
 		JabberIqHandler *jih = g_hash_table_lookup(iq_handlers, key);
+		int signal_ref = GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers, key));
 		g_free(key);
 
+		if (signal_ref > 0) {
+			signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, "jabber-watched-iq",
+					js->gc, iq_type, id, from, child));
+			if (signal_return)
+				return;
+		}
+
 		if(jih) {
 			jih(js, from, type, id, child);
 			return;
@@ -408,9 +425,48 @@
 	g_hash_table_replace(iq_handlers, key, handlerfunc);
 }
 
+void jabber_iq_signal_register(const gchar *node, const gchar *xmlns)
+{
+	gchar *key;
+	int ref;
+
+	g_return_if_fail(node != NULL && *node != '\0');
+	g_return_if_fail(xmlns != NULL && *xmlns != '\0');
+
+	key = g_strdup_printf("%s %s", node, xmlns);
+	ref = GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers, key));
+	if (ref == 0) {
+		g_hash_table_insert(signal_iq_handlers, key, GINT_TO_POINTER(1));
+	} else {
+		g_hash_table_insert(signal_iq_handlers, key, GINT_TO_POINTER(ref + 1));
+		g_free(key);
+	}
+}
+
+void jabber_iq_signal_unregister(const gchar *node, const gchar *xmlns)
+{
+	gchar *key;
+	int ref;
+
+	g_return_if_fail(node != NULL && *node != '\0');
+	g_return_if_fail(xmlns != NULL && *xmlns != '\0');
+
+	key = g_strdup_printf("%s %s", node, xmlns);
+	ref = GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers, key));
+
+	if (ref == 1) {
+		g_hash_table_remove(signal_iq_handlers, key);
+	} else if (ref > 1) {
+		g_hash_table_insert(signal_iq_handlers, key, GINT_TO_POINTER(ref - 1));
+	}
+
+	g_free(key);
+}
+
 void jabber_iq_init(void)
 {
 	iq_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+	signal_iq_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
 
 	jabber_iq_register_handler("jingle", JINGLE, jingle_parse);
 	jabber_iq_register_handler("mailbox", "google:mail:notify",
@@ -446,5 +502,6 @@
 void jabber_iq_uninit(void)
 {
 	g_hash_table_destroy(iq_handlers);
-	iq_handlers = NULL;
+	g_hash_table_destroy(signal_iq_handlers);
+	iq_handlers = signal_iq_handlers = NULL;
 }
--- a/libpurple/protocols/jabber/iq.h	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/iq.h	Wed May 13 20:37:46 2009 +0000
@@ -31,6 +31,7 @@
 } JabberIqType;
 
 #include "jabber.h"
+#include "connection.h"
 
 typedef struct _JabberIq JabberIq;
 
@@ -106,4 +107,8 @@
 void jabber_iq_register_handler(const char *node, const char *xmlns,
                                 JabberIqHandler *func);
 
+/* Connected to namespace-handler registration signals */
+void jabber_iq_signal_register(const gchar *node, const gchar *xmlns);
+void jabber_iq_signal_unregister(const gchar *node, const gchar *xmlns);
+
 #endif /* PURPLE_JABBER_IQ_H_ */
--- a/libpurple/protocols/jabber/jabber.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Wed May 13 20:37:46 2009 +0000
@@ -3397,7 +3397,7 @@
 	jabber_add_feature(XEP_0224_NAMESPACE, jabber_buzz_isenabled);
 
 	/* Bits Of Binary */
-	jabber_add_feature(XEP_0231_NAMESPACE, jabber_custom_smileys_isenabled);
+	jabber_add_feature(XEP_0231_NAMESPACE, 0);
 
 	/* Jingle features! */
 	jabber_add_feature(JINGLE, 0);
@@ -3418,6 +3418,7 @@
 							 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_ACCOUNT),
 							 purple_value_new(PURPLE_TYPE_STRING),
 							 purple_value_new(PURPLE_TYPE_STRING));
+
 	purple_plugin_ipc_register(plugin, "add_feature", PURPLE_CALLBACK(jabber_ipc_add_feature),
 							 purple_marshal_VOID__POINTER,
 							 NULL, 1,
@@ -3427,6 +3428,8 @@
 void
 jabber_uninit_plugin(void)
 {
+	purple_plugin_ipc_unregister_all(jabber_plugin);
+
 	jabber_features_destroy();
 	jabber_identities_destroy();
 }
--- a/libpurple/protocols/jabber/jabber.h	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Wed May 13 20:37:46 2009 +0000
@@ -75,6 +75,8 @@
 /* Index into attention_types list */
 #define JABBER_BUZZ 0
 
+PurplePlugin *jabber_plugin;
+
 typedef enum {
 	JABBER_STREAM_OFFLINE,
 	JABBER_STREAM_CONNECTING,
--- a/libpurple/protocols/jabber/libxmpp.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Wed May 13 20:37:46 2009 +0000
@@ -46,6 +46,8 @@
 #include "data.h"
 #include "ibb.h"
 
+PurplePlugin *jabber_plugin = NULL;
+
 static PurplePluginProtocolInfo prpl_info =
 {
 	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_MAIL_CHECK |
@@ -125,6 +127,8 @@
 
 static gboolean load_plugin(PurplePlugin *plugin)
 {
+	jabber_plugin = plugin;
+
 	purple_signal_register(plugin, "jabber-receiving-xmlnode",
 			purple_marshal_VOID__POINTER_POINTER, NULL, 2,
 			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
@@ -140,16 +144,65 @@
 			     purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
 			     purple_value_new_outgoing(PURPLE_TYPE_STRING));
 
+	purple_signal_register(plugin, "jabber-receiving-message",
+			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER,
+			purple_value_new(PURPLE_TYPE_BOOLEAN), 6,
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			purple_value_new(PURPLE_TYPE_STRING), /* type */
+			purple_value_new(PURPLE_TYPE_STRING), /* id */
+			purple_value_new(PURPLE_TYPE_STRING), /* from */
+			purple_value_new(PURPLE_TYPE_STRING), /* to */
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
+
+	purple_signal_register(plugin, "jabber-receiving-iq",
+			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
+			purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			purple_value_new(PURPLE_TYPE_STRING), /* type */
+			purple_value_new(PURPLE_TYPE_STRING), /* id */
+			purple_value_new(PURPLE_TYPE_STRING), /* from */
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
+
+	purple_signal_register(plugin, "jabber-watched-iq",
+			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
+			purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			purple_value_new(PURPLE_TYPE_STRING), /* type */
+			purple_value_new(PURPLE_TYPE_STRING), /* id */
+			purple_value_new(PURPLE_TYPE_STRING), /* from */
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); /* child */
+
+	purple_signal_register(plugin, "jabber-register-namespace-watcher",
+			purple_marshal_VOID__POINTER_POINTER_POINTER,
+			NULL, 2,
+			purple_value_new(PURPLE_TYPE_STRING),  /* node */
+			purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
+
+	purple_signal_register(plugin, "jabber-unregister-namespace-watcher",
+			purple_marshal_VOID__POINTER_POINTER_POINTER,
+			NULL, 2,
+			purple_value_new(PURPLE_TYPE_STRING),  /* node */
+			purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
+
+	purple_signal_connect(plugin, "jabber-register-namespace-watcher",
+			plugin, PURPLE_CALLBACK(jabber_iq_signal_register), NULL);
+	purple_signal_connect(plugin, "jabber-unregister-namespace-watcher",
+			plugin, PURPLE_CALLBACK(jabber_iq_signal_unregister), NULL);
+
+	purple_signal_register(plugin, "jabber-receiving-presence",
+			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER,
+			purple_value_new(PURPLE_TYPE_BOOLEAN), 4,
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			purple_value_new(PURPLE_TYPE_STRING), /* type */
+			purple_value_new(PURPLE_TYPE_STRING), /* from */
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
+
 	return TRUE;
 }
 
 static gboolean unload_plugin(PurplePlugin *plugin)
 {
-	purple_signal_unregister(plugin, "jabber-receiving-xmlnode");
-
-	purple_signal_unregister(plugin, "jabber-sending-xmlnode");
-
-	purple_signal_unregister(plugin, "jabber-sending-text");
+	purple_signals_unregister_by_instance(plugin);
 
 	/* reverse order of init_plugin */
 	jabber_bosh_uninit();
@@ -166,6 +219,8 @@
 	/* Stay on target...stay on target... Almost there... */
 	jabber_uninit_plugin();
 
+	jabber_plugin = NULL;
+
 	return TRUE;
 }
 
--- a/libpurple/protocols/jabber/message.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/message.c	Wed May 13 20:37:46 2009 +0000
@@ -532,16 +532,25 @@
 void jabber_message_parse(JabberStream *js, xmlnode *packet)
 {
 	JabberMessage *jm;
-	const char *type;
+	const char *id, *from, *to, *type;
 	xmlnode *child;
+	gboolean signal_return;
+
+	from = xmlnode_get_attrib(packet, "from");
+	id   = xmlnode_get_attrib(packet, "id");
+	to   = xmlnode_get_attrib(packet, "to");
+	type = xmlnode_get_attrib(packet, "type");
+
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+			"jabber-receiving-message", js->gc, type, id, from, to, packet));
+	if (signal_return)
+		return;
 
 	jm = g_new0(JabberMessage, 1);
 	jm->js = js;
 	jm->sent = time(NULL);
 	jm->delayed = FALSE;
 
-	type = xmlnode_get_attrib(packet, "type");
-
 	if(type) {
 		if(!strcmp(type, "normal"))
 			jm->type = JABBER_MESSAGE_NORMAL;
@@ -559,9 +568,9 @@
 		jm->type = JABBER_MESSAGE_NORMAL;
 	}
 
-	jm->from = g_strdup(xmlnode_get_attrib(packet, "from"));
-	jm->to = g_strdup(xmlnode_get_attrib(packet, "to"));
-	jm->id = g_strdup(xmlnode_get_attrib(packet, "id"));
+	jm->from = g_strdup(from);
+	jm->to   = g_strdup(to);
+	jm->id   = g_strdup(id);
 
 	for(child = packet->child; child; child = child->next) {
 		const char *xmlns = xmlnode_get_namespace(child);
@@ -912,11 +921,12 @@
 
 static gboolean
 jabber_conv_support_custom_smileys(const PurpleConnection *gc,
-								   const PurpleConversation *conv,
+								   PurpleConversation *conv,
 								   const gchar *who)
 {
 	JabberStream *js = (JabberStream *) gc->proto_data;
 	JabberBuddy *jb;
+	JabberChat *chat;
 
 	if (!js) {
 		purple_debug_error("jabber",
@@ -925,7 +935,6 @@
 	}
 
 	switch (purple_conversation_get_type(conv)) {
-		/* for the time being, we will not support custom smileys in MUCs */
 		case PURPLE_CONV_TYPE_IM:
 			jb = jabber_buddy_find(js, who, FALSE);
 			if (jb) {
@@ -934,6 +943,18 @@
 				return FALSE;
 			}
 			break;
+		case PURPLE_CONV_TYPE_CHAT:
+			chat = jabber_chat_find_by_conv(conv);
+			if (chat) {
+				/* do not attempt to send custom smileys in a MUC with more than
+				 10 people, to avoid getting too many BoB requests */
+				return jabber_chat_get_num_participants(chat) <= 10 &&
+					jabber_chat_all_participants_have_capability(chat, 
+						XEP_0231_NAMESPACE);
+			} else {
+				return FALSE;
+			}
+			break;
 		default:
 			return FALSE;
 			break;
@@ -1194,6 +1215,7 @@
 	JabberMessage *jm;
 	JabberStream *js;
 	char *xhtml;
+	char *tmp;
 
 	if(!msg || !gc)
 		return 0;
@@ -1211,6 +1233,11 @@
 	jm->id = jabber_get_next_id(jm->js);
 
 	purple_markup_html_to_xhtml(msg, &xhtml, &jm->body);
+	tmp = jabber_message_smileyfy_xhtml(jm, xhtml);
+	if (tmp) {
+		g_free(xhtml);
+		xhtml = tmp;
+	}
 
 	if (chat->xhtml && !jabber_xhtml_plain_equal(xhtml, jm->body))
 		jm->xhtml = g_strdup_printf("<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html>", xhtml);
--- a/libpurple/protocols/jabber/oob.h	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/oob.h	Wed May 13 20:37:46 2009 +0000
@@ -1,5 +1,5 @@
 /**
- * @file jutil.h utility functions
+ * @file oob.h out-of-band transfer functions
  *
  * purple
  *
--- a/libpurple/protocols/jabber/ping.h	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/ping.h	Wed May 13 20:37:46 2009 +0000
@@ -1,5 +1,5 @@
 /**
- * @file ping.h utility functions
+ * @file ping.h ping functions
  *
  * purple
  *
--- a/libpurple/protocols/jabber/presence.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Wed May 13 20:37:46 2009 +0000
@@ -429,13 +429,15 @@
 	jbr->caps.info = info;
 	jbr->caps.exts = exts;
 
-	if (jabber_resource_has_capability(jbr, "http://jabber.org/protocol/commands")) {
+	if (!jbr->commands_fetched && jabber_resource_has_capability(jbr, "http://jabber.org/protocol/commands")) {
 		JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
 		xmlnode *query = xmlnode_get_child_with_namespace(iq->node, "query", "http://jabber.org/protocol/disco#items");
 		xmlnode_set_attrib(iq->node, "to", userdata->from);
 		xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/commands");
 		jabber_iq_set_callback(iq, jabber_adhoc_disco_result_cb, NULL);
 		jabber_iq_send(iq);
+
+		jbr->commands_fetched = TRUE;
 	}
 
 	g_free(userdata->from);
@@ -444,8 +446,8 @@
 
 void jabber_presence_parse(JabberStream *js, xmlnode *packet)
 {
-	const char *from = xmlnode_get_attrib(packet, "from");
-	const char *type = xmlnode_get_attrib(packet, "type");
+	const char *from;
+	const char *type;
 	const char *real_jid = NULL;
 	const char *affiliation = NULL;
 	const char *role = NULL;
@@ -467,10 +469,19 @@
 	xmlnode *caps = NULL;
 	int idle = 0;
 	gchar *nickname = NULL;
+	gboolean signal_return;
+
+	from = xmlnode_get_attrib(packet, "from");
+	type = xmlnode_get_attrib(packet, "type");
 
 	if(!(jb = jabber_buddy_find(js, from, TRUE)))
 		return;
 
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+			"jabber-receiving-presence", js->gc, type, from, packet));
+	if (signal_return)
+		return;
+
 	if(!(jid = jabber_id_new(from)))
 		return;
 
@@ -854,13 +865,26 @@
 		/* v1.3 uses: node, ver, and optionally ext.
 		 * v1.5 uses: node, ver, and hash. */
 		if (node && *node && ver && *ver) {
-			JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1);
-			userdata->js = js;
-			userdata->jb = jb;
-			userdata->from = g_strdup(from);
-			jabber_caps_get_info(js, from, node, ver, hash, ext,
-			    (jabber_caps_get_info_cb)jabber_presence_set_capabilities,
-			    userdata);
+			gchar **exts = ext && *ext ? g_strsplit(ext, " ", -1) : NULL;
+			jbr = jabber_buddy_find_resource(jb, jid->resource);
+
+			/* Look it up if we don't already have all this information */
+			if (!jbr || !jbr->caps.info ||
+					!g_str_equal(node, jbr->caps.info->tuple.node) ||
+					!g_str_equal(ver, jbr->caps.info->tuple.ver) ||
+					!purple_strequal(hash, jbr->caps.info->tuple.hash) ||
+					!jabber_caps_exts_known(jbr->caps.info, (gchar **)exts)) {
+				JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1);
+				userdata->js = js;
+				userdata->jb = jb;
+				userdata->from = g_strdup(from);
+				jabber_caps_get_info(js, from, node, ver, hash, exts,
+				    (jabber_caps_get_info_cb)jabber_presence_set_capabilities,
+				    userdata);
+			} else {
+				if (exts)
+					g_strfreev(exts);
+			}
 		}
 	}
 
--- a/libpurple/protocols/jabber/si.h	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/jabber/si.h	Wed May 13 20:37:46 2009 +0000
@@ -1,5 +1,5 @@
 /**
- * @file jutil.h utility functions
+ * @file si.h SI transfer functions
  *
  * purple
  *
--- a/libpurple/protocols/msn/notification.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/msn/notification.c	Wed May 13 20:37:46 2009 +0000
@@ -1891,14 +1891,11 @@
 
 		if (count > 0)
 		{
-			const char *passport;
-			const char *url;
-
-			passport = msn_user_get_passport(session->user);
-			url = session->passport_info.mail_url;
+			const char *passports[2] = { msn_user_get_passport(session->user) };
+			const char *urls[2] = { session->passport_info.mail_url };
 
 			purple_notify_emails(gc, count, FALSE, NULL, NULL,
-							   &passport, &url, NULL, NULL);
+							   passports, urls, NULL, NULL);
 		}
 	}
 
@@ -1960,14 +1957,11 @@
 
 		if (count > 0)
 		{
-			const char *passport;
-			const char *url;
-
-			passport = msn_user_get_passport(session->user);
-			url = session->passport_info.mail_url;
+			const char *passports[2] = { msn_user_get_passport(session->user) };
+			const char *urls[2] = { session->passport_info.mail_url };
 
 			purple_notify_emails(gc, count, FALSE, NULL, NULL,
-							   &passport, &url, NULL, NULL);
+							   passports, urls, NULL, NULL);
 		}
 	}
 
--- a/libpurple/protocols/msn/oim.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/msn/oim.c	Wed May 13 20:37:46 2009 +0000
@@ -764,14 +764,14 @@
 	if (iu_node != NULL && purple_account_get_check_mail(session->account))
 	{
 		char *unread = xmlnode_get_data(iu_node);
-		const char *passport = msn_user_get_passport(session->user);
-		const char *url = session->passport_info.mail_url;
+		const char *passports[2] = { msn_user_get_passport(session->user) };
+		const char *urls[2] = { session->passport_info.mail_url };
 		int count = atoi(unread);
 
 		/* XXX/khc: pretty sure this is wrong */
 		if (count > 0)
 			purple_notify_emails(session->account->gc, count, FALSE, NULL,
-				NULL, &passport, &url, NULL, NULL);
+				NULL, passports, urls, NULL, NULL);
 		g_free(unread);
 	}
 
--- a/libpurple/protocols/msn/slplink.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/msn/slplink.c	Wed May 13 20:37:46 2009 +0000
@@ -683,9 +683,9 @@
 		size = st.st_size;
 
 	if(!file_name) {
-		base = g_path_get_basename(file_path);
-		u8 = purple_utf8_try_convert(base);
-		g_free(base);
+		gchar *basename = g_path_get_basename(file_path);
+		u8 = purple_utf8_try_convert(basename);
+		g_free(basename);
 		file_name = u8;
 	}
 
--- a/libpurple/protocols/msnp9/notification.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/msnp9/notification.c	Wed May 13 20:37:46 2009 +0000
@@ -1280,14 +1280,11 @@
 
 		if (count > 0)
 		{
-			const char *passport;
-			const char *url;
+			const char *passports[2] = { msn_user_get_passport(session->user) };
+			const char *urls[2] = { session->passport_info.file };
 
-			passport = msn_user_get_passport(session->user);
-			url = session->passport_info.file;
-
-			purple_notify_emails(gc, atoi(unread), FALSE, NULL, NULL,
-							   &passport, &url, NULL, NULL);
+			purple_notify_emails(gc, count, FALSE, NULL, NULL,
+							   passports, urls, NULL, NULL);
 		}
 	}
 
--- a/libpurple/protocols/myspace/myspace.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Wed May 13 20:37:46 2009 +0000
@@ -847,8 +847,6 @@
 	MsimMessage *body;
 	guint old_inbox_status;
 	guint i, n;
-	const gchar *froms[5], *tos[5], *urls[5], *subjects[5];
-
 	/* Information for each new inbox message type. */
 	static struct
 	{
@@ -863,16 +861,22 @@
 		{ "FriendRequest", MSIM_INBOX_FRIEND_REQUEST, "http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests", NULL },
 		{ "PictureComment", MSIM_INBOX_PICTURE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL }
 	};
+	const gchar *froms[G_N_ELEMENTS(message_types) + 1] = { "" },
+		*tos[G_N_ELEMENTS(message_types) + 1] = { "" },
+		*urls[G_N_ELEMENTS(message_types) + 1] = { "" },
+		*subjects[G_N_ELEMENTS(message_types) + 1] = { "" };
+
+	g_return_if_fail(reply != NULL);
 
 	/* Can't write _()'d strings in array initializers. Workaround. */
+	/* khc: then use N_() in the array initializer and use _() when they are
+	   used */
 	message_types[0].text = _("New mail messages");
 	message_types[1].text = _("New blog comments");
 	message_types[2].text = _("New profile comments");
 	message_types[3].text = _("New friend requests!");
 	message_types[4].text = _("New picture comments");
 
-	g_return_if_fail(reply != NULL);
-
 	body = msim_msg_get_dictionary(reply, "body");
 
 	if (body == NULL)
@@ -882,7 +886,7 @@
 
 	n = 0;
 
-	for (i = 0; i < sizeof(message_types) / sizeof(message_types[0]); ++i) {
+	for (i = 0; i < G_N_ELEMENTS(message_types); ++i) {
 		const gchar *key;
 		guint bit;
 
--- a/libpurple/protocols/oscar/oscar.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Wed May 13 20:37:46 2009 +0000
@@ -930,7 +930,7 @@
 	PurpleGroup *g = NULL;
 	struct buddyinfo *bi = NULL;
 	char *tmp;
-	const char *bname, *gname = NULL;
+	const char *bname = NULL, *gname = NULL;
 
 	od = purple_connection_get_protocol_data(gc);
 	account = purple_connection_get_account(gc);
@@ -938,14 +938,14 @@
 	if ((user_info == NULL) || ((b == NULL) && (userinfo == NULL)))
 		return;
 
-	bname = purple_buddy_get_name(b);
 	if (userinfo == NULL)
-		userinfo = aim_locate_finduserinfo(od, bname);
+		userinfo = aim_locate_finduserinfo(od, purple_buddy_get_name(b));
 
 	if (b == NULL)
 		b = purple_find_buddy(account, userinfo->bn);
 
 	if (b != NULL) {
+		bname = purple_buddy_get_name(b);
 		g = purple_buddy_get_group(b);
 		gname = purple_group_get_name(g);
 		presence = purple_buddy_get_presence(b);
@@ -3571,8 +3571,10 @@
 				purple_account_get_username(account),
 				emailinfo->domain ? "@" : "",
 				emailinfo->domain ? emailinfo->domain : "");
+		const char *tos[2] = { to };
+		const char *urls[2] = { emailinfo->url };
 		purple_notify_emails(gc, emailinfo->nummsgs, FALSE, NULL, NULL,
-				(const char **)&to, (const char **)&emailinfo->url, NULL, NULL);
+				tos, urls, NULL, NULL);
 		g_free(to);
 	}
 
--- a/libpurple/protocols/qq/qq_crypt.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/qq/qq_crypt.c	Wed May 13 20:37:46 2009 +0000
@@ -163,9 +163,11 @@
 		c32_prev[0] = crypted32[0]; c32_prev[1] = crypted32[1];
 		
 		/* set next 64 bits want to crypt*/
-		crypted_ptr += 8;
-		memcpy(crypted32, crypted_ptr, sizeof(crypted32));
-		plain32[0] = crypted32[0] ^ c32_prev[0]; plain32[1] = crypted32[1] ^ c32_prev[1];
+		if (count64 > 0) {
+			crypted_ptr += 8;
+			memcpy(crypted32, crypted_ptr, sizeof(crypted32));
+			plain32[0] = crypted32[0] ^ c32_prev[0]; plain32[1] = crypted32[1] ^ c32_prev[1];
+		}
 	}
 }
 
--- a/libpurple/protocols/yahoo/yahoo.c	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Wed May 13 20:37:46 2009 +0000
@@ -1520,10 +1520,10 @@
 		g_free(dec_subj);
 		g_free(from);
 	} else if (count > 0) {
-		const char *to = purple_account_get_username(account);
-		const char *url = yahoo_mail_url;
-
-		purple_notify_emails(gc, count, FALSE, NULL, NULL, &to, &url,
+		const char *tos[2] = { purple_account_get_username(account) };
+		const char *urls[2] = { yahoo_mail_url };
+
+		purple_notify_emails(gc, count, FALSE, NULL, NULL, tos, urls,
 						   NULL, NULL);
 	}
 }
@@ -4753,8 +4753,10 @@
 			status = purple_presence_get_active_status(purple_account_get_presence(purple_connection_get_account(gc)));
 		tmp = purple_status_get_attr_string(status, "message");
 		if (tmp != NULL) {
-			msg = yahoo_string_encode(gc, tmp, NULL);
+			gboolean utf8 = TRUE;
+			msg = yahoo_string_encode(gc, tmp, &utf8);
 			msg2 = purple_markup_strip_html(msg);
+			yahoo_packet_hash_str(pkt, 97, utf8 ? "1" : 0);
 			yahoo_packet_hash_str(pkt, 19, msg2);
 		} else {
 			/* get_yahoo_status_from_purple_status() returns YAHOO_STATUS_CUSTOM for
--- a/libpurple/win32/global.mak	Thu May 07 21:20:16 2009 +0000
+++ b/libpurple/win32/global.mak	Wed May 13 20:37:46 2009 +0000
@@ -103,6 +103,7 @@
 endif
 GMSGFMT ?= $(GTK_BIN)/msgfmt
 MAKENSIS ?= makensis.exe
+MAKENSISOPT ?= /
 PERL ?= /cygdrive/c/perl/bin/perl
 WINDRES ?= windres
 STRIP ?= strip
--- a/pidgin/gtkconv.c	Thu May 07 21:20:16 2009 +0000
+++ b/pidgin/gtkconv.c	Wed May 13 20:37:46 2009 +0000
@@ -8324,6 +8324,9 @@
 			gtkconv->unseen_state = state;
 	}
 
+	purple_conversation_set_data(gtkconv->active_conv, "unseen-count", GINT_TO_POINTER(gtkconv->unseen_count));
+	purple_conversation_set_data(gtkconv->active_conv, "unseen-state", GINT_TO_POINTER(gtkconv->unseen_state));
+
 	purple_conversation_update(gtkconv->active_conv, PURPLE_CONV_UPDATE_UNSEEN);
 }
 
--- a/pidgin/win32/pidgin_dll_rc.rc.in	Thu May 07 21:20:16 2009 +0000
+++ b/pidgin/win32/pidgin_dll_rc.rc.in	Wed May 13 20:37:46 2009 +0000
@@ -2,8 +2,6 @@
 #include "version.h"
 #include "resource.h"
 
-#define PIXMAPDIR "pixmaps/tray/16/"
-
 VS_VERSION_INFO VERSIONINFO
   FILEVERSION PURPLE_MAJOR_VERSION,PURPLE_MINOR_VERSION,PURPLE_MICRO_VERSION,0
   PRODUCTVERSION PURPLE_MAJOR_VERSION,PURPLE_MINOR_VERSION,PURPLE_MICRO_VERSION,0
@@ -33,11 +31,11 @@
     END
   END
 
-PIDGIN_TRAY_AVAILABLE_4BIT	ICON PIXMAPDIR "available_4bit.ico"
-PIDGIN_TRAY_AWAY_4BIT		ICON PIXMAPDIR "away_4bit.ico"
-PIDGIN_TRAY_BUSY_4BIT		ICON PIXMAPDIR "busy_4bit.ico"
-PIDGIN_TRAY_XA_4BIT		ICON PIXMAPDIR "extended-away_4bit.ico"
-PIDGIN_TRAY_OFFLINE_4BIT	ICON PIXMAPDIR "offline_4bit.ico"
-PIDGIN_TRAY_CONNECTING_4BIT	ICON PIXMAPDIR "connecting_4bit.ico"
-PIDGIN_TRAY_PENDING_4BIT	ICON PIXMAPDIR "message_4bit.ico"
-PIDGIN_TRAY_INVISIBLE_4BIT	ICON PIXMAPDIR "invisible_4bit.ico"
+PIDGIN_TRAY_AVAILABLE_4BIT	ICON "pixmaps/tray/16/available_4bit.ico"
+PIDGIN_TRAY_AWAY_4BIT		ICON "pixmaps/tray/16/away_4bit.ico"
+PIDGIN_TRAY_BUSY_4BIT		ICON "pixmaps/tray/16/busy_4bit.ico"
+PIDGIN_TRAY_XA_4BIT		ICON "pixmaps/tray/16/extended-away_4bit.ico"
+PIDGIN_TRAY_OFFLINE_4BIT	ICON "pixmaps/tray/16/offline_4bit.ico"
+PIDGIN_TRAY_CONNECTING_4BIT	ICON "pixmaps/tray/16/connecting_4bit.ico"
+PIDGIN_TRAY_PENDING_4BIT	ICON "pixmaps/tray/16/message_4bit.ico"
+PIDGIN_TRAY_INVISIBLE_4BIT	ICON "pixmaps/tray/16/invisible_4bit.ico"
--- a/pidgin/win32/pidgin_exe_rc.rc.in	Thu May 07 21:20:16 2009 +0000
+++ b/pidgin/win32/pidgin_exe_rc.rc.in	Wed May 13 20:37:46 2009 +0000
@@ -2,8 +2,6 @@
 #include "resource.h"
 #include "version.h"
 
-#define PIXMAPDIR "pixmaps/"
-
 VS_VERSION_INFO VERSIONINFO
   FILEVERSION PURPLE_MAJOR_VERSION,PURPLE_MINOR_VERSION,PURPLE_MICRO_VERSION,0
   PRODUCTVERSION PURPLE_MAJOR_VERSION,PURPLE_MINOR_VERSION,PURPLE_MICRO_VERSION,0
@@ -33,4 +31,4 @@
     END
   END
 
-PIDGIN_ICON			ICON PIXMAPDIR "pidgin.ico"
+PIDGIN_ICON			ICON "pixmaps/pidgin.ico"